Skip to content

Commit 84e4bd7

Browse files
committed
Improve abort-current-script scriptlet
1 parent 2ced2a6 commit 84e4bd7

3 files changed

Lines changed: 134 additions & 164 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
*/
22+
23+
import { getExceptionTokenFn, trapPropertyFn } from './utils.js';
24+
import { registerScriptlet } from './base.js';
25+
import { runAtHtmlElementFn } from './run-at.js';
26+
import { safeSelf } from './safe-self.js';
27+
28+
/******************************************************************************/
29+
30+
// Issues to mind before changing anything:
31+
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
32+
function abortCurrentScriptFn(
33+
target = '',
34+
needle = '',
35+
context = ''
36+
) {
37+
if ( typeof target !== 'string' ) { return; }
38+
if ( target === '' ) { return; }
39+
const safe = safeSelf();
40+
const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context);
41+
const reNeedle = safe.patternToRegex(needle);
42+
const reContext = safe.patternToRegex(context);
43+
const thisScript = document.currentScript;
44+
const exceptionToken = getExceptionTokenFn();
45+
const scriptTexts = new WeakMap();
46+
const textContentGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent').get;
47+
const getScriptText = elem => {
48+
let text = textContentGetter.call(elem);
49+
if ( text.trim() !== '' ) { return text; }
50+
if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); }
51+
const [ , mime, content ] = /^data:([^,]*),(.+)$/.exec(elem.src.trim()) ||
52+
[ '', '', '' ];
53+
try {
54+
switch ( true ) {
55+
case mime.endsWith(';base64'):
56+
text = self.atob(content);
57+
break;
58+
default:
59+
text = self.decodeURIComponent(content);
60+
break;
61+
}
62+
} catch {
63+
}
64+
scriptTexts.set(elem, text);
65+
return text;
66+
};
67+
const validate = ( ) => {
68+
const e = document.currentScript;
69+
if ( e instanceof HTMLScriptElement === false ) { return; }
70+
if ( e === thisScript ) { return; }
71+
if ( context !== '' && reContext.test(e.src) === false ) { return; }
72+
if ( safe.logLevel > 1 && context !== '' ) {
73+
safe.uboLog(logPrefix, `Matched src\n${e.src}`);
74+
}
75+
const scriptText = getScriptText(e);
76+
if ( reNeedle.test(scriptText) === false ) { return; }
77+
if ( safe.logLevel > 1 ) {
78+
safe.uboLog(logPrefix, `Matched text\n${scriptText}`);
79+
}
80+
safe.uboLog(logPrefix, 'Aborted');
81+
throw new ReferenceError(exceptionToken);
82+
};
83+
let currentValue = trapPropertyFn(target, {
84+
get: function() {
85+
validate();
86+
return currentValue;
87+
},
88+
set: function(a) {
89+
validate();
90+
currentValue = a;
91+
}
92+
}, { canThrow: true });
93+
}
94+
registerScriptlet(abortCurrentScriptFn , {
95+
name: 'abort-current-script.fn',
96+
dependencies: [
97+
getExceptionTokenFn,
98+
safeSelf,
99+
trapPropertyFn,
100+
],
101+
});
102+
103+
/******************************************************************************/
104+
105+
// Issues to mind before changing anything:
106+
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
107+
export function abortCurrentScript(...args) {
108+
runAtHtmlElementFn(( ) => {
109+
abortCurrentScriptFn(...args);
110+
});
111+
}
112+
registerScriptlet(abortCurrentScript , {
113+
name: 'abort-current-script.js',
114+
aliases: [
115+
'abort-current-inline-script.js',
116+
'acis.js',
117+
'acs.js',
118+
],
119+
dependencies: [
120+
abortCurrentScriptFn,
121+
runAtHtmlElementFn,
122+
],
123+
});

src/js/resources/scriptlets.js

Lines changed: 1 addition & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
2121
*/
2222

23+
import './abort-current-script.js';
2324
import './attribute.js';
2425
import './create-html.js';
2526
import './href-sanitizer.js';
@@ -58,9 +59,6 @@ import { registeredScriptlets } from './base.js';
5859
import { safeSelf } from './safe-self.js';
5960
import { validateConstantFn } from './set-constant.js';
6061

