mirror of
https://git.axenov.dev/mirrors/cursor-free-vip.git
synced 2026-01-04 01:31:36 +03:00
Big Change Update
This commit is contained in:
305
uBlock0.chromium/js/resources/attribute.js
Normal file
305
uBlock0.chromium/js/resources/attribute.js
Normal file
@@ -0,0 +1,305 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function setAttrFn(
|
||||
trusted = false,
|
||||
logPrefix,
|
||||
selector = '',
|
||||
attr = '',
|
||||
value = ''
|
||||
) {
|
||||
if ( selector === '' ) { return; }
|
||||
if ( attr === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const copyFrom = trusted === false && /^\[.+\]$/.test(value)
|
||||
? value.slice(1, -1)
|
||||
: '';
|
||||
|
||||
const extractValue = elem => copyFrom !== ''
|
||||
? elem.getAttribute(copyFrom) || ''
|
||||
: value;
|
||||
|
||||
const applySetAttr = ( ) => {
|
||||
let elems;
|
||||
try {
|
||||
elems = document.querySelectorAll(selector);
|
||||
} catch(_) {
|
||||
return false;
|
||||
}
|
||||
for ( const elem of elems ) {
|
||||
const before = elem.getAttribute(attr);
|
||||
const after = extractValue(elem);
|
||||
if ( after === before ) { continue; }
|
||||
if ( after !== '' && /^on/i.test(attr) ) {
|
||||
if ( attr.toLowerCase() in elem ) { continue; }
|
||||
}
|
||||
elem.setAttribute(attr, after);
|
||||
safe.uboLog(logPrefix, `${attr}="${after}"`);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
let observer, timer;
|
||||
const onDomChanged = mutations => {
|
||||
if ( timer !== undefined ) { return; }
|
||||
let shouldWork = false;
|
||||
for ( const mutation of mutations ) {
|
||||
if ( mutation.addedNodes.length === 0 ) { continue; }
|
||||
for ( const node of mutation.addedNodes ) {
|
||||
if ( node.nodeType !== 1 ) { continue; }
|
||||
shouldWork = true;
|
||||
break;
|
||||
}
|
||||
if ( shouldWork ) { break; }
|
||||
}
|
||||
if ( shouldWork === false ) { return; }
|
||||
timer = self.requestAnimationFrame(( ) => {
|
||||
timer = undefined;
|
||||
applySetAttr();
|
||||
});
|
||||
};
|
||||
|
||||
const start = ( ) => {
|
||||
if ( applySetAttr() === false ) { return; }
|
||||
observer = new MutationObserver(onDomChanged);
|
||||
observer.observe(document.body, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
});
|
||||
};
|
||||
runAt(( ) => { start(); }, 'idle');
|
||||
}
|
||||
registerScriptlet(setAttrFn, {
|
||||
name: 'set-attr.fn',
|
||||
dependencies: [
|
||||
runAt,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet set-attr
|
||||
*
|
||||
* @description
|
||||
* Sets the specified attribute on the specified elements. This scriptlet runs
|
||||
* once when the page loads then afterward on DOM mutations.
|
||||
*
|
||||
* Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-attr.js
|
||||
*
|
||||
* @param selector
|
||||
* A CSS selector for the elements to target.
|
||||
*
|
||||
* @param attr
|
||||
* The name of the attribute to modify.
|
||||
*
|
||||
* @param value
|
||||
* The new value of the attribute. Supported values:
|
||||
* - `''`: empty string (default)
|
||||
* - `true`
|
||||
* - `false`
|
||||
* - positive decimal integer 0 <= value < 32768
|
||||
* - `[other]`: copy the value from attribute `other` on the same element
|
||||
*
|
||||
* */
|
||||
|
||||
export function setAttr(
|
||||
selector = '',
|
||||
attr = '',
|
||||
value = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-attr', selector, attr, value);
|
||||
const validValues = [ '', 'false', 'true' ];
|
||||
|
||||
if ( validValues.includes(value.toLowerCase()) === false ) {
|
||||
if ( /^\d+$/.test(value) ) {
|
||||
const n = parseInt(value, 10);
|
||||
if ( n >= 32768 ) { return; }
|
||||
value = `${n}`;
|
||||
} else if ( /^\[.+\]$/.test(value) === false ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setAttrFn(false, logPrefix, selector, attr, value);
|
||||
}
|
||||
registerScriptlet(setAttr, {
|
||||
name: 'set-attr.js',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
setAttrFn,
|
||||
],
|
||||
world: 'ISOLATED',
|
||||
});
|
||||
|
||||
/**
|
||||
* @trustedScriptlet trusted-set-attr
|
||||
*
|
||||
* @description
|
||||
* Sets the specified attribute on the specified elements. This scriptlet runs
|
||||
* once when the page loads then afterward on DOM mutations.
|
||||
*
|
||||
* Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#-%EF%B8%8F-trusted-set-attr
|
||||
*
|
||||
* @param selector
|
||||
* A CSS selector for the elements to target.
|
||||
*
|
||||
* @param attr
|
||||
* The name of the attribute to modify.
|
||||
*
|
||||
* @param value
|
||||
* The new value of the attribute. Since the scriptlet requires a trusted
|
||||
* source, the value can be anything.
|
||||
*
|
||||
* */
|
||||
|
||||
export function trustedSetAttr(
|
||||
selector = '',
|
||||
attr = '',
|
||||
value = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('trusted-set-attr', selector, attr, value);
|
||||
setAttrFn(true, logPrefix, selector, attr, value);
|
||||
}
|
||||
registerScriptlet(trustedSetAttr, {
|
||||
name: 'trusted-set-attr.js',
|
||||
requiresTrust: true,
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
setAttrFn,
|
||||
],
|
||||
world: 'ISOLATED',
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet remove-attr
|
||||
*
|
||||
* @description
|
||||
* Remove one or more attributes from a set of elements.
|
||||
*
|
||||
* @param attribute
|
||||
* The name of the attribute(s) to remove. This can be a list of space-
|
||||
* separated attribute names.
|
||||
*
|
||||
* @param [selector]
|
||||
* Optional. A CSS selector for the elements to target. Default to
|
||||
* `[attribute]`, or `[attribute1],[attribute2],...` if more than one
|
||||
* attribute name is specified.
|
||||
*
|
||||
* @param [behavior]
|
||||
* Optional. Space-separated tokens which modify the default behavior.
|
||||
* - `asap`: Try to remove the attribute as soon as possible. Default behavior
|
||||
* is to remove the attribute(s) asynchronously.
|
||||
* - `stay`: Keep trying to remove the specified attribute(s) on DOM mutations.
|
||||
* */
|
||||
|
||||
export function removeAttr(
|
||||
rawToken = '',
|
||||
rawSelector = '',
|
||||
behavior = ''
|
||||
) {
|
||||
if ( typeof rawToken !== 'string' ) { return; }
|
||||
if ( rawToken === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
|
||||
const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
|
||||
const selector = tokens
|
||||
.map(a => `${rawSelector}[${CSS.escape(a)}]`)
|
||||
.join(',');
|
||||
if ( safe.logLevel > 1 ) {
|
||||
safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
|
||||
}
|
||||
const asap = /\basap\b/.test(behavior);
|
||||
let timerId;
|
||||
const rmattrAsync = ( ) => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
timerId = safe.onIdle(( ) => {
|
||||
timerId = undefined;
|
||||
rmattr();
|
||||
}, { timeout: 17 });
|
||||
};
|
||||
const rmattr = ( ) => {
|
||||
if ( timerId !== undefined ) {
|
||||
safe.offIdle(timerId);
|
||||
timerId = undefined;
|
||||
}
|
||||
try {
|
||||
const nodes = document.querySelectorAll(selector);
|
||||
for ( const node of nodes ) {
|
||||
for ( const attr of tokens ) {
|
||||
if ( node.hasAttribute(attr) === false ) { continue; }
|
||||
node.removeAttribute(attr);
|
||||
safe.uboLog(logPrefix, `Removed attribute '${attr}'`);
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
};
|
||||
const mutationHandler = mutations => {
|
||||
if ( timerId !== undefined ) { return; }
|
||||
let skip = true;
|
||||
for ( let i = 0; i < mutations.length && skip; i++ ) {
|
||||
const { type, addedNodes, removedNodes } = mutations[i];
|
||||
if ( type === 'attributes' ) { skip = false; }
|
||||
for ( let j = 0; j < addedNodes.length && skip; j++ ) {
|
||||
if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
for ( let j = 0; j < removedNodes.length && skip; j++ ) {
|
||||
if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
|
||||
}
|
||||
}
|
||||
if ( skip ) { return; }
|
||||
asap ? rmattr() : rmattrAsync();
|
||||
};
|
||||
const start = ( ) => {
|
||||
rmattr();
|
||||
if ( /\bstay\b/.test(behavior) === false ) { return; }
|
||||
const observer = new MutationObserver(mutationHandler);
|
||||
observer.observe(document, {
|
||||
attributes: true,
|
||||
attributeFilter: tokens,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/));
|
||||
}
|
||||
registerScriptlet(removeAttr, {
|
||||
name: 'remove-attr.js',
|
||||
aliases: [
|
||||
'ra.js',
|
||||
],
|
||||
dependencies: [
|
||||
runAt,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
38
uBlock0.chromium/js/resources/base.js
Normal file
38
uBlock0.chromium/js/resources/base.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
export const registeredScriptlets = [];
|
||||
|
||||
export const registerScriptlet = (fn, details) => {
|
||||
if ( typeof details !== 'object' ) {
|
||||
throw new ReferenceError('Missing scriptlet details');
|
||||
}
|
||||
details.fn = fn;
|
||||
fn.details = details;
|
||||
if ( Array.isArray(details.dependencies) ) {
|
||||
details.dependencies.forEach((fn, i, array) => {
|
||||
if ( typeof fn !== 'function' ) { return; }
|
||||
array[i] = fn.details.name;
|
||||
});
|
||||
}
|
||||
registeredScriptlets.push(details);
|
||||
};
|
||||
419
uBlock0.chromium/js/resources/cookie.js
Normal file
419
uBlock0.chromium/js/resources/cookie.js
Normal file
@@ -0,0 +1,419 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getSafeCookieValuesFn() {
|
||||
return [
|
||||
'accept', 'reject',
|
||||
'accepted', 'rejected', 'notaccepted',
|
||||
'allow', 'disallow', 'deny',
|
||||
'allowed', 'denied',
|
||||
'approved', 'disapproved',
|
||||
'checked', 'unchecked',
|
||||
'dismiss', 'dismissed',
|
||||
'enable', 'disable',
|
||||
'enabled', 'disabled',
|
||||
'essential', 'nonessential',
|
||||
'forbidden', 'forever',
|
||||
'hide', 'hidden',
|
||||
'necessary', 'required',
|
||||
'ok',
|
||||
'on', 'off',
|
||||
'true', 't', 'false', 'f',
|
||||
'yes', 'y', 'no', 'n',
|
||||
'all', 'none', 'functional',
|
||||
'granted', 'done',
|
||||
];
|
||||
}
|
||||
registerScriptlet(getSafeCookieValuesFn, {
|
||||
name: 'get-safe-cookie-values.fn',
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getAllCookiesFn() {
|
||||
const safe = safeSelf();
|
||||
return safe.String_split.call(document.cookie, /\s*;\s*/).map(s => {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === 0 ) { return; }
|
||||
if ( pos === -1 ) { return `${s.trim()}=`; }
|
||||
const key = s.slice(0, pos).trim();
|
||||
const value = s.slice(pos+1).trim();
|
||||
return { key, value };
|
||||
}).filter(s => s !== undefined);
|
||||
}
|
||||
registerScriptlet(getAllCookiesFn, {
|
||||
name: 'get-all-cookies.fn',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function getCookieFn(
|
||||
name = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
for ( const s of safe.String_split.call(document.cookie, /\s*;\s*/) ) {
|
||||
const pos = s.indexOf('=');
|
||||
if ( pos === -1 ) { continue; }
|
||||
if ( s.slice(0, pos) !== name ) { continue; }
|
||||
return s.slice(pos+1).trim();
|
||||
}
|
||||
}
|
||||
registerScriptlet(getCookieFn, {
|
||||
name: 'get-cookie.fn',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function setCookieFn(
|
||||
trusted = false,
|
||||
name = '',
|
||||
value = '',
|
||||
expires = '',
|
||||
path = '',
|
||||
options = {},
|
||||
) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/2777
|
||||
if ( trusted === false && /[^!#$%&'*+\-.0-9A-Z[\]^_`a-z|~]/.test(name) ) {
|
||||
name = encodeURIComponent(name);
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
|
||||
// The characters [",] are given a pass from the RFC requirements because
|
||||
// apparently browsers do not follow the RFC to the letter.
|
||||
if ( /[^ -:<-[\]-~]/.test(value) ) {
|
||||
value = encodeURIComponent(value);
|
||||
}
|
||||
|
||||
const cookieBefore = getCookieFn(name);
|
||||
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
|
||||
if ( cookieBefore === value && options.reload ) { return; }
|
||||
|
||||
const cookieParts = [ name, '=', value ];
|
||||
if ( expires !== '' ) {
|
||||
cookieParts.push('; expires=', expires);
|
||||
}
|
||||
|
||||
if ( path === '' ) { path = '/'; }
|
||||
else if ( path === 'none' ) { path = ''; }
|
||||
if ( path !== '' && path !== '/' ) { return; }
|
||||
if ( path === '/' ) {
|
||||
cookieParts.push('; path=/');
|
||||
}
|
||||
|
||||
if ( trusted ) {
|
||||
if ( options.domain ) {
|
||||
cookieParts.push(`; domain=${options.domain}`);
|
||||
}
|
||||
cookieParts.push('; Secure');
|
||||
} else if ( /^__(Host|Secure)-/.test(name) ) {
|
||||
cookieParts.push('; Secure');
|
||||
}
|
||||
|
||||
try {
|
||||
document.cookie = cookieParts.join('');
|
||||
} catch(_) {
|
||||
}
|
||||
|
||||
const done = getCookieFn(name) === value;
|
||||
if ( done && options.reload ) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
registerScriptlet(setCookieFn, {
|
||||
name: 'set-cookie.fn',
|
||||
dependencies: [
|
||||
getCookieFn,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet set-cookie
|
||||
*
|
||||
* @description
|
||||
* Set a cookie to a safe value.
|
||||
*
|
||||
* @param name
|
||||
* The name of the cookie to set.
|
||||
*
|
||||
* @param value
|
||||
* The value of the cookie to set. Must be a safe value. Unsafe values will be
|
||||
* ignored and no cookie will be set. See getSafeCookieValuesFn() helper above.
|
||||
*
|
||||
* @param [path]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
||||
* */
|
||||
|
||||
export function setCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const normalized = value.toLowerCase();
|
||||
const match = /^("?)(.+)\1$/.exec(normalized);
|
||||
const unquoted = match && match[2] || normalized;
|
||||
const validValues = getSafeCookieValuesFn();
|
||||
if ( validValues.includes(unquoted) === false ) {
|
||||
if ( /^-?\d+$/.test(unquoted) === false ) { return; }
|
||||
const n = parseInt(value, 10) || 0;
|
||||
if ( n < -32767 || n > 32767 ) { return; }
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
false,
|
||||
name,
|
||||
value,
|
||||
'',
|
||||
path,
|
||||
safe.getExtraArgs(Array.from(arguments), 3)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
registerScriptlet(setCookie, {
|
||||
name: 'set-cookie.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
getSafeCookieValuesFn,
|
||||
safeSelf,
|
||||
setCookieFn,
|
||||
],
|
||||
});
|
||||
|
||||
// For compatibility with AdGuard
|
||||
export function setCookieReload(name, value, path, ...args) {
|
||||
setCookie(name, value, path, 'reload', '1', ...args);
|
||||
}
|
||||
registerScriptlet(setCookieReload, {
|
||||
name: 'set-cookie-reload.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
setCookie,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @trustedScriptlet trusted-set-cookie
|
||||
*
|
||||
* @description
|
||||
* Set a cookie to any value. This scriptlet can be used only from a trusted
|
||||
* source.
|
||||
*
|
||||
* @param name
|
||||
* The name of the cookie to set.
|
||||
*
|
||||
* @param value
|
||||
* The value of the cookie to set. Must be a safe value. Unsafe values will be
|
||||
* ignored and no cookie will be set. See getSafeCookieValuesFn() helper above.
|
||||
*
|
||||
* @param [offsetExpiresSec]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* @param [path]
|
||||
* Optional. The path of the cookie to set. Default to `/`.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js
|
||||
* */
|
||||
|
||||
export function trustedSetCookie(
|
||||
name = '',
|
||||
value = '',
|
||||
offsetExpiresSec = '',
|
||||
path = ''
|
||||
) {
|
||||
if ( name === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-cookie', name, value, path);
|
||||
const time = new Date();
|
||||
|
||||
if ( value.includes('$now$') ) {
|
||||
value = value.replaceAll('$now$', time.getTime());
|
||||
}
|
||||
if ( value.includes('$currentDate$') ) {
|
||||
value = value.replaceAll('$currentDate$', time.toUTCString());
|
||||
}
|
||||
if ( value.includes('$currentISODate$') ) {
|
||||
value = value.replaceAll('$currentISODate$', time.toISOString());
|
||||
}
|
||||
|
||||
let expires = '';
|
||||
if ( offsetExpiresSec !== '' ) {
|
||||
if ( offsetExpiresSec === '1day' ) {
|
||||
time.setDate(time.getDate() + 1);
|
||||
} else if ( offsetExpiresSec === '1year' ) {
|
||||
time.setFullYear(time.getFullYear() + 1);
|
||||
} else {
|
||||
if ( /^\d+$/.test(offsetExpiresSec) === false ) { return; }
|
||||
time.setSeconds(time.getSeconds() + parseInt(offsetExpiresSec, 10));
|
||||
}
|
||||
expires = time.toUTCString();
|
||||
}
|
||||
|
||||
const done = setCookieFn(
|
||||
true,
|
||||
name,
|
||||
value,
|
||||
expires,
|
||||
path,
|
||||
safeSelf().getExtraArgs(Array.from(arguments), 4)
|
||||
);
|
||||
|
||||
if ( done ) {
|
||||
safe.uboLog(logPrefix, 'Done');
|
||||
}
|
||||
}
|
||||
registerScriptlet(trustedSetCookie, {
|
||||
name: 'trusted-set-cookie.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
setCookieFn,
|
||||
],
|
||||
});
|
||||
|
||||
// For compatibility with AdGuard
|
||||
export function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) {
|
||||
trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args);
|
||||
}
|
||||
registerScriptlet(trustedSetCookieReload, {
|
||||
name: 'trusted-set-cookie-reload.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
trustedSetCookie,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet remove-cookie
|
||||
*
|
||||
* @description
|
||||
* Removes current site cookies specified by name. The removal operation occurs
|
||||
* immediately when the scriptlet is injected, then when the page is unloaded.
|
||||
*
|
||||
* @param needle
|
||||
* A string or a regex matching the name of the cookie(s) to remove.
|
||||
*
|
||||
* @param ['when', token]
|
||||
* Vararg, optional. The parameter following 'when' tells when extra removal
|
||||
* operations should take place.
|
||||
* - `scroll`: when the page is scrolled
|
||||
* - `keydown`: when a keyboard touch is pressed
|
||||
*
|
||||
* */
|
||||
|
||||
export function removeCookie(
|
||||
needle = ''
|
||||
) {
|
||||
if ( typeof needle !== 'string' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const reName = safe.patternToRegex(needle);
|
||||
const extraArgs = safe.getExtraArgs(Array.from(arguments), 1);
|
||||
const throttle = (fn, ms = 500) => {
|
||||
if ( throttle.timer !== undefined ) { return; }
|
||||
throttle.timer = setTimeout(( ) => {
|
||||
throttle.timer = undefined;
|
||||
fn();
|
||||
}, ms);
|
||||
};
|
||||
const remove = ( ) => {
|
||||
safe.String_split.call(document.cookie, ';').forEach(cookieStr => {
|
||||
const pos = cookieStr.indexOf('=');
|
||||
if ( pos === -1 ) { return; }
|
||||
const cookieName = cookieStr.slice(0, pos).trim();
|
||||
if ( reName.test(cookieName) === false ) { return; }
|
||||
const part1 = cookieName + '=';
|
||||
const part2a = '; domain=' + document.location.hostname;
|
||||
const part2b = '; domain=.' + document.location.hostname;
|
||||
let part2c, part2d;
|
||||
const domain = document.domain;
|
||||
if ( domain ) {
|
||||
if ( domain !== document.location.hostname ) {
|
||||
part2c = '; domain=.' + domain;
|
||||
}
|
||||
if ( domain.startsWith('www.') ) {
|
||||
part2d = '; domain=' + domain.replace('www', '');
|
||||
}
|
||||
}
|
||||
const part3 = '; path=/';
|
||||
const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
document.cookie = part1 + part4;
|
||||
document.cookie = part1 + part2a + part4;
|
||||
document.cookie = part1 + part2b + part4;
|
||||
document.cookie = part1 + part3 + part4;
|
||||
document.cookie = part1 + part2a + part3 + part4;
|
||||
document.cookie = part1 + part2b + part3 + part4;
|
||||
if ( part2c !== undefined ) {
|
||||
document.cookie = part1 + part2c + part3 + part4;
|
||||
}
|
||||
if ( part2d !== undefined ) {
|
||||
document.cookie = part1 + part2d + part3 + part4;
|
||||
}
|
||||
});
|
||||
};
|
||||
remove();
|
||||
window.addEventListener('beforeunload', remove);
|
||||
if ( typeof extraArgs.when !== 'string' ) { return; }
|
||||
const supportedEventTypes = [ 'scroll', 'keydown' ];
|
||||
const eventTypes = safe.String_split.call(extraArgs.when, /\s/);
|
||||
for ( const type of eventTypes ) {
|
||||
if ( supportedEventTypes.includes(type) === false ) { continue; }
|
||||
document.addEventListener(type, ( ) => {
|
||||
throttle(remove);
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
||||
registerScriptlet(removeCookie, {
|
||||
name: 'remove-cookie.js',
|
||||
aliases: [
|
||||
'cookie-remover.js',
|
||||
],
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
188
uBlock0.chromium/js/resources/href-sanitizer.js
Normal file
188
uBlock0.chromium/js/resources/href-sanitizer.js
Normal file
@@ -0,0 +1,188 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
import { urlSkip } from '../urlskip.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
registerScriptlet(urlSkip, {
|
||||
name: 'urlskip.fn',
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet href-sanitizer
|
||||
*
|
||||
* @description
|
||||
* Set the `href` attribute to a value found in the DOM at, or below the
|
||||
* targeted `a` element, and optionally with transformation steps.
|
||||
*
|
||||
* @param selector
|
||||
* A plain CSS selector for elements which `href` property must be sanitized.
|
||||
*
|
||||
* @param source
|
||||
* One or more tokens to lookup the source of the `href` property, and
|
||||
* optionally the transformation steps to perform:
|
||||
* - `text`: Use the text content of the element as the URL
|
||||
* - `[name]`: Use the value of the attribute `name` as the URL
|
||||
* - Transformation steps: see `urlskip` documentation
|
||||
*
|
||||
* If `text` or `[name]` is not present, the URL will be the value of `href`
|
||||
* attribute.
|
||||
*
|
||||
* @example
|
||||
* `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)`
|
||||
* `example.org##+js(href-sanitizer, a[href*="/redirect"], ?url ?url -base64)`
|
||||
*
|
||||
* */
|
||||
|
||||
function hrefSanitizer(
|
||||
selector = '',
|
||||
source = ''
|
||||
) {
|
||||
if ( typeof selector !== 'string' ) { return; }
|
||||
if ( selector === '' ) { return; }
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('href-sanitizer', selector, source);
|
||||
if ( source === '' ) { source = 'text'; }
|
||||
const sanitizeCopycats = (href, text) => {
|
||||
let elems = [];
|
||||
try {
|
||||
elems = document.querySelectorAll(`a[href="${href}"`);
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
for ( const elem of elems ) {
|
||||
elem.setAttribute('href', text);
|
||||
}
|
||||
return elems.length;
|
||||
};
|
||||
const validateURL = text => {
|
||||
if ( typeof text !== 'string' ) { return ''; }
|
||||
if ( text === '' ) { return ''; }
|
||||
if ( /[\x00-\x20\x7f]/.test(text) ) { return ''; }
|
||||
try {
|
||||
const url = new URL(text, document.location);
|
||||
return url.href;
|
||||
} catch(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)); }
|
||||
return value;
|
||||
} catch(x) {
|
||||
}
|
||||
return href;
|
||||
};
|
||||
const extractURL = (elem, source) => {
|
||||
if ( /^\[.*\]$/.test(source) ) {
|
||||
return elem.getAttribute(source.slice(1,-1).trim()) || '';
|
||||
}
|
||||
if ( source === 'text' ) {
|
||||
return elem.textContent
|
||||
.replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters
|
||||
.replace(/[^\x21-\x7e]+$/, '') // remove trailing invalid characters
|
||||
;
|
||||
}
|
||||
if ( source.startsWith('?') === false ) { return ''; }
|
||||
const steps = source.replace(/(\S)\?/g, '\\1?').split(/\s+/);
|
||||
const url = steps.length === 1
|
||||
? extractParam(elem.href, source)
|
||||
: urlSkip(elem.href, false, steps);
|
||||
if ( url === undefined ) { return; }
|
||||
return url.replace(/ /g, '%20');
|
||||
};
|
||||
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 = extractURL(elem, source);
|
||||
const hrefAfter = validateURL(text);
|
||||
if ( hrefAfter === '' ) { continue; }
|
||||
if ( hrefAfter === href ) { continue; }
|
||||
elem.setAttribute('href', hrefAfter);
|
||||
const count = sanitizeCopycats(href, hrefAfter);
|
||||
safe.uboLog(logPrefix, `Sanitized ${count+1} links to\n${hrefAfter}`);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
let observer, timer;
|
||||
const onDomChanged = mutations => {
|
||||
if ( timer !== undefined ) { return; }
|
||||
let shouldSanitize = false;
|
||||
for ( const mutation of mutations ) {
|
||||
if ( mutation.addedNodes.length === 0 ) { continue; }
|
||||
for ( const node of mutation.addedNodes ) {
|
||||
if ( node.nodeType !== 1 ) { continue; }
|
||||
shouldSanitize = true;
|
||||
break;
|
||||
}
|
||||
if ( shouldSanitize ) { break; }
|
||||
}
|
||||
if ( shouldSanitize === false ) { return; }
|
||||
timer = 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');
|
||||
}
|
||||
registerScriptlet(hrefSanitizer, {
|
||||
name: 'href-sanitizer.js',
|
||||
world: 'ISOLATED',
|
||||
aliases: [
|
||||
'urlskip.js',
|
||||
],
|
||||
dependencies: [
|
||||
runAt,
|
||||
safeSelf,
|
||||
urlSkip,
|
||||
],
|
||||
});
|
||||
235
uBlock0.chromium/js/resources/localstorage.js
Normal file
235
uBlock0.chromium/js/resources/localstorage.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { getSafeCookieValuesFn } from './cookie.js';
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export 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;
|
||||
}
|
||||
registerScriptlet(getAllLocalStorageFn, {
|
||||
name: 'get-all-local-storage.fn',
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export 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) || 0;
|
||||
if ( n < -32767 || 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) {
|
||||
}
|
||||
}
|
||||
registerScriptlet(setLocalStorageItemFn, {
|
||||
name: 'set-local-storage-item.fn',
|
||||
dependencies: [
|
||||
getSafeCookieValuesFn,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export 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}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
registerScriptlet(removeCacheStorageItem, {
|
||||
name: 'remove-cache-storage-item.fn',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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
|
||||
*
|
||||
**/
|
||||
|
||||
export function setLocalStorageItem(key = '', value = '') {
|
||||
setLocalStorageItemFn('local', false, key, value);
|
||||
}
|
||||
registerScriptlet(setLocalStorageItem, {
|
||||
name: 'set-local-storage-item.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
setLocalStorageItemFn,
|
||||
],
|
||||
});
|
||||
|
||||
export function setSessionStorageItem(key = '', value = '') {
|
||||
setLocalStorageItemFn('session', false, key, value);
|
||||
}
|
||||
registerScriptlet(setSessionStorageItem, {
|
||||
name: 'set-session-storage-item.js',
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
setLocalStorageItemFn,
|
||||
],
|
||||
});
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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
|
||||
*
|
||||
**/
|
||||
|
||||
export function trustedSetLocalStorageItem(key = '', value = '') {
|
||||
setLocalStorageItemFn('local', true, key, value);
|
||||
}
|
||||
registerScriptlet(trustedSetLocalStorageItem, {
|
||||
name: 'trusted-set-local-storage-item.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
setLocalStorageItemFn,
|
||||
],
|
||||
});
|
||||
|
||||
export function trustedSetSessionStorageItem(key = '', value = '') {
|
||||
setLocalStorageItemFn('session', true, key, value);
|
||||
}
|
||||
registerScriptlet(trustedSetSessionStorageItem, {
|
||||
name: 'trusted-set-session-storage-item.js',
|
||||
requiresTrust: true,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
setLocalStorageItemFn,
|
||||
],
|
||||
});
|
||||
54
uBlock0.chromium/js/resources/parse-replace.js
Normal file
54
uBlock0.chromium/js/resources/parse-replace.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { createArglistParser } from './shared.js';
|
||||
import { registerScriptlet } from './base.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function parseReplaceFn(s) {
|
||||
if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; }
|
||||
const parser = createArglistParser('/');
|
||||
parser.nextArg(s, 1);
|
||||
let pattern = s.slice(parser.argBeg, parser.argEnd);
|
||||
if ( parser.transform ) {
|
||||
pattern = parser.normalizeArg(pattern);
|
||||
}
|
||||
if ( pattern === '' ) { return; }
|
||||
parser.nextArg(s, parser.separatorEnd);
|
||||
let replacement = s.slice(parser.argBeg, parser.argEnd);
|
||||
if ( parser.separatorEnd === parser.separatorBeg ) { return; }
|
||||
if ( parser.transform ) {
|
||||
replacement = parser.normalizeArg(replacement);
|
||||
}
|
||||
const flags = s.slice(parser.separatorEnd);
|
||||
try {
|
||||
return { re: new RegExp(pattern, flags), replacement };
|
||||
} catch(_) {
|
||||
}
|
||||
}
|
||||
registerScriptlet(parseReplaceFn, {
|
||||
name: 'parse-replace.fn',
|
||||
dependencies: [
|
||||
createArglistParser,
|
||||
],
|
||||
});
|
||||
236
uBlock0.chromium/js/resources/prevent-settimeout.js
Normal file
236
uBlock0.chromium/js/resources/prevent-settimeout.js
Normal file
@@ -0,0 +1,236 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { proxyApplyFn } from './proxy-apply.js';
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class RangeParser {
|
||||
constructor(s) {
|
||||
this.not = s.charAt(0) === '!';
|
||||
if ( this.not ) { s = s.slice(1); }
|
||||
if ( s === '' ) { return; }
|
||||
const pos = s.indexOf('-');
|
||||
if ( pos !== 0 ) {
|
||||
this.min = this.max = parseInt(s, 10) || 0;
|
||||
}
|
||||
if ( pos !== -1 ) {
|
||||
this.max = parseInt(s.slice(1), 10) || Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
}
|
||||
unbound() {
|
||||
return this.min === undefined && this.max === undefined;
|
||||
}
|
||||
test(v) {
|
||||
const n = Math.min(Math.max(Number(v) || 0, 0), Number.MAX_SAFE_INTEGER);
|
||||
if ( this.min === this.max ) {
|
||||
return (this.min === undefined || n === this.min) !== this.not;
|
||||
}
|
||||
if ( this.min === undefined ) {
|
||||
return (n <= this.max) !== this.not;
|
||||
}
|
||||
if ( this.max === undefined ) {
|
||||
return (n >= this.min) !== this.not;
|
||||
}
|
||||
return (n >= this.min && n <= this.max) !== this.not;
|
||||
}
|
||||
}
|
||||
registerScriptlet(RangeParser, {
|
||||
name: 'range-parser.fn',
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet prevent-setTimeout
|
||||
*
|
||||
* @description
|
||||
* Conditionally prevent execution of the callback function passed to native
|
||||
* setTimeout method. With no parameters, all calls to setTimeout will be
|
||||
* shown in the logger.
|
||||
*
|
||||
* @param [needle]
|
||||
* A pattern to match against the stringified callback. The pattern can be a
|
||||
* plain string, or a regex. Prepend with `!` to reverse the match condition.
|
||||
*
|
||||
* @param [delay]
|
||||
* A value to match against the delay. Can be a single value for exact match,
|
||||
* or a range:
|
||||
* - `min-max`: matches if delay >= min and delay <= max
|
||||
* - `min-`: matches if delay >= min
|
||||
* - `-max`: matches if delay <= max
|
||||
* No delay means to match any delay value.
|
||||
* Prepend with `!` to reverse the match condition.
|
||||
*
|
||||
* */
|
||||
|
||||
export function preventSetTimeout(
|
||||
needleRaw = '',
|
||||
delayRaw = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('prevent-setTimeout', needleRaw, delayRaw);
|
||||
const needleNot = needleRaw.charAt(0) === '!';
|
||||
const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw);
|
||||
const range = new RangeParser(delayRaw);
|
||||
proxyApplyFn('setTimeout', function(context) {
|
||||
const { callArgs } = context;
|
||||
const a = callArgs[0] instanceof Function
|
||||
? String(safe.Function_toString(callArgs[0]))
|
||||
: String(callArgs[0]);
|
||||
const b = callArgs[1];
|
||||
if ( needleRaw === '' && range.unbound() ) {
|
||||
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
||||
return context.reflect();
|
||||
}
|
||||
if ( reNeedle.test(a) !== needleNot && range.test(b) ) {
|
||||
callArgs[0] = function(){};
|
||||
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
||||
}
|
||||
return context.reflect();
|
||||
});
|
||||
}
|
||||
registerScriptlet(preventSetTimeout, {
|
||||
name: 'prevent-setTimeout.js',
|
||||
aliases: [
|
||||
'no-setTimeout-if.js',
|
||||
'nostif.js',
|
||||
'setTimeout-defuser.js',
|
||||
],
|
||||
dependencies: [
|
||||
proxyApplyFn,
|
||||
RangeParser,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet prevent-setInterval
|
||||
*
|
||||
* @description
|
||||
* Conditionally prevent execution of the callback function passed to native
|
||||
* setInterval method. With no parameters, all calls to setInterval will be
|
||||
* shown in the logger.
|
||||
*
|
||||
* @param [needle]
|
||||
* A pattern to match against the stringified callback. The pattern can be a
|
||||
* plain string, or a regex. Prepend with `!` to reverse the match condition.
|
||||
* No pattern means to match anything.
|
||||
*
|
||||
* @param [delay]
|
||||
* A value to match against the delay. Can be a single value for exact match,
|
||||
* or a range:
|
||||
* - `min-max`: matches if delay >= min and delay <= max
|
||||
* - `min-`: matches if delay >= min
|
||||
* - `-max`: matches if delay <= max
|
||||
* No delay means to match any delay value.
|
||||
* Prepend with `!` to reverse the match condition.
|
||||
*
|
||||
* */
|
||||
|
||||
export function preventSetInterval(
|
||||
needleRaw = '',
|
||||
delayRaw = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('prevent-setInterval', needleRaw, delayRaw);
|
||||
const needleNot = needleRaw.charAt(0) === '!';
|
||||
const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw);
|
||||
const range = new RangeParser(delayRaw);
|
||||
proxyApplyFn('setInterval', function(context) {
|
||||
const { callArgs } = context;
|
||||
const a = callArgs[0] instanceof Function
|
||||
? String(safe.Function_toString(callArgs[0]))
|
||||
: String(callArgs[0]);
|
||||
const b = callArgs[1];
|
||||
if ( needleRaw === '' && range.unbound() ) {
|
||||
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
|
||||
return context.reflect();
|
||||
}
|
||||
if ( reNeedle.test(a) !== needleNot && range.test(b) ) {
|
||||
callArgs[0] = function(){};
|
||||
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
|
||||
}
|
||||
return context.reflect();
|
||||
});
|
||||
}
|
||||
registerScriptlet(preventSetInterval, {
|
||||
name: 'prevent-setInterval.js',
|
||||
aliases: [
|
||||
'no-setInterval-if.js',
|
||||
'nosiif.js',
|
||||
'setInterval-defuser.js',
|
||||
],
|
||||
dependencies: [
|
||||
proxyApplyFn,
|
||||
RangeParser,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @scriptlet prevent-requestAnimationFrame
|
||||
*
|
||||
* @description
|
||||
* Conditionally prevent execution of the callback function passed to native
|
||||
* requestAnimationFrame method. With no parameters, all calls to
|
||||
* requestAnimationFrame will be shown in the logger.
|
||||
*
|
||||
* @param [needle]
|
||||
* A pattern to match against the stringified callback. The pattern can be a
|
||||
* plain string, or a regex.
|
||||
* Prepend with `!` to reverse the match condition.
|
||||
*
|
||||
* */
|
||||
|
||||
export function preventRequestAnimationFrame(
|
||||
needleRaw = ''
|
||||
) {
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('prevent-requestAnimationFrame', needleRaw);
|
||||
const needleNot = needleRaw.charAt(0) === '!';
|
||||
const reNeedle = safe.patternToRegex(needleNot ? needleRaw.slice(1) : needleRaw);
|
||||
proxyApplyFn('requestAnimationFrame', function(context) {
|
||||
const { callArgs } = context;
|
||||
const a = callArgs[0] instanceof Function
|
||||
? String(safe.Function_toString(callArgs[0]))
|
||||
: String(callArgs[0]);
|
||||
if ( needleRaw === '' ) {
|
||||
safe.uboLog(logPrefix, `Called:\n${a}`);
|
||||
} else if ( reNeedle.test(a) !== needleNot ) {
|
||||
callArgs[0] = function(){};
|
||||
safe.uboLog(logPrefix, `Prevented:\n${a}`);
|
||||
}
|
||||
return context.reflect();
|
||||
});
|
||||
}
|
||||
registerScriptlet(preventRequestAnimationFrame, {
|
||||
name: 'prevent-requestAnimationFrame.js',
|
||||
aliases: [
|
||||
'no-requestAnimationFrame-if.js',
|
||||
'norafif.js',
|
||||
],
|
||||
dependencies: [
|
||||
proxyApplyFn,
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
109
uBlock0.chromium/js/resources/proxy-apply.js
Normal file
109
uBlock0.chromium/js/resources/proxy-apply.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function proxyApplyFn(
|
||||
target = '',
|
||||
handler = ''
|
||||
) {
|
||||
let context = globalThis;
|
||||
let prop = target;
|
||||
for (;;) {
|
||||
const pos = prop.indexOf('.');
|
||||
if ( pos === -1 ) { break; }
|
||||
context = context[prop.slice(0, pos)];
|
||||
if ( context instanceof Object === false ) { return; }
|
||||
prop = prop.slice(pos+1);
|
||||
}
|
||||
const fn = context[prop];
|
||||
if ( typeof fn !== 'function' ) { return; }
|
||||
if ( proxyApplyFn.CtorContext === undefined ) {
|
||||
proxyApplyFn.ctorContexts = [];
|
||||
proxyApplyFn.CtorContext = class {
|
||||
constructor(...args) {
|
||||
this.init(...args);
|
||||
}
|
||||
init(callFn, callArgs) {
|
||||
this.callFn = callFn;
|
||||
this.callArgs = callArgs;
|
||||
return this;
|
||||
}
|
||||
reflect() {
|
||||
const r = Reflect.construct(this.callFn, this.callArgs);
|
||||
this.callFn = this.callArgs = this.private = undefined;
|
||||
proxyApplyFn.ctorContexts.push(this);
|
||||
return r;
|
||||
}
|
||||
static factory(...args) {
|
||||
return proxyApplyFn.ctorContexts.length !== 0
|
||||
? proxyApplyFn.ctorContexts.pop().init(...args)
|
||||
: new proxyApplyFn.CtorContext(...args);
|
||||
}
|
||||
};
|
||||
proxyApplyFn.applyContexts = [];
|
||||
proxyApplyFn.ApplyContext = class {
|
||||
constructor(...args) {
|
||||
this.init(...args);
|
||||
}
|
||||
init(callFn, thisArg, callArgs) {
|
||||
this.callFn = callFn;
|
||||
this.thisArg = thisArg;
|
||||
this.callArgs = callArgs;
|
||||
return this;
|
||||
}
|
||||
reflect() {
|
||||
const r = Reflect.apply(this.callFn, this.thisArg, this.callArgs);
|
||||
this.callFn = this.thisArg = this.callArgs = this.private = undefined;
|
||||
proxyApplyFn.applyContexts.push(this);
|
||||
return r;
|
||||
}
|
||||
static factory(...args) {
|
||||
return proxyApplyFn.applyContexts.length !== 0
|
||||
? proxyApplyFn.applyContexts.pop().init(...args)
|
||||
: new proxyApplyFn.ApplyContext(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
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);
|
||||
}
|
||||
registerScriptlet(proxyApplyFn, {
|
||||
name: 'proxy-apply.fn',
|
||||
});
|
||||
120
uBlock0.chromium/js/resources/replace-argument.js
Normal file
120
uBlock0.chromium/js/resources/replace-argument.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { parseReplaceFn } from './parse-replace.js';
|
||||
import { proxyApplyFn } from './proxy-apply.js';
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
import { validateConstantFn } from './set-constant.js';
|
||||
|
||||
/**
|
||||
* @scriptlet trusted-replace-argument.js
|
||||
*
|
||||
* @description
|
||||
* Replace an argument passed to a method. Requires a trusted source.
|
||||
*
|
||||
* @param propChain
|
||||
* The property chain to the function which argument must be replaced when
|
||||
* called.
|
||||
*
|
||||
* @param argposRaw
|
||||
* The zero-based position of the argument in the argument list. Use a negative
|
||||
* number for a position relative to the last argument. Use literal `this` to
|
||||
* replace the value used in `prototype`-based methods.
|
||||
*
|
||||
* @param argraw
|
||||
* The replacement value, validated using the same heuristic as with the
|
||||
* `set-constant.js` scriptlet.
|
||||
* If the replacement value matches `json:...`, the value will be the
|
||||
* json-parsed string after `json:`.
|
||||
* If the replacement value matches `repl:/.../.../`, the target argument will
|
||||
* be replaced according the regex-replacement directive following `repl:`
|
||||
*
|
||||
* @param [, condition, pattern]
|
||||
* Optional. The replacement will occur only when pattern matches the target
|
||||
* argument.
|
||||
*
|
||||
* */
|
||||
|
||||
export 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 replacer = argraw.startsWith('repl:/') &&
|
||||
parseReplaceFn(argraw.slice(5)) || undefined;
|
||||
const value = replacer === undefined &&
|
||||
validateConstantFn(true, argraw, extraArgs) || undefined;
|
||||
const reCondition = extraArgs.condition
|
||||
? safe.patternToRegex(extraArgs.condition)
|
||||
: /^/;
|
||||
const getArg = context => {
|
||||
if ( argposRaw === 'this' ) { return context.thisArg; }
|
||||
const { callArgs } = context;
|
||||
const argpos = argoffset >= 0 ? argoffset : callArgs.length - argoffset;
|
||||
if ( argpos < 0 || argpos >= callArgs.length ) { return; }
|
||||
context.private = { argpos };
|
||||
return callArgs[argpos];
|
||||
};
|
||||
const setArg = (context, value) => {
|
||||
if ( argposRaw === 'this' ) {
|
||||
if ( value !== context.thisArg ) {
|
||||
context.thisArg = value;
|
||||
}
|
||||
} else if ( context.private ) {
|
||||
context.callArgs[context.private.argpos] = value;
|
||||
}
|
||||
};
|
||||
proxyApplyFn(propChain, function(context) {
|
||||
if ( argposRaw === '' ) {
|
||||
safe.uboLog(logPrefix, `Arguments:\n${context.callArgs.join('\n')}`);
|
||||
return context.reflect();
|
||||
}
|
||||
const argBefore = getArg(context);
|
||||
if ( safe.RegExp_test.call(reCondition, argBefore) === false ) {
|
||||
return context.reflect();
|
||||
}
|
||||
const argAfter = replacer && typeof argBefore === 'string'
|
||||
? argBefore.replace(replacer.re, replacer.replacement)
|
||||
: value;
|
||||
if ( argAfter !== argBefore ) {
|
||||
setArg(context, argAfter);
|
||||
safe.uboLog(logPrefix, `Replaced argument:\nBefore: ${JSON.stringify(argBefore)}\nAfter: ${argAfter}`);
|
||||
}
|
||||
return context.reflect();
|
||||
});
|
||||
}
|
||||
registerScriptlet(trustedReplaceArgument, {
|
||||
name: 'trusted-replace-argument.js',
|
||||
requiresTrust: true,
|
||||
dependencies: [
|
||||
parseReplaceFn,
|
||||
proxyApplyFn,
|
||||
safeSelf,
|
||||
validateConstantFn,
|
||||
],
|
||||
});
|
||||
96
uBlock0.chromium/js/resources/run-at.js
Normal file
96
uBlock0.chromium/js/resources/run-at.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
/**
|
||||
* @helperScriptlet run-at.fn
|
||||
*
|
||||
* @description
|
||||
* Execute a function at a specific page-load milestone.
|
||||
*
|
||||
* @param fn
|
||||
* The function to call.
|
||||
*
|
||||
* @param when
|
||||
* An identifier which tells when the function should be executed.
|
||||
* See <https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState>.
|
||||
*
|
||||
* @example
|
||||
* `runAt(( ) => { start(); }, 'interactive')`
|
||||
*
|
||||
* */
|
||||
|
||||
export function runAt(fn, when) {
|
||||
const intFromReadyState = state => {
|
||||
const targets = {
|
||||
'loading': 1, 'asap': 1,
|
||||
'interactive': 2, 'end': 2, '2': 2,
|
||||
'complete': 3, 'idle': 3, '3': 3,
|
||||
};
|
||||
const tokens = Array.isArray(state) ? state : [ state ];
|
||||
for ( const token of tokens ) {
|
||||
const prop = `${token}`;
|
||||
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
||||
return targets[prop];
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const runAt = intFromReadyState(when);
|
||||
if ( intFromReadyState(document.readyState) >= runAt ) {
|
||||
fn(); return;
|
||||
}
|
||||
const onStateChange = ( ) => {
|
||||
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
||||
fn();
|
||||
safe.removeEventListener.apply(document, args);
|
||||
};
|
||||
const safe = safeSelf();
|
||||
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
||||
safe.addEventListener.apply(document, args);
|
||||
}
|
||||
registerScriptlet(runAt, {
|
||||
name: 'run-at.fn',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function runAtHtmlElementFn(fn) {
|
||||
if ( document.documentElement ) {
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
const observer = new MutationObserver(( ) => {
|
||||
observer.disconnect();
|
||||
fn();
|
||||
});
|
||||
observer.observe(document, { childList: true });
|
||||
}
|
||||
registerScriptlet(runAtHtmlElementFn, {
|
||||
name: 'run-at-html-element.fn',
|
||||
});
|
||||
219
uBlock0.chromium/js/resources/safe-self.js
Normal file
219
uBlock0.chromium/js/resources/safe-self.js
Normal file
@@ -0,0 +1,219 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Externally added to the private namespace in which scriptlets execute.
|
||||
/* global scriptletGlobals */
|
||||
|
||||
export function safeSelf() {
|
||||
if ( scriptletGlobals.safeSelf ) {
|
||||
return scriptletGlobals.safeSelf;
|
||||
}
|
||||
const self = globalThis;
|
||||
const safe = {
|
||||
'Array_from': Array.from,
|
||||
'Error': self.Error,
|
||||
'Function_toStringFn': self.Function.prototype.toString,
|
||||
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
|
||||
'Math_floor': Math.floor,
|
||||
'Math_max': Math.max,
|
||||
'Math_min': Math.min,
|
||||
'Math_random': Math.random,
|
||||
'Object': Object,
|
||||
'Object_defineProperty': Object.defineProperty.bind(Object),
|
||||
'Object_defineProperties': Object.defineProperties.bind(Object),
|
||||
'Object_fromEntries': Object.fromEntries.bind(Object),
|
||||
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
|
||||
'RegExp': self.RegExp,
|
||||
'RegExp_test': self.RegExp.prototype.test,
|
||||
'RegExp_exec': self.RegExp.prototype.exec,
|
||||
'Request_clone': self.Request.prototype.clone,
|
||||
'String_fromCharCode': String.fromCharCode,
|
||||
'String_split': String.prototype.split,
|
||||
'XMLHttpRequest': self.XMLHttpRequest,
|
||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||
'fetch': self.fetch,
|
||||
'JSON': self.JSON,
|
||||
'JSON_parseFn': self.JSON.parse,
|
||||
'JSON_stringifyFn': self.JSON.stringify,
|
||||
'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args),
|
||||
'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args),
|
||||
'log': console.log.bind(console),
|
||||
// Properties
|
||||
logLevel: 0,
|
||||
// Methods
|
||||
makeLogPrefix(...args) {
|
||||
return this.sendToLogger && `[${args.join(' \u205D ')}]` || '';
|
||||
},
|
||||
uboLog(...args) {
|
||||
if ( this.sendToLogger === undefined ) { return; }
|
||||
if ( args === undefined || args[0] === '' ) { return; }
|
||||
return this.sendToLogger('info', ...args);
|
||||
|
||||
},
|
||||
uboErr(...args) {
|
||||
if ( this.sendToLogger === undefined ) { return; }
|
||||
if ( args === undefined || args[0] === '' ) { return; }
|
||||
return this.sendToLogger('error', ...args);
|
||||
},
|
||||
escapeRegexChars(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
},
|
||||
initPattern(pattern, options = {}) {
|
||||
if ( pattern === '' ) {
|
||||
return { matchAll: true, expect: true };
|
||||
}
|
||||
const expect = (options.canNegate !== true || pattern.startsWith('!') === false);
|
||||
if ( expect === false ) {
|
||||
pattern = pattern.slice(1);
|
||||
}
|
||||
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
||||
if ( match !== null ) {
|
||||
return {
|
||||
re: new this.RegExp(
|
||||
match[1],
|
||||
match[2] || options.flags
|
||||
),
|
||||
expect,
|
||||
};
|
||||
}
|
||||
if ( options.flags !== undefined ) {
|
||||
return {
|
||||
re: new this.RegExp(this.escapeRegexChars(pattern),
|
||||
options.flags
|
||||
),
|
||||
expect,
|
||||
};
|
||||
}
|
||||
return { pattern, expect };
|
||||
},
|
||||
testPattern(details, haystack) {
|
||||
if ( details.matchAll ) { return true; }
|
||||
if ( details.re ) {
|
||||
return this.RegExp_test.call(details.re, haystack) === details.expect;
|
||||
}
|
||||
return haystack.includes(details.pattern) === details.expect;
|
||||
},
|
||||
patternToRegex(pattern, flags = undefined, verbatim = false) {
|
||||
if ( pattern === '' ) { return /^/; }
|
||||
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
||||
if ( match === null ) {
|
||||
const reStr = this.escapeRegexChars(pattern);
|
||||
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
|
||||
}
|
||||
try {
|
||||
return new RegExp(match[1], match[2] || undefined);
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
return /^/;
|
||||
},
|
||||
getExtraArgs(args, offset = 0) {
|
||||
const entries = args.slice(offset).reduce((out, v, i, a) => {
|
||||
if ( (i & 1) === 0 ) {
|
||||
const rawValue = a[i+1];
|
||||
const value = /^\d+$/.test(rawValue)
|
||||
? parseInt(rawValue, 10)
|
||||
: rawValue;
|
||||
out.push([ a[i], value ]);
|
||||
}
|
||||
return out;
|
||||
}, []);
|
||||
return this.Object_fromEntries(entries);
|
||||
},
|
||||
onIdle(fn, options) {
|
||||
if ( self.requestIdleCallback ) {
|
||||
return self.requestIdleCallback(fn, options);
|
||||
}
|
||||
return self.requestAnimationFrame(fn);
|
||||
},
|
||||
offIdle(id) {
|
||||
if ( self.requestIdleCallback ) {
|
||||
return self.cancelIdleCallback(id);
|
||||
}
|
||||
return self.cancelAnimationFrame(id);
|
||||
}
|
||||
};
|
||||
scriptletGlobals.safeSelf = safe;
|
||||
if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
|
||||
// This is executed only when the logger is opened
|
||||
safe.logLevel = scriptletGlobals.logLevel || 1;
|
||||
let lastLogType = '';
|
||||
let lastLogText = '';
|
||||
let lastLogTime = 0;
|
||||
safe.toLogText = (type, ...args) => {
|
||||
if ( args.length === 0 ) { return; }
|
||||
const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
|
||||
if ( text === lastLogText && type === lastLogType ) {
|
||||
if ( (Date.now() - lastLogTime) < 5000 ) { return; }
|
||||
}
|
||||
lastLogType = type;
|
||||
lastLogText = text;
|
||||
lastLogTime = Date.now();
|
||||
return text;
|
||||
};
|
||||
try {
|
||||
const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
|
||||
let bcBuffer = [];
|
||||
safe.sendToLogger = (type, ...args) => {
|
||||
const text = safe.toLogText(type, ...args);
|
||||
if ( text === undefined ) { return; }
|
||||
if ( bcBuffer === undefined ) {
|
||||
return bc.postMessage({ what: 'messageToLogger', type, text });
|
||||
}
|
||||
bcBuffer.push({ type, text });
|
||||
};
|
||||
bc.onmessage = ev => {
|
||||
const msg = ev.data;
|
||||
switch ( msg ) {
|
||||
case 'iamready!':
|
||||
if ( bcBuffer === undefined ) { break; }
|
||||
bcBuffer.forEach(({ type, text }) =>
|
||||
bc.postMessage({ what: 'messageToLogger', type, text })
|
||||
);
|
||||
bcBuffer = undefined;
|
||||
break;
|
||||
case 'setScriptletLogLevelToOne':
|
||||
safe.logLevel = 1;
|
||||
break;
|
||||
case 'setScriptletLogLevelToTwo':
|
||||
safe.logLevel = 2;
|
||||
break;
|
||||
}
|
||||
};
|
||||
bc.postMessage('areyouready?');
|
||||
} catch(_) {
|
||||
safe.sendToLogger = (type, ...args) => {
|
||||
const text = safe.toLogText(type, ...args);
|
||||
if ( text === undefined ) { return; }
|
||||
safe.log(`uBO ${text}`);
|
||||
};
|
||||
}
|
||||
return safe;
|
||||
}
|
||||
registerScriptlet(safeSelf, {
|
||||
name: 'safe-self.fn',
|
||||
});
|
||||
3530
uBlock0.chromium/js/resources/scriptlets.js
Normal file
3530
uBlock0.chromium/js/resources/scriptlets.js
Normal file
File diff suppressed because it is too large
Load Diff
287
uBlock0.chromium/js/resources/set-constant.js
Normal file
287
uBlock0.chromium/js/resources/set-constant.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export 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;
|
||||
}
|
||||
registerScriptlet(validateConstantFn, {
|
||||
name: 'validate-constant.fn',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export 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);
|
||||
if ( aborted ) {
|
||||
safe.uboLog(logPrefix, `Aborted because value set to ${v}`);
|
||||
}
|
||||
return aborted;
|
||||
};
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
||||
// Support multiple trappers for the same property.
|
||||
const trapProp = function(owner, prop, configurable, handler) {
|
||||
if ( handler.init(configurable ? owner[prop] : normalValue) === false ) { return; }
|
||||
const odesc = safe.Object_getOwnPropertyDescriptor(owner, prop);
|
||||
let prevGetter, prevSetter;
|
||||
if ( odesc instanceof safe.Object ) {
|
||||
owner[prop] = normalValue;
|
||||
if ( odesc.get instanceof Function ) {
|
||||
prevGetter = odesc.get;
|
||||
}
|
||||
if ( odesc.set instanceof Function ) {
|
||||
prevSetter = odesc.set;
|
||||
}
|
||||
}
|
||||
try {
|
||||
safe.Object_defineProperty(owner, prop, {
|
||||
configurable,
|
||||
get() {
|
||||
if ( prevGetter !== undefined ) {
|
||||
prevGetter();
|
||||
}
|
||||
return handler.getter();
|
||||
},
|
||||
set(a) {
|
||||
if ( prevSetter !== undefined ) {
|
||||
prevSetter(a);
|
||||
}
|
||||
handler.setter(a);
|
||||
}
|
||||
});
|
||||
safe.uboLog(logPrefix, 'Trap installed');
|
||||
} catch(ex) {
|
||||
safe.uboErr(logPrefix, ex);
|
||||
}
|
||||
};
|
||||
const trapChain = function(owner, chain) {
|
||||
const pos = chain.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
trapProp(owner, chain, false, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
if ( mustAbort(v) ) { return false; }
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
if ( document.currentScript === thisScript ) {
|
||||
return this.v;
|
||||
}
|
||||
safe.uboLog(logPrefix, 'Property read');
|
||||
return normalValue;
|
||||
},
|
||||
setter: function(a) {
|
||||
if ( mustAbort(a) === false ) { return; }
|
||||
normalValue = a;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prop = chain.slice(0, pos);
|
||||
const v = owner[prop];
|
||||
chain = chain.slice(pos + 1);
|
||||
if ( v instanceof safe.Object || typeof v === 'object' && v !== null ) {
|
||||
trapChain(v, chain);
|
||||
return;
|
||||
}
|
||||
trapProp(owner, prop, true, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
return this.v;
|
||||
},
|
||||
setter: function(a) {
|
||||
this.v = a;
|
||||
if ( a instanceof safe.Object ) {
|
||||
trapChain(a, chain);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
trapChain(window, chain);
|
||||
}
|
||||
runAt(( ) => {
|
||||
setConstant(chain, rawValue);
|
||||
}, extraArgs.runAt);
|
||||
}
|
||||
registerScriptlet(setConstantFn, {
|
||||
name: 'set-constant.fn',
|
||||
dependencies: [
|
||||
runAt,
|
||||
safeSelf,
|
||||
validateConstantFn,
|
||||
],
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function setConstant(
|
||||
...args
|
||||
) {
|
||||
setConstantFn(false, ...args);
|
||||
}
|
||||
registerScriptlet(setConstant, {
|
||||
name: 'set-constant.js',
|
||||
aliases: [
|
||||
'set.js',
|
||||
],
|
||||
dependencies: [
|
||||
setConstantFn,
|
||||
],
|
||||
});
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
**/
|
||||
|
||||
export function trustedSetConstant(
|
||||
...args
|
||||
) {
|
||||
setConstantFn(true, ...args);
|
||||
}
|
||||
registerScriptlet(trustedSetConstant, {
|
||||
name: 'trusted-set-constant.js',
|
||||
requiresTrust: true,
|
||||
aliases: [
|
||||
'trusted-set.js',
|
||||
],
|
||||
dependencies: [
|
||||
setConstantFn,
|
||||
],
|
||||
});
|
||||
44
uBlock0.chromium/js/resources/shared.js
Normal file
44
uBlock0.chromium/js/resources/shared.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
// Code imported from main code base and exposed as injectable scriptlets
|
||||
import { ArglistParser } from '../arglist-parser.js';
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
registerScriptlet(ArglistParser, {
|
||||
name: 'arglist-parser.fn',
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function createArglistParser(...args) {
|
||||
return new ArglistParser(...args);
|
||||
}
|
||||
registerScriptlet(createArglistParser, {
|
||||
name: 'create-arglist-parser.fn',
|
||||
dependencies: [
|
||||
ArglistParser,
|
||||
],
|
||||
});
|
||||
163
uBlock0.chromium/js/resources/spoof-css.js
Normal file
163
uBlock0.chromium/js/resources/spoof-css.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
import { registerScriptlet } from './base.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/**
|
||||
* @scriptlet spoof-css.js
|
||||
*
|
||||
* @description
|
||||
* Spoof the value of CSS properties.
|
||||
*
|
||||
* @param selector
|
||||
* A CSS selector for the element(s) to target.
|
||||
*
|
||||
* @param [property, value, ...]
|
||||
* A list of property-value pairs of the style properties to spoof to the
|
||||
* specified values.
|
||||
*
|
||||
* */
|
||||
|
||||
export function spoofCSS(
|
||||
selector,
|
||||
...args
|
||||
) {
|
||||
if ( typeof selector !== 'string' ) { return; }
|
||||
if ( selector === '' ) { return; }
|
||||
const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase());
|
||||
const propToValueMap = new Map();
|
||||
const privatePropToValueMap = new Map();
|
||||
for ( let i = 0; i < args.length; i += 2 ) {
|
||||
const prop = toCamelCase(args[i+0]);
|
||||
if ( prop === '' ) { break; }
|
||||
const value = args[i+1];
|
||||
if ( typeof value !== 'string' ) { break; }
|
||||
if ( prop.charCodeAt(0) === 0x5F /* _ */ ) {
|
||||
privatePropToValueMap.set(prop, value);
|
||||
} else {
|
||||
propToValueMap.set(prop, value);
|
||||
}
|
||||
}
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
|
||||
const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
|
||||
const spoofStyle = (prop, real) => {
|
||||
const normalProp = toCamelCase(prop);
|
||||
const shouldSpoof = propToValueMap.has(normalProp);
|
||||
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
|
||||
if ( shouldSpoof ) {
|
||||
safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const cloackFunc = (fn, thisArg, name) => {
|
||||
const trap = fn.bind(thisArg);
|
||||
Object.defineProperty(trap, 'name', { value: name });
|
||||
Object.defineProperty(trap, 'toString', {
|
||||
value: ( ) => `function ${name}() { [native code] }`
|
||||
});
|
||||
return trap;
|
||||
};
|
||||
self.getComputedStyle = new Proxy(self.getComputedStyle, {
|
||||
apply: function(target, thisArg, args) {
|
||||
// eslint-disable-next-line no-debugger
|
||||
if ( privatePropToValueMap.has('_debug') ) { debugger; }
|
||||
const style = Reflect.apply(target, thisArg, args);
|
||||
const targetElements = new WeakSet(document.querySelectorAll(selector));
|
||||
if ( targetElements.has(args[0]) === false ) { return style; }
|
||||
const proxiedStyle = new Proxy(style, {
|
||||
get(target, prop) {
|
||||
if ( typeof target[prop] === 'function' ) {
|
||||
if ( prop === 'getPropertyValue' ) {
|
||||
return cloackFunc(function getPropertyValue(prop) {
|
||||
return spoofStyle(prop, target[prop]);
|
||||
}, target, 'getPropertyValue');
|
||||
}
|
||||
return cloackFunc(target[prop], target, prop);
|
||||
}
|
||||
if ( instanceProperties.includes(prop) ) {
|
||||
return Reflect.get(target, prop);
|
||||
}
|
||||
return spoofStyle(prop, Reflect.get(target, prop));
|
||||
},
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
if ( propToValueMap.has(prop) ) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: propToValueMap.get(prop),
|
||||
writable: true,
|
||||
};
|
||||
}
|
||||
return Reflect.getOwnPropertyDescriptor(target, prop);
|
||||
},
|
||||
});
|
||||
return proxiedStyle;
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return target.toString.bind(target);
|
||||
}
|
||||
return Reflect.get(target, prop);
|
||||
},
|
||||
});
|
||||
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
|
||||
apply: function(target, thisArg, args) {
|
||||
// eslint-disable-next-line no-debugger
|
||||
if ( privatePropToValueMap.has('_debug') ) { debugger; }
|
||||
const rect = Reflect.apply(target, thisArg, args);
|
||||
const targetElements = new WeakSet(document.querySelectorAll(selector));
|
||||
if ( targetElements.has(thisArg) === false ) { return rect; }
|
||||
let { x, y, height, width } = rect;
|
||||
if ( privatePropToValueMap.has('_rectx') ) {
|
||||
x = parseFloat(privatePropToValueMap.get('_rectx'));
|
||||
}
|
||||
if ( privatePropToValueMap.has('_recty') ) {
|
||||
y = parseFloat(privatePropToValueMap.get('_recty'));
|
||||
}
|
||||
if ( privatePropToValueMap.has('_rectw') ) {
|
||||
width = parseFloat(privatePropToValueMap.get('_rectw'));
|
||||
} else if ( propToValueMap.has('width') ) {
|
||||
width = parseFloat(propToValueMap.get('width'));
|
||||
}
|
||||
if ( privatePropToValueMap.has('_recth') ) {
|
||||
height = parseFloat(privatePropToValueMap.get('_recth'));
|
||||
} else if ( propToValueMap.has('height') ) {
|
||||
height = parseFloat(propToValueMap.get('height'));
|
||||
}
|
||||
return new self.DOMRect(x, y, width, height);
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return target.toString.bind(target);
|
||||
}
|
||||
return Reflect.get(target, prop);
|
||||
},
|
||||
});
|
||||
}
|
||||
registerScriptlet(spoofCSS, {
|
||||
name: 'spoof-css.js',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user