mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-26 04:12:50 +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.
|
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
|
||||||
|
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/*.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/
|
||||||
|
Loading…
Reference in New Issue
Block a user