Skip to content

Commit 11ca4a3

Browse files
committed
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.
1 parent 0851015 commit 11ca4a3

File tree

5 files changed

+512
-340
lines changed

5 files changed

+512
-340
lines changed

assets/resources/run-at.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
The scriptlets below are meant to be injected only into a
22+
web page context.
23+
*/
24+
25+
import { safeSelf } from './safe-self.js';
26+
27+
/* eslint no-prototype-builtins: 0 */
28+
29+
/******************************************************************************/
30+
31+
export function runAt(fn, when) {
32+
const intFromReadyState = state => {
33+
const targets = {
34+
'loading': 1, 'asap': 1,
35+
'interactive': 2, 'end': 2, '2': 2,
36+
'complete': 3, 'idle': 3, '3': 3,
37+
};
38+
const tokens = Array.isArray(state) ? state : [ state ];
39+
for ( const token of tokens ) {
40+
const prop = `${token}`;
41+
if ( targets.hasOwnProperty(prop) === false ) { continue; }
42+
return targets[prop];
43+
}
44+
return 0;
45+
};
46+
const runAt = intFromReadyState(when);
47+
if ( intFromReadyState(document.readyState) >= runAt ) {
48+
fn(); return;
49+
}
50+
const onStateChange = ( ) => {
51+
if ( intFromReadyState(document.readyState) < runAt ) { return; }
52+
fn();
53+
safe.removeEventListener.apply(document, args);
54+
};
55+
const safe = safeSelf();
56+
const args = [ 'readystatechange', onStateChange, { capture: true } ];
57+
safe.addEventListener.apply(document, args);
58+
}
59+
runAt.details = {
60+
name: 'run-at.fn',
61+
dependencies: [
62+
safeSelf,
63+
],
64+
};

