@browserless.io/browserless 2.23.0 → 2.24.0-beta-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/routes/chrome/http/pdf.post.body.json +8 -8
- package/build/routes/chrome/http/scrape.post.body.json +8 -8
- package/build/routes/chrome/http/screenshot.post.body.json +8 -8
- package/build/routes/chromium/http/pdf.post.body.json +8 -8
- package/build/routes/chromium/http/screenshot.post.body.json +8 -8
- package/docker/multi/Dockerfile +2 -1
- package/docker/webkit/Dockerfile +2 -1
- package/extensions/ublock/_locales/ar/messages.json +5 -1
- package/extensions/ublock/_locales/az/messages.json +4 -0
- package/extensions/ublock/_locales/be/messages.json +5 -1
- package/extensions/ublock/_locales/bg/messages.json +4 -0
- package/extensions/ublock/_locales/bn/messages.json +4 -0
- package/extensions/ublock/_locales/br_FR/messages.json +4 -0
- package/extensions/ublock/_locales/bs/messages.json +4 -0
- package/extensions/ublock/_locales/ca/messages.json +4 -0
- package/extensions/ublock/_locales/cs/messages.json +6 -2
- package/extensions/ublock/_locales/cv/messages.json +4 -0
- package/extensions/ublock/_locales/cy/messages.json +15 -11
- package/extensions/ublock/_locales/da/messages.json +4 -0
- package/extensions/ublock/_locales/de/messages.json +8 -4
- package/extensions/ublock/_locales/el/messages.json +4 -0
- package/extensions/ublock/_locales/en/messages.json +4 -0
- package/extensions/ublock/_locales/en_GB/messages.json +4 -0
- package/extensions/ublock/_locales/eo/messages.json +4 -0
- package/extensions/ublock/_locales/es/messages.json +5 -1
- package/extensions/ublock/_locales/et/messages.json +4 -0
- package/extensions/ublock/_locales/eu/messages.json +4 -0
- package/extensions/ublock/_locales/fa/messages.json +14 -10
- package/extensions/ublock/_locales/fi/messages.json +7 -3
- package/extensions/ublock/_locales/fil/messages.json +4 -0
- package/extensions/ublock/_locales/fr/messages.json +5 -1
- package/extensions/ublock/_locales/fy/messages.json +4 -0
- package/extensions/ublock/_locales/gl/messages.json +4 -0
- package/extensions/ublock/_locales/gu/messages.json +4 -0
- package/extensions/ublock/_locales/he/messages.json +4 -0
- package/extensions/ublock/_locales/hi/messages.json +4 -0
- package/extensions/ublock/_locales/hr/messages.json +4 -0
- package/extensions/ublock/_locales/hu/messages.json +4 -0
- package/extensions/ublock/_locales/hy/messages.json +4 -0
- package/extensions/ublock/_locales/id/messages.json +4 -0
- package/extensions/ublock/_locales/it/messages.json +4 -0
- package/extensions/ublock/_locales/ja/messages.json +6 -2
- package/extensions/ublock/_locales/ka/messages.json +5 -1
- package/extensions/ublock/_locales/kk/messages.json +4 -0
- package/extensions/ublock/_locales/kn/messages.json +4 -0
- package/extensions/ublock/_locales/ko/messages.json +5 -1
- package/extensions/ublock/_locales/lt/messages.json +4 -0
- package/extensions/ublock/_locales/lv/messages.json +4 -0
- package/extensions/ublock/_locales/mk/messages.json +4 -0
- package/extensions/ublock/_locales/ml/messages.json +4 -0
- package/extensions/ublock/_locales/mr/messages.json +4 -0
- package/extensions/ublock/_locales/ms/messages.json +4 -0
- package/extensions/ublock/_locales/nb/messages.json +4 -0
- package/extensions/ublock/_locales/nl/messages.json +4 -0
- package/extensions/ublock/_locales/no/messages.json +4 -0
- package/extensions/ublock/_locales/oc/messages.json +4 -0
- package/extensions/ublock/_locales/pa/messages.json +5 -1
- package/extensions/ublock/_locales/pl/messages.json +4 -0
- package/extensions/ublock/_locales/pt_BR/messages.json +4 -0
- package/extensions/ublock/_locales/pt_PT/messages.json +4 -0
- package/extensions/ublock/_locales/ro/messages.json +4 -0
- package/extensions/ublock/_locales/ru/messages.json +5 -1
- package/extensions/ublock/_locales/si/messages.json +4 -0
- package/extensions/ublock/_locales/sk/messages.json +4 -0
- package/extensions/ublock/_locales/sl/messages.json +4 -0
- package/extensions/ublock/_locales/so/messages.json +4 -0
- package/extensions/ublock/_locales/sq/messages.json +4 -0
- package/extensions/ublock/_locales/sr/messages.json +4 -0
- package/extensions/ublock/_locales/sv/messages.json +5 -1
- package/extensions/ublock/_locales/sw/messages.json +4 -0
- package/extensions/ublock/_locales/ta/messages.json +4 -0
- package/extensions/ublock/_locales/te/messages.json +4 -0
- package/extensions/ublock/_locales/th/messages.json +4 -0
- package/extensions/ublock/_locales/tr/messages.json +4 -0
- package/extensions/ublock/_locales/uk/messages.json +7 -3
- package/extensions/ublock/_locales/ur/messages.json +4 -0
- package/extensions/ublock/_locales/vi/messages.json +4 -0
- package/extensions/ublock/_locales/zh_CN/messages.json +4 -0
- package/extensions/ublock/_locales/zh_TW/messages.json +4 -0
- package/extensions/ublock/assets/assets.json +14 -14
- package/extensions/ublock/assets/resources/run-at.js +80 -0
- package/extensions/ublock/assets/resources/safe-self.js +218 -0
- package/extensions/ublock/assets/resources/scriptlets.js +461 -538
- package/extensions/ublock/assets/resources/set-attr.js +201 -0
- package/extensions/ublock/assets/thirdparties/easylist/easylist.txt +4002 -11582
- package/extensions/ublock/assets/thirdparties/easylist/easyprivacy.txt +257 -83
- package/extensions/ublock/assets/thirdparties/pgl.yoyo.org/as/serverlist +22 -38
- package/extensions/ublock/assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat +102 -110
- package/extensions/ublock/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt +2776 -3613
- package/extensions/ublock/assets/ublock/badware.min.txt +581 -382
- package/extensions/ublock/assets/ublock/filters.min.txt +997 -990
- package/extensions/ublock/assets/ublock/privacy.min.txt +145 -26
- package/extensions/ublock/assets/ublock/quick-fixes.min.txt +134 -83
- package/extensions/ublock/assets/ublock/unbreak.min.txt +69 -46
- package/extensions/ublock/css/document-blocked.css +28 -1
- package/extensions/ublock/document-blocked.html +9 -7
- package/extensions/ublock/js/assets.js +15 -11
- package/extensions/ublock/js/benchmarks.js +13 -9
- package/extensions/ublock/js/biditrie.js +1 -1
- package/extensions/ublock/js/document-blocked.js +41 -5
- package/extensions/ublock/js/hntrie.js +1 -1
- package/extensions/ublock/js/i18n.js +2 -4
- package/extensions/ublock/js/pagestore.js +61 -43
- package/extensions/ublock/js/s14e-serializer.js +47 -22
- package/extensions/ublock/js/scriptlet-filtering.js +21 -18
- package/extensions/ublock/js/scriptlets/load-large-media-interactive.js +61 -48
- package/extensions/ublock/js/scriptlets/should-inject-contentscript.js +1 -3
- package/extensions/ublock/js/static-dnr-filtering.js +9 -7
- package/extensions/ublock/js/static-filtering-parser.js +87 -54
- package/extensions/ublock/js/static-net-filtering.js +118 -42
- package/extensions/ublock/js/traffic.js +37 -18
- package/extensions/ublock/js/vapi-background-ext.js +21 -49
- package/extensions/ublock/js/vapi-background.js +8 -0
- package/extensions/ublock/manifest.json +1 -1
- package/extensions/ublock/web_accessible_resources/googlesyndication_adsbygoogle.js +3 -1
- package/package.json +15 -15
- package/static/docs/swagger.json +1 -1
- package/static/docs/swagger.min.json +1 -1
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
web page context.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { setAttr, setAttrFn, trustedSetAttr } from './set-attr.js';
|
|
26
|
+
import { runAt } from './run-at.js';
|
|
27
|
+
import { safeSelf } from './safe-self.js';
|
|
28
|
+
|
|
25
29
|
/* eslint no-prototype-builtins: 0 */
|
|
26
30
|
|
|
27
31
|
// Externally added to the private namespace in which scriptlets execute.
|
|
@@ -29,6 +33,30 @@
|
|
|
29
33
|
|
|
30
34
|
export const builtinScriptlets = [];
|
|
31
35
|
|
|
36
|
+
/******************************************************************************/
|
|
37
|
+
|
|
38
|
+
// Register scriptlets declared in other files.
|
|
39
|
+
|
|
40
|
+
const registerScriptlet = fn => {
|
|
41
|
+
const details = fn.details;
|
|
42
|
+
if ( typeof details !== 'object' ) {
|
|
43
|
+
throw new ReferenceError('Unknown scriptlet function');
|
|
44
|
+
}
|
|
45
|
+
details.fn = fn;
|
|
46
|
+
if ( Array.isArray(details.dependencies) ) {
|
|
47
|
+
details.dependencies.forEach((fn, i, array) => {
|
|
48
|
+
if ( typeof fn !== 'function' ) { return; }
|
|
49
|
+
array[i] = fn.details.name;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
builtinScriptlets.push(details);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
registerScriptlet(safeSelf);
|
|
56
|
+
registerScriptlet(setAttrFn);
|
|
57
|
+
registerScriptlet(setAttr);
|
|
58
|
+
registerScriptlet(trustedSetAttr);
|
|
59
|
+
|
|
32
60
|
/*******************************************************************************
|
|
33
61
|
|
|
34
62
|
Helper functions
|
|
@@ -37,186 +65,6 @@ export const builtinScriptlets = [];
|
|
|
37
65
|
|
|
38
66
|
*******************************************************************************/
|
|
39
67
|
|
|
40
|
-
builtinScriptlets.push({
|
|
41
|
-
name: 'safe-self.fn',
|
|
42
|
-
fn: safeSelf,
|
|
43
|
-
});
|
|
44
|
-
function safeSelf() {
|
|
45
|
-
if ( scriptletGlobals.safeSelf ) {
|
|
46
|
-
return scriptletGlobals.safeSelf;
|
|
47
|
-
}
|
|
48
|
-
const self = globalThis;
|
|
49
|
-
const safe = {
|
|
50
|
-
'Array_from': Array.from,
|
|
51
|
-
'Error': self.Error,
|
|
52
|
-
'Function_toStringFn': self.Function.prototype.toString,
|
|
53
|
-
'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg),
|
|
54
|
-
'Math_floor': Math.floor,
|
|
55
|
-
'Math_max': Math.max,
|
|
56
|
-
'Math_min': Math.min,
|
|
57
|
-
'Math_random': Math.random,
|
|
58
|
-
'Object': Object,
|
|
59
|
-
'Object_defineProperty': Object.defineProperty.bind(Object),
|
|
60
|
-
'Object_defineProperties': Object.defineProperties.bind(Object),
|
|
61
|
-
'Object_fromEntries': Object.fromEntries.bind(Object),
|
|
62
|
-
'Object_getOwnPropertyDescriptor': Object.getOwnPropertyDescriptor.bind(Object),
|
|
63
|
-
'RegExp': self.RegExp,
|
|
64
|
-
'RegExp_test': self.RegExp.prototype.test,
|
|
65
|
-
'RegExp_exec': self.RegExp.prototype.exec,
|
|
66
|
-
'Request_clone': self.Request.prototype.clone,
|
|
67
|
-
'String_fromCharCode': String.fromCharCode,
|
|
68
|
-
'XMLHttpRequest': self.XMLHttpRequest,
|
|
69
|
-
'addEventListener': self.EventTarget.prototype.addEventListener,
|
|
70
|
-
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
|
71
|
-
'fetch': self.fetch,
|
|
72
|
-
'JSON': self.JSON,
|
|
73
|
-
'JSON_parseFn': self.JSON.parse,
|
|
74
|
-
'JSON_stringifyFn': self.JSON.stringify,
|
|
75
|
-
'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args),
|
|
76
|
-
'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args),
|
|
77
|
-
'log': console.log.bind(console),
|
|
78
|
-
// Properties
|
|
79
|
-
logLevel: 0,
|
|
80
|
-
// Methods
|
|
81
|
-
makeLogPrefix(...args) {
|
|
82
|
-
return this.sendToLogger && `[${args.join(' \u205D ')}]` || '';
|
|
83
|
-
},
|
|
84
|
-
uboLog(...args) {
|
|
85
|
-
if ( this.sendToLogger === undefined ) { return; }
|
|
86
|
-
if ( args === undefined || args[0] === '' ) { return; }
|
|
87
|
-
return this.sendToLogger('info', ...args);
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
uboErr(...args) {
|
|
91
|
-
if ( this.sendToLogger === undefined ) { return; }
|
|
92
|
-
if ( args === undefined || args[0] === '' ) { return; }
|
|
93
|
-
return this.sendToLogger('error', ...args);
|
|
94
|
-
},
|
|
95
|
-
escapeRegexChars(s) {
|
|
96
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
97
|
-
},
|
|
98
|
-
initPattern(pattern, options = {}) {
|
|
99
|
-
if ( pattern === '' ) {
|
|
100
|
-
return { matchAll: true, expect: true };
|
|
101
|
-
}
|
|
102
|
-
const expect = (options.canNegate !== true || pattern.startsWith('!') === false);
|
|
103
|
-
if ( expect === false ) {
|
|
104
|
-
pattern = pattern.slice(1);
|
|
105
|
-
}
|
|
106
|
-
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
|
107
|
-
if ( match !== null ) {
|
|
108
|
-
return {
|
|
109
|
-
re: new this.RegExp(
|
|
110
|
-
match[1],
|
|
111
|
-
match[2] || options.flags
|
|
112
|
-
),
|
|
113
|
-
expect,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if ( options.flags !== undefined ) {
|
|
117
|
-
return {
|
|
118
|
-
re: new this.RegExp(this.escapeRegexChars(pattern),
|
|
119
|
-
options.flags
|
|
120
|
-
),
|
|
121
|
-
expect,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return { pattern, expect };
|
|
125
|
-
},
|
|
126
|
-
testPattern(details, haystack) {
|
|
127
|
-
if ( details.matchAll ) { return true; }
|
|
128
|
-
if ( details.re ) {
|
|
129
|
-
return this.RegExp_test.call(details.re, haystack) === details.expect;
|
|
130
|
-
}
|
|
131
|
-
return haystack.includes(details.pattern) === details.expect;
|
|
132
|
-
},
|
|
133
|
-
patternToRegex(pattern, flags = undefined, verbatim = false) {
|
|
134
|
-
if ( pattern === '' ) { return /^/; }
|
|
135
|
-
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
|
|
136
|
-
if ( match === null ) {
|
|
137
|
-
const reStr = this.escapeRegexChars(pattern);
|
|
138
|
-
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
return new RegExp(match[1], match[2] || undefined);
|
|
142
|
-
}
|
|
143
|
-
catch(ex) {
|
|
144
|
-
}
|
|
145
|
-
return /^/;
|
|
146
|
-
},
|
|
147
|
-
getExtraArgs(args, offset = 0) {
|
|
148
|
-
const entries = args.slice(offset).reduce((out, v, i, a) => {
|
|
149
|
-
if ( (i & 1) === 0 ) {
|
|
150
|
-
const rawValue = a[i+1];
|
|
151
|
-
const value = /^\d+$/.test(rawValue)
|
|
152
|
-
? parseInt(rawValue, 10)
|
|
153
|
-
: rawValue;
|
|
154
|
-
out.push([ a[i], value ]);
|
|
155
|
-
}
|
|
156
|
-
return out;
|
|
157
|
-
}, []);
|
|
158
|
-
return this.Object_fromEntries(entries);
|
|
159
|
-
},
|
|
160
|
-
onIdle(fn, options) {
|
|
161
|
-
if ( self.requestIdleCallback ) {
|
|
162
|
-
return self.requestIdleCallback(fn, options);
|
|
163
|
-
}
|
|
164
|
-
return self.requestAnimationFrame(fn);
|
|
165
|
-
},
|
|
166
|
-
offIdle(id) {
|
|
167
|
-
if ( self.requestIdleCallback ) {
|
|
168
|
-
return self.cancelIdleCallback(id);
|
|
169
|
-
}
|
|
170
|
-
return self.cancelAnimationFrame(id);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
scriptletGlobals.safeSelf = safe;
|
|
174
|
-
if ( scriptletGlobals.bcSecret === undefined ) { return safe; }
|
|
175
|
-
// This is executed only when the logger is opened
|
|
176
|
-
const bc = new self.BroadcastChannel(scriptletGlobals.bcSecret);
|
|
177
|
-
let bcBuffer = [];
|
|
178
|
-
safe.logLevel = scriptletGlobals.logLevel || 1;
|
|
179
|
-
let lastLogType = '';
|
|
180
|
-
let lastLogText = '';
|
|
181
|
-
let lastLogTime = 0;
|
|
182
|
-
safe.sendToLogger = (type, ...args) => {
|
|
183
|
-
if ( args.length === 0 ) { return; }
|
|
184
|
-
const text = `[${document.location.hostname || document.location.href}]${args.join(' ')}`;
|
|
185
|
-
if ( text === lastLogText && type === lastLogType ) {
|
|
186
|
-
if ( (Date.now() - lastLogTime) < 5000 ) { return; }
|
|
187
|
-
}
|
|
188
|
-
lastLogType = type;
|
|
189
|
-
lastLogText = text;
|
|
190
|
-
lastLogTime = Date.now();
|
|
191
|
-
if ( bcBuffer === undefined ) {
|
|
192
|
-
return bc.postMessage({ what: 'messageToLogger', type, text });
|
|
193
|
-
}
|
|
194
|
-
bcBuffer.push({ type, text });
|
|
195
|
-
};
|
|
196
|
-
bc.onmessage = ev => {
|
|
197
|
-
const msg = ev.data;
|
|
198
|
-
switch ( msg ) {
|
|
199
|
-
case 'iamready!':
|
|
200
|
-
if ( bcBuffer === undefined ) { break; }
|
|
201
|
-
bcBuffer.forEach(({ type, text }) =>
|
|
202
|
-
bc.postMessage({ what: 'messageToLogger', type, text })
|
|
203
|
-
);
|
|
204
|
-
bcBuffer = undefined;
|
|
205
|
-
break;
|
|
206
|
-
case 'setScriptletLogLevelToOne':
|
|
207
|
-
safe.logLevel = 1;
|
|
208
|
-
break;
|
|
209
|
-
case 'setScriptletLogLevelToTwo':
|
|
210
|
-
safe.logLevel = 2;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
bc.postMessage('areyouready?');
|
|
215
|
-
return safe;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/******************************************************************************/
|
|
219
|
-
|
|
220
68
|
builtinScriptlets.push({
|
|
221
69
|
name: 'get-random-token.fn',
|
|
222
70
|
fn: getRandomToken,
|
|
@@ -270,34 +118,6 @@ builtinScriptlets.push({
|
|
|
270
118
|
'safe-self.fn',
|
|
271
119
|
],
|
|
272
120
|
});
|
|
273
|
-
function runAt(fn, when) {
|
|
274
|
-
const intFromReadyState = state => {
|
|
275
|
-
const targets = {
|
|
276
|
-
'loading': 1, 'asap': 1,
|
|
277
|
-
'interactive': 2, 'end': 2, '2': 2,
|
|
278
|
-
'complete': 3, 'idle': 3, '3': 3,
|
|
279
|
-
};
|
|
280
|
-
const tokens = Array.isArray(state) ? state : [ state ];
|
|
281
|
-
for ( const token of tokens ) {
|
|
282
|
-
const prop = `${token}`;
|
|
283
|
-
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
|
284
|
-
return targets[prop];
|
|
285
|
-
}
|
|
286
|
-
return 0;
|
|
287
|
-
};
|
|
288
|
-
const runAt = intFromReadyState(when);
|
|
289
|
-
if ( intFromReadyState(document.readyState) >= runAt ) {
|
|
290
|
-
fn(); return;
|
|
291
|
-
}
|
|
292
|
-
const onStateChange = ( ) => {
|
|
293
|
-
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
|
294
|
-
fn();
|
|
295
|
-
safe.removeEventListener.apply(document, args);
|
|
296
|
-
};
|
|
297
|
-
const safe = safeSelf();
|
|
298
|
-
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
|
299
|
-
safe.addEventListener.apply(document, args);
|
|
300
|
-
}
|
|
301
121
|
|
|
302
122
|
/******************************************************************************/
|
|
303
123
|
|
|
@@ -321,6 +141,9 @@ function runAtHtmlElementFn(fn) {
|
|
|
321
141
|
|
|
322
142
|
// Reference:
|
|
323
143
|
// https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr
|
|
144
|
+
//
|
|
145
|
+
// Added `trusted` argument to allow for returning arbitrary text. Can only
|
|
146
|
+
// be used through scriptlets requiring trusted source.
|
|
324
147
|
|
|
325
148
|
builtinScriptlets.push({
|
|
326
149
|
name: 'generate-content.fn',
|
|
@@ -329,7 +152,7 @@ builtinScriptlets.push({
|
|
|
329
152
|
'safe-self.fn',
|
|
330
153
|
],
|
|
331
154
|
});
|
|
332
|
-
function generateContentFn(directive) {
|
|
155
|
+
function generateContentFn(trusted, directive) {
|
|
333
156
|
const safe = safeSelf();
|
|
334
157
|
const randomize = len => {
|
|
335
158
|
const chunks = [];
|
|
@@ -343,27 +166,27 @@ function generateContentFn(directive) {
|
|
|
343
166
|
return chunks.join(' ').slice(0, len);
|
|
344
167
|
};
|
|
345
168
|
if ( directive === 'true' ) {
|
|
346
|
-
return
|
|
169
|
+
return randomize(10);
|
|
347
170
|
}
|
|
348
171
|
if ( directive === 'emptyObj' ) {
|
|
349
|
-
return
|
|
172
|
+
return '{}';
|
|
350
173
|
}
|
|
351
174
|
if ( directive === 'emptyArr' ) {
|
|
352
|
-
return
|
|
175
|
+
return '[]';
|
|
353
176
|
}
|
|
354
177
|
if ( directive === 'emptyStr' ) {
|
|
355
|
-
return
|
|
178
|
+
return '';
|
|
356
179
|
}
|
|
357
180
|
if ( directive.startsWith('length:') ) {
|
|
358
181
|
const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive);
|
|
359
|
-
if ( match ) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
182
|
+
if ( match === null ) { return ''; }
|
|
183
|
+
const min = parseInt(match[1], 10);
|
|
184
|
+
const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min;
|
|
185
|
+
const len = safe.Math_min(min + extent * safe.Math_random(), 500000);
|
|
186
|
+
return randomize(len | 0);
|
|
365
187
|
}
|
|
366
|
-
if ( directive.startsWith('war:')
|
|
188
|
+
if ( directive.startsWith('war:') ) {
|
|
189
|
+
if ( scriptletGlobals.warOrigin === undefined ) { return ''; }
|
|
367
190
|
return new Promise(resolve => {
|
|
368
191
|
const warOrigin = scriptletGlobals.warOrigin;
|
|
369
192
|
const warName = directive.slice(4);
|
|
@@ -379,9 +202,12 @@ function generateContentFn(directive) {
|
|
|
379
202
|
};
|
|
380
203
|
warXHR.open('GET', fullpath.join(''));
|
|
381
204
|
warXHR.send();
|
|
382
|
-
});
|
|
205
|
+
}).catch(( ) => '');
|
|
383
206
|
}
|
|
384
|
-
|
|
207
|
+
if ( trusted ) {
|
|
208
|
+
return directive;
|
|
209
|
+
}
|
|
210
|
+
return '';
|
|
385
211
|
}
|
|
386
212
|
|
|
387
213
|
/******************************************************************************/
|
|
@@ -983,6 +809,7 @@ function getSafeCookieValuesFn() {
|
|
|
983
809
|
'enable', 'disable',
|
|
984
810
|
'enabled', 'disabled',
|
|
985
811
|
'essential', 'nonessential',
|
|
812
|
+
'forbidden', 'forever',
|
|
986
813
|
'hide', 'hidden',
|
|
987
814
|
'necessary', 'required',
|
|
988
815
|
'ok',
|
|
@@ -1565,6 +1392,196 @@ function proxyApplyFn(
|
|
|
1565
1392
|
context[prop] = new Proxy(fn, proxyDetails);
|
|
1566
1393
|
}
|
|
1567
1394
|
|
|
1395
|
+
/******************************************************************************/
|
|
1396
|
+
|
|
1397
|
+
builtinScriptlets.push({
|
|
1398
|
+
name: 'prevent-xhr.fn',
|
|
1399
|
+
fn: preventXhrFn,
|
|
1400
|
+
dependencies: [
|
|
1401
|
+
'generate-content.fn',
|
|
1402
|
+
'match-object-properties.fn',
|
|
1403
|
+
'parse-properties-to-match.fn',
|
|
1404
|
+
'safe-self.fn',
|
|
1405
|
+
],
|
|
1406
|
+
});
|
|
1407
|
+
function preventXhrFn(
|
|
1408
|
+
trusted = false,
|
|
1409
|
+
propsToMatch = '',
|
|
1410
|
+
directive = ''
|
|
1411
|
+
) {
|
|
1412
|
+
if ( typeof propsToMatch !== 'string' ) { return; }
|
|
1413
|
+
const safe = safeSelf();
|
|
1414
|
+
const scriptletName = trusted ? 'trusted-prevent-xhr' : 'prevent-xhr';
|
|
1415
|
+
const logPrefix = safe.makeLogPrefix(scriptletName, propsToMatch, directive);
|
|
1416
|
+
const xhrInstances = new WeakMap();
|
|
1417
|
+
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
|
1418
|
+
const warOrigin = scriptletGlobals.warOrigin;
|
|
1419
|
+
const safeDispatchEvent = (xhr, type) => {
|
|
1420
|
+
try {
|
|
1421
|
+
xhr.dispatchEvent(new Event(type));
|
|
1422
|
+
} catch(_) {
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const XHRBefore = XMLHttpRequest.prototype;
|
|
1426
|
+
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
|
1427
|
+
open(method, url, ...args) {
|
|
1428
|
+
xhrInstances.delete(this);
|
|
1429
|
+
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
|
|
1430
|
+
return super.open(method, url, ...args);
|
|
1431
|
+
}
|
|
1432
|
+
const haystack = { method, url };
|
|
1433
|
+
if ( propsToMatch === '' && directive === '' ) {
|
|
1434
|
+
safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
|
|
1435
|
+
return super.open(method, url, ...args);
|
|
1436
|
+
}
|
|
1437
|
+
if ( matchObjectProperties(propNeedles, haystack) ) {
|
|
1438
|
+
const xhrDetails = Object.assign(haystack, {
|
|
1439
|
+
xhr: this,
|
|
1440
|
+
defer: args.length === 0 || !!args[0],
|
|
1441
|
+
directive,
|
|
1442
|
+
headers: {
|
|
1443
|
+
'date': '',
|
|
1444
|
+
'content-type': '',
|
|
1445
|
+
'content-length': '',
|
|
1446
|
+
},
|
|
1447
|
+
props: {
|
|
1448
|
+
response: { value: '' },
|
|
1449
|
+
responseText: { value: '' },
|
|
1450
|
+
responseXML: { value: null },
|
|
1451
|
+
responseURL: { value: haystack.url },
|
|
1452
|
+
},
|
|
1453
|
+
});
|
|
1454
|
+
xhrInstances.set(this, xhrDetails);
|
|
1455
|
+
}
|
|
1456
|
+
return super.open(method, url, ...args);
|
|
1457
|
+
}
|
|
1458
|
+
send(...args) {
|
|
1459
|
+
const xhrDetails = xhrInstances.get(this);
|
|
1460
|
+
if ( xhrDetails === undefined ) {
|
|
1461
|
+
return super.send(...args);
|
|
1462
|
+
}
|
|
1463
|
+
xhrDetails.headers['date'] = (new Date()).toUTCString();
|
|
1464
|
+
let xhrText = '';
|
|
1465
|
+
switch ( this.responseType ) {
|
|
1466
|
+
case 'arraybuffer':
|
|
1467
|
+
xhrDetails.props.response.value = new ArrayBuffer(0);
|
|
1468
|
+
xhrDetails.headers['content-type'] = 'application/octet-stream';
|
|
1469
|
+
break;
|
|
1470
|
+
case 'blob':
|
|
1471
|
+
xhrDetails.props.response.value = new Blob([]);
|
|
1472
|
+
xhrDetails.headers['content-type'] = 'application/octet-stream';
|
|
1473
|
+
break;
|
|
1474
|
+
case 'document': {
|
|
1475
|
+
const parser = new DOMParser();
|
|
1476
|
+
const doc = parser.parseFromString('', 'text/html');
|
|
1477
|
+
xhrDetails.props.response.value = doc;
|
|
1478
|
+
xhrDetails.props.responseXML.value = doc;
|
|
1479
|
+
xhrDetails.headers['content-type'] = 'text/html';
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
case 'json':
|
|
1483
|
+
xhrDetails.props.response.value = {};
|
|
1484
|
+
xhrDetails.props.responseText.value = '{}';
|
|
1485
|
+
xhrDetails.headers['content-type'] = 'application/json';
|
|
1486
|
+
break;
|
|
1487
|
+
default: {
|
|
1488
|
+
if ( directive === '' ) { break; }
|
|
1489
|
+
xhrText = generateContentFn(trusted, xhrDetails.directive);
|
|
1490
|
+
if ( xhrText instanceof Promise ) {
|
|
1491
|
+
xhrText = xhrText.then(text => {
|
|
1492
|
+
xhrDetails.props.response.value = text;
|
|
1493
|
+
xhrDetails.props.responseText.value = text;
|
|
1494
|
+
});
|
|
1495
|
+
} else {
|
|
1496
|
+
xhrDetails.props.response.value = xhrText;
|
|
1497
|
+
xhrDetails.props.responseText.value = xhrText;
|
|
1498
|
+
}
|
|
1499
|
+
xhrDetails.headers['content-type'] = 'text/plain';
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
if ( xhrDetails.defer === false ) {
|
|
1504
|
+
xhrDetails.headers['content-length'] = `${xhrDetails.props.response.value}`.length;
|
|
1505
|
+
Object.defineProperties(xhrDetails.xhr, {
|
|
1506
|
+
readyState: { value: 4 },
|
|
1507
|
+
status: { value: 200 },
|
|
1508
|
+
statusText: { value: 'OK' },
|
|
1509
|
+
});
|
|
1510
|
+
Object.defineProperties(xhrDetails.xhr, xhrDetails.props);
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
Promise.resolve(xhrText).then(( ) => xhrDetails).then(details => {
|
|
1514
|
+
Object.defineProperties(details.xhr, {
|
|
1515
|
+
readyState: { value: 1, configurable: true },
|
|
1516
|
+
});
|
|
1517
|
+
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
1518
|
+
return details;
|
|
1519
|
+
}).then(details => {
|
|
1520
|
+
xhrDetails.headers['content-length'] = `${details.props.response.value}`.length;
|
|
1521
|
+
Object.defineProperties(details.xhr, {
|
|
1522
|
+
readyState: { value: 2, configurable: true },
|
|
1523
|
+
status: { value: 200 },
|
|
1524
|
+
statusText: { value: 'OK' },
|
|
1525
|
+
});
|
|
1526
|
+
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
1527
|
+
return details;
|
|
1528
|
+
}).then(details => {
|
|
1529
|
+
Object.defineProperties(details.xhr, {
|
|
1530
|
+
readyState: { value: 3, configurable: true },
|
|
1531
|
+
});
|
|
1532
|
+
Object.defineProperties(details.xhr, details.props);
|
|
1533
|
+
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
1534
|
+
return details;
|
|
1535
|
+
}).then(details => {
|
|
1536
|
+
Object.defineProperties(details.xhr, {
|
|
1537
|
+
readyState: { value: 4 },
|
|
1538
|
+
});
|
|
1539
|
+
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
1540
|
+
safeDispatchEvent(details.xhr, 'load');
|
|
1541
|
+
safeDispatchEvent(details.xhr, 'loadend');
|
|
1542
|
+
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
getResponseHeader(headerName) {
|
|
1546
|
+
const xhrDetails = xhrInstances.get(this);
|
|
1547
|
+
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
1548
|
+
return super.getResponseHeader(headerName);
|
|
1549
|
+
}
|
|
1550
|
+
const value = xhrDetails.headers[headerName.toLowerCase()];
|
|
1551
|
+
if ( value !== undefined && value !== '' ) { return value; }
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
getAllResponseHeaders() {
|
|
1555
|
+
const xhrDetails = xhrInstances.get(this);
|
|
1556
|
+
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
1557
|
+
return super.getAllResponseHeaders();
|
|
1558
|
+
}
|
|
1559
|
+
const out = [];
|
|
1560
|
+
for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) {
|
|
1561
|
+
if ( !value ) { continue; }
|
|
1562
|
+
out.push(`${name}: ${value}`);
|
|
1563
|
+
}
|
|
1564
|
+
if ( out.length !== 0 ) { out.push(''); }
|
|
1565
|
+
return out.join('\r\n');
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
self.XMLHttpRequest.prototype.open.toString = function() {
|
|
1569
|
+
return XHRBefore.open.toString();
|
|
1570
|
+
};
|
|
1571
|
+
self.XMLHttpRequest.prototype.send.toString = function() {
|
|
1572
|
+
return XHRBefore.send.toString();
|
|
1573
|
+
};
|
|
1574
|
+
self.XMLHttpRequest.prototype.getResponseHeader.toString = function() {
|
|
1575
|
+
return XHRBefore.getResponseHeader.toString();
|
|
1576
|
+
};
|
|
1577
|
+
self.XMLHttpRequest.prototype.getAllResponseHeaders.toString = function() {
|
|
1578
|
+
return XHRBefore.getAllResponseHeaders.toString();
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
|
|
1568
1585
|
/*******************************************************************************
|
|
1569
1586
|
|
|
1570
1587
|
Injectable scriptlets
|
|
@@ -2271,7 +2288,7 @@ function noFetchIf(
|
|
|
2271
2288
|
if ( proceed ) {
|
|
2272
2289
|
return context.reflect();
|
|
2273
2290
|
}
|
|
2274
|
-
return generateContentFn(responseBody).then(text => {
|
|
2291
|
+
return Promise.resolve(generateContentFn(false, responseBody)).then(text => {
|
|
2275
2292
|
safe.uboLog(logPrefix, `Prevented with response "${text}"`);
|
|
2276
2293
|
const response = new Response(text, {
|
|
2277
2294
|
headers: {
|
|
@@ -2298,30 +2315,34 @@ builtinScriptlets.push({
|
|
|
2298
2315
|
fn: preventRefresh,
|
|
2299
2316
|
world: 'ISOLATED',
|
|
2300
2317
|
dependencies: [
|
|
2301
|
-
'run-at.fn',
|
|
2302
2318
|
'safe-self.fn',
|
|
2303
2319
|
],
|
|
2304
2320
|
});
|
|
2305
2321
|
// https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/
|
|
2306
2322
|
function preventRefresh(
|
|
2307
|
-
|
|
2323
|
+
delay = ''
|
|
2308
2324
|
) {
|
|
2309
|
-
if ( typeof
|
|
2325
|
+
if ( typeof delay !== 'string' ) { return; }
|
|
2310
2326
|
const safe = safeSelf();
|
|
2311
|
-
const logPrefix = safe.makeLogPrefix('prevent-refresh',
|
|
2327
|
+
const logPrefix = safe.makeLogPrefix('prevent-refresh', delay);
|
|
2328
|
+
const stop = content => {
|
|
2329
|
+
window.stop();
|
|
2330
|
+
safe.uboLog(logPrefix, `Prevented "${content}"`);
|
|
2331
|
+
};
|
|
2312
2332
|
const defuse = ( ) => {
|
|
2313
2333
|
const meta = document.querySelector('meta[http-equiv="refresh" i][content]');
|
|
2314
2334
|
if ( meta === null ) { return; }
|
|
2315
|
-
|
|
2316
|
-
const
|
|
2317
|
-
?
|
|
2318
|
-
:
|
|
2319
|
-
|
|
2320
|
-
|
|
2335
|
+
const content = meta.getAttribute('content') || '';
|
|
2336
|
+
const ms = delay === ''
|
|
2337
|
+
? Math.max(parseFloat(content) || 0, 0) * 500
|
|
2338
|
+
: 0;
|
|
2339
|
+
if ( ms === 0 ) {
|
|
2340
|
+
stop(content);
|
|
2341
|
+
} else {
|
|
2342
|
+
setTimeout(( ) => { stop(content); }, ms);
|
|
2343
|
+
}
|
|
2321
2344
|
};
|
|
2322
|
-
|
|
2323
|
-
defuse();
|
|
2324
|
-
}, 'interactive');
|
|
2345
|
+
self.addEventListener('load', defuse, { capture: true, once: true });
|
|
2325
2346
|
}
|
|
2326
2347
|
|
|
2327
2348
|
/******************************************************************************/
|
|
@@ -2730,201 +2751,58 @@ function webrtcIf(
|
|
|
2730
2751
|
/******************************************************************************/
|
|
2731
2752
|
|
|
2732
2753
|
builtinScriptlets.push({
|
|
2733
|
-
name: '
|
|
2754
|
+
name: 'prevent-xhr.js',
|
|
2734
2755
|
aliases: [
|
|
2735
|
-
'
|
|
2756
|
+
'no-xhr-if.js',
|
|
2736
2757
|
],
|
|
2737
|
-
fn:
|
|
2758
|
+
fn: preventXhr,
|
|
2738
2759
|
dependencies: [
|
|
2739
|
-
'
|
|
2740
|
-
'match-object-properties.fn',
|
|
2741
|
-
'parse-properties-to-match.fn',
|
|
2742
|
-
'safe-self.fn',
|
|
2760
|
+
'prevent-xhr.fn',
|
|
2743
2761
|
],
|
|
2744
2762
|
});
|
|
2745
|
-
function
|
|
2746
|
-
|
|
2747
|
-
directive = ''
|
|
2748
|
-
) {
|
|
2749
|
-
if ( typeof propsToMatch !== 'string' ) { return; }
|
|
2750
|
-
const safe = safeSelf();
|
|
2751
|
-
const logPrefix = safe.makeLogPrefix('prevent-xhr', propsToMatch, directive);
|
|
2752
|
-
const xhrInstances = new WeakMap();
|
|
2753
|
-
const propNeedles = parsePropertiesToMatch(propsToMatch, 'url');
|
|
2754
|
-
const warOrigin = scriptletGlobals.warOrigin;
|
|
2755
|
-
const headers = {
|
|
2756
|
-
'date': '',
|
|
2757
|
-
'content-type': '',
|
|
2758
|
-
'content-length': '',
|
|
2759
|
-
};
|
|
2760
|
-
const safeDispatchEvent = (xhr, type) => {
|
|
2761
|
-
try {
|
|
2762
|
-
xhr.dispatchEvent(new Event(type));
|
|
2763
|
-
} catch(_) {
|
|
2764
|
-
}
|
|
2765
|
-
};
|
|
2766
|
-
const XHRBefore = XMLHttpRequest.prototype;
|
|
2767
|
-
self.XMLHttpRequest = class extends self.XMLHttpRequest {
|
|
2768
|
-
open(method, url, ...args) {
|
|
2769
|
-
xhrInstances.delete(this);
|
|
2770
|
-
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
|
|
2771
|
-
return super.open(method, url, ...args);
|
|
2772
|
-
}
|
|
2773
|
-
const haystack = { method, url };
|
|
2774
|
-
if ( propsToMatch === '' && directive === '' ) {
|
|
2775
|
-
safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
|
|
2776
|
-
return super.open(method, url, ...args);
|
|
2777
|
-
}
|
|
2778
|
-
if ( matchObjectProperties(propNeedles, haystack) ) {
|
|
2779
|
-
xhrInstances.set(this, haystack);
|
|
2780
|
-
}
|
|
2781
|
-
haystack.headers = Object.assign({}, headers);
|
|
2782
|
-
return super.open(method, url, ...args);
|
|
2783
|
-
}
|
|
2784
|
-
send(...args) {
|
|
2785
|
-
const haystack = xhrInstances.get(this);
|
|
2786
|
-
if ( haystack === undefined ) {
|
|
2787
|
-
return super.send(...args);
|
|
2788
|
-
}
|
|
2789
|
-
haystack.headers['date'] = (new Date()).toUTCString();
|
|
2790
|
-
let promise = Promise.resolve({
|
|
2791
|
-
xhr: this,
|
|
2792
|
-
directive,
|
|
2793
|
-
response: {
|
|
2794
|
-
response: { value: '' },
|
|
2795
|
-
responseText: { value: '' },
|
|
2796
|
-
responseXML: { value: null },
|
|
2797
|
-
responseURL: { value: haystack.url },
|
|
2798
|
-
}
|
|
2799
|
-
});
|
|
2800
|
-
switch ( this.responseType ) {
|
|
2801
|
-
case 'arraybuffer':
|
|
2802
|
-
promise = promise.then(details => {
|
|
2803
|
-
const response = details.response;
|
|
2804
|
-
response.response.value = new ArrayBuffer(0);
|
|
2805
|
-
return details;
|
|
2806
|
-
});
|
|
2807
|
-
haystack.headers['content-type'] = 'application/octet-stream';
|
|
2808
|
-
break;
|
|
2809
|
-
case 'blob':
|
|
2810
|
-
promise = promise.then(details => {
|
|
2811
|
-
const response = details.response;
|
|
2812
|
-
response.response.value = new Blob([]);
|
|
2813
|
-
return details;
|
|
2814
|
-
});
|
|
2815
|
-
haystack.headers['content-type'] = 'application/octet-stream';
|
|
2816
|
-
break;
|
|
2817
|
-
case 'document': {
|
|
2818
|
-
promise = promise.then(details => {
|
|
2819
|
-
const parser = new DOMParser();
|
|
2820
|
-
const doc = parser.parseFromString('', 'text/html');
|
|
2821
|
-
const response = details.response;
|
|
2822
|
-
response.response.value = doc;
|
|
2823
|
-
response.responseXML.value = doc;
|
|
2824
|
-
return details;
|
|
2825
|
-
});
|
|
2826
|
-
haystack.headers['content-type'] = 'text/html';
|
|
2827
|
-
break;
|
|
2828
|
-
}
|
|
2829
|
-
case 'json':
|
|
2830
|
-
promise = promise.then(details => {
|
|
2831
|
-
const response = details.response;
|
|
2832
|
-
response.response.value = {};
|
|
2833
|
-
response.responseText.value = '{}';
|
|
2834
|
-
return details;
|
|
2835
|
-
});
|
|
2836
|
-
haystack.headers['content-type'] = 'application/json';
|
|
2837
|
-
break;
|
|
2838
|
-
default:
|
|
2839
|
-
if ( directive === '' ) { break; }
|
|
2840
|
-
promise = promise.then(details => {
|
|
2841
|
-
return generateContentFn(details.directive).then(text => {
|
|
2842
|
-
const response = details.response;
|
|
2843
|
-
response.response.value = text;
|
|
2844
|
-
response.responseText.value = text;
|
|
2845
|
-
return details;
|
|
2846
|
-
});
|
|
2847
|
-
});
|
|
2848
|
-
haystack.headers['content-type'] = 'text/plain';
|
|
2849
|
-
break;
|
|
2850
|
-
}
|
|
2851
|
-
promise.then(details => {
|
|
2852
|
-
Object.defineProperties(details.xhr, {
|
|
2853
|
-
readyState: { value: 1, configurable: true },
|
|
2854
|
-
});
|
|
2855
|
-
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
2856
|
-
return details;
|
|
2857
|
-
}).then(details => {
|
|
2858
|
-
const response = details.response;
|
|
2859
|
-
haystack.headers['content-length'] = `${response.response.value}`.length;
|
|
2860
|
-
Object.defineProperties(details.xhr, {
|
|
2861
|
-
readyState: { value: 2, configurable: true },
|
|
2862
|
-
status: { value: 200 },
|
|
2863
|
-
statusText: { value: 'OK' },
|
|
2864
|
-
});
|
|
2865
|
-
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
2866
|
-
return details;
|
|
2867
|
-
}).then(details => {
|
|
2868
|
-
Object.defineProperties(details.xhr, {
|
|
2869
|
-
readyState: { value: 3, configurable: true },
|
|
2870
|
-
});
|
|
2871
|
-
Object.defineProperties(details.xhr, details.response);
|
|
2872
|
-
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
2873
|
-
return details;
|
|
2874
|
-
}).then(details => {
|
|
2875
|
-
Object.defineProperties(details.xhr, {
|
|
2876
|
-
readyState: { value: 4 },
|
|
2877
|
-
});
|
|
2878
|
-
safeDispatchEvent(details.xhr, 'readystatechange');
|
|
2879
|
-
safeDispatchEvent(details.xhr, 'load');
|
|
2880
|
-
safeDispatchEvent(details.xhr, 'loadend');
|
|
2881
|
-
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
|
|
2882
|
-
});
|
|
2883
|
-
}
|
|
2884
|
-
getResponseHeader(headerName) {
|
|
2885
|
-
const haystack = xhrInstances.get(this);
|
|
2886
|
-
if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
2887
|
-
return super.getResponseHeader(headerName);
|
|
2888
|
-
}
|
|
2889
|
-
const value = haystack.headers[headerName.toLowerCase()];
|
|
2890
|
-
if ( value !== undefined && value !== '' ) { return value; }
|
|
2891
|
-
return null;
|
|
2892
|
-
}
|
|
2893
|
-
getAllResponseHeaders() {
|
|
2894
|
-
const haystack = xhrInstances.get(this);
|
|
2895
|
-
if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) {
|
|
2896
|
-
return super.getAllResponseHeaders();
|
|
2897
|
-
}
|
|
2898
|
-
const out = [];
|
|
2899
|
-
for ( const [ name, value ] of Object.entries(haystack.headers) ) {
|
|
2900
|
-
if ( !value ) { continue; }
|
|
2901
|
-
out.push(`${name}: ${value}`);
|
|
2902
|
-
}
|
|
2903
|
-
if ( out.length !== 0 ) { out.push(''); }
|
|
2904
|
-
return out.join('\r\n');
|
|
2905
|
-
}
|
|
2906
|
-
};
|
|
2907
|
-
self.XMLHttpRequest.prototype.open.toString = function() {
|
|
2908
|
-
return XHRBefore.open.toString();
|
|
2909
|
-
};
|
|
2910
|
-
self.XMLHttpRequest.prototype.send.toString = function() {
|
|
2911
|
-
return XHRBefore.send.toString();
|
|
2912
|
-
};
|
|
2913
|
-
self.XMLHttpRequest.prototype.getResponseHeader.toString = function() {
|
|
2914
|
-
return XHRBefore.getResponseHeader.toString();
|
|
2915
|
-
};
|
|
2916
|
-
self.XMLHttpRequest.prototype.getAllResponseHeaders.toString = function() {
|
|
2917
|
-
return XHRBefore.getAllResponseHeaders.toString();
|
|
2918
|
-
};
|
|
2763
|
+
function preventXhr(...args) {
|
|
2764
|
+
return preventXhrFn(false, ...args);
|
|
2919
2765
|
}
|
|
2920
2766
|
|
|
2921
|
-
|
|
2767
|
+
/**
|
|
2768
|
+
* @scriptlet prevent-window-open
|
|
2769
|
+
*
|
|
2770
|
+
* @description
|
|
2771
|
+
* Prevent a webpage from opening new tabs through `window.open()`.
|
|
2772
|
+
*
|
|
2773
|
+
* @param pattern
|
|
2774
|
+
* A plain string or regex to match against the `url` argument for the
|
|
2775
|
+
* prevention to be triggered. If not provided, all calls to `window.open()`
|
|
2776
|
+
* are prevented.
|
|
2777
|
+
* If set to the special value `debug` *and* the logger is opened, the scriptlet
|
|
2778
|
+
* will trigger a `debugger` statement and the prevention will not occur.
|
|
2779
|
+
*
|
|
2780
|
+
* @param [delay]
|
|
2781
|
+
* If provided, a decoy will be created or opened, and this parameter states
|
|
2782
|
+
* the number of seconds to wait for before the decoy is terminated, i.e.
|
|
2783
|
+
* either removed from the DOM or closed.
|
|
2784
|
+
*
|
|
2785
|
+
* @param [decoy]
|
|
2786
|
+
* A string representing the type of decoy to use:
|
|
2787
|
+
* - `blank`: replace the `url` parameter with `about:blank`
|
|
2788
|
+
* - `object`: create and append an `object` element to the DOM, and return
|
|
2789
|
+
* its `contentWindow` property.
|
|
2790
|
+
* - `frame`: create and append an `iframe` element to the DOM, and return
|
|
2791
|
+
* its `contentWindow` property.
|
|
2792
|
+
*
|
|
2793
|
+
* @example
|
|
2794
|
+
* ##+js(prevent-window-open, ads.example.com/)
|
|
2795
|
+
*
|
|
2796
|
+
* @example
|
|
2797
|
+
* ##+js(prevent-window-open, ads.example.com/, 1, iframe)
|
|
2798
|
+
*
|
|
2799
|
+
* */
|
|
2922
2800
|
|
|
2923
2801
|
builtinScriptlets.push({
|
|
2924
|
-
name: '
|
|
2802
|
+
name: 'prevent-window-open.js',
|
|
2925
2803
|
aliases: [
|
|
2926
2804
|
'nowoif.js',
|
|
2927
|
-
'
|
|
2805
|
+
'no-window-open-if.js',
|
|
2928
2806
|
'window.open-defuser.js',
|
|
2929
2807
|
],
|
|
2930
2808
|
fn: noWindowOpenIf,
|
|
@@ -2960,6 +2838,10 @@ function noWindowOpenIf(
|
|
|
2960
2838
|
};
|
|
2961
2839
|
const noopFunc = function(){};
|
|
2962
2840
|
proxyApplyFn('open', function open(context) {
|
|
2841
|
+
if ( pattern === 'debug' && safe.logLevel !== 0 ) {
|
|
2842
|
+
debugger; // eslint-disable-line no-debugger
|
|
2843
|
+
return context.reflect();
|
|
2844
|
+
}
|
|
2963
2845
|
const { callArgs } = context;
|
|
2964
2846
|
const haystack = callArgs.join(' ');
|
|
2965
2847
|
if ( rePattern.test(haystack) !== targetMatchResult ) {
|
|
@@ -4048,124 +3930,6 @@ function setSessionStorageItem(key = '', value = '') {
|
|
|
4048
3930
|
setLocalStorageItemFn('session', false, key, value);
|
|
4049
3931
|
}
|
|
4050
3932
|
|
|
4051
|
-
/*******************************************************************************
|
|
4052
|
-
*
|
|
4053
|
-
* @scriptlet set-attr
|
|
4054
|
-
*
|
|
4055
|
-
* @description
|
|
4056
|
-
* Sets the specified attribute on the specified elements. This scriptlet runs
|
|
4057
|
-
* once when the page loads then afterward on DOM mutations.
|
|
4058
|
-
|
|
4059
|
-
* Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-attr.js
|
|
4060
|
-
*
|
|
4061
|
-
* ### Syntax
|
|
4062
|
-
*
|
|
4063
|
-
* ```text
|
|
4064
|
-
* example.org##+js(set-attr, selector, attr [, value])
|
|
4065
|
-
* ```
|
|
4066
|
-
*
|
|
4067
|
-
* - `selector`: CSS selector of DOM elements for which the attribute `attr`
|
|
4068
|
-
* must be modified.
|
|
4069
|
-
* - `attr`: the name of the attribute to modify
|
|
4070
|
-
* - `value`: the value to assign to the target attribute. Possible values:
|
|
4071
|
-
* - `''`: empty string (default)
|
|
4072
|
-
* - `true`
|
|
4073
|
-
* - `false`
|
|
4074
|
-
* - positive decimal integer 0 <= value < 32768
|
|
4075
|
-
* - `[other]`: copy the value from attribute `other` on the same element
|
|
4076
|
-
* */
|
|
4077
|
-
|
|
4078
|
-
builtinScriptlets.push({
|
|
4079
|
-
name: 'set-attr.js',
|
|
4080
|
-
fn: setAttr,
|
|
4081
|
-
world: 'ISOLATED',
|
|
4082
|
-
dependencies: [
|
|
4083
|
-
'run-at.fn',
|
|
4084
|
-
'safe-self.fn',
|
|
4085
|
-
],
|
|
4086
|
-
});
|
|
4087
|
-
function setAttr(
|
|
4088
|
-
selector = '',
|
|
4089
|
-
attr = '',
|
|
4090
|
-
value = ''
|
|
4091
|
-
) {
|
|
4092
|
-
if ( selector === '' ) { return; }
|
|
4093
|
-
if ( attr === '' ) { return; }
|
|
4094
|
-
|
|
4095
|
-
const safe = safeSelf();
|
|
4096
|
-
const logPrefix = safe.makeLogPrefix('set-attr', attr, value);
|
|
4097
|
-
const validValues = [ '', 'false', 'true' ];
|
|
4098
|
-
let copyFrom = '';
|
|
4099
|
-
|
|
4100
|
-
if ( validValues.includes(value.toLowerCase()) === false ) {
|
|
4101
|
-
if ( /^\d+$/.test(value) ) {
|
|
4102
|
-
const n = parseInt(value, 10);
|
|
4103
|
-
if ( n >= 32768 ) { return; }
|
|
4104
|
-
value = `${n}`;
|
|
4105
|
-
} else if ( /^\[.+\]$/.test(value) ) {
|
|
4106
|
-
copyFrom = value.slice(1, -1);
|
|
4107
|
-
} else {
|
|
4108
|
-
return;
|
|
4109
|
-
}
|
|
4110
|
-
}
|
|
4111
|
-
|
|
4112
|
-
const extractValue = elem => {
|
|
4113
|
-
if ( copyFrom !== '' ) {
|
|
4114
|
-
return elem.getAttribute(copyFrom) || '';
|
|
4115
|
-
}
|
|
4116
|
-
return value;
|
|
4117
|
-
};
|
|
4118
|
-
|
|
4119
|
-
const applySetAttr = ( ) => {
|
|
4120
|
-
const elems = [];
|
|
4121
|
-
try {
|
|
4122
|
-
elems.push(...document.querySelectorAll(selector));
|
|
4123
|
-
}
|
|
4124
|
-
catch(ex) {
|
|
4125
|
-
return false;
|
|
4126
|
-
}
|
|
4127
|
-
for ( const elem of elems ) {
|
|
4128
|
-
const before = elem.getAttribute(attr);
|
|
4129
|
-
const after = extractValue(elem);
|
|
4130
|
-
if ( after === before ) { continue; }
|
|
4131
|
-
if ( after !== '' && /^on/i.test(attr) ) {
|
|
4132
|
-
if ( attr.toLowerCase() in elem ) { continue; }
|
|
4133
|
-
}
|
|
4134
|
-
elem.setAttribute(attr, after);
|
|
4135
|
-
safe.uboLog(logPrefix, `${attr}="${after}"`);
|
|
4136
|
-
}
|
|
4137
|
-
return true;
|
|
4138
|
-
};
|
|
4139
|
-
let observer, timer;
|
|
4140
|
-
const onDomChanged = mutations => {
|
|
4141
|
-
if ( timer !== undefined ) { return; }
|
|
4142
|
-
let shouldWork = false;
|
|
4143
|
-
for ( const mutation of mutations ) {
|
|
4144
|
-
if ( mutation.addedNodes.length === 0 ) { continue; }
|
|
4145
|
-
for ( const node of mutation.addedNodes ) {
|
|
4146
|
-
if ( node.nodeType !== 1 ) { continue; }
|
|
4147
|
-
shouldWork = true;
|
|
4148
|
-
break;
|
|
4149
|
-
}
|
|
4150
|
-
if ( shouldWork ) { break; }
|
|
4151
|
-
}
|
|
4152
|
-
if ( shouldWork === false ) { return; }
|
|
4153
|
-
timer = self.requestAnimationFrame(( ) => {
|
|
4154
|
-
timer = undefined;
|
|
4155
|
-
applySetAttr();
|
|
4156
|
-
});
|
|
4157
|
-
};
|
|
4158
|
-
const start = ( ) => {
|
|
4159
|
-
if ( applySetAttr() === false ) { return; }
|
|
4160
|
-
observer = new MutationObserver(onDomChanged);
|
|
4161
|
-
observer.observe(document.body, {
|
|
4162
|
-
subtree: true,
|
|
4163
|
-
childList: true,
|
|
4164
|
-
});
|
|
4165
|
-
};
|
|
4166
|
-
runAt(( ) => { start(); }, 'idle');
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
3933
|
/*******************************************************************************
|
|
4170
3934
|
*
|
|
4171
3935
|
* @scriptlet prevent-canvas
|
|
@@ -5103,4 +4867,163 @@ function trustedSuppressNativeMethod(
|
|
|
5103
4867
|
});
|
|
5104
4868
|
}
|
|
5105
4869
|
|
|
4870
|
+
/*******************************************************************************
|
|
4871
|
+
*
|
|
4872
|
+
* Trusted version of prevent-xhr(), which allows the use of an arbitrary
|
|
4873
|
+
* string as response text.
|
|
4874
|
+
*
|
|
4875
|
+
* */
|
|
4876
|
+
|
|
4877
|
+
builtinScriptlets.push({
|
|
4878
|
+
name: 'trusted-prevent-xhr.js',
|
|
4879
|
+
requiresTrust: true,
|
|
4880
|
+
fn: trustedPreventXhr,
|
|
4881
|
+
dependencies: [
|
|
4882
|
+
'prevent-xhr.fn',
|
|
4883
|
+
],
|
|
4884
|
+
});
|
|
4885
|
+
function trustedPreventXhr(...args) {
|
|
4886
|
+
return preventXhrFn(true, ...args);
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
/**
|
|
4890
|
+
* @trustedScriptlet trusted-prevent-dom-bypass
|
|
4891
|
+
*
|
|
4892
|
+
* @description
|
|
4893
|
+
* Prevent the bypassing of uBO scriptlets through anonymous embedded context.
|
|
4894
|
+
*
|
|
4895
|
+
* Ensure that a target method in the embedded context is using the
|
|
4896
|
+
* corresponding parent context's method (which is assumed to be
|
|
4897
|
+
* properly patched), or to replace the embedded context with that of the
|
|
4898
|
+
* parent context.
|
|
4899
|
+
*
|
|
4900
|
+
* Root issue:
|
|
4901
|
+
* https://issues.chromium.org/issues/40202434
|
|
4902
|
+
*
|
|
4903
|
+
* @param methodPath
|
|
4904
|
+
* The method which calls must be intercepted. The arguments
|
|
4905
|
+
* of the intercepted calls are assumed to be HTMLElement, anything else will
|
|
4906
|
+
* be ignored.
|
|
4907
|
+
*
|
|
4908
|
+
* @param [targetProp]
|
|
4909
|
+
* The method in the embedded context which should be delegated to the
|
|
4910
|
+
* parent context. If no method is specified, the embedded context becomes
|
|
4911
|
+
* the parent one, i.e. all properties of the embedded context will be that
|
|
4912
|
+
* of the parent context.
|
|
4913
|
+
*
|
|
4914
|
+
* @example
|
|
4915
|
+
* ##+js(trusted-prevent-dom-bypass, Element.prototype.append, open)
|
|
4916
|
+
*
|
|
4917
|
+
* @example
|
|
4918
|
+
* ##+js(trusted-prevent-dom-bypass, Element.prototype.appendChild, XMLHttpRequest)
|
|
4919
|
+
*
|
|
4920
|
+
* */
|
|
4921
|
+
|
|
4922
|
+
builtinScriptlets.push({
|
|
4923
|
+
name: 'trusted-prevent-dom-bypass.js',
|
|
4924
|
+
requiresTrust: true,
|
|
4925
|
+
fn: trustedPreventDomBypass,
|
|
4926
|
+
dependencies: [
|
|
4927
|
+
'proxy-apply.fn',
|
|
4928
|
+
'safe-self.fn',
|
|
4929
|
+
],
|
|
4930
|
+
});
|
|
4931
|
+
function trustedPreventDomBypass(
|
|
4932
|
+
methodPath = '',
|
|
4933
|
+
targetProp = ''
|
|
4934
|
+
) {
|
|
4935
|
+
if ( methodPath === '' ) { return; }
|
|
4936
|
+
const safe = safeSelf();
|
|
4937
|
+
const logPrefix = safe.makeLogPrefix('trusted-prevent-dom-bypass', methodPath, targetProp);
|
|
4938
|
+
proxyApplyFn(methodPath, function(context) {
|
|
4939
|
+
const elems = new Set(context.callArgs.filter(e => e instanceof HTMLElement));
|
|
4940
|
+
const r = context.reflect();
|
|
4941
|
+
if ( elems.length === 0 ) { return r; }
|
|
4942
|
+
for ( const elem of elems ) {
|
|
4943
|
+
try {
|
|
4944
|
+
if ( `${elem.contentWindow}` !== '[object Window]' ) { continue; }
|
|
4945
|
+
if ( elem.contentWindow.location.href !== 'about:blank' ) {
|
|
4946
|
+
if ( elem.contentWindow.location.href !== self.location.href ) {
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
if ( targetProp !== '' ) {
|
|
4951
|
+
elem.contentWindow[targetProp] = self[targetProp];
|
|
4952
|
+
} else {
|
|
4953
|
+
Object.defineProperty(elem, 'contentWindow', { value: self });
|
|
4954
|
+
}
|
|
4955
|
+
safe.uboLog(logPrefix, 'Bypass prevented');
|
|
4956
|
+
} catch(_) {
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
return r;
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
/**
|
|
4964
|
+
* @trustedScriptlet trusted-override-element-method
|
|
4965
|
+
*
|
|
4966
|
+
* @description
|
|
4967
|
+
* Override the behavior of a method on matching elements.
|
|
4968
|
+
*
|
|
4969
|
+
* @param methodPath
|
|
4970
|
+
* The method which calls must be intercepted.
|
|
4971
|
+
*
|
|
4972
|
+
* @param [selector]
|
|
4973
|
+
* A CSS selector which the target element must match. If not specified,
|
|
4974
|
+
* the override will occur for all elements.
|
|
4975
|
+
*
|
|
4976
|
+
* @param [disposition]
|
|
4977
|
+
* How the override should be handled. If not specified, the overridden call
|
|
4978
|
+
* will be equivalent to an empty function. If set to `throw`, an exception
|
|
4979
|
+
* will be thrown. Any other value will be validated and returned as a
|
|
4980
|
+
* supported safe constant.
|
|
4981
|
+
*
|
|
4982
|
+
* @example
|
|
4983
|
+
* ##+js(trusted-override-element-method, HTMLAnchorElement.prototype.click, a[target="_blank"][style])
|
|
4984
|
+
*
|
|
4985
|
+
* */
|
|
4986
|
+
|
|
4987
|
+
builtinScriptlets.push({
|
|
4988
|
+
name: 'trusted-override-element-method.js',
|
|
4989
|
+
requiresTrust: true,
|
|
4990
|
+
fn: trustedOverrideElementMethod,
|
|
4991
|
+
dependencies: [
|
|
4992
|
+
'proxy-apply.fn',
|
|
4993
|
+
'safe-self.fn',
|
|
4994
|
+
'validate-constant.fn',
|
|
4995
|
+
],
|
|
4996
|
+
});
|
|
4997
|
+
function trustedOverrideElementMethod(
|
|
4998
|
+
methodPath = '',
|
|
4999
|
+
selector = '',
|
|
5000
|
+
disposition = ''
|
|
5001
|
+
) {
|
|
5002
|
+
if ( methodPath === '' ) { return; }
|
|
5003
|
+
const safe = safeSelf();
|
|
5004
|
+
const logPrefix = safe.makeLogPrefix('trusted-override-element-method', methodPath, selector, disposition);
|
|
5005
|
+
proxyApplyFn(methodPath, function(context) {
|
|
5006
|
+
let override = selector === '';
|
|
5007
|
+
if ( override === false ) {
|
|
5008
|
+
const { thisArg } = context;
|
|
5009
|
+
try {
|
|
5010
|
+
override = thisArg.closest(selector) === thisArg;
|
|
5011
|
+
} catch(_) {
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
if ( override === false ) {
|
|
5015
|
+
return context.reflect();
|
|
5016
|
+
}
|
|
5017
|
+
safe.uboLog(logPrefix, 'Overridden');
|
|
5018
|
+
if ( disposition === '' ) { return; }
|
|
5019
|
+
if ( disposition === 'debug' && safe.logLevel !== 0 ) {
|
|
5020
|
+
debugger; // eslint-disable-line no-debugger
|
|
5021
|
+
}
|
|
5022
|
+
if ( disposition === 'throw' ) {
|
|
5023
|
+
throw new ReferenceError();
|
|
5024
|
+
}
|
|
5025
|
+
return validateConstantFn(false, disposition);
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
|
|
5106
5029
|
/******************************************************************************/
|