/
utils.ts
131 lines (117 loc) · 3.36 KB
/
utils.ts
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
import { KEYS } from './jsdom-keys'
const allowRewrite = [
'Event',
'EventTarget',
]
const skipKeys = [
'window',
'self',
]
export function getWindowKeys(global: any, win: any) {
const keys = new Set(KEYS.concat(Object.getOwnPropertyNames(win))
.filter((k) => {
if (k.startsWith('_') || skipKeys.includes(k))
return false
if (k in global)
return allowRewrite.includes(k)
return true
}))
return keys
}
export function populateGlobal(global: any, win: any) {
const keys = getWindowKeys(global, win)
const overrideObject = new Map<string | symbol, any>()
for (const key of keys) {
Object.defineProperty(global, key, {
get() {
if (overrideObject.has(key))
return overrideObject.get(key)
return win[key]
},
set(v) {
overrideObject.set(key, v)
},
configurable: true,
})
}
const globalKeys = ['window', 'self', 'GLOBAL', 'global']
global.globalThis = new Proxy(global.globalThis, {
set(target, key, value, receiver) {
overrideObject.set(key, value)
return Reflect.set(target, key, value, receiver)
},
deleteProperty(target, key) {
overrideObject.delete(key)
return Reflect.deleteProperty(target, key)
},
defineProperty(target, p, attributes) {
if (attributes.writable && 'value' in attributes) {
// skip - already covered by "set"
}
else if (attributes.get) {
globalKeys.forEach((key) => {
if (win[key])
Object.defineProperty(win[key], p, attributes)
})
}
return Reflect.defineProperty(target, p, attributes)
},
})
globalKeys.forEach((key) => {
if (!win[key])
return
const proxy = new Proxy(win[key], {
get(target, p, receiver) {
if (overrideObject.has(p))
return overrideObject.get(p)
return Reflect.get(target, p, receiver)
},
set(target, p, value, receiver) {
try {
// if property is defined with configurable: false,
// this will throw an error, but `self.prop = value` should not throw
// this matches browser behaviour where it silently ignores the error
// and returns previously defined value, which is a hell for debugging
Object.defineProperty(global, p, {
get: () => overrideObject.get(p),
set: value => overrideObject.set(p, value),
configurable: true,
})
overrideObject.set(p, value)
Reflect.set(target, p, value, receiver)
}
catch {
// ignore
}
return true
},
deleteProperty(target, p) {
Reflect.deleteProperty(global, p)
overrideObject.delete(p)
return Reflect.deleteProperty(target, p)
},
defineProperty(target, p, attributes) {
if (attributes.writable && 'value' in attributes) {
// skip - already covered by "set"
}
else if (attributes.get) {
overrideObject.delete(p)
Reflect.defineProperty(global, p, attributes)
}
return Reflect.defineProperty(target, p, attributes)
},
})
Object.defineProperty(global, key, {
get() {
return proxy
},
configurable: true,
})
})
skipKeys.forEach(k => keys.add(k))
return {
keys,
skipKeys,
allowRewrite,
}
}