-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathlog-on-stack-trace.js
136 lines (125 loc) · 4.19 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
135
136
import {
getPropertyInChain,
setPropertyAccess,
hit,
logMessage,
isEmptyObject,
backupRegExpValues,
restoreRegExpValues,
} from '../helpers';
/* 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);
}
export const logOnStackTraceNames = [
'log-on-stack-trace',
];
// eslint-disable-next-line prefer-destructuring
logOnStackTrace.primaryName = logOnStackTraceNames[0];
logOnStackTrace.injections = [
getPropertyInChain,
setPropertyAccess,
hit,
logMessage,
isEmptyObject,
backupRegExpValues,
restoreRegExpValues,
];