uBlock/assets/resources/scriptlets.js

4623 lines
155 KiB
JavaScript
Raw Normal View History

/*******************************************************************************
uBlock Origin - a comprehensive, efficient content blocker
Copyright (C) 2019-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
The scriptlets below are meant to be injected only into a
web page context.
*/
import {
getAllCookiesFn,
getCookieFn,
getSafeCookieValuesFn,
removeCookie,
setCookie,
setCookieFn,
setCookieReload,
trustedSetCookie,
trustedSetCookieReload,
} from './cookie.js';
import {
removeAttr,
setAttr,
setAttrFn,
trustedSetAttr,
} from './attribute.js';
import { runAt } from './run-at.js';
import { safeSelf } from './safe-self.js';
2024-03-20 07:31:17 -06:00
/* eslint no-prototype-builtins: 0 */
// Externally added to the private namespace in which scriptlets execute.
/* global scriptletGlobals */
export const builtinScriptlets = [];
/******************************************************************************/
// Register scriptlets declared in other files.
const registerScriptlet = fn => {
const details = fn.details;
if ( typeof details !== 'object' ) {
throw new ReferenceError('Unknown scriptlet function');
}
details.fn = fn;
if ( Array.isArray(details.dependencies) ) {
details.dependencies.forEach((fn, i, array) => {
if ( typeof fn !== 'function' ) { return; }
array[i] = fn.details.name;
});
}
builtinScriptlets.push(details);
};
registerScriptlet(safeSelf);
registerScriptlet(removeAttr);
registerScriptlet(setAttrFn);
registerScriptlet(setAttr);
registerScriptlet(trustedSetAttr);
registerScriptlet(getAllCookiesFn);
registerScriptlet(getCookieFn);
registerScriptlet(getSafeCookieValuesFn);
registerScriptlet(removeCookie);
registerScriptlet(setCookie);
registerScriptlet(setCookieFn);
registerScriptlet(setCookieReload);
registerScriptlet(trustedSetCookie);
registerScriptlet(trustedSetCookieReload);
/*******************************************************************************
Helper functions
These are meant to be used as dependencies to injectable scriptlets.
*******************************************************************************/
builtinScriptlets.push({
name: 'get-random-token.fn',
fn: getRandomToken,
dependencies: [
'safe-self.fn',
],
});
function getRandomToken() {
const safe = safeSelf();
return safe.String_fromCharCode(Date.now() % 26 + 97) +
safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'get-exception-token.fn',
fn: getExceptionToken,
dependencies: [
'get-random-token.fn',
],
});
function getExceptionToken() {
const token = getRandomToken();
const oe = self.onerror;
self.onerror = function(msg, ...args) {
if ( typeof msg === 'string' && msg.includes(token) ) { return true; }
if ( oe instanceof Function ) {
return oe.call(this, msg, ...args);
}
}.bind();
return token;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'should-debug.fn',
fn: shouldDebug,
});
function shouldDebug(details) {
if ( details instanceof Object === false ) { return false; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
return scriptletGlobals.canDebug && details.debug;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'run-at.fn',
fn: runAt,
dependencies: [
'safe-self.fn',
],
});
/******************************************************************************/
builtinScriptlets.push({
name: 'run-at-html-element.fn',
fn: runAtHtmlElementFn,
});
function runAtHtmlElementFn(fn) {
if ( document.documentElement ) {
fn();
return;
}
const observer = new MutationObserver(( ) => {
observer.disconnect();
2023-05-24 13:33:46 -06:00
fn();
});
observer.observe(document, { childList: true });
}
/******************************************************************************/
// Reference:
// https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr
//
// Added `trusted` argument to allow for returning arbitrary text. Can only
// be used through scriptlets requiring trusted source.
builtinScriptlets.push({
name: 'generate-content.fn',
fn: generateContentFn,
dependencies: [
'safe-self.fn',
],
});
function generateContentFn(trusted, directive) {
const safe = safeSelf();
const randomize = len => {
const chunks = [];
let textSize = 0;
do {
const s = safe.Math_random().toString(36).slice(2);
chunks.push(s);
textSize += s.length;
}
while ( textSize < len );
return chunks.join(' ').slice(0, len);
};
if ( directive === 'true' ) {
return randomize(10);
}
if ( directive === 'emptyObj' ) {
return '{}';
}
if ( directive === 'emptyArr' ) {
return '[]';
}
if ( directive === 'emptyStr' ) {
return '';
}
if ( directive.startsWith('length:') ) {
const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive);
if ( match === null ) { return ''; }
const min = parseInt(match[1], 10);
const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min;
const len = safe.Math_min(min + extent * safe.Math_random(), 500000);
return randomize(len | 0);
}
if ( directive.startsWith('war:') ) {
if ( scriptletGlobals.warOrigin === undefined ) { return ''; }
return new Promise(resolve => {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const warOrigin = scriptletGlobals.warOrigin;
const warName = directive.slice(4);
const fullpath = [ warOrigin, '/', warName ];
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const warSecret = scriptletGlobals.warSecret;
if ( warSecret !== undefined ) {
fullpath.push('?secret=', warSecret);
}
const warXHR = new safe.XMLHttpRequest();
warXHR.responseType = 'text';
warXHR.onloadend = ev => {
resolve(ev.target.responseText || '');
};
warXHR.open('GET', fullpath.join(''));
warXHR.send();
}).catch(( ) => '');
}
if ( trusted ) {
return directive;
}
return '';
}
/******************************************************************************/
builtinScriptlets.push({
name: 'abort-current-script-core.fn',
fn: abortCurrentScriptCore,
dependencies: [
'get-exception-token.fn',
'safe-self.fn',
'should-debug.fn',
],
});
// Issues to mind before changing anything:
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
function abortCurrentScriptCore(
target = '',
needle = '',
context = ''
) {
if ( typeof target !== 'string' ) { return; }
if ( target === '' ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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);
const thisScript = document.currentScript;
const chain = target.split('.');
let owner = window;
let prop;
for (;;) {
prop = chain.shift();
if ( chain.length === 0 ) { break; }
if ( prop in owner === false ) { break; }
owner = owner[prop];
if ( owner instanceof Object === false ) { return; }
}
let value;
let desc = Object.getOwnPropertyDescriptor(owner, prop);
if (
desc instanceof Object === false ||
desc.get instanceof Function === false
) {
value = owner[prop];
desc = undefined;
}
const debug = shouldDebug(extraArgs);
const exceptionToken = getExceptionToken();
const scriptTexts = new WeakMap();
const getScriptText = elem => {
let text = elem.textContent;
if ( text.trim() !== '' ) { return text; }
if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); }
const [ , mime, content ] =
/^data:([^,]*),(.+)$/.exec(elem.src.trim()) ||
[ '', '', '' ];
try {
switch ( true ) {
case mime.endsWith(';base64'):
text = self.atob(content);
break;
default:
text = self.decodeURIComponent(content);
break;
}
} catch(ex) {
}
scriptTexts.set(elem, text);
return text;
};
const validate = ( ) => {
const e = document.currentScript;
if ( e instanceof HTMLScriptElement === false ) { return; }
if ( e === thisScript ) { return; }
2023-08-19 17:21:22 -06:00
if ( context !== '' && reContext.test(e.src) === false ) {
2024-03-20 07:31:17 -06:00
// eslint-disable-next-line no-debugger
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
2023-08-19 17:21:22 -06:00
return;
}
if ( safe.logLevel > 1 && context !== '' ) {
safe.uboLog(logPrefix, `Matched src\n${e.src}`);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
}
const scriptText = getScriptText(e);
2023-08-19 17:21:22 -06:00
if ( reNeedle.test(scriptText) === false ) {
2024-03-20 07:31:17 -06:00
// eslint-disable-next-line no-debugger
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
2023-08-19 17:21:22 -06:00
return;
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched text\n${scriptText}`);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
}
2024-03-20 07:31:17 -06:00
// eslint-disable-next-line no-debugger
if ( debug === 'match' || debug === 'all' ) { debugger; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
};
2024-03-20 07:31:17 -06:00
// eslint-disable-next-line no-debugger
if ( debug === 'install' ) { debugger; }
try {
Object.defineProperty(owner, prop, {
get: function() {
validate();
return desc instanceof Object
? desc.get.call(owner)
: value;
},
set: function(a) {
validate();
if ( desc instanceof Object ) {
desc.set.call(owner, a);
} else {
value = a;
}
}
});
} catch(ex) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, `Error: ${ex}`);
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'validate-constant.fn',
fn: validateConstantFn,
dependencies: [
'safe-self.fn',
],
});
function validateConstantFn(trusted, raw, extraArgs = {}) {
const safe = safeSelf();
let value;
if ( raw === 'undefined' ) {
value = undefined;
} else if ( raw === 'false' ) {
value = false;
} else if ( raw === 'true' ) {
value = true;
} else if ( raw === 'null' ) {
value = null;
} else if ( raw === "''" || raw === '' ) {
value = '';
} else if ( raw === '[]' || raw === 'emptyArr' ) {
value = [];
} else if ( raw === '{}' || raw === 'emptyObj' ) {
value = {};
} else if ( raw === 'noopFunc' ) {
value = function(){};
} else if ( raw === 'trueFunc' ) {
value = function(){ return true; };
} else if ( raw === 'falseFunc' ) {
value = function(){ return false; };
} else if ( raw === 'throwFunc' ) {
value = function(){ throw ''; };
} else if ( /^-?\d+$/.test(raw) ) {
value = parseInt(raw);
if ( isNaN(raw) ) { return; }
if ( Math.abs(raw) > 0x7FFF ) { return; }
} else if ( trusted ) {
if ( raw.startsWith('json:') ) {
try { value = safe.JSON_parse(raw.slice(5)); } catch(ex) { return; }
} else 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;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'set-constant.fn',
fn: setConstantFn,
dependencies: [
'run-at.fn',
'safe-self.fn',
'validate-constant.fn',
],
});
function setConstantFn(
trusted = false,
chain = '',
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, rawValue) {
const trappedProp = (( ) => {
const pos = chain.lastIndexOf('.');
if ( pos === -1 ) { return chain; }
return chain.slice(pos+1);
})();
const cloakFunc = fn => {
safe.Object_defineProperty(fn, 'name', { value: trappedProp });
return new Proxy(fn, {
defineProperty(target, prop) {
if ( prop !== 'toString' ) {
return Reflect.defineProperty(...arguments);
}
return true;
},
deleteProperty(target, prop) {
if ( prop !== 'toString' ) {
return Reflect.deleteProperty(...arguments);
}
return true;
},
get(target, prop) {
if ( prop === 'toString' ) {
return function() {
return `function ${trappedProp}() { [native code] }`;
}.bind(null);
}
return Reflect.get(...arguments);
},
});
};
if ( trappedProp === '' ) { return; }
const thisScript = document.currentScript;
let normalValue = validateConstantFn(trusted, rawValue, extraArgs);
if ( rawValue === 'noopFunc' || rawValue === 'trueFunc' || rawValue === 'falseFunc' ) {
normalValue = cloakFunc(normalValue);
}
let aborted = false;
const mustAbort = function(v) {
if ( trusted ) { return false; }
if ( aborted ) { return true; }
aborted =
(v !== undefined && v !== null) &&
(normalValue !== undefined && normalValue !== null) &&
(typeof v !== typeof normalValue);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( aborted ) {
safe.uboLog(logPrefix, `Aborted because value set to ${v}`);
}
return aborted;
};
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
// Support multiple trappers for the same property.
const trapProp = function(owner, prop, configurable, handler) {
if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; }
const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop);
let prevGetter, prevSetter;
if ( odesc instanceof safe.Object ) {
owner[prop] = normalValue;
if ( odesc.get instanceof Function ) {
prevGetter = odesc.get;
}
if ( odesc.set instanceof Function ) {
prevSetter = odesc.set;
}
}
try {
safe.Object_defineProperty(owner, prop, {
configurable,
get() {
if ( prevGetter !== undefined ) {
prevGetter();
}
return handler.getter();
},
set(a) {
if ( prevSetter !== undefined ) {
prevSetter(a);
}
handler.setter(a);
}
});
safe.uboLog(logPrefix, 'Trap installed');
} catch(ex) {
safe.uboErr(logPrefix, ex);
}
};
const trapChain = function(owner, chain) {
const pos = chain.indexOf('.');
if ( pos === -1 ) {
trapProp(owner, chain, false, {
v: undefined,
init: function(v) {
if ( mustAbort(v) ) { return false; }
this.v = v;
return true;
},
getter: function() {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( document.currentScript === thisScript ) {
return this.v;
}
safe.uboLog(logPrefix, 'Property read');
return normalValue;
},
setter: function(a) {
if ( mustAbort(a) === false ) { return; }
normalValue = a;
}
});
return;
}
const prop = chain.slice(0, pos);
const v = owner[prop];
chain = chain.slice(pos + 1);
if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) {
trapChain(v, chain);
return;
}
trapProp(owner, prop, true, {
v: undefined,
init: function(v) {
this.v = v;
return true;
},
getter: function() {
return this.v;
},
setter: function(a) {
this.v = a;
if ( a instanceof safe.Object ) {
trapChain(a, chain);
}
}
});
};
trapChain(window, chain);
}
runAt(( ) => {
setConstant(chain, rawValue);
}, extraArgs.runAt);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'replace-node-text.fn',
fn: replaceNodeTextFn,
dependencies: [
'get-random-token.fn',
'run-at.fn',
'safe-self.fn',
],
});
function replaceNodeTextFn(
nodeName = '',
pattern = '',
replacement = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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 reIncludes = extraArgs.includes || extraArgs.condition
? safe.patternToRegex(extraArgs.includes || extraArgs.condition, 'ms')
: null;
const reExcludes = extraArgs.excludes
? safe.patternToRegex(extraArgs.excludes, 'ms')
: null;
const stop = (takeRecord = true) => {
if ( takeRecord ) {
handleMutations(observer.takeRecords());
}
observer.disconnect();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, 'Quitting');
}
};
const textContentFactory = (( ) => {
const out = { createScript: s => s };
const { trustedTypes: tt } = self;
if ( tt instanceof Object ) {
if ( typeof tt.getPropertyType === 'function' ) {
if ( tt.getPropertyType('script', 'textContent') === 'TrustedScript' ) {
return tt.createPolicy(getRandomToken(), out);
}
}
}
return out;
})();
let sedCount = extraArgs.sedCount || 0;
const handleNode = node => {
const before = node.textContent;
if ( reIncludes ) {
reIncludes.lastIndex = 0;
if ( safe.RegExp_test.call(reIncludes, before) === false ) { return true; }
}
if ( reExcludes ) {
reExcludes.lastIndex = 0;
if ( safe.RegExp_test.call(reExcludes, before) ) { return true; }
}
rePattern.lastIndex = 0;
if ( safe.RegExp_test.call(rePattern, before) === false ) { return true; }
rePattern.lastIndex = 0;
const after = pattern !== ''
? before.replace(rePattern, replacement)
: replacement;
node.textContent = node.nodeName === 'SCRIPT'
? textContentFactory.createScript(after)
: after;
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Text before:\n${before.trim()}`);
}
safe.uboLog(logPrefix, `Text after:\n${after.trim()}`);
return sedCount === 0 || (sedCount -= 1) !== 0;
};
const handleMutations = mutations => {
for ( const mutation of mutations ) {
for ( const node of mutation.addedNodes ) {
if ( reNodeName.test(node.nodeName) === false ) { continue; }
if ( handleNode(node) ) { continue; }
stop(false); return;
}
}
};
const observer = new MutationObserver(handleMutations);
observer.observe(document, { childList: true, subtree: true });
if ( document.documentElement ) {
const treeWalker = document.createTreeWalker(
document.documentElement,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
);
let count = 0;
for (;;) {
const node = treeWalker.nextNode();
count += 1;
if ( node === null ) { break; }
if ( reNodeName.test(node.nodeName) === false ) { continue; }
if ( node === document.currentScript ) { continue; }
if ( handleNode(node) ) { continue; }
stop(); break;
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, `${count} nodes present before installing mutation observer`);
}
if ( extraArgs.stay ) { return; }
runAt(( ) => {
const quitAfter = extraArgs.quitAfter || 0;
if ( quitAfter !== 0 ) {
setTimeout(( ) => { stop(); }, quitAfter);
} else {
stop();
}
}, 'interactive');
}
/******************************************************************************/
builtinScriptlets.push({
name: 'object-prune.fn',
fn: objectPruneFn,
dependencies: [
'matches-stack-trace.fn',
'object-find-owner.fn',
],
});
// When no "prune paths" argument is provided, the scriptlet is
// used for logging purpose and the "needle paths" argument is
// used to filter logging output.
//
// https://github.com/uBlockOrigin/uBlock-issues/issues/1545
// - Add support for "remove everything if needle matches" case
function objectPruneFn(
obj,
rawPrunePaths,
rawNeedlePaths,
stackNeedleDetails = { matchAll: true },
extraArgs = {}
) {
if ( typeof rawPrunePaths !== 'string' ) { return; }
const prunePaths = rawPrunePaths !== ''
? rawPrunePaths.split(/ +/)
: [];
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
? rawNeedlePaths.split(/ +/)
: [];
if ( stackNeedleDetails.matchAll !== true ) {
if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) {
return;
}
}
if ( objectPruneFn.mustProcess === undefined ) {
objectPruneFn.mustProcess = (root, needlePaths) => {
for ( const needlePath of needlePaths ) {
if ( objectFindOwnerFn(root, needlePath) === false ) {
return false;
}
}
return true;
};
}
if ( prunePaths.length === 0 ) { return; }
let outcome = 'nomatch';
if ( objectPruneFn.mustProcess(obj, needlePaths) ) {
for ( const path of prunePaths ) {
if ( objectFindOwnerFn(obj, path, true) ) {
outcome = 'match';
}
}
}
if ( outcome === 'match' ) { return obj; }
}
/******************************************************************************/
builtinScriptlets.push({
name: 'object-find-owner.fn',
fn: objectFindOwnerFn,
});
function objectFindOwnerFn(
root,
path,
prune = false
) {
let owner = root;
let chain = path;
for (;;) {
if ( typeof owner !== 'object' || owner === null ) { return false; }
const pos = chain.indexOf('.');
if ( pos === -1 ) {
if ( prune === false ) {
return owner.hasOwnProperty(chain);
}
let modified = false;
if ( chain === '*' ) {
for ( const key in owner ) {
if ( owner.hasOwnProperty(key) === false ) { continue; }
delete owner[key];
modified = true;
}
} else if ( owner.hasOwnProperty(chain) ) {
delete owner[chain];
modified = true;
}
return modified;
}
const prop = chain.slice(0, pos);
const next = chain.slice(pos + 1);
let found = false;
if ( prop === '[-]' && Array.isArray(owner) ) {
let i = owner.length;
while ( i-- ) {
if ( objectFindOwnerFn(owner[i], next) === false ) { continue; }
owner.splice(i, 1);
found = true;
}
return found;
}
if ( prop === '{-}' && owner instanceof Object ) {
for ( const key of Object.keys(owner) ) {
if ( objectFindOwnerFn(owner[key], next) === false ) { continue; }
delete owner[key];
found = true;
}
return found;
}
if (
prop === '[]' && Array.isArray(owner) ||
prop === '{}' && owner instanceof Object ||
prop === '*' && owner instanceof Object
) {
for ( const key of Object.keys(owner) ) {
if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; }
found = true;
}
return found;
}
if ( owner.hasOwnProperty(prop) === false ) { return false; }
owner = owner[prop];
chain = chain.slice(pos + 1);
}
}
/******************************************************************************/
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: 'set-local-storage-item.fn',
fn: setLocalStorageItemFn,
dependencies: [
'get-safe-cookie-values.fn',
'safe-self.fn',
],
});
function setLocalStorageItemFn(
which = 'local',
trusted = false,
key = '',
value = '',
) {
if ( key === '' ) { return; }
// For increased compatibility with AdGuard
if ( value === 'emptyArr' ) {
value = '[]';
} else if ( value === 'emptyObj' ) {
value = '{}';
}
const trustedValues = [
'',
'undefined', 'null',
'{}', '[]', '""',
'$remove$',
...getSafeCookieValuesFn(),
];
if ( trusted ) {
if ( value.includes('$now$') ) {
value = value.replaceAll('$now$', Date.now());
}
if ( value.includes('$currentDate$') ) {
value = value.replaceAll('$currentDate$', `${Date()}`);
}
if ( value.includes('$currentISODate$') ) {
value = value.replaceAll('$currentISODate$', (new Date()).toISOString());
}
} else {
const normalized = value.toLowerCase();
const match = /^("?)(.+)\1$/.exec(normalized);
const unquoted = match && match[2] || normalized;
if ( trustedValues.includes(unquoted) === false ) {
if ( /^\d+$/.test(unquoted) === false ) { return; }
const n = parseInt(unquoted, 10);
if ( n > 32767 ) { return; }
}
}
try {
const storage = self[`${which}Storage`];
if ( value === '$remove$' ) {
const safe = safeSelf();
const pattern = safe.patternToRegex(key, undefined, true );
const toRemove = [];
for ( let i = 0, n = storage.length; i < n; i++ ) {
const key = storage.key(i);
if ( pattern.test(key) ) { toRemove.push(key); }
}
for ( const key of toRemove ) {
storage.removeItem(key);
}
} else {
storage.setItem(key, `${value}`);
}
} catch(ex) {
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'matches-stack-trace.fn',
fn: matchesStackTrace,
dependencies: [
'get-exception-token.fn',
'safe-self.fn',
],
});
function matchesStackTrace(
needleDetails,
logLevel = ''
) {
const safe = safeSelf();
const exceptionToken = getExceptionToken();
const error = new safe.Error(exceptionToken);
const docURL = new URL(self.location.href);
docURL.hash = '';
// Normalize stack trace
const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/;
const lines = [];
for ( let line of error.stack.split(/[\n\r]+/) ) {
if ( line.includes(exceptionToken) ) { continue; }
line = line.trim();
const match = safe.RegExp_exec.call(reLine, line);
if ( match === null ) { continue; }
let url = match[2];
if ( url.startsWith('(') ) { url = url.slice(1); }
if ( url === docURL.href ) {
url = 'inlineScript';
} else if ( url.startsWith('<anonymous>') ) {
url = 'injectedScript';
}
let fn = match[1] !== undefined
? match[1].slice(0, -1)
: line.slice(0, match.index).trim();
if ( fn.startsWith('at') ) { fn = fn.slice(2).trim(); }
let rowcol = match[3];
lines.push(' ' + `${fn} ${url}${rowcol}:1`.trim());
}
lines[0] = `stackDepth:${lines.length-1}`;
const stack = lines.join('\t');
const r = needleDetails.matchAll !== true &&
safe.testPattern(needleDetails, stack);
if (
logLevel === 'all' ||
logLevel === 'match' && r ||
logLevel === 'nomatch' && !r
) {
safe.uboLog(stack.replace(/\t/g, '\n'));
}
return r;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'parse-properties-to-match.fn',
fn: parsePropertiesToMatch,
dependencies: [
'safe-self.fn',
],
});
function parsePropertiesToMatch(propsToMatch, implicit = '') {
const safe = safeSelf();
const needles = new Map();
if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; }
const options = { canNegate: true };
for ( const needle of propsToMatch.split(/\s+/) ) {
const [ prop, pattern ] = needle.split(':');
if ( prop === '' ) { continue; }
if ( pattern !== undefined ) {
needles.set(prop, safe.initPattern(pattern, options));
} else if ( implicit !== '' ) {
needles.set(implicit, safe.initPattern(prop, options));
}
}
return needles;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'match-object-properties.fn',
fn: matchObjectProperties,
dependencies: [
'safe-self.fn',
],
});
function matchObjectProperties(propNeedles, ...objs) {
if ( matchObjectProperties.extractProperties === undefined ) {
matchObjectProperties.extractProperties = (src, des, props) => {
for ( const p of props ) {
const v = src[p];
if ( v === undefined ) { continue; }
des[p] = src[p];
}
};
}
const safe = safeSelf();
const haystack = {};
const props = safe.Array_from(propNeedles.keys());
for ( const obj of objs ) {
if ( obj instanceof Object === false ) { continue; }
matchObjectProperties.extractProperties(obj, haystack, props);
}
for ( const [ prop, details ] of propNeedles ) {
let value = haystack[prop];
if ( value === undefined ) { continue; }
if ( typeof value !== 'string' ) {
try { value = safe.JSON_stringify(value); }
catch(ex) { }
if ( typeof value !== 'string' ) { continue; }
}
if ( safe.testPattern(details, value) ) { continue; }
return false;
}
return true;
}
/******************************************************************************/
builtinScriptlets.push({
name: 'json-prune-fetch-response.fn',
fn: jsonPruneFetchResponseFn,
dependencies: [
'match-object-properties.fn',
'object-prune.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function jsonPruneFetchResponseFn(
rawPrunePaths = '',
rawNeedlePaths = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('json-prune-fetch-response', rawPrunePaths, rawNeedlePaths);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
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);
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 ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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]);
}
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
outcome = 'nomatch';
}
}
if ( logall === false && outcome === 'nomatch' ) { return fetchPromise; }
if ( safe.logLevel > 1 && outcome !== 'nomatch' && propNeedles.size !== 0 ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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,
rawNeedlePaths,
stackNeedle,
extraArgs
);
if ( typeof objAfter !== 'object' ) { return responseBefore; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Pruned');
const responseAfter = Response.json(objAfter, {
status: responseBefore.status,
statusText: responseBefore.statusText,
headers: responseBefore.headers,
});
Object.defineProperties(responseAfter, {
ok: { value: responseBefore.ok },
redirected: { value: responseBefore.redirected },
type: { value: responseBefore.type },
url: { value: responseBefore.url },
});
return responseAfter;
}).catch(reason => {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, 'Error:', reason);
return responseBefore;
});
}).catch(reason => {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, 'Error:', reason);
return fetchPromise;
});
};
self.fetch = new Proxy(self.fetch, {
apply: applyHandler
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'replace-fetch-response.fn',
fn: replaceFetchResponseFn,
dependencies: [
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function replaceFetchResponseFn(
trusted = false,
pattern = '',
replacement = '',
propsToMatch = ''
) {
if ( trusted !== true ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('replace-fetch-response', pattern, replacement, propsToMatch);
if ( pattern === '*' ) { pattern = '.*'; }
const rePattern = safe.patternToRegex(pattern);
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null;
self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) {
const fetchPromise = Reflect.apply(target, thisArg, args);
if ( pattern === '' ) { return fetchPromise; }
let outcome = 'match';
if ( propNeedles.size !== 0 ) {
const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ];
if ( objs[0] instanceof Request ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
try {
objs[0] = safe.Request_clone.call(objs[0]);
}
catch(ex) {
safe.uboErr(logPrefix, ex);
}
}
if ( args[1] instanceof Object ) {
objs.push(args[1]);
}
if ( matchObjectProperties(propNeedles, ...objs) === false ) {
outcome = 'nomatch';
}
}
if ( outcome === 'nomatch' ) { return fetchPromise; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch"\n${propsToMatch}`);
}
return fetchPromise.then(responseBefore => {
const response = responseBefore.clone();
return response.text().then(textBefore => {
if ( reIncludes && reIncludes.test(textBefore) === false ) {
return responseBefore;
}
const textAfter = textBefore.replace(rePattern, replacement);
const outcome = textAfter !== textBefore ? 'match' : 'nomatch';
if ( outcome === 'nomatch' ) { return responseBefore; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Replaced');
const responseAfter = new Response(textAfter, {
status: responseBefore.status,
statusText: responseBefore.statusText,
headers: responseBefore.headers,
});
Object.defineProperties(responseAfter, {
ok: { value: responseBefore.ok },
redirected: { value: responseBefore.redirected },
type: { value: responseBefore.type },
url: { value: responseBefore.url },
});
return responseAfter;
}).catch(reason => {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, reason);
return responseBefore;
});
}).catch(reason => {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, reason);
return fetchPromise;
});
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'proxy-apply.fn',
fn: proxyApplyFn,
});
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 = 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 = undefined;
proxyApplyFn.applyContexts.push(this);
return r;
}
static factory(...args) {
return proxyApplyFn.applyContexts.length !== 0
? proxyApplyFn.applyContexts.pop().init(...args)
: new proxyApplyFn.ApplyContext(...args);
}
};
}
2024-08-18 07:12:54 -06:00
const fnStr = fn.toString();
const toString = (function toString() { return fnStr; }).bind(null);
const proxyDetails = {
apply(target, thisArg, args) {
return handler(proxyApplyFn.ApplyContext.factory(target, thisArg, args));
},
get(target, prop) {
if ( prop === 'toString' ) { return toString; }
return Reflect.get(target, prop);
},
};
if ( fn.prototype?.constructor === fn ) {
proxyDetails.construct = function(target, args) {
return handler(proxyApplyFn.CtorContext.factory(target, args));
};
}
context[prop] = new Proxy(fn, proxyDetails);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'prevent-xhr.fn',
fn: preventXhrFn,
dependencies: [
'generate-content.fn',
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function preventXhrFn(
trusted = false,
propsToMatch = '',
directive = ''
) {
if ( typeof propsToMatch !== 'string' ) { return; }
const safe = safeSelf();
const scriptletName = trusted ? 'trusted-prevent-xhr' : 'prevent-xhr';
const logPrefix = safe.makeLogPrefix(scriptletName, propsToMatch, directive);
const xhrInstances = new WeakMap();
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
const warOrigin = scriptletGlobals.warOrigin;
const safeDispatchEvent = (xhr, type) => {
try {
xhr.dispatchEvent(new Event(type));
} catch(_) {
}
};
const XHRBefore = XMLHttpRequest.prototype;
self.XMLHttpRequest = class extends self.XMLHttpRequest {
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) ) {
const xhrDetails = Object.assign(haystack, {
xhr: this,
defer: args.length === 0 || !!args[0],
directive,
headers: {
'date': '',
'content-type': '',
'content-length': '',
},
2024-11-08 06:48:07 -07:00
url: haystack.url,
props: {
response: { value: '' },
responseText: { value: '' },
responseXML: { value: null },
},
});
xhrInstances.set(this, xhrDetails);
}
return super.open(method, url, ...args);
}
send(...args) {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined ) {
return super.send(...args);
}
xhrDetails.headers['date'] = (new Date()).toUTCString();
let xhrText = '';
switch ( this.responseType ) {
case 'arraybuffer':
xhrDetails.props.response.value = new ArrayBuffer(0);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'blob':
xhrDetails.props.response.value = new Blob([]);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'document': {
const parser = new DOMParser();
const doc = parser.parseFromString('', 'text/html');
xhrDetails.props.response.value = doc;
xhrDetails.props.responseXML.value = doc;
xhrDetails.headers['content-type'] = 'text/html';
break;
}
case 'json':
xhrDetails.props.response.value = {};
xhrDetails.props.responseText.value = '{}';
xhrDetails.headers['content-type'] = 'application/json';
break;
default: {
if ( directive === '' ) { break; }
xhrText = generateContentFn(trusted, xhrDetails.directive);
if ( xhrText instanceof Promise ) {
xhrText = xhrText.then(text => {
xhrDetails.props.response.value = text;
xhrDetails.props.responseText.value = text;
});
} else {
xhrDetails.props.response.value = xhrText;
xhrDetails.props.responseText.value = xhrText;
}
xhrDetails.headers['content-type'] = 'text/plain';
break;
}
}
if ( xhrDetails.defer === false ) {
xhrDetails.headers['content-length'] = `${xhrDetails.props.response.value}`.length;
Object.defineProperties(xhrDetails.xhr, {
readyState: { value: 4 },
2024-11-08 06:48:07 -07:00
responseURL: { value: xhrDetails.url },
status: { value: 200 },
statusText: { value: 'OK' },
});
Object.defineProperties(xhrDetails.xhr, xhrDetails.props);
return;
}
Promise.resolve(xhrText).then(( ) => xhrDetails).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 1, configurable: true },
2024-11-08 06:48:07 -07:00
responseURL: { value: xhrDetails.url },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
xhrDetails.headers['content-length'] = `${details.props.response.value}`.length;
Object.defineProperties(details.xhr, {
readyState: { value: 2, configurable: true },
status: { value: 200 },
statusText: { value: 'OK' },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 3, configurable: true },
});
Object.defineProperties(details.xhr, details.props);
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 4 },
});
safeDispatchEvent(details.xhr, 'readystatechange');
safeDispatchEvent(details.xhr, 'load');
safeDispatchEvent(details.xhr, 'loadend');
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
});
}
getResponseHeader(headerName) {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
return super.getResponseHeader(headerName);
}
const value = xhrDetails.headers[headerName.toLowerCase()];
if ( value !== undefined && value !== '' ) { return value; }
return null;
}
getAllResponseHeaders() {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
return super.getAllResponseHeaders();
}
const out = [];
for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) {
if ( !value ) { continue; }
out.push(`${name}: ${value}`);
}
if ( out.length !== 0 ) { out.push(''); }
return out.join('\r\n');
}
};
self.XMLHttpRequest.prototype.open.toString = function() {
return XHRBefore.open.toString();
};
self.XMLHttpRequest.prototype.send.toString = function() {
return XHRBefore.send.toString();
};
self.XMLHttpRequest.prototype.getResponseHeader.toString = function() {
return XHRBefore.getResponseHeader.toString();
};
self.XMLHttpRequest.prototype.getAllResponseHeaders.toString = function() {
return XHRBefore.getAllResponseHeaders.toString();
};
}
/*******************************************************************************
Injectable scriptlets
These are meant to be used in the MAIN (webpage) execution world.
*******************************************************************************/
builtinScriptlets.push({
name: 'abort-current-script.js',
aliases: [
'acs.js',
'abort-current-inline-script.js',
'acis.js',
],
fn: abortCurrentScript,
dependencies: [
'abort-current-script-core.fn',
'run-at-html-element.fn',
],
});
// Issues to mind before changing anything:
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
function abortCurrentScript(...args) {
runAtHtmlElementFn(( ) => {
abortCurrentScriptCore(...args);
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'abort-on-property-read.js',
aliases: [
'aopr.js',
],
fn: abortOnPropertyRead,
dependencies: [
'get-exception-token.fn',
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
'safe-self.fn',
],
});
function abortOnPropertyRead(
chain = ''
) {
if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('abort-on-property-read', chain);
const exceptionToken = getExceptionToken();
const abort = function() {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
};
const makeProxy = function(owner, chain) {
const pos = chain.indexOf('.');
if ( pos === -1 ) {
const desc = Object.getOwnPropertyDescriptor(owner, chain);
if ( !desc || desc.get !== abort ) {
Object.defineProperty(owner, chain, {
get: abort,
set: function(){}
});
}
return;
}
const prop = chain.slice(0, pos);
let v = owner[prop];
chain = chain.slice(pos + 1);
if ( v ) {
makeProxy(v, chain);
return;
}
const desc = Object.getOwnPropertyDescriptor(owner, prop);
if ( desc && desc.set !== undefined ) { return; }
Object.defineProperty(owner, prop, {
get: function() { return v; },
set: function(a) {
v = a;
if ( a instanceof Object ) {
makeProxy(a, chain);
}
}
});
};
const owner = window;
makeProxy(owner, chain);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'abort-on-property-write.js',
aliases: [
'aopw.js',
],
fn: abortOnPropertyWrite,
dependencies: [
'get-exception-token.fn',
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
'safe-self.fn',
],
});
function abortOnPropertyWrite(
prop = ''
) {
if ( typeof prop !== 'string' ) { return; }
if ( prop === '' ) { return; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const safe = safeSelf();
2024-02-15 07:47:15 -07:00
const logPrefix = safe.makeLogPrefix('abort-on-property-write', prop);
const exceptionToken = getExceptionToken();
let owner = window;
for (;;) {
const pos = prop.indexOf('.');
if ( pos === -1 ) { break; }
owner = owner[prop.slice(0, pos)];
if ( owner instanceof Object === false ) { return; }
prop = prop.slice(pos + 1);
}
delete owner[prop];
Object.defineProperty(owner, prop, {
set: function() {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Aborted');
throw new ReferenceError(exceptionToken);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'abort-on-stack-trace.js',
aliases: [
'aost.js',
],
fn: abortOnStackTrace,
dependencies: [
'get-exception-token.fn',
'matches-stack-trace.fn',
'safe-self.fn',
],
});
function abortOnStackTrace(
chain = '',
needle = ''
) {
if ( typeof chain !== 'string' ) { return; }
const safe = safeSelf();
const needleDetails = safe.initPattern(needle, { canNegate: true });
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
if ( needle === '' ) { extraArgs.log = 'all'; }
const makeProxy = function(owner, chain) {
const pos = chain.indexOf('.');
if ( pos === -1 ) {
let v = owner[chain];
Object.defineProperty(owner, chain, {
get: function() {
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
throw new ReferenceError(getExceptionToken());
}
return v;
},
set: function(a) {
if ( matchesStackTrace(needleDetails, extraArgs.log) ) {
throw new ReferenceError(getExceptionToken());
}
v = a;
},
});
return;
}
const prop = chain.slice(0, pos);
let v = owner[prop];
chain = chain.slice(pos + 1);
if ( v ) {
makeProxy(v, chain);
return;
}
const desc = Object.getOwnPropertyDescriptor(owner, prop);
if ( desc && desc.set !== undefined ) { return; }
Object.defineProperty(owner, prop, {
get: function() { return v; },
set: function(a) {
v = a;
if ( a instanceof Object ) {
makeProxy(a, chain);
}
}
});
};
const owner = window;
makeProxy(owner, chain);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'addEventListener-defuser.js',
aliases: [
'aeld.js',
'prevent-addEventListener.js',
],
fn: addEventListenerDefuser,
dependencies: [
'proxy-apply.fn',
'run-at.fn',
'safe-self.fn',
'should-debug.fn',
],
});
// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120
function addEventListenerDefuser(
type = '',
pattern = ''
) {
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 debug = shouldDebug(extraArgs);
const targetSelector = extraArgs.elements || undefined;
const elementMatches = elem => {
if ( targetSelector === 'window' ) { return elem === window; }
if ( targetSelector === 'document' ) { return elem === document; }
if ( elem && elem.matches && elem.matches(targetSelector) ) { return true; }
const elems = Array.from(document.querySelectorAll(targetSelector));
return elems.includes(elem);
};
const elementDetails = elem => {
if ( elem instanceof Window ) { return 'window'; }
if ( elem instanceof Document ) { return 'document'; }
if ( elem instanceof Element === false ) { return '?'; }
const parts = [];
// https://github.com/uBlockOrigin/uAssets/discussions/17907#discussioncomment-9871079
const id = String(elem.id);
if ( id !== '' ) { parts.push(`#${CSS.escape(id)}`); }
for ( let i = 0; i < elem.classList.length; i++ ) {
parts.push(`.${CSS.escape(elem.classList.item(i))}`);
}
for ( let i = 0; i < elem.attributes.length; i++ ) {
const attr = elem.attributes.item(i);
if ( attr.name === 'id' ) { continue; }
if ( attr.name === 'class' ) { continue; }
parts.push(`[${CSS.escape(attr.name)}="${attr.value}"]`);
}
return parts.join('');
};
const shouldPrevent = (thisArg, type, handler) => {
const matchesType = safe.RegExp_test.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 ) {
2024-03-20 07:31:17 -06:00
debugger; // eslint-disable-line no-debugger
}
if ( matchesBoth && targetSelector !== undefined ) {
if ( elementMatches(thisArg) === false ) { return false; }
}
return matchesBoth;
};
runAt(( ) => {
proxyApplyFn('EventTarget.prototype.addEventListener', function(context) {
const { callArgs, thisArg } = context;
let t, h;
try {
t = String(callArgs[0]);
if ( typeof callArgs[1] === 'function' ) {
h = String(safe.Function_toString(callArgs[1]));
} else if ( typeof callArgs[1] === 'object' && callArgs[1] !== null ) {
if ( typeof callArgs[1].handleEvent === 'function' ) {
h = String(safe.Function_toString(callArgs[1].handleEvent));
}
} else {
h = String(callArgs[1]);
2023-05-28 12:56:31 -06:00
}
} catch(ex) {
}
if ( type === '' && pattern === '' ) {
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
} else if ( shouldPrevent(thisArg, t, h) ) {
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
}
return context.reflect();
});
}, extraArgs.runAt);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'json-prune.js',
fn: jsonPrune,
dependencies: [
'object-prune.fn',
'safe-self.fn',
],
});
function jsonPrune(
rawPrunePaths = '',
rawNeedlePaths = '',
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
);
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;
},
});
}
/*******************************************************************************
*
* json-prune-fetch-response.js
*
* Prune JSON response of fetch requests.
*
**/
builtinScriptlets.push({
name: 'json-prune-fetch-response.js',
fn: jsonPruneFetchResponse,
dependencies: [
'json-prune-fetch-response.fn',
],
});
function jsonPruneFetchResponse(...args) {
jsonPruneFetchResponseFn(...args);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'json-prune-xhr-response.js',
fn: jsonPruneXhrResponse,
dependencies: [
'match-object-properties.fn',
'object-prune.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function jsonPruneXhrResponse(
rawPrunePaths = '',
rawNeedlePaths = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('json-prune-xhr-response', rawPrunePaths, rawNeedlePaths);
const xhrInstances = new WeakMap();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 2);
const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url');
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
self.XMLHttpRequest = class extends self.XMLHttpRequest {
open(method, url, ...args) {
const xhrDetails = { method, url };
let outcome = 'match';
if ( propNeedles.size !== 0 ) {
if ( matchObjectProperties(propNeedles, xhrDetails) === false ) {
outcome = 'nomatch';
}
}
if ( outcome === 'match' ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched optional "propsToMatch", "${extraArgs.propsToMatch}"`);
}
xhrInstances.set(this, xhrDetails);
}
return super.open(method, url, ...args);
}
get response() {
const innerResponse = super.response;
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined ) {
return innerResponse;
}
2023-09-22 07:33:02 -06:00
const responseLength = typeof innerResponse === 'string'
? innerResponse.length
: undefined;
if ( xhrDetails.lastResponseLength !== responseLength ) {
xhrDetails.response = undefined;
2023-09-22 07:33:02 -06:00
xhrDetails.lastResponseLength = responseLength;
}
if ( xhrDetails.response !== undefined ) {
return xhrDetails.response;
}
let objBefore;
if ( typeof innerResponse === 'object' ) {
objBefore = innerResponse;
} else if ( typeof innerResponse === 'string' ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
try {
objBefore = safe.JSON_parse(innerResponse);
} catch(ex) {
}
}
if ( typeof objBefore !== 'object' ) {
return (xhrDetails.response = innerResponse);
}
const objAfter = objectPruneFn(
objBefore,
rawPrunePaths,
rawNeedlePaths,
stackNeedle,
extraArgs
);
let outerResponse;
if ( typeof objAfter === 'object' ) {
outerResponse = typeof innerResponse === 'string'
? safe.JSON_stringify(objAfter)
: objAfter;
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Pruned');
} else {
outerResponse = innerResponse;
}
return (xhrDetails.response = outerResponse);
}
get responseText() {
const response = this.response;
return typeof response !== 'string'
? super.responseText
: response;
}
};
}
/******************************************************************************/
// There is still code out there which uses `eval` in lieu of `JSON.parse`.
builtinScriptlets.push({
name: 'evaldata-prune.js',
fn: evaldataPrune,
dependencies: [
'object-prune.fn',
],
});
function evaldataPrune(
rawPrunePaths = '',
rawNeedlePaths = ''
) {
self.eval = new Proxy(self.eval, {
apply(target, thisArg, args) {
const before = Reflect.apply(target, thisArg, args);
if ( typeof before === 'object' ) {
const after = objectPruneFn(before, rawPrunePaths, rawNeedlePaths);
return after || before;
}
return before;
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'adjust-setInterval.js',
aliases: [
'nano-setInterval-booster.js',
'nano-sib.js',
],
fn: adjustSetInterval,
dependencies: [
'safe-self.fn',
],
});
// Imported from:
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126
//
// Speed up or down setInterval, 3 optional arguments.
// The payload matcher, a string literal or a JavaScript RegExp, defaults
// to match all.
// delayMatcher
// The delay matcher, an integer, defaults to 1000.
// Use `*` to match any delay.
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
// 20 times. Speed up and down both cap at 50 times.
function adjustSetInterval(
needleArg = '',
delayArg = '',
boostArg = ''
) {
if ( typeof needleArg !== 'string' ) { return; }
const safe = safeSelf();
const reNeedle = safe.patternToRegex(needleArg);
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
let boost = parseFloat(boostArg);
boost = isNaN(boost) === false && isFinite(boost)
? Math.min(Math.max(boost, 0.001), 50)
: 0.05;
self.setInterval = new Proxy(self.setInterval, {
apply: function(target, thisArg, args) {
const [ a, b ] = args;
if (
(delay === -1 || b === delay) &&
reNeedle.test(a.toString())
) {
args[1] = b * boost;
}
return target.apply(thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'adjust-setTimeout.js',
aliases: [
'nano-setTimeout-booster.js',
'nano-stb.js',
],
fn: adjustSetTimeout,
dependencies: [
'safe-self.fn',
],
});
// Imported from:
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82
//
// Speed up or down setTimeout, 3 optional arguments.
// funcMatcher
// The payload matcher, a string literal or a JavaScript RegExp, defaults
// to match all.
// delayMatcher
// The delay matcher, an integer, defaults to 1000.
// Use `*` to match any delay.
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
// 20 times. Speed up and down both cap at 50 times.
function adjustSetTimeout(
needleArg = '',
delayArg = '',
boostArg = ''
) {
if ( typeof needleArg !== 'string' ) { return; }
const safe = safeSelf();
const reNeedle = safe.patternToRegex(needleArg);
let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; }
let boost = parseFloat(boostArg);
boost = isNaN(boost) === false && isFinite(boost)
? Math.min(Math.max(boost, 0.001), 50)
: 0.05;
self.setTimeout = new Proxy(self.setTimeout, {
apply: function(target, thisArg, args) {
const [ a, b ] = args;
if (
(delay === -1 || b === delay) &&
reNeedle.test(a.toString())
) {
args[1] = b * boost;
}
return target.apply(thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'noeval-if.js',
aliases: [
'prevent-eval-if.js',
],
fn: noEvalIf,
dependencies: [
'safe-self.fn',
],
});
function noEvalIf(
needle = ''
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('noeval-if', needle);
const reNeedle = safe.patternToRegex(needle);
window.eval = new Proxy(window.eval, { // jshint ignore: line
apply: function(target, thisArg, args) {
const a = String(args[0]);
if ( needle !== '' && reNeedle.test(a) ) {
safe.uboLog(logPrefix, 'Prevented:\n', a);
return;
}
if ( needle === '' || safe.logLevel > 1 ) {
safe.uboLog(logPrefix, 'Not prevented:\n', a);
}
return Reflect.apply(target, thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
name: 'prevent-fetch.js',
aliases: [
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
'no-fetch-if.js',
],
fn: noFetchIf,
dependencies: [
'generate-content.fn',
'proxy-apply.fn',
'safe-self.fn',
],
});
function noFetchIf(
propsToMatch = '',
responseBody = '',
responseType = ''
) {
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('prevent-fetch', propsToMatch, responseBody, responseType);
const needles = [];
for ( const condition of propsToMatch.split(/\s+/) ) {
if ( condition === '' ) { continue; }
const pos = condition.indexOf(':');
let key, value;
if ( pos !== -1 ) {
key = condition.slice(0, pos);
value = condition.slice(pos + 1);
} else {
key = 'url';
value = condition;
}
needles.push({ key, pattern: safe.initPattern(value, { canNegate: true }) });
}
const validResponseProps = {
ok: [ false, true ],
statusText: [ '', 'Not Found' ],
type: [ 'basic', 'cors', 'default', 'error', 'opaque' ],
};
const responseProps = {
statusText: { value: 'OK' },
};
if ( /^\{.*\}$/.test(responseType) ) {
try {
Object.entries(JSON.parse(responseType)).forEach(([ p, v ]) => {
if ( validResponseProps[p] === undefined ) { return; }
if ( validResponseProps[p].includes(v) === false ) { return; }
responseProps[p] = { value: v };
});
}
catch(ex) {}
} else if ( responseType !== '' ) {
if ( validResponseProps.type.includes(responseType) ) {
responseProps.type = { value: responseType };
}
}
proxyApplyFn('fetch', function fetch(context) {
const { callArgs } = context;
const details = callArgs[0] instanceof self.Request
? callArgs[0]
: Object.assign({ url: callArgs[0] }, callArgs[1]);
let proceed = true;
try {
const props = new Map();
for ( const prop in details ) {
let v = details[prop];
if ( typeof v !== 'string' ) {
try { v = safe.JSON_stringify(v); }
catch(ex) { }
}
if ( typeof v !== 'string' ) { continue; }
props.set(prop, v);
}
if ( safe.logLevel > 1 || propsToMatch === '' && responseBody === '' ) {
const out = Array.from(props).map(a => `${a[0]}:${a[1]}`);
safe.uboLog(logPrefix, `Called: ${out.join('\n')}`);
}
if ( propsToMatch === '' && responseBody === '' ) {
return context.reflect();
}
proceed = needles.length === 0;
for ( const { key, pattern } of needles ) {
if (
pattern.expect && props.has(key) === false ||
safe.testPattern(pattern, props.get(key)) === false
) {
proceed = true;
break;
}
}
} catch(ex) {
}
if ( proceed ) {
return context.reflect();
}
return Promise.resolve(generateContentFn(false, responseBody)).then(text => {
safe.uboLog(logPrefix, `Prevented with response "${text}"`);
const response = new Response(text, {
headers: {
'Content-Length': text.length,
}
});
const props = Object.assign(
{ url: { value: details.url } },
responseProps
);
safe.Object_defineProperties(response, props);
return response;
});
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'prevent-refresh.js',
aliases: [
'refresh-defuser.js',
],
fn: preventRefresh,
world: 'ISOLATED',
dependencies: [
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
'safe-self.fn',
],
});
// https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/
function preventRefresh(
delay = ''
) {
if ( typeof delay !== 'string' ) { return; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('prevent-refresh', delay);
const stop = content => {
window.stop();
safe.uboLog(logPrefix, `Prevented "${content}"`);
};
const defuse = ( ) => {
const meta = document.querySelector('meta[http-equiv="refresh" i][content]');
if ( meta === null ) { return; }
const content = meta.getAttribute('content') || '';
const ms = delay === ''
? Math.max(parseFloat(content) || 0, 0) * 500
: 0;
if ( ms === 0 ) {
stop(content);
} else {
setTimeout(( ) => { stop(content); }, ms);
}
};
self.addEventListener('load', defuse, { capture: true, once: true });
}
/******************************************************************************/
builtinScriptlets.push({
name: 'remove-class.js',
aliases: [
'rc.js',
],
fn: removeClass,
world: 'ISOLATED',
dependencies: [
'run-at.fn',
'safe-self.fn',
],
});
function removeClass(
rawToken = '',
rawSelector = '',
behavior = ''
) {
if ( typeof rawToken !== 'string' ) { return; }
if ( rawToken === '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('remove-class', rawToken, rawSelector, behavior);
const tokens = rawToken.split(/\s*\|\s*/);
const selector = tokens
.map(a => `${rawSelector}.${CSS.escape(a)}`)
.join(',');
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
}
const mustStay = /\bstay\b/.test(behavior);
let timer;
const rmclass = ( ) => {
timer = undefined;
try {
const nodes = document.querySelectorAll(selector);
for ( const node of nodes ) {
node.classList.remove(...tokens);
safe.uboLog(logPrefix, 'Removed class(es)');
}
} catch(ex) {
}
if ( mustStay ) { return; }
if ( document.readyState !== 'complete' ) { return; }
observer.disconnect();
};
const mutationHandler = mutations => {
if ( timer !== undefined ) { return; }
let skip = true;
for ( let i = 0; i < mutations.length && skip; i++ ) {
const { type, addedNodes, removedNodes } = mutations[i];
if ( type === 'attributes' ) { skip = false; }
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
}
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
}
}
if ( skip ) { return; }
timer = safe.onIdle(rmclass, { timeout: 67 });
};
const observer = new MutationObserver(mutationHandler);
const start = ( ) => {
rmclass();
observer.observe(document, {
attributes: true,
attributeFilter: [ 'class' ],
childList: true,
subtree: true,
});
};
runAt(( ) => {
start();
}, /\bcomplete\b/.test(behavior) ? 'idle' : 'loading');
}
/******************************************************************************/
builtinScriptlets.push({
name: 'no-requestAnimationFrame-if.js',
aliases: [
'norafif.js',
'prevent-requestAnimationFrame.js',
],
fn: noRequestAnimationFrameIf,
dependencies: [
'safe-self.fn',
],
});
function noRequestAnimationFrameIf(
needle = ''
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); }
const log = needleNot === false && needle === '' ? console.log : undefined;
const reNeedle = safe.patternToRegex(needle);
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, {
apply: function(target, thisArg, args) {
const a = args[0] instanceof Function
? String(safe.Function_toString(args[0]))
: String(args[0]);
let defuse = false;
if ( log !== undefined ) {
log('uBO: requestAnimationFrame("%s")', a);
} else {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse ) {
args[0] = function(){};
}
return target.apply(thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'set-constant.js',
aliases: [
'set.js',
],
fn: setConstant,
dependencies: [
'set-constant.fn'
],
});
function setConstant(
...args
) {
setConstantFn(false, ...args);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'no-setInterval-if.js',
aliases: [
'nosiif.js',
'prevent-setInterval.js',
'setInterval-defuser.js',
],
fn: noSetIntervalIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function noSetIntervalIf(
needle = '',
delay = ''
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('prevent-setInterval', needle, delay);
const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); }
if ( delay === '' ) { delay = undefined; }
let delayNot = false;
if ( delay !== undefined ) {
delayNot = delay.charAt(0) === '!';
if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10);
}
const reNeedle = safe.patternToRegex(needle);
proxyApplyFn('setInterval', function setInterval(context) {
const { callArgs } = context;
const a = callArgs[0] instanceof Function
? String(safe.Function_toString(callArgs[0]))
: String(callArgs[0]);
const b = callArgs[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return context.reflect();
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
callArgs[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return context.reflect();
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'no-setTimeout-if.js',
aliases: [
'nostif.js',
'prevent-setTimeout.js',
'setTimeout-defuser.js',
],
fn: noSetTimeoutIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function noSetTimeoutIf(
needle = '',
delay = ''
) {
if ( typeof needle !== 'string' ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needle, delay);
const needleNot = needle.charAt(0) === '!';
if ( needleNot ) { needle = needle.slice(1); }
if ( delay === '' ) { delay = undefined; }
let delayNot = false;
if ( delay !== undefined ) {
delayNot = delay.charAt(0) === '!';
if ( delayNot ) { delay = delay.slice(1); }
delay = parseInt(delay, 10);
}
const reNeedle = safe.patternToRegex(needle);
proxyApplyFn('setTimeout', function setTimeout(context) {
const { callArgs } = context;
const a = callArgs[0] instanceof Function
? String(safe.Function_toString(callArgs[0]))
: String(callArgs[0]);
const b = callArgs[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return context.reflect();
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
callArgs[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return context.reflect();
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'webrtc-if.js',
fn: webrtcIf,
dependencies: [
'safe-self.fn',
],
});
function webrtcIf(
good = ''
) {
if ( typeof good !== 'string' ) { return; }
const safe = safeSelf();
const reGood = safe.patternToRegex(good);
const rtcName = window.RTCPeerConnection
? 'RTCPeerConnection'
: (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '');
if ( rtcName === '' ) { return; }
const log = console.log.bind(console);
const neuteredPeerConnections = new WeakSet();
const isGoodConfig = function(instance, config) {
if ( neuteredPeerConnections.has(instance) ) { return false; }
if ( config instanceof Object === false ) { return true; }
if ( Array.isArray(config.iceServers) === false ) { return true; }
for ( const server of config.iceServers ) {
const urls = typeof server.urls === 'string'
? [ server.urls ]
: server.urls;
if ( Array.isArray(urls) ) {
for ( const url of urls ) {
if ( reGood.test(url) ) { return true; }
}
}
if ( typeof server.username === 'string' ) {
if ( reGood.test(server.username) ) { return true; }
}
if ( typeof server.credential === 'string' ) {
if ( reGood.test(server.credential) ) { return true; }
}
}
neuteredPeerConnections.add(instance);
return false;
};
const peerConnectionCtor = window[rtcName];
const peerConnectionProto = peerConnectionCtor.prototype;
peerConnectionProto.createDataChannel =
new Proxy(peerConnectionProto.createDataChannel, {
apply: function(target, thisArg, args) {
if ( isGoodConfig(target, args[1]) === false ) {
log('uBO:', args[1]);
return Reflect.apply(target, thisArg, args.slice(0, 1));
}
return Reflect.apply(target, thisArg, args);
},
});
window[rtcName] =
new Proxy(peerConnectionCtor, {
construct: function(target, args) {
if ( isGoodConfig(target, args[0]) === false ) {
log('uBO:', args[0]);
return Reflect.construct(target);
}
return Reflect.construct(target, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'prevent-xhr.js',
aliases: [
'no-xhr-if.js',
],
fn: preventXhr,
dependencies: [
'prevent-xhr.fn',
],
});
function preventXhr(...args) {
return preventXhrFn(false, ...args);
}
/**
* @scriptlet prevent-window-open
*
* @description
* Prevent a webpage from opening new tabs through `window.open()`.
*
* @param pattern
* A plain string or regex to match against the `url` argument for the
* prevention to be triggered. If not provided, all calls to `window.open()`
* are prevented.
* If set to the special value `debug` *and* the logger is opened, the scriptlet
* will trigger a `debugger` statement and the prevention will not occur.
*
* @param [delay]
* If provided, a decoy will be created or opened, and this parameter states
* the number of seconds to wait for before the decoy is terminated, i.e.
* either removed from the DOM or closed.
*
* @param [decoy]
* A string representing the type of decoy to use:
* - `blank`: replace the `url` parameter with `about:blank`
* - `object`: create and append an `object` element to the DOM, and return
* its `contentWindow` property.
* - `frame`: create and append an `iframe` element to the DOM, and return
* its `contentWindow` property.
*
* @example
* ##+js(prevent-window-open, ads.example.com/)
*
* @example
* ##+js(prevent-window-open, ads.example.com/, 1, iframe)
*
* */
builtinScriptlets.push({
name: 'prevent-window-open.js',
aliases: [
'nowoif.js',
'no-window-open-if.js',
'window.open-defuser.js',
],
fn: noWindowOpenIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function noWindowOpenIf(
pattern = '',
delay = '',
decoy = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('no-window-open-if', pattern, delay, decoy);
const targetMatchResult = pattern.startsWith('!') === false;
if ( targetMatchResult === false ) {
pattern = pattern.slice(1);
}
const rePattern = safe.patternToRegex(pattern);
const autoRemoveAfter = (parseFloat(delay) || 0) * 1000;
const setTimeout = self.setTimeout;
const createDecoy = function(tag, urlProp, url) {
const decoyElem = document.createElement(tag);
decoyElem[urlProp] = url;
decoyElem.style.setProperty('height','1px', 'important');
decoyElem.style.setProperty('position','fixed', 'important');
decoyElem.style.setProperty('top','-1px', 'important');
decoyElem.style.setProperty('width','1px', 'important');
document.body.appendChild(decoyElem);
setTimeout(( ) => { decoyElem.remove(); }, autoRemoveAfter);
return decoyElem;
};
const noopFunc = function(){};
proxyApplyFn('open', function open(context) {
if ( pattern === 'debug' && safe.logLevel !== 0 ) {
debugger; // eslint-disable-line no-debugger
return context.reflect();
}
const { callArgs } = context;
const haystack = callArgs.join(' ');
if ( rePattern.test(haystack) !== targetMatchResult ) {
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Allowed (${callArgs.join(', ')})`);
}
return context.reflect();
}
safe.uboLog(logPrefix, `Prevented (${callArgs.join(', ')})`);
if ( delay === '' ) { return null; }
if ( decoy === 'blank' ) {
callArgs[0] = 'about:blank';
const r = context.reflect();
setTimeout(( ) => { r.close(); }, autoRemoveAfter);
return r;
}
const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...callArgs)
: createDecoy('iframe', 'src', ...callArgs);
let popup = decoyElem.contentWindow;
if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false });
} else {
popup = new Proxy(self, {
get: function(target, prop, ...args) {
if ( prop === 'closed' ) { return false; }
const r = Reflect.get(target, prop, ...args);
if ( typeof r === 'function' ) { return noopFunc; }
return r;
},
set: function(...args) {
return Reflect.set(...args);
},
});
}
if ( safe.logLevel !== 0 ) {
popup = new Proxy(popup, {
get: function(target, prop, ...args) {
const r = Reflect.get(target, prop, ...args);
safe.uboLog(logPrefix, `popup / get ${prop} === ${r}`);
if ( typeof r === 'function' ) {
return (...args) => { return r.call(target, ...args); };
}
return r;
},
set: function(target, prop, value, ...args) {
safe.uboLog(logPrefix, `popup / set ${prop} = ${value}`);
return Reflect.set(target, prop, value, ...args);
},
});
}
return popup;
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'close-window.js',
aliases: [
'window-close-if.js',
],
fn: closeWindow,
world: 'ISOLATED',
dependencies: [
'safe-self.fn',
],
});
// https://github.com/uBlockOrigin/uAssets/issues/10323#issuecomment-992312847
// https://github.com/AdguardTeam/Scriptlets/issues/158
// https://github.com/uBlockOrigin/uBlock-issues/discussions/2270
function closeWindow(
arg1 = ''
) {
if ( typeof arg1 !== 'string' ) { return; }
const safe = safeSelf();
let subject = '';
if ( /^\/.*\/$/.test(arg1) ) {
subject = window.location.href;
} else if ( arg1 !== '' ) {
subject = `${window.location.pathname}${window.location.search}`;
}
try {
const re = safe.patternToRegex(arg1);
if ( re.test(subject) ) {
window.close();
}
} catch(ex) {
console.log(ex);
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'window.name-defuser.js',
fn: windowNameDefuser,
});
// https://github.com/gorhill/uBlock/issues/1228
function windowNameDefuser() {
if ( window === window.top ) {
window.name = '';
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'overlay-buster.js',
fn: overlayBuster,
});
// Experimental: Generic nuisance overlay buster.
// if this works well and proves to be useful, this may end up
// as a stock tool in uBO's popup panel.
function overlayBuster() {
if ( window !== window.top ) { return; }
var tstart;
var ttl = 30000;
var delay = 0;
var delayStep = 50;
var buster = function() {
var docEl = document.documentElement,
bodyEl = document.body,
vw = Math.min(docEl.clientWidth, window.innerWidth),
vh = Math.min(docEl.clientHeight, window.innerHeight),
tol = Math.min(vw, vh) * 0.05,
el = document.elementFromPoint(vw/2, vh/2),
style, rect;
for (;;) {
if ( el === null || el.parentNode === null || el === bodyEl ) {
break;
}
style = window.getComputedStyle(el);
if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) {
rect = el.getBoundingClientRect();
if ( rect.left <= tol && rect.top <= tol && (vw - rect.right) <= tol && (vh - rect.bottom) < tol ) {
el.parentNode.removeChild(el);
tstart = Date.now();
el = document.elementFromPoint(vw/2, vh/2);
bodyEl.style.setProperty('overflow', 'auto', 'important');
docEl.style.setProperty('overflow', 'auto', 'important');
continue;
}
}
el = el.parentNode;
}
if ( (Date.now() - tstart) < ttl ) {
delay = Math.min(delay + delayStep, 1000);
setTimeout(buster, delay);
}
};
var domReady = function(ev) {
if ( ev ) {
document.removeEventListener(ev.type, domReady);
}
tstart = Date.now();
setTimeout(buster, delay);
};
if ( document.readyState === 'loading' ) {
document.addEventListener('DOMContentLoaded', domReady);
} else {
domReady();
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'alert-buster.js',
fn: alertBuster,
});
// https://github.com/uBlockOrigin/uAssets/issues/8
function alertBuster() {
window.alert = new Proxy(window.alert, {
apply: function(a) {
console.info(a);
},
get(target, prop) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
}
return Reflect.get(target, prop);
},
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'nowebrtc.js',
fn: noWebrtc,
});
// Prevent web pages from using RTCPeerConnection(), and report attempts in console.
function noWebrtc() {
var rtcName = window.RTCPeerConnection ? 'RTCPeerConnection' : (
window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : ''
);
if ( rtcName === '' ) { return; }
var log = console.log.bind(console);
var pc = function(cfg) {
log('Document tried to create an RTCPeerConnection: %o', cfg);
};
const noop = function() {
};
pc.prototype = {
close: noop,
createDataChannel: noop,
createOffer: noop,
setRemoteDescription: noop,
toString: function() {
return '[object RTCPeerConnection]';
}
};
var z = window[rtcName];
window[rtcName] = pc.bind(window);
if ( z.prototype ) {
z.prototype.createDataChannel = function() {
return {
close: function() {},
send: function() {}
};
}.bind(null);
}
}
/******************************************************************************/
builtinScriptlets.push({
name: 'disable-newtab-links.js',
fn: disableNewtabLinks,
});
// https://github.com/uBlockOrigin/uAssets/issues/913
function disableNewtabLinks() {
document.addEventListener('click', function(ev) {
var target = ev.target;
while ( target !== null ) {
if ( target.localName === 'a' && target.hasAttribute('target') ) {
ev.stopPropagation();
ev.preventDefault();
break;
}
target = target.parentNode;
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'xml-prune.js',
fn: xmlPrune,
dependencies: [
'safe-self.fn',
],
});
function xmlPrune(
selector = '',
selectorCheck = '',
urlPattern = ''
) {
if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('xml-prune', selector, selectorCheck, urlPattern);
const reUrl = safe.patternToRegex(urlPattern);
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const queryAll = (xmlDoc, selector) => {
const isXpath = /^xpath\(.+\)$/.test(selector);
if ( isXpath === false ) {
return Array.from(xmlDoc.querySelectorAll(selector));
}
const xpr = xmlDoc.evaluate(
selector.slice(6, -1),
xmlDoc,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
const out = [];
for ( let i = 0; i < xpr.snapshotLength; i++ ) {
const node = xpr.snapshotItem(i);
out.push(node);
}
return out;
};
const pruneFromDoc = xmlDoc => {
try {
if ( selectorCheck !== '' && xmlDoc.querySelector(selectorCheck) === null ) {
return xmlDoc;
}
if ( extraArgs.logdoc ) {
const serializer = new XMLSerializer();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, `Document is\n\t${serializer.serializeToString(xmlDoc)}`);
}
const items = queryAll(xmlDoc, selector);
if ( items.length === 0 ) { return xmlDoc; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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);
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, `${item.constructor.name}.${item.nodeName} removed`);
}
} catch(ex) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboErr(logPrefix, `Error: ${ex}`);
}
return xmlDoc;
};
const pruneFromText = text => {
if ( (/^\s*</.test(text) && />\s*$/.test(text)) === false ) {
return text;
}
try {
const xmlParser = new DOMParser();
const xmlDoc = xmlParser.parseFromString(text, 'text/xml');
pruneFromDoc(xmlDoc);
const serializer = new XMLSerializer();
text = serializer.serializeToString(xmlDoc);
} catch(ex) {
}
return text;
};
2022-09-25 04:49:41 -06:00
const urlFromArg = arg => {
if ( typeof arg === 'string' ) { return arg; }
if ( arg instanceof Request ) { return arg.url; }
return String(arg);
};
self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) {
const fetchPromise = Reflect.apply(target, thisArg, args);
if ( reUrl.test(urlFromArg(args[0])) === false ) {
return fetchPromise;
}
return fetchPromise.then(responseBefore => {
const response = responseBefore.clone();
return response.text().then(text => {
const responseAfter = new Response(pruneFromText(text), {
status: responseBefore.status,
statusText: responseBefore.statusText,
headers: responseBefore.headers,
});
Object.defineProperties(responseAfter, {
ok: { value: responseBefore.ok },
redirected: { value: responseBefore.redirected },
type: { value: responseBefore.type },
url: { value: responseBefore.url },
});
return responseAfter;
}).catch(( ) =>
responseBefore
);
});
}
});
self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, {
apply: async (target, thisArg, args) => {
if ( reUrl.test(urlFromArg(args[1])) === false ) {
return Reflect.apply(target, thisArg, args);
}
thisArg.addEventListener('readystatechange', function() {
if ( thisArg.readyState !== 4 ) { return; }
const type = thisArg.responseType;
if (
type === 'document' ||
type === '' && thisArg.responseXML instanceof XMLDocument
) {
pruneFromDoc(thisArg.responseXML);
const serializer = new XMLSerializer();
const textout = serializer.serializeToString(thisArg.responseXML);
Object.defineProperty(thisArg, 'responseText', { value: textout });
if ( typeof thisArg.response === 'string' ) {
Object.defineProperty(thisArg, 'response', { value: textout });
}
return;
}
if (
type === 'text' ||
type === '' && typeof thisArg.responseText === 'string'
) {
const textin = thisArg.responseText;
const textout = pruneFromText(textin);
if ( textout === textin ) { return; }
Object.defineProperty(thisArg, 'response', { value: textout });
Object.defineProperty(thisArg, 'responseText', { value: textout });
return;
}
});
return Reflect.apply(target, thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'm3u-prune.js',
fn: m3uPrune,
dependencies: [
'safe-self.fn',
],
});
// https://en.wikipedia.org/wiki/M3U
function m3uPrune(
m3uPattern = '',
urlPattern = ''
) {
if ( typeof m3uPattern !== 'string' ) { return; }
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('m3u-prune', m3uPattern, urlPattern);
const toLog = [];
const regexFromArg = arg => {
if ( arg === '' ) { return /^/; }
const match = /^\/(.+)\/([gms]*)$/.exec(arg);
if ( match !== null ) {
let flags = match[2] || '';
if ( flags.includes('m') ) { flags += 's'; }
return new RegExp(match[1], flags);
}
return new RegExp(
arg.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*+/g, '.*?')
);
};
const reM3u = regexFromArg(m3uPattern);
const reUrl = regexFromArg(urlPattern);
2022-09-26 20:37:11 -06:00
const pruneSpliceoutBlock = (lines, i) => {
if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) {
return false;
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-CUE-IN') ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
}
if ( lines[i].startsWith('#EXT-X-SCTE35:') ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
}
return true;
};
const pruneInfBlock = (lines, i) => {
if ( lines[i].startsWith('#EXTINF') === false ) { return false; }
if ( reM3u.test(lines[i+1]) === false ) { return false; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push('Discarding', `\t${lines[i]}, \t${lines[i+1]}`);
2022-09-26 20:37:11 -06:00
lines[i] = lines[i+1] = undefined; i += 2;
if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push(`\t${lines[i]}`);
2022-09-26 20:37:11 -06:00
lines[i] = undefined; i += 1;
}
return true;
};
const pruner = text => {
if ( (/^\s*#EXTM3U/.test(text)) === false ) { return text; }
if ( m3uPattern === '' ) {
safe.uboLog(` Content:\n${text}`);
return text;
}
if ( reM3u.multiline ) {
reM3u.lastIndex = 0;
for (;;) {
const match = reM3u.exec(text);
if ( match === null ) { break; }
let discard = match[0];
let before = text.slice(0, match.index);
if (
/^[\n\r]+/.test(discard) === false &&
/[\n\r]+$/.test(before) === false
) {
const startOfLine = /[^\n\r]+$/.exec(before);
if ( startOfLine !== null ) {
before = before.slice(0, startOfLine.index);
discard = startOfLine[0] + discard;
}
}
let after = text.slice(match.index + match[0].length);
if (
/[\n\r]+$/.test(discard) === false &&
/^[\n\r]+/.test(after) === false
) {
const endOfLine = /^[^\n\r]+/.exec(after);
if ( endOfLine !== null ) {
after = after.slice(endOfLine.index);
discard += discard + endOfLine[0];
}
}
text = before.trim() + '\n' + after.trim();
reM3u.lastIndex = before.length + 1;
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
toLog.push('Discarding', ...discard.split(/\n+/).map(s => `\t${s}`));
if ( reM3u.global === false ) { break; }
}
return text;
}
2022-09-26 20:37:11 -06:00
const lines = text.split(/\n\r|\n|\r/);
for ( let i = 0; i < lines.length; i++ ) {
2022-09-26 20:37:11 -06:00
if ( lines[i] === undefined ) { continue; }
if ( pruneSpliceoutBlock(lines, i) ) { continue; }
if ( pruneInfBlock(lines, i) ) { continue; }
}
return lines.filter(l => l !== undefined).join('\n');
};
const urlFromArg = arg => {
if ( typeof arg === 'string' ) { return arg; }
if ( arg instanceof Request ) { return arg.url; }
return String(arg);
};
const realFetch = self.fetch;
self.fetch = new Proxy(self.fetch, {
apply: function(target, thisArg, args) {
if ( reUrl.test(urlFromArg(args[0])) === false ) {
return Reflect.apply(target, thisArg, args);
}
return realFetch(...args).then(realResponse =>
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
realResponse.text().then(text => {
const response = new Response(pruner(text), {
status: realResponse.status,
statusText: realResponse.statusText,
headers: realResponse.headers,
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
});
if ( toLog.length !== 0 ) {
toLog.unshift(logPrefix);
safe.uboLog(toLog.join('\n'));
}
return response;
})
);
}
});
self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, {
apply: async (target, thisArg, args) => {
if ( reUrl.test(urlFromArg(args[1])) === false ) {
return Reflect.apply(target, thisArg, args);
}
thisArg.addEventListener('readystatechange', function() {
if ( thisArg.readyState !== 4 ) { return; }
const type = thisArg.responseType;
if ( type !== '' && type !== 'text' ) { return; }
const textin = thisArg.responseText;
const textout = pruner(textin);
if ( textout === textin ) { return; }
2022-09-26 20:37:11 -06:00
Object.defineProperty(thisArg, 'response', { value: textout });
Object.defineProperty(thisArg, 'responseText', { value: textout });
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( toLog.length !== 0 ) {
toLog.unshift(logPrefix);
safe.uboLog(toLog.join('\n'));
}
});
return Reflect.apply(target, thisArg, args);
}
});
}
/*******************************************************************************
*
* @scriptlet href-sanitizer
*
* @description
* Set the `href` attribute to a value found in the DOM at, or below the
* targeted `a` element.
*
* ### Syntax
*
* ```text
* example.org##+js(href-sanitizer, selector [, source])
* ```
*
* - `selector`: required, CSS selector, specifies `a` elements for which the
2023-07-14 12:19:30 -06:00
* `href` attribute must be overridden.
* - `source`: optional, default to `text`, specifies from where to get the
* value which will override the `href` attribute.
* - `text`: the value will be the first valid URL found in the text
* content of the targeted `a` element.
* - `[attr]`: the value will be the attribute _attr_ of the targeted `a`
* element.
* - `?param`: the value will be the query parameter _param_ of the URL
* found in the `href` attribute of the targeted `a` element.
*
* ### Examples
*
* example.org##+js(href-sanitizer, a)
* example.org##+js(href-sanitizer, a[title], [title])
* example.org##+js(href-sanitizer, a[href*="/away.php?to="], ?to)
*
* */
builtinScriptlets.push({
name: 'href-sanitizer.js',
fn: hrefSanitizer,
world: 'ISOLATED',
dependencies: [
'run-at.fn',
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
'safe-self.fn',
],
});
function hrefSanitizer(
selector = '',
source = ''
) {
if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source);
if ( source === '' ) { source = 'text'; }
const sanitizeCopycats = (href, text) => {
let elems = [];
try {
elems = document.querySelectorAll(`a[href="${href}"`);
}
catch(ex) {
}
for ( const elem of elems ) {
elem.setAttribute('href', text);
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
return elems.length;
};
2023-05-30 07:13:46 -06:00
const validateURL = text => {
if ( text === '' ) { return ''; }
if ( /[\x00-\x20\x7f]/.test(text) ) { return ''; }
2023-05-30 07:13:46 -06:00
try {
const url = new URL(text, document.location);
return url.href;
} catch(ex) {
}
return '';
};
const extractParam = (href, source) => {
if ( Boolean(source) === false ) { return href; }
const recursive = source.includes('?', 1);
const end = recursive ? source.indexOf('?', 1) : source.length;
try {
const url = new URL(href, document.location);
let value = url.searchParams.get(source.slice(1, end));
if ( value === null ) { return href }
if ( recursive ) { return extractParam(value, source.slice(end)); }
if ( value.includes(' ') ) {
value = value.replace(/ /g, '%20');
}
return value;
} catch(x) {
}
return href;
};
const extractText = (elem, source) => {
if ( /^\[.*\]$/.test(source) ) {
2023-05-30 07:13:46 -06:00
return elem.getAttribute(source.slice(1,-1).trim()) || '';
}
if ( source.startsWith('?') ) {
return extractParam(elem.href, source);
2023-05-30 07:13:46 -06:00
}
if ( source === 'text' ) {
return elem.textContent
.replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters
.replace(/[^\x21-\x7e]+$/, '') // remove trailing invalid characters
2024-03-20 07:31:17 -06:00
;
2023-05-30 07:13:46 -06:00
}
return '';
};
const sanitize = ( ) => {
let elems = [];
try {
elems = document.querySelectorAll(selector);
}
catch(ex) {
return false;
}
for ( const elem of elems ) {
if ( elem.localName !== 'a' ) { continue; }
if ( elem.hasAttribute('href') === false ) { continue; }
const href = elem.getAttribute('href');
const text = extractText(elem, source);
2023-05-30 07:13:46 -06:00
const hrefAfter = validateURL(text);
if ( hrefAfter === '' ) { continue; }
if ( hrefAfter === href ) { continue; }
elem.setAttribute('href', hrefAfter);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const count = sanitizeCopycats(href, hrefAfter);
safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`);
}
return true;
};
let observer, timer;
const onDomChanged = mutations => {
if ( timer !== undefined ) { return; }
let shouldSanitize = false;
for ( const mutation of mutations ) {
if ( mutation.addedNodes.length === 0 ) { continue; }
for ( const node of mutation.addedNodes ) {
if ( node.nodeType !== 1 ) { continue; }
shouldSanitize = true;
break;
}
if ( shouldSanitize ) { break; }
}
if ( shouldSanitize === false ) { return; }
timer = safe.onIdle(( ) => {
timer = undefined;
sanitize();
});
};
const start = ( ) => {
if ( sanitize() === false ) { return; }
observer = new MutationObserver(onDomChanged);
observer.observe(document.body, {
subtree: true,
childList: true,
});
};
runAt(( ) => { start(); }, 'interactive');
}
/*******************************************************************************
*
* @scriptlet call-nothrow
*
* @description
* Prevent a function call from throwing. The function will be called, however
* should it throw, the scriptlet will silently process the exception and
* returns as if no exception has occurred.
*
* ### Syntax
*
* ```text
* example.org##+js(call-nothrow, propertyChain)
* ```
*
* - `propertyChain`: a chain of dot-separated properties which leads to the
* function to be trapped.
*
* ### Examples
*
* example.org##+js(call-nothrow, Object.defineProperty)
*
* */
builtinScriptlets.push({
name: 'call-nothrow.js',
fn: callNothrow,
});
function callNothrow(
chain = ''
) {
if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; }
const parts = chain.split('.');
let owner = window, prop;
for (;;) {
prop = parts.shift();
if ( parts.length === 0 ) { break; }
owner = owner[prop];
if ( owner instanceof Object === false ) { return; }
}
if ( prop === '' ) { return; }
const fn = owner[prop];
if ( typeof fn !== 'function' ) { return; }
owner[prop] = new Proxy(fn, {
apply: function(...args) {
let r;
try {
r = Reflect.apply(...args);
} catch(ex) {
}
return r;
},
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'spoof-css.js',
fn: spoofCSS,
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
dependencies: [
'safe-self.fn',
],
});
function spoofCSS(
selector,
...args
) {
if ( typeof selector !== 'string' ) { return; }
if ( selector === '' ) { return; }
const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase());
const propToValueMap = new Map();
for ( let i = 0; i < args.length; i += 2 ) {
if ( typeof args[i+0] !== 'string' ) { break; }
if ( args[i+0] === '' ) { break; }
if ( typeof args[i+1] !== 'string' ) { break; }
propToValueMap.set(toCamelCase(args[i+0]), args[i+1]);
}
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
const canDebug = scriptletGlobals.canDebug;
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
const shouldDebug = canDebug && propToValueMap.get('debug') || 0;
2024-03-20 07:31:17 -06:00
const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
const spoofStyle = (prop, real) => {
const normalProp = toCamelCase(prop);
const shouldSpoof = propToValueMap.has(normalProp);
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( shouldSpoof ) {
safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
}
return value;
};
2024-03-02 06:11:29 -07:00
const cloackFunc = (fn, thisArg, name) => {
const trap = fn.bind(thisArg);
Object.defineProperty(trap, 'name', { value: name });
2024-04-09 18:05:05 -06:00
Object.defineProperty(trap, 'toString', {
value: ( ) => `function ${name}() { [native code] }`
});
2024-03-02 06:11:29 -07:00
return trap;
};
self.getComputedStyle = new Proxy(self.getComputedStyle, {
apply: function(target, thisArg, args) {
2024-03-20 07:31:17 -06:00
// 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; }
const proxiedStyle = new Proxy(style, {
get(target, prop) {
if ( typeof target[prop] === 'function' ) {
2023-05-24 14:50:34 -06:00
if ( prop === 'getPropertyValue' ) {
2024-04-09 18:05:05 -06:00
return cloackFunc(function getPropertyValue(prop) {
2023-05-24 14:50:34 -06:00
return spoofStyle(prop, target[prop]);
2024-03-02 06:11:29 -07:00
}, target, 'getPropertyValue');
2023-05-24 14:50:34 -06:00
}
2024-03-02 06:11:29 -07:00
return cloackFunc(target[prop], target, prop);
}
2024-03-20 07:31:17 -06:00
if ( instanceProperties.includes(prop) ) {
return Reflect.get(target, prop);
}
return spoofStyle(prop, Reflect.get(target, prop));
},
2023-08-02 09:36:54 -06:00
getOwnPropertyDescriptor(target, prop) {
if ( propToValueMap.has(prop) ) {
return {
configurable: true,
enumerable: true,
value: propToValueMap.get(prop),
writable: true,
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
},
});
return proxiedStyle;
},
get(target, prop) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
}
return Reflect.get(target, prop);
},
});
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
apply: function(target, thisArg, args) {
2024-03-20 07:31:17 -06:00
// 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; }
let { height, width } = rect;
if ( propToValueMap.has('width') ) {
width = parseFloat(propToValueMap.get('width'));
}
if ( propToValueMap.has('height') ) {
height = parseFloat(propToValueMap.get('height'));
}
return new self.DOMRect(rect.x, rect.y, width, height);
},
get(target, prop) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
}
return Reflect.get(target, prop);
},
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'remove-node-text.js',
aliases: [
'rmnt.js',
],
fn: removeNodeText,
world: 'ISOLATED',
dependencies: [
'replace-node-text.fn',
],
});
function removeNodeText(
nodeName,
includes,
...extraArgs
) {
replaceNodeTextFn(nodeName, '', '', 'includes', includes || '', ...extraArgs);
}
/*******************************************************************************
*
* set-local-storage-item.js
* set-session-storage-item.js
*
* Set a local/session storage entry to a specific, allowed value.
*
* Reference:
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-local-storage-item.js
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-session-storage-item.js
*
**/
builtinScriptlets.push({
name: 'set-local-storage-item.js',
fn: setLocalStorageItem,
world: 'ISOLATED',
dependencies: [
'set-local-storage-item.fn',
],
});
function setLocalStorageItem(key = '', value = '') {
setLocalStorageItemFn('local', false, key, value);
}
builtinScriptlets.push({
name: 'set-session-storage-item.js',
fn: setSessionStorageItem,
world: 'ISOLATED',
dependencies: [
'set-local-storage-item.fn',
],
});
function setSessionStorageItem(key = '', value = '') {
setLocalStorageItemFn('session', false, key, value);
}
/*******************************************************************************
*
* @scriptlet prevent-canvas
*
* @description
* Prevent usage of specific or all (default) canvas APIs.
*
* ### Syntax
*
* ```text
* example.com##+js(prevent-canvas [, contextType])
* ```
*
* - `contextType`: A specific type of canvas API to prevent (default to all
* APIs). Can be a string or regex which will be matched against the type
* used in getContext() call. Prepend with `!` to test for no-match.
*
* ### Examples
*
* 1. Prevent `example.com` from accessing all canvas APIs
*
* ```adblock
* example.com##+js(prevent-canvas)
* ```
*
* 2. Prevent access to any flavor of WebGL API, everywhere
*
* ```adblock
* *##+js(prevent-canvas, /webgl/)
* ```
*
* 3. Prevent `example.com` from accessing any flavor of canvas API except `2d`
*
* ```adblock
* example.com##+js(prevent-canvas, !2d)
* ```
*
* ### References
*
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
*
* */
builtinScriptlets.push({
name: 'prevent-canvas.js',
fn: preventCanvas,
dependencies: [
'safe-self.fn',
],
});
function preventCanvas(
contextType = ''
) {
const safe = safeSelf();
const pattern = safe.initPattern(contextType, { canNegate: true });
const proto = globalThis.HTMLCanvasElement.prototype;
proto.getContext = new Proxy(proto.getContext, {
apply(target, thisArg, args) {
if ( safe.testPattern(pattern, args[0]) ) { return null; }
return Reflect.apply(target, thisArg, args);
}
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'multiup.js',
fn: multiup,
world: 'ISOLATED',
});
function multiup() {
const handler = ev => {
const target = ev.target;
if ( target.matches('button[link]') === false ) { return; }
const ancestor = target.closest('form');
if ( ancestor === null ) { return; }
if ( ancestor !== target.parentElement ) { return; }
const link = (target.getAttribute('link') || '').trim();
if ( link === '' ) { return; }
ev.preventDefault();
ev.stopPropagation();
document.location.href = link;
};
document.addEventListener('click', handler, { capture: true });
}
/******************************************************************************/
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}`);
});
}
});
});
}
});
}
2023-05-23 07:03:19 -06:00
/*******************************************************************************
*
* Scriplets below this section are only available for filter lists from
* trusted sources. They all have the property `requiresTrust` set to `true`.
*
* Trusted sources are:
*
* - uBO's own filter lists, which name starts with "uBlock filters ", and
* maintained at: https://github.com/uBlockOrigin/uAssets
*
* - The user's own filters as seen in "My filters" pane in uBO's dashboard.
*
* The trustworthiness of filters using these privileged scriptlets are
* evaluated at filter list compiled time: when a filter using one of the
* privileged scriptlet originates from a non-trusted filter list source, it
* is discarded at compile time, specifically from within:
*
* - Source: ./src/js/scriptlet-filtering.js
* - Method: scriptletFilteringEngine.compile(), via normalizeRawFilter()
*
**/
/*******************************************************************************
*
2023-05-24 13:56:42 -06:00
* replace-node-text.js
2023-05-23 07:03:19 -06:00
*
* Replace text instance(s) with another text instance inside specific
* DOM nodes. By default, the scriplet stops and quits at the interactive
* stage of a document.
*
* See commit messages for usage:
* - https://github.com/gorhill/uBlock/commit/99ce027fd702
* - https://github.com/gorhill/uBlock/commit/41876336db48
*
**/
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
builtinScriptlets.push({
name: 'trusted-replace-node-text.js',
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
requiresTrust: true,
aliases: [
'trusted-rpnt.js',
'replace-node-text.js',
'rpnt.js',
],
fn: replaceNodeText,
world: 'ISOLATED',
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
dependencies: [
'replace-node-text.fn',
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
],
});
function replaceNodeText(
nodeName,
pattern,
replacement,
...extraArgs
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
) {
replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs);
Add trusted-source support for privileged scriptlets At the moment, the only filter lists deemed from a "trusted source" are uBO-specific filter lists (i.e. "uBlock filters -- ..."), and the user's own filters from "My filters". A new scriptlet which can only be used by filter lists from trusted sources has been introduced: `sed.js`. The new `sed.js` scriptlet provides the ability to perform text-level substitutions. Usage: example.org##+js(sed, nodeName, pattern, replacement, ...) `nodeName` The name of the node for which the text content must be substituted. Valid node names can be found at: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName `pattern` A string or regex to find in the text content of the node as the target of substitution. `replacement` The replacement text. Can be omitted if the goal is to delete the text which matches the pattern. Cannot be omitted if extra pairs of parameters have to be used (see below). Optionally, extra pairs of parameters to modify the behavior of the scriptlet: `condition, pattern` A string or regex which must be found in the text content of the node in order for the substitution to occur. `sedCount, n` This will cause the scriptlet to stop after n instances of substitution. Since a mutation oberver is used by the scriptlet, it's advised to stop it whenever it becomes pointless. Default to zero, which means the scriptlet never stops. `tryCount, n` This will cause the scriptlet to stop after n instances of mutation observer run (regardless of whether a substitution occurred). Default to zero, which means the scriptlet never stops. `log, 1` This will cause the scriptlet to output information at the console, useful as a debugging tool for filter authors. The logging ability is supported only in the dev build of uBO. Examples of usage: example.com##+js(sed, script, /devtoolsDetector\.launch\(\)\;/, , sedCount, 1) example.com##+js(sed, #text, /^Advertisement$/)
2023-05-21 12:16:56 -06:00
}
2023-05-24 13:56:42 -06:00
/*******************************************************************************
*
* trusted-set-constant.js
*
* Set specified property to any value. This is essentially the same as
* set-constant.js, but with no restriction as to which values can be used.
*
**/
builtinScriptlets.push({
name: 'trusted-set-constant.js',
requiresTrust: true,
aliases: [
'trusted-set.js',
],
fn: trustedSetConstant,
dependencies: [
'set-constant.fn'
],
});
function trustedSetConstant(
...args
) {
setConstantFn(true, ...args);
}
/*******************************************************************************
*
* trusted-set-local-storage-item.js
*
* Set a local storage entry to an arbitrary value.
*
* Reference:
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-local-storage-item.js
*
**/
builtinScriptlets.push({
name: 'trusted-set-local-storage-item.js',
requiresTrust: true,
fn: trustedSetLocalStorageItem,
world: 'ISOLATED',
dependencies: [
'set-local-storage-item.fn',
],
});
function trustedSetLocalStorageItem(key = '', value = '') {
setLocalStorageItemFn('local', true, key, value);
}
builtinScriptlets.push({
name: 'trusted-set-session-storage-item.js',
requiresTrust: true,
fn: trustedSetSessionStorageItem,
world: 'ISOLATED',
dependencies: [
'set-local-storage-item.fn',
],
});
function trustedSetSessionStorageItem(key = '', value = '') {
setLocalStorageItemFn('session', true, key, value);
}
/*******************************************************************************
*
* trusted-replace-fetch-response.js
*
* Replaces response text content of fetch requests if all given parameters
* match.
*
* Reference:
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-replace-fetch-response.js
*
**/
builtinScriptlets.push({
name: 'trusted-replace-fetch-response.js',
requiresTrust: true,
aliases: [
'trusted-rpfr.js',
],
fn: trustedReplaceFetchResponse,
dependencies: [
'replace-fetch-response.fn',
],
});
function trustedReplaceFetchResponse(...args) {
replaceFetchResponseFn(true, ...args);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'trusted-replace-xhr-response.js',
requiresTrust: true,
fn: trustedReplaceXhrResponse,
dependencies: [
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function trustedReplaceXhrResponse(
pattern = '',
replacement = '',
propsToMatch = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
const logPrefix = safe.makeLogPrefix('trusted-replace-xhr-response', pattern, replacement, propsToMatch);
const xhrInstances = new WeakMap();
if ( pattern === '*' ) { pattern = '.*'; }
const rePattern = safe.patternToRegex(pattern);
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const reIncludes = extraArgs.includes ? safe.patternToRegex(extraArgs.includes) : null;
self.XMLHttpRequest = class extends self.XMLHttpRequest {
open(method, url, ...args) {
const outerXhr = this;
const xhrDetails = { method, url };
let outcome = 'match';
if ( propNeedles.size !== 0 ) {
if ( matchObjectProperties(propNeedles, xhrDetails) === false ) {
outcome = 'nomatch';
}
}
if ( outcome === 'match' ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Matched "propsToMatch"`);
}
xhrInstances.set(outerXhr, xhrDetails);
}
return super.open(method, url, ...args);
}
get response() {
const innerResponse = super.response;
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined ) {
return innerResponse;
}
2023-09-22 07:33:02 -06:00
const responseLength = typeof innerResponse === 'string'
? innerResponse.length
: undefined;
if ( xhrDetails.lastResponseLength !== responseLength ) {
xhrDetails.response = undefined;
xhrDetails.lastResponseLength = responseLength;
}
2023-09-22 07:33:02 -06:00
if ( xhrDetails.response !== undefined ) {
return xhrDetails.response;
}
if ( typeof innerResponse !== 'string' ) {
return (xhrDetails.response = innerResponse);
}
if ( reIncludes && reIncludes.test(innerResponse) === false ) {
return (xhrDetails.response = innerResponse);
}
const textBefore = innerResponse;
const textAfter = textBefore.replace(rePattern, replacement);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
if ( textAfter !== textBefore ) {
safe.uboLog(logPrefix, 'Match');
}
2023-09-22 07:33:02 -06:00
return (xhrDetails.response = textAfter);
}
get responseText() {
const response = this.response;
if ( typeof response !== 'string' ) {
return super.responseText;
}
return response;
}
};
}
/*******************************************************************************
*
* trusted-click-element.js
*
* Reference API:
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-click-element.js
*
**/
builtinScriptlets.push({
name: 'trusted-click-element.js',
requiresTrust: true,
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 = '',
delay = ''
) {
const safe = safeSelf();
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
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 getShadowRoot = elem => {
// Firefox
if ( elem.openOrClosedShadowRoot ) {
return elem.openOrClosedShadowRoot;
}
// Chromium
if ( typeof chrome === 'object' ) {
if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) {
return chrome.dom.openOrClosedShadowRoot(elem);
}
}
return null;
};
const querySelectorEx = (selector, context = document) => {
const pos = selector.indexOf(' >>> ');
if ( pos === -1 ) { return context.querySelector(selector); }
const outside = selector.slice(0, pos).trim();
const inside = selector.slice(pos + 5).trim();
const elem = context.querySelector(outside);
if ( elem === null ) { return null; }
const shadowRoot = getShadowRoot(elem);
return shadowRoot && querySelectorEx(inside, shadowRoot);
};
const selectorList = selectors.split(/\s*,\s*/)
.filter(s => {
try {
void querySelectorEx(s);
} catch(_) {
return false;
}
return true;
});
if ( selectorList.length === 0 ) { return; }
const clickDelay = parseInt(delay, 10) || 1;
const t0 = Date.now();
const tbye = t0 + 10000;
let tnext = selectorList.length !== 1 ? t0 : t0 + clickDelay;
const terminate = ( ) => {
selectorList.length = 0;
next.stop();
observe.stop();
};
const next = notFound => {
if ( selectorList.length === 0 ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Completed');
return terminate();
}
const tnow = Date.now();
if ( tnow >= tbye ) {
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, 'Timed out');
return terminate();
}
if ( notFound ) { observe(); }
const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1);
next.timer = setTimeout(( ) => {
next.timer = undefined;
process();
}, delay);
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, `Waiting for ${selectorList[0]}...`);
};
next.stop = ( ) => {
if ( next.timer === undefined ) { return; }
clearTimeout(next.timer);
next.timer = undefined;
};
const observe = ( ) => {
if ( observe.observer !== undefined ) { return; }
observe.observer = new MutationObserver(( ) => {
if ( observe.timer !== undefined ) { return; }
observe.timer = setTimeout(( ) => {
observe.timer = undefined;
process();
}, 20);
});
observe.observer.observe(document, {
attributes: true,
childList: true,
subtree: true,
});
};
observe.stop = ( ) => {
if ( observe.timer !== undefined ) {
clearTimeout(observe.timer);
observe.timer = undefined;
}
if ( observe.observer ) {
observe.observer.disconnect();
observe.observer = undefined;
}
};
const process = ( ) => {
next.stop();
if ( Date.now() < tnext ) { return next(); }
const selector = selectorList.shift();
if ( selector === undefined ) { return terminate(); }
const elem = querySelectorEx(selector);
if ( elem === null ) {
selectorList.unshift(selector);
return next(true);
}
Output scriptlet logging information to the logger This commit brings the following changes to the logger: All logging output generated by injected scriptlets are now sent to the logger, the developer console will no longer be used to log scriptlet logging information. When the logger is not opened, the scriplets will not output any logging information. The goal with this new approach is to allow filter authors to more easily assess the working of scriptlets without having to go through scriptlet parameters to enable logging. Consequently all the previous ways to tell scriptlets to log information are now obsolete: if the logger is opened, the scriptlets will log information to the logger. Another benefit of this approach is that the dev tools do not need to be open to obtain scriptlets logging information. Accordingly, new filter expressions have been added to the logger: "info" and "error". Selecting the "scriptlet" expression will also keep the logging information from scriptlets. A new button has been added to the logger (not yet i18n-ed): a "volume" icon, which allows to enable verbose mode. When verbose mode is enabled, the scriptlets may choose to output more information regarding their inner working. The entries in the logger will automatically expand on mouse hover. This allows to scroll through entries which text does not fit into a single row. Clicking anywhere on an entry in the logger will open the detailed view when applicable. Generic information/errors will now be rendered regardless of which tab is currently selected in the logger (similar to how tabless entries are already being rendered).
2024-01-25 10:20:38 -07:00
safe.uboLog(logPrefix, `Clicked ${selector}`);
2023-10-15 14:40:35 -06:00
elem.click();
tnext += clickDelay;
next();
};
runAtHtmlElementFn(process);
}
/******************************************************************************/
builtinScriptlets.push({
name: 'trusted-prune-inbound-object.js',
requiresTrust: true,
fn: trustedPruneInboundObject,
dependencies: [
'object-find-owner.fn',
'object-prune.fn',
'safe-self.fn',
],
});
function trustedPruneInboundObject(
entryPoint = '',
argPos = '',
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; }
const argIndex = parseInt(argPos);
if ( isNaN(argIndex) ) { return; }
if ( argIndex < 1 ) { return; }
const safe = safeSelf();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
const needlePaths = [];
if ( rawPrunePaths !== '' ) {
needlePaths.push(...rawPrunePaths.split(/ +/));
}
if ( rawNeedlePaths !== '' ) {
needlePaths.push(...rawNeedlePaths.split(/ +/));
}
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
const mustProcess = root => {
for ( const needlePath of needlePaths ) {
if ( objectFindOwnerFn(root, needlePath) === false ) {
return false;
}
}
return true;
};
context[prop] = new Proxy(context[prop], {
apply: function(target, thisArg, args) {
const targetArg = argIndex <= args.length
? args[argIndex-1]
: undefined;
if ( targetArg instanceof Object && mustProcess(targetArg) ) {
let objBefore = targetArg;
if ( extraArgs.dontOverwrite ) {
try {
objBefore = safe.JSON_parse(safe.JSON_stringify(targetArg));
} catch(_) {
objBefore = undefined;
}
}
if ( objBefore !== undefined ) {
const objAfter = objectPruneFn(
objBefore,
rawPrunePaths,
rawNeedlePaths,
stackNeedle,
extraArgs
);
args[argIndex-1] = objAfter || objBefore;
}
}
return Reflect.apply(target, thisArg, args);
},
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'trusted-prune-outbound-object.js',
requiresTrust: true,
fn: trustedPruneOutboundObject,
dependencies: [
'object-prune.fn',
'proxy-apply.fn',
'safe-self.fn',
],
});
function trustedPruneOutboundObject(
propChain = '',
rawPrunePaths = '',
rawNeedlePaths = ''
) {
if ( propChain === '' ) { return; }
const safe = safeSelf();
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
proxyApplyFn(propChain, function(context) {
const objBefore = context.reflect();
if ( objBefore instanceof Object === false ) { return objBefore; }
const objAfter = objectPruneFn(
objBefore,
rawPrunePaths,
rawNeedlePaths,
{ matchAll: true },
extraArgs
);
return objAfter || objBefore;
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'trusted-replace-argument.js',
requiresTrust: true,
fn: trustedReplaceArgument,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
'validate-constant.fn',
],
});
function trustedReplaceArgument(
propChain = '',
argposRaw = '',
argraw = ''
) {
if ( propChain === '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-replace-argument', propChain, argposRaw, argraw);
const argoffset = parseInt(argposRaw, 10) || 0;
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
const normalValue = validateConstantFn(true, argraw, extraArgs);
const reCondition = extraArgs.condition
? safe.patternToRegex(extraArgs.condition)
: /^/;
proxyApplyFn(propChain, function(context) {
const { callArgs } = context;
if ( argposRaw === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
return context.reflect();
}
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
if ( argpos >= 0 && argpos < callArgs.length ) {
const argBefore = callArgs[argpos];
if ( safe.RegExp_test.call(reCondition, argBefore) ) {
callArgs[argpos] = normalValue;
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${normalValue}`);
}
}
return context.reflect();
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'trusted-replace-outbound-text.js',
requiresTrust: true,
fn: trustedReplaceOutboundText,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function trustedReplaceOutboundText(
propChain = '',
rawPattern = '',
rawReplacement = '',
...args
) {
if ( propChain === '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-replace-outbound-text', propChain, rawPattern, rawReplacement, ...args);
const rePattern = safe.patternToRegex(rawPattern);
const replacement = rawReplacement.startsWith('json:')
? safe.JSON_parse(rawReplacement.slice(5))
: rawReplacement;
const extraArgs = safe.getExtraArgs(args);
const reCondition = safe.patternToRegex(extraArgs.condition || '');
proxyApplyFn(propChain, function(context) {
const encodedTextBefore = context.reflect();
let textBefore = encodedTextBefore;
if ( extraArgs.encoding === 'base64' ) {
try { textBefore = self.atob(encodedTextBefore); }
catch(ex) { return encodedTextBefore; }
}
if ( rawPattern === '' ) {
safe.uboLog(logPrefix, 'Decoded outbound text:\n', textBefore);
return encodedTextBefore;
}
reCondition.lastIndex = 0;
if ( reCondition.test(textBefore) === false ) { return encodedTextBefore; }
const textAfter = textBefore.replace(rePattern, replacement);
if ( textAfter === textBefore ) { return encodedTextBefore; }
safe.uboLog(logPrefix, 'Matched and replaced');
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, 'Modified decoded outbound text:\n', textAfter);
}
let encodedTextAfter = textAfter;
if ( extraArgs.encoding === 'base64' ) {
encodedTextAfter = self.btoa(textAfter);
}
return encodedTextAfter;
});
}
/*******************************************************************************
*
* Reference:
* https://github.com/AdguardTeam/Scriptlets/blob/5a92d79489/wiki/about-trusted-scriptlets.md#trusted-suppress-native-method
*
* This is a first version with current limitations:
* - Does not support matching arguments which are object or array
* - Does not support `stack` parameter
*
* If `signatureStr` parameter is not declared, the scriptlet will log all calls
* to `methodPath` along with the arguments passed and will not prevent the
* trapped method.
*
* */
builtinScriptlets.push({
name: 'trusted-suppress-native-method.js',
requiresTrust: true,
fn: trustedSuppressNativeMethod,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function trustedSuppressNativeMethod(
methodPath = '',
signature = '',
how = '',
stack = ''
) {
if ( methodPath === '' ) { return; }
if ( stack !== '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-suppress-native-method', methodPath, signature, how);
const signatureArgs = signature.split(/\s*\|\s*/).map(v => {
if ( /^".*"$/.test(v) ) {
return { type: 'pattern', re: safe.patternToRegex(v.slice(1, -1)) };
}
if ( v === 'false' ) {
return { type: 'exact', value: false };
}
if ( v === 'true' ) {
return { type: 'exact', value: true };
}
if ( v === 'null' ) {
return { type: 'exact', value: null };
}
if ( v === 'undefined' ) {
return { type: 'exact', value: undefined };
}
});
proxyApplyFn(methodPath, function(context) {
const { callArgs } = context;
if ( signature === '' ) {
safe.uboLog(logPrefix, `Arguments:\n${callArgs.join('\n')}`);
return context.reflect();
}
for ( let i = 0; i < signatureArgs.length; i++ ) {
const signatureArg = signatureArgs[i];
if ( signatureArg === undefined ) { continue; }
const targetArg = i < callArgs.length ? callArgs[i] : undefined;
if ( signatureArg.type === 'exact' ) {
if ( targetArg !== signatureArg.value ) {
return context.reflect();
}
}
if ( signatureArg.type === 'pattern' ) {
if ( safe.RegExp_test.call(signatureArg.re, targetArg) === false ) {
return context.reflect();
}
}
}
if ( how === 'debug' ) {
debugger; // eslint-disable-line no-debugger
return context.reflect();
}
safe.uboLog(logPrefix, `Suppressed:\n${callArgs.join('\n')}`);
if ( how === 'abort' ) {
throw new ReferenceError();
}
});
}
/*******************************************************************************
*
* Trusted version of prevent-xhr(), which allows the use of an arbitrary
* string as response text.
*
* */
builtinScriptlets.push({
name: 'trusted-prevent-xhr.js',
requiresTrust: true,
fn: trustedPreventXhr,
dependencies: [
'prevent-xhr.fn',
],
});
function trustedPreventXhr(...args) {
return preventXhrFn(true, ...args);
}
/**
* @trustedScriptlet trusted-prevent-dom-bypass
*
* @description
* Prevent the bypassing of uBO scriptlets through anonymous embedded context.
*
* Ensure that a target method in the embedded context is using the
* corresponding parent context's method (which is assumed to be
* properly patched), or to replace the embedded context with that of the
* parent context.
*
* Root issue:
* https://issues.chromium.org/issues/40202434
*
* @param methodPath
* The method which calls must be intercepted. The arguments
* of the intercepted calls are assumed to be HTMLElement, anything else will
* be ignored.
*
* @param [targetProp]
* The method in the embedded context which should be delegated to the
* parent context. If no method is specified, the embedded context becomes
* the parent one, i.e. all properties of the embedded context will be that
* of the parent context.
*
* @example
* ##+js(trusted-prevent-dom-bypass, Element.prototype.append, open)
*
* @example
* ##+js(trusted-prevent-dom-bypass, Element.prototype.appendChild, XMLHttpRequest)
*
* */
builtinScriptlets.push({
name: 'trusted-prevent-dom-bypass.js',
requiresTrust: true,
fn: trustedPreventDomBypass,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
function trustedPreventDomBypass(
methodPath = '',
targetProp = ''
) {
if ( methodPath === '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-prevent-dom-bypass', methodPath, targetProp);
proxyApplyFn(methodPath, function(context) {
const elems = new Set(context.callArgs.filter(e => e instanceof HTMLElement));
const r = context.reflect();
if ( elems.length === 0 ) { return r; }
for ( const elem of elems ) {
try {
if ( `${elem.contentWindow}` !== '[object Window]' ) { continue; }
if ( elem.contentWindow.location.href !== 'about:blank' ) {
if ( elem.contentWindow.location.href !== self.location.href ) {
continue;
}
}
if ( targetProp !== '' ) {
elem.contentWindow[targetProp] = self[targetProp];
} else {
Object.defineProperty(elem, 'contentWindow', { value: self });
}
safe.uboLog(logPrefix, 'Bypass prevented');
} catch(_) {
}
}
return r;
});
}
/**
* @trustedScriptlet trusted-override-element-method
*
* @description
* Override the behavior of a method on matching elements.
*
* @param methodPath
* The method which calls must be intercepted.
*
* @param [selector]
* A CSS selector which the target element must match. If not specified,
* the override will occur for all elements.
*
* @param [disposition]
* How the override should be handled. If not specified, the overridden call
* will be equivalent to an empty function. If set to `throw`, an exception
* will be thrown. Any other value will be validated and returned as a
* supported safe constant.
*
* @example
* ##+js(trusted-override-element-method, HTMLAnchorElement.prototype.click, a[target="_blank"][style])
*
* */
builtinScriptlets.push({
name: 'trusted-override-element-method.js',
requiresTrust: true,
fn: trustedOverrideElementMethod,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
'validate-constant.fn',
],
});
function trustedOverrideElementMethod(
methodPath = '',
selector = '',
disposition = ''
) {
if ( methodPath === '' ) { return; }
const safe = safeSelf();
const logPrefix = safe.makeLogPrefix('trusted-override-element-method', methodPath, selector, disposition);
proxyApplyFn(methodPath, function(context) {
let override = selector === '';
if ( override === false ) {
const { thisArg } = context;
try {
override = thisArg.closest(selector) === thisArg;
} catch(_) {
}
}
if ( override === false ) {
return context.reflect();
}
safe.uboLog(logPrefix, 'Overridden');
if ( disposition === '' ) { return; }
if ( disposition === 'debug' && safe.logLevel !== 0 ) {
debugger; // eslint-disable-line no-debugger
}
if ( disposition === 'throw' ) {
throw new ReferenceError();
}
return validateConstantFn(false, disposition);
});
}
/******************************************************************************/