forked from AdguardTeam/Scriptlets
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdebug-current-inline-script.js
More file actions
127 lines (113 loc) · 3.95 KB
/
debug-current-inline-script.js
File metadata and controls
127 lines (113 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import {
randomId,
setPropertyAccess,
getPropertyInChain,
toRegExp,
createOnErrorHandler,
hit,
} from '../helpers/index';
/* eslint-disable max-len */
/**
* @scriptlet debug-current-inline-script
*
* @description
* This scriptlet is basically the same as [abort-current-inline-script](#abort-current-inline-script), but instead of aborting it starts the debugger.
*
* **It is not supposed to be used in production filter lists!**
*
* **Syntax**
*```
* ! Aborts script when it tries to access `window.alert`
* example.org#%#//scriptlet('debug-current-inline-script', 'alert')
* ```
*/
/* eslint-enable max-len */
export function debugCurrentInlineScript(source, property, search) {
const searchRegexp = toRegExp(search);
const rid = randomId();
const getCurrentScript = () => {
if ('currentScript' in document) {
return document.currentScript; // eslint-disable-line compat/compat
}
const scripts = document.getElementsByTagName('script');
return scripts[scripts.length - 1];
};
const ourScript = getCurrentScript();
const abort = () => {
const scriptEl = getCurrentScript();
if (!scriptEl) {
return;
}
let content = scriptEl.textContent;
// We are using Node.prototype.textContent property descriptor
// to get the real script content
// even when document.currentScript.textContent is replaced.
// https://github.com/AdguardTeam/Scriptlets/issues/57#issuecomment-593638991
try {
const textContentGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent').get;
content = textContentGetter.call(scriptEl);
} catch (e) { } // eslint-disable-line no-empty
if (scriptEl instanceof HTMLScriptElement
&& content.length > 0
&& scriptEl !== ourScript
&& searchRegexp.test(content)) {
hit(source);
debugger; // eslint-disable-line no-debugger
}
};
const setChainPropAccess = (owner, property) => {
const chainInfo = getPropertyInChain(owner, property);
let { base } = chainInfo;
const { prop, chain } = chainInfo;
// The scriptlet might be executed before the chain property has been created
// (for instance, document.body before the HTML body was loaded).
// In this case we're checking whether the base element exists or not
// and if not, we simply exit without overriding anything.
// e.g. https://github.com/AdguardTeam/Scriptlets/issues/57#issuecomment-575841092
if (base instanceof Object === false && base === null) {
const props = property.split('.');
const propIndex = props.indexOf(prop);
const baseName = props[propIndex - 1];
console.log(`The scriptlet had been executed before the ${baseName} was loaded.`); // eslint-disable-line no-console, max-len
return;
}
if (chain) {
const setter = (a) => {
base = a;
if (a instanceof Object) {
setChainPropAccess(a, chain);
}
};
Object.defineProperty(owner, prop, {
get: () => base,
set: setter,
});
return;
}
let currentValue = base[prop];
setPropertyAccess(base, prop, {
set: (value) => {
abort();
currentValue = value;
},
get: () => {
abort();
return currentValue;
},
});
};
setChainPropAccess(window, property);
window.onerror = createOnErrorHandler(rid)
.bind();
}
debugCurrentInlineScript.names = [
'debug-current-inline-script',
];
debugCurrentInlineScript.injections = [
randomId,
setPropertyAccess,
getPropertyInChain,
toRegExp,
createOnErrorHandler,
hit,
];