1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-06 02:42:33 +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:
Raymond Hill 2024-10-25 19:12:08 -04:00
parent 0851015d7d
commit 11ca4a3923
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
5 changed files with 512 additions and 340 deletions

View 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,
],
};

View 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',
};

View File

@ -22,6 +22,10 @@
web page context. 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 */ /* eslint no-prototype-builtins: 0 */
// Externally added to the private namespace in which scriptlets execute. // Externally added to the private namespace in which scriptlets execute.
@ -29,6 +33,30 @@
export const builtinScriptlets = []; 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 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({ builtinScriptlets.push({
name: 'get-random-token.fn', name: 'get-random-token.fn',
fn: getRandomToken, fn: getRandomToken,
@ -283,34 +118,6 @@ builtinScriptlets.push({
'safe-self.fn', '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); 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 * @scriptlet prevent-canvas

View 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',
};
/******************************************************************************/

View File

@ -109,7 +109,7 @@ if [ "$QUICK" != "yes" ]; then
cp platform/mv3/*.mjs "$TMPDIR"/ cp platform/mv3/*.mjs "$TMPDIR"/
cp platform/mv3/extension/js/utils.js "$TMPDIR"/js/ cp platform/mv3/extension/js/utils.js "$TMPDIR"/js/
cp "$UBO_DIR"/assets/assets.json "$TMPDIR"/ 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"/ cp -R platform/mv3/scriptlets "$TMPDIR"/
mkdir -p "$TMPDIR"/web_accessible_resources mkdir -p "$TMPDIR"/web_accessible_resources
cp "$UBO_DIR"/src/web_accessible_resources/* "$TMPDIR"/web_accessible_resources/ cp "$UBO_DIR"/src/web_accessible_resources/* "$TMPDIR"/web_accessible_resources/