-
Notifications
You must be signed in to change notification settings - Fork 29
/
log-on-stack-trace.js
134 lines (125 loc) · 4.18 KB
/
log-on-stack-trace.js
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
128
129
130
131
132
133
134
import {
getPropertyInChain,
setPropertyAccess,
hit,
logMessage,
// following helpers should be imported and injected
// because they are used by helpers above
isEmptyObject,
backupRegExpValues,
restoreRegExpValues,
} from '../helpers/index';
/* eslint-disable max-len */
/**
* @scriptlet log-on-stack-trace
*
* @description
* This scriptlet is basically the same as [abort-on-stack-trace](#abort-on-stack-trace),
* but instead of aborting it logs:
*
* - function and source script names pairs that access the given property
* - was that get or set attempt
* - script being injected or inline
*
* ### Syntax
*
* ```text
* example.com#%#//scriptlet('log-on-stack-trace', 'property')
* ```
*
* - `property` — required, path to a property. The property must be attached to window.
*
* @added v1.5.0.
*/
/* eslint-enable max-len */
export function logOnStacktrace(source, property) {
if (!property) {
return;
}
const refineStackTrace = (stackString) => {
const regExpValues = backupRegExpValues();
// Split stack trace string by lines and remove first two elements ('Error' and getter call)
// Remove ' at ' at the start of each string
const stackSteps = stackString.split('\n').slice(2).map((line) => line.replace(/ {4}at /, ''));
// Trim each line extracting funcName : fullPath pair
const logInfoArray = stackSteps.map((line) => {
let funcName;
let funcFullPath;
/* eslint-disable-next-line no-useless-escape */
const reg = /\(([^\)]+)\)/;
const regFirefox = /(.*?@)(\S+)(:\d+):\d+\)?$/;
if (line.match(reg)) {
funcName = line.split(' ').slice(0, -1).join(' ');
/* eslint-disable-next-line prefer-destructuring */
funcFullPath = line.match(reg)[1];
} else if (line.match(regFirefox)) {
funcName = line.split('@').slice(0, -1).join(' ');
/* eslint-disable-next-line prefer-destructuring */
funcFullPath = line.match(regFirefox)[2];
} else {
// For when func name is not available
funcName = 'function name is not available';
funcFullPath = line;
}
return [funcName, funcFullPath];
});
// Convert array into object for better display using console.table
const logInfoObject = {};
logInfoArray.forEach((pair) => {
/* eslint-disable-next-line prefer-destructuring */
logInfoObject[pair[0]] = pair[1];
});
if (regExpValues.length && regExpValues[0] !== RegExp.$1) {
restoreRegExpValues(regExpValues);
}
return logInfoObject;
};
const setChainPropAccess = (owner, property) => {
const chainInfo = getPropertyInChain(owner, property);
let { base } = chainInfo;
const { prop, chain } = chainInfo;
if (chain) {
const setter = (a) => {
base = a;
if (a instanceof Object) {
setChainPropAccess(a, chain);
}
};
Object.defineProperty(owner, prop, {
get: () => base,
set: setter,
});
return;
}
let value = base[prop];
/* eslint-disable no-console */
setPropertyAccess(base, prop, {
get() {
hit(source);
logMessage(source, `Get ${prop}`, true);
console.table(refineStackTrace(new Error().stack));
return value;
},
set(newValue) {
hit(source);
logMessage(source, `Set ${prop}`, true);
console.table(refineStackTrace(new Error().stack));
value = newValue;
},
});
/* eslint-enable no-console */
};
setChainPropAccess(window, property);
}
logOnStacktrace.names = [
'log-on-stack-trace',
];
logOnStacktrace.injections = [
getPropertyInChain,
setPropertyAccess,
hit,
logMessage,
isEmptyObject,
backupRegExpValues,
restoreRegExpValues,
];