61-
// Externally added to the private namespace in which scriptlets execute.
62-
/* global scriptletGlobals */
63-
6462
/* eslint no-prototype-builtins: 0 */
6563

6664
export const builtinScriptlets = registeredScriptlets;
@@ -71,137 +69,6 @@ export const builtinScriptlets = registeredScriptlets;
7169
7270
These are meant to be used as dependencies to injectable scriptlets.
7371
74-
*******************************************************************************/
75-
76-
builtinScriptlets.push({
77-
name: 'should-debug.fn',
78-
fn: shouldDebug,
79-
});
80-
function shouldDebug(details) {
81-
if ( details instanceof Object === false ) { return false; }
82-
return scriptletGlobals.canDebug && details.debug;
83-
}
84-
85-
/******************************************************************************/
86-
87-
builtinScriptlets.push({
88-
name: 'abort-current-script.fn',
89-
fn: abortCurrentScriptFn,
90-
dependencies: [
91-
'get-exception-token.fn',
92-
'safe-self.fn',
93-
'should-debug.fn',
94-
],
95-
});
96-
// Issues to mind before changing anything:
97-
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
98-
function abortCurrentScriptFn(
99-
target = '',
100-
needle = '',
101-
context = ''
102-
) {
103-
if ( typeof target !== 'string' ) { return; }
104-
if ( target === '' ) { return; }
105-
const safe = safeSelf();
106-
const logPrefix = safe.makeLogPrefix('abort-current-script', target, needle, context);
107-
const reNeedle = safe.patternToRegex(needle);
108-
const reContext = safe.patternToRegex(context);
109-
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
110-
const thisScript = document.currentScript;
111-
const chain = safe.String_split.call(target, '.');
112-
let owner = window;
113-
let prop;
114-
for (;;) {
115-
prop = chain.shift();
116-
if ( chain.length === 0 ) { break; }
117-
if ( prop in owner === false ) { break; }
118-
owner = owner[prop];
119-
if ( owner instanceof Object === false ) { return; }
120-
}
121-
let value;
122-
let desc = Object.getOwnPropertyDescriptor(owner, prop);
123-
if (
124-
desc instanceof Object === false ||
125-
desc.get instanceof Function === false
126-
) {
127-
value = owner[prop];
128-
desc = undefined;
129-
}
130-
const debug = shouldDebug(extraArgs);
131-
const exceptionToken = getExceptionTokenFn();
132-
const scriptTexts = new WeakMap();
133-
const textContentGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent').get;
134-
const getScriptText = elem => {
135-
let text = textContentGetter.call(elem);
136-
if ( text.trim() !== '' ) { return text; }
137-
if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); }
138-
const [ , mime, content ] =
139-
/^data:([^,]*),(.+)$/.exec(elem.src.trim()) ||
140-
[ '', '', '' ];
141-
try {
142-
switch ( true ) {
143-
case mime.endsWith(';base64'):
144-
text = self.atob(content);
145-
break;
146-
default:
147-
text = self.decodeURIComponent(content);
148-
break;
149-
}
150-
} catch {
151-
}
152-
scriptTexts.set(elem, text);
153-
return text;
154-
};
155-
const validate = ( ) => {
156-
const e = document.currentScript;
157-
if ( e instanceof HTMLScriptElement === false ) { return; }
158-
if ( e === thisScript ) { return; }
159-
if ( context !== '' && reContext.test(e.src) === false ) {
160-
// eslint-disable-next-line no-debugger
161-
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
162-
return;
163-
}
164-
if ( safe.logLevel > 1 && context !== '' ) {
165-
safe.uboLog(logPrefix, `Matched src\n${e.src}`);
166-
}
167-
const scriptText = getScriptText(e);
168-
if ( reNeedle.test(scriptText) === false ) {
169-
// eslint-disable-next-line no-debugger
170-
if ( debug === 'nomatch' || debug === 'all' ) { debugger; }
171-
return;
172-
}
173-
if ( safe.logLevel > 1 ) {
174-
safe.uboLog(logPrefix, `Matched text\n${scriptText}`);
175-
}
176-
// eslint-disable-next-line no-debugger
177-
if ( debug === 'match' || debug === 'all' ) { debugger; }
178-
safe.uboLog(logPrefix, 'Aborted');
179-
throw new ReferenceError(exceptionToken);
180-
};
181-
// eslint-disable-next-line no-debugger
182-
if ( debug === 'install' ) { debugger; }
183-
try {
184-
Object.defineProperty(owner, prop, {
185-
get: function() {
186-
validate();
187-
return desc instanceof Object
188-
? desc.get.call(owner)
189-
: value;
190-
},
191-
set: function(a) {
192-
validate();
193-
if ( desc instanceof Object ) {
194-
desc.set.call(owner, a);
195-
} else {
196-
value = a;
197-
}
198-
}
199-
});
200-
} catch(ex) {
201-
safe.uboErr(logPrefix, `Error: ${ex}`);
202-
}
203-
}
204-
20572
/******************************************************************************/
20673

