mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-22 02:12:44 +01:00
Add trusted-set-attr
scriptlet
@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. ===== Additionally, start to move scriptlets into their own source files for easier maintenance and code review.
This commit is contained in:
parent
0851015d7d
commit
11ca4a3923
64
assets/resources/run-at.js
Normal file
64
assets/resources/run-at.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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 { safeSelf } from './safe-self.js';
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
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);
|
||||
}
|
||||
runAt.details = {
|
||||
name: 'run-at.fn',
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
],
|
||||
};
|
218
assets/resources/safe-self.js
Normal file
218
assets/resources/safe-self.js
Normal file
@ -0,0 +1,218 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// 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,
|
||||
'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;
|
||||
}
|
||||
safeSelf.details = {
|
||||
name: 'safe-self.fn',
|
||||
};
|
@ -22,6 +22,10 @@
|
||||
web page context.
|
||||
*/
|
||||
|
||||
import { setAttr, setAttrFn, trustedSetAttr } from './set-attr.js';
|
||||
import { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
// Externally added to the private namespace in which scriptlets execute.
|
||||
@ -29,6 +33,30 @@
|
||||
|
||||
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(setAttrFn);
|
||||
registerScriptlet(setAttr);
|
||||
registerScriptlet(trustedSetAttr);
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
Helper functions
|
||||
@ -37,199 +65,6 @@ export const builtinScriptlets = [];
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'safe-self.fn',
|
||||
fn: safeSelf,
|
||||
});
|
||||
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,
|
||||
'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;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'get-random-token.fn',
|
||||
fn: getRandomToken,
|
||||
@ -283,34 +118,6 @@ builtinScriptlets.push({
|
||||
'safe-self.fn',
|
||||
],
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -4119,124 +3926,6 @@ function setSessionStorageItem(key = '', value = '') {
|
||||
setLocalStorageItemFn('session', false, key, value);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* ```text
|
||||
* example.org##+js(set-attr, selector, attr [, value])
|
||||
* ```
|
||||
*
|
||||
* - `selector`: CSS selector of DOM elements for which the attribute `attr`
|
||||
* must be modified.
|
||||
* - `attr`: the name of the attribute to modify
|
||||
* - `value`: the value to assign to the target attribute. Possible values:
|
||||
* - `''`: empty string (default)
|
||||
* - `true`
|
||||
* - `false`
|
||||
* - positive decimal integer 0 <= value < 32768
|
||||
* - `[other]`: copy the value from attribute `other` on the same element
|
||||
* */
|
||||
|
||||
builtinScriptlets.push({
|
||||
name: 'set-attr.js',
|
||||
fn: setAttr,
|
||||
world: 'ISOLATED',
|
||||
dependencies: [
|
||||
'run-at.fn',
|
||||
'safe-self.fn',
|
||||
],
|
||||
});
|
||||
function setAttr(
|
||||
selector = '',
|
||||
attr = '',
|
||||
value = ''
|
||||
) {
|
||||
if ( selector === '' ) { return; }
|
||||
if ( attr === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const logPrefix = safe.makeLogPrefix('set-attr', attr, value);
|
||||
const validValues = [ '', 'false', 'true' ];
|
||||
let copyFrom = '';
|
||||
|
||||
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) ) {
|
||||
copyFrom = value.slice(1, -1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const extractValue = elem => {
|
||||
if ( copyFrom !== '' ) {
|
||||
return elem.getAttribute(copyFrom) || '';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const applySetAttr = ( ) => {
|
||||
const elems = [];
|
||||
try {
|
||||
elems.push(...document.querySelectorAll(selector));
|
||||
}
|
||||
catch(ex) {
|
||||
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');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* @scriptlet prevent-canvas
|
||||
|
201
assets/resources/set-attr.js
Normal file
201
assets/resources/set-attr.js
Normal file
@ -0,0 +1,201 @@
|
||||
/*******************************************************************************
|
||||
|
||||
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 { runAt } from './run-at.js';
|
||||
import { safeSelf } from './safe-self.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function setAttrFn(
|
||||
logPrefix,
|
||||
selector = '',
|
||||
attr = '',
|
||||
value = ''
|
||||
) {
|
||||
if ( selector === '' ) { return; }
|
||||
if ( attr === '' ) { return; }
|
||||
|
||||
const safe = safeSelf();
|
||||
const copyFrom = /^\[.+\]$/.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');
|
||||
}
|
||||
setAttrFn.details = {
|
||||
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(logPrefix, selector, attr, value);
|
||||
}
|
||||
setAttr.details = {
|
||||
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(logPrefix, selector, attr, value);
|
||||
}
|
||||
trustedSetAttr.details = {
|
||||
name: 'trusted-set-attr.js',
|
||||
requiresTrust: true,
|
||||
dependencies: [
|
||||
safeSelf,
|
||||
setAttrFn,
|
||||
],
|
||||
world: 'ISOLATED',
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
@ -109,7 +109,7 @@ if [ "$QUICK" != "yes" ]; then
|
||||
cp platform/mv3/*.mjs "$TMPDIR"/
|
||||
cp platform/mv3/extension/js/utils.js "$TMPDIR"/js/
|
||||
cp "$UBO_DIR"/assets/assets.json "$TMPDIR"/
|
||||
cp "$UBO_DIR"/assets/resources/scriptlets.js "$TMPDIR"/
|
||||
cp "$UBO_DIR"/assets/resources/*.js "$TMPDIR"/
|
||||
cp -R platform/mv3/scriptlets "$TMPDIR"/
|
||||
mkdir -p "$TMPDIR"/web_accessible_resources
|
||||
cp "$UBO_DIR"/src/web_accessible_resources/* "$TMPDIR"/web_accessible_resources/
|
||||
|
Loading…
Reference in New Issue
Block a user