assets/resources/safe-self.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
The scriptlets below are meant to be injected only into a
22+
web page context.
23+
*/
24+
25+
/******************************************************************************/
26+
27+
// Externally added to the private namespace in which scriptlets execute.
28+
/* global scriptletGlobals */
29+
30+
export function safeSelf() {
31+
if ( scriptletGlobals.safeSelf ) {
32+
return scriptletGlobals.safeSelf;
33+
}
34+
const self = globalThis;
35+
const safe = {
36+
'Array_from': Array.from,
37+
'Error': self.Error,
38+
'Function_toStringFn': self.Function.prototype.toString,
39+
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
40+
'Math_floor': Math.floor,
41+
'Math_max': Math.max,
42+
'Math_min': Math.min,
43+
'Math_random': Math.random,
44+
'Object': Object,
45+
'Object_defineProperty': Object.defineProperty.bind(Object),
46+
'Object_defineProperties': Object.defineProperties.bind(Object),
47+
'Object_fromEntries': Object.fromEntries.bind(Object),
48+
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
49+
'RegExp': self.RegExp,
50+
'RegExp_test': self.RegExp.prototype.test,
51+
'RegExp_exec': self.RegExp.prototype.exec,
52+
'Request_clone': self.Request.prototype.clone,
53+
'String_fromCharCode': String.fromCharCode,
54+
'XMLHttpRequest': self.XMLHttpRequest,
55+
'addEventListener': self.EventTarget.prototype.addEventListener,
56+
'removeEventListener': self.EventTarget.prototype.removeEventListener,
57+
'fetch': self.fetch,
58+
'JSON': self.JSON,
59+
'JSON_parseFn': self.JSON.parse,
60+
'JSON_stringifyFn': self.JSON.stringify,
61+
'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args),
62+
'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args),
63+
'log': console.log.bind(console),
64+
// Properties
65+
logLevel: 0,
66+
// Methods
67+
makeLogPrefix(...args) {
68+
return this.sendToLogger && `[${args.join(' \u205D ')}]` || '';
69+
},
70+
uboLog(...args) {
71+
if ( this.sendToLogger === undefined ) { return; }
72+
if ( args === undefined || args[0] === '' ) { return; }
73+
return this.sendToLogger('info', ...args);
74+
75+
},
76+
uboErr(...args) {
77+
if ( this.sendToLogger === undefined ) { return; }
78+
if ( args === undefined || args[0] === '' ) { return; }
79+
return this.sendToLogger('error', ...args);
80+
},
81+
escapeRegexChars(s) {
82+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
83+
},
84+
initPattern(pattern, options = {}) {
85+
if ( pattern === '' ) {
86+
return { matchAll: true, expect: true };
87+
}
88+
const expect = (options.canNegate !== true || pattern.startsWith('!') === false);
89+
if ( expect === false ) {
90+
pattern = pattern.slice(1);
91+
}
92+
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
93+
if ( match !== null ) {
94+
return {
95+
re: new this.RegExp(
96+
match[1],
97+
match[2] || options.flags
98+
),
99+
expect,
100+
};
101+
}
102+
if ( options.flags !== undefined ) {
103+
return {
104+
re: new this.RegExp(this.escapeRegexChars(pattern),
105+
options.flags
106+
),
107+
expect,
108+
};
109+
}
110+
return { pattern, expect };
111+
},
112+
testPattern(details, haystack) {
113+
if ( details.matchAll ) { return true; }
114+
if ( details.re ) {
115+
return this.RegExp_test.call(details.re, haystack) === details.expect;
116+
}
117+
return haystack.includes(details.pattern) === details.expect;
118+
},
119+
patternToRegex(pattern, flags = undefined, verbatim = false) {
120+
if ( pattern === '' ) { return /^/; }
121+
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
122+
if ( match === null ) {
123+
const reStr = this.escapeRegexChars(pattern);
124+
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
125+
}
126+
try {
127+
return new RegExp(match[1], match[2] || undefined);
128+
}
129+
catch(ex) {
130+
}
131+
return /^/;
132+
},
133+
getExtraArgs(args, offset = 0) {
134+
const entries = args.slice(offset).reduce((out, v, i, a) => {
135+
if ( (i & 1) === 0 ) {
136+
const rawValue = a[i+1];
137+
const value = /^\d+$/.test(rawValue)
138+
? parseInt(rawValue, 10)
139+
: rawValue;
140+
out.push([ a[i], value ]);
141+
}
142+
return out;
143+
}, []);
144+
return this.Object_fromEntries(entries);
145+
},
146+
onIdle(fn, options) {
147+
if ( self.requestIdleCallback ) {
148+
return self.requestIdleCallback(fn, options);
149+
}
150+
return self.requestAnimationFrame(fn);
151+
},
152+
offIdle(id) {
153+
if ( self.requestIdleCallback ) {
154+
return self.cancelIdleCallback(id);
155+
}
156+
return self.cancelAnimationFrame(id);
157+
}
158+
};
159+
scriptletGlobals.safeSelf = safe;
160+
if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
161+
// This is executed only when the logger is opened
162+
safe.logLevel = scriptletGlobals.logLevel || 1;
163+
let lastLogType = '';
164+
let lastLogText = '';
165+
let lastLogTime = 0;
166+
safe.toLogText = (type, ...args) => {
167+
if ( args.length === 0 ) { return; }
168+
const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
169+
if ( text === lastLogText && type === lastLogType ) {
170+
if ( (Date.now() - lastLogTime) < 5000 ) { return; }
171+
}
172+
lastLogType = type;
173+
lastLogText = text;
174+
lastLogTime = Date.now();
175+
return text;
176+
};
177+
try {
178+
const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
179+
let bcBuffer = [];
180+
safe.sendToLogger = (type, ...args) => {
181+
const text = safe.toLogText(type, ...args);
182+
if ( text === undefined ) { return; }
183+
if ( bcBuffer === undefined ) {
184+
return bc.postMessage({ what: 'messageToLogger', type, text });
185+
}
186+
bcBuffer.push({ type, text });
187+
};
188+
bc.onmessage = ev => {
189+
const msg = ev.data;
190+
switch ( msg ) {
191+
case 'iamready!':
192+
if ( bcBuffer === undefined ) { break; }
193+
bcBuffer.forEach(({ type, text }) =>
194+
bc.postMessage({ what: 'messageToLogger', type, text })
195+
);
196+
bcBuffer = undefined;
197+
break;
198+
case 'setScriptletLogLevelToOne':
199+
safe.logLevel = 1;
200+
break;
201+
case 'setScriptletLogLevelToTwo':
202+
safe.logLevel = 2;
203+
break;
204+
}
205+
};
206+
bc.postMessage('areyouready?');
207+
} catch(_) {
208+
safe.sendToLogger = (type, ...args) => {
209+
const text = safe.toLogText(type, ...args);
210+
if ( text === undefined ) { return; }
211+
safe.log(`uBO ${text}`);
212+
};
213+
}
214+
return safe;
215+
}
216+
safeSelf.details = {
217+
name: 'safe-self.fn',
218+
};

0 commit comments

Comments
 (0)