20774
builtinScriptlets.push({
@@ -394,29 +261,6 @@ function replaceFetchResponseFn(
394261
395262
These are meant to be used in the MAIN (webpage) execution world.
396263
397-
*******************************************************************************/
398-
399-
builtinScriptlets.push({
400-
name: 'abort-current-script.js',
401-
aliases: [
402-
'acs.js',
403-
'abort-current-inline-script.js',
404-
'acis.js',
405-
],
406-
fn: abortCurrentScript,
407-
dependencies: [
408-
'abort-current-script.fn',
409-
'run-at-html-element.fn',
410-
],
411-
});
412-
// Issues to mind before changing anything:
413-
// https://github.com/uBlockOrigin/uBlock-issues/issues/2154
414-
function abortCurrentScript(...args) {
415-
runAtHtmlElementFn(( ) => {
416-
abortCurrentScriptFn(...args);
417-
});
418-
}
419-
420264
/******************************************************************************/
421265

422266
builtinScriptlets.push({

src/js/resources/utils.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ registerScriptlet(getExceptionTokenFn, {
6262

6363
/******************************************************************************/
6464

65-
export function trapPropertyFn(propChain, handler) {
65+
export function trapPropertyFn(propChain, handler, options = {}) {
6666
if ( propChain === '' ) { return; }
6767
let owner = self;
6868
let prop = propChain;
@@ -85,7 +85,9 @@ export function trapPropertyFn(propChain, handler) {
8585
if ( entry === undefined ) { return; }
8686
let r = entry.value;
8787
for ( const desc of entry.stack ) {
88-
try { r = desc.get(); } catch { }
88+
try { r = desc.get(); } catch (e) {
89+
if ( entry.canThrow ) { throw e; }
90+
}
8991
}
9092
return r;
9193
};
@@ -94,7 +96,9 @@ export function trapPropertyFn(propChain, handler) {
9496
if ( entry === undefined ) { return; }
9597
entry.value = value;
9698
for ( const desc of entry.stack ) {
97-
try { desc.set(value); } catch { }
99+
try { desc.set(value); } catch (e) {
100+
if ( entry.canThrow ) { throw e; }
101+
}
98102
}
99103
};
100104
}
@@ -109,6 +113,7 @@ export function trapPropertyFn(propChain, handler) {
109113
};
110114
entry.stack.push(handler);
111115
if ( entry.stack.length > 1 ) { return entry.value; }
116+
Object.assign(entry, options);
112117
handlers.set(prop, entry);
113118
const desc = safe.Object_getOwnPropertyDescriptor(owner, prop);
114119
if ( desc instanceof safe.Object ) {
@@ -367,10 +372,8 @@ export function lookupElementsFn(directive, until = 0) {
367372
if ( elem.openOrClosedShadowRoot ) { // Firefox
368373
return elem.openOrClosedShadowRoot;
369374
}
370-
if ( typeof chrome === 'object' ) { // Chromium
371-
if ( chrome.dom && chrome.dom.openOrClosedShadowRoot ) {
372-
return chrome.dom.openOrClosedShadowRoot(elem);
373-
}
375+
if ( self.chrome?.dom?.openOrClosedShadowRoot ) { // Chromium
376+
return self.chrome.dom.openOrClosedShadowRoot(elem);
374377
}
375378
return elem.shadowRoot;
376379
};

0 commit comments

Comments
 (0)