Skip to content

Commit b271e33

Browse files
author
Guillaume Chau
committed
fix(clone): handle DOM objects
1 parent edd42a9 commit b271e33

File tree

2 files changed

+123
-72
lines changed

2 files changed

+123
-72
lines changed

packages/app-backend/src/clone.js

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Clone deep utility for cloning initial state of the store
22
// REFERENCE: https://github.com/pvorb/clone
3+
// last updated: 2019-10-30
4+
// ⚠️ DON'T FORGET TO UPDATE IT IN ./hook.js
35

46
let NativeMap
57
try {
@@ -52,6 +54,8 @@ export default function clone (parent, {
5254
return parent
5355
}
5456

57+
let walkProperties = true
58+
5559
if (_instanceof(parent, NativeMap)) {
5660
child = new NativeMap()
5761
} else if (_instanceof(parent, NativeSet)) {
@@ -85,7 +89,10 @@ export default function clone (parent, {
8589
} else if (_instanceof(parent, Error)) {
8690
child = Object.create(parent)
8791
} else {
88-
if (typeof prototype === 'undefined') {
92+
if (parent instanceof HTMLElement) {
93+
child = parent.cloneNode(false)
94+
walkProperties = false
95+
} else if (typeof prototype === 'undefined') {
8996
proto = Object.getPrototypeOf(parent)
9097
child = Object.create(proto)
9198
} else {
@@ -118,43 +125,64 @@ export default function clone (parent, {
118125
})
119126
}
120127

121-
for (let i in parent) {
122-
let attrs = Object.getOwnPropertyDescriptor(parent, i)
123-
if (attrs) {
124-
if (attrs.hasOwnProperty('get') && attrs.get.name === 'computedGetter') {
125-
Object.defineProperty(child, i, attrs)
126-
continue
128+
if (walkProperties) {
129+
for (let i in parent) {
130+
let attrs = Object.getOwnPropertyDescriptor(parent, i)
131+
if (attrs) {
132+
if (attrs.hasOwnProperty('get') && attrs.get.name === 'computedGetter') {
133+
Object.defineProperty(child, i, attrs)
134+
continue
135+
}
136+
137+
child[i] = _clone(parent[i], depth - 1)
127138
}
128139

129-
child[i] = _clone(parent[i], depth - 1)
140+
// Huge performance impact
141+
// try {
142+
// const objProperty = Object.getOwnPropertyDescriptor(parent, i)
143+
// if (objProperty.set === 'undefined') {
144+
// // no setter defined. Skip cloning this property
145+
// continue
146+
// }
147+
// child[i] = _clone(parent[i], depth - 1)
148+
// } catch (e) {
149+
// if (e instanceof TypeError) {
150+
// // when in strict mode, TypeError will be thrown if child[i] property only has a getter
151+
// // we can't do anything about this, other than inform the user that this property cannot be set.
152+
// continue
153+
// } else if (e instanceof ReferenceError) {
154+
// // this may happen in non strict mode
155+
// continue
156+
// }
157+
// }
130158
}
131-
}
132159

133-
if (Object.getOwnPropertySymbols) {
134-
let symbols = Object.getOwnPropertySymbols(parent)
135-
for (let i = 0; i < symbols.length; i++) {
136-
// Don't need to worry about cloning a symbol because it is a primitive,
137-
// like a number or string.
138-
let symbol = symbols[i]
139-
let descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
140-
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
141-
continue
160+
if (Object.getOwnPropertySymbols) {
161+
let symbols = Object.getOwnPropertySymbols(parent)
162+
for (let i = 0; i < symbols.length; i++) {
163+
// Don't need to worry about cloning a symbol because it is a primitive,
164+
// like a number or string.
165+
const symbol = symbols[i]
166+
const descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
167+
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
168+
continue
169+
}
170+
child[symbol] = _clone(parent[symbol], depth - 1)
171+
Object.defineProperty(child, symbol, descriptor)
142172
}
143-
child[symbol] = _clone(parent[symbol], depth - 1)
144-
Object.defineProperty(child, symbol, descriptor)
145173
}
146-
}
147174

148-
if (includeNonEnumerable) {
149-
let allPropertyNames = Object.getOwnPropertyNames(parent)
150-
for (let i = 0; i < allPropertyNames.length; i++) {
151-
const propertyName = allPropertyNames[i]
152-
let descriptor = Object.getOwnPropertyDescriptor(parent, propertyName)
153-
if (descriptor && descriptor.enumerable) {
154-
continue
175+
if (includeNonEnumerable) {
176+
let allPropertyNames = Object.getOwnPropertyNames(parent)
177+
for (let i = 0; i < allPropertyNames.length; i++) {
178+
const propertyName = allPropertyNames[i]
179+
const descriptor = Object.getOwnPropertyDescriptor(parent, propertyName)
180+
if (descriptor && descriptor.enumerable) {
181+
continue
182+
}
183+
child[propertyName] = _clone(parent[propertyName], depth - 1)
184+
Object.defineProperty(child, propertyName, descriptor)
155185
}
156-
child[propertyName] = _clone(parent[propertyName], depth - 1)
157-
Object.defineProperty(child, propertyName, descriptor)
158186
}
159187
}
160188

packages/app-backend/src/hook.js

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ export function installHook (target) {
143143

144144
// Clone deep utility for cloning initial state of the store
145145
// REFERENCE: https://github.com/pvorb/clone
146+
// last updated: 2019-10-30
147+
// ⚠️ DON'T FORGET TO UPDATE IT IN ./hook.js
146148

147149
let NativeMap
148150
try {
@@ -167,25 +169,20 @@ export function installHook (target) {
167169
NativePromise = function () {}
168170
}
169171

170-
function clone (parent, circular, depth, prototype, includeNonEnumerable) {
171-
if (typeof circular === 'object') {
172-
depth = circular.depth
173-
prototype = circular.prototype
174-
includeNonEnumerable = circular.includeNonEnumerable
175-
circular = circular.circular
176-
}
172+
function clone (parent, {
173+
circular = true,
174+
depth = Infinity,
175+
prototype,
176+
includeNonEnumerable
177+
} = {}) {
177178
// maintain two arrays for circular references, where corresponding parents
178179
// and children have the same index
179180
let allParents = []
180181
let allChildren = []
181182

182183
let useBuffer = typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function'
183184

184-
let isBuffer = typeof window !== 'undefined' ? browserIsBuffer : Buffer.isBuffer
185-
186-
if (typeof circular === 'undefined') { circular = true }
187-
188-
if (typeof depth === 'undefined') { depth = Infinity }
185+
const isBuffer = typeof window !== 'undefined' ? browserIsBuffer : Buffer.isBuffer
189186

190187
// recurse this function so we don't reset allParents and allChildren
191188
function _clone (parent, depth) {
@@ -200,6 +197,8 @@ export function installHook (target) {
200197
return parent
201198
}
202199

200+
let walkProperties = true
201+
203202
if (_instanceof(parent, NativeMap)) {
204203
child = new NativeMap()
205204
} else if (_instanceof(parent, NativeSet)) {
@@ -233,7 +232,10 @@ export function installHook (target) {
233232
} else if (_instanceof(parent, Error)) {
234233
child = Object.create(parent)
235234
} else {
236-
if (typeof prototype === 'undefined') {
235+
if (parent instanceof HTMLElement) {
236+
child = parent.cloneNode(false)
237+
walkProperties = false
238+
} else if (typeof prototype === 'undefined') {
237239
proto = Object.getPrototypeOf(parent)
238240
child = Object.create(proto)
239241
} else {
@@ -266,43 +268,64 @@ export function installHook (target) {
266268
})
267269
}
268270

269-
for (let i in parent) {
270-
let attrs = Object.getOwnPropertyDescriptor(parent, i)
271-
if (attrs) {
272-
if (attrs.hasOwnProperty('get') && attrs.get.name === 'computedGetter') {
273-
Object.defineProperty(child, i, attrs)
274-
continue
271+
if (walkProperties) {
272+
for (let i in parent) {
273+
let attrs = Object.getOwnPropertyDescriptor(parent, i)
274+
if (attrs) {
275+
if (attrs.hasOwnProperty('get') && attrs.get.name === 'computedGetter') {
276+
Object.defineProperty(child, i, attrs)
277+
continue
278+
}
279+
280+
child[i] = _clone(parent[i], depth - 1)
275281
}
276282

277-
child[i] = _clone(parent[i], depth - 1)
283+
// Huge performance impact
284+
// try {
285+
// const objProperty = Object.getOwnPropertyDescriptor(parent, i)
286+
// if (objProperty.set === 'undefined') {
287+
// // no setter defined. Skip cloning this property
288+
// continue
289+
// }
290+
// child[i] = _clone(parent[i], depth - 1)
291+
// } catch (e) {
292+
// if (e instanceof TypeError) {
293+
// // when in strict mode, TypeError will be thrown if child[i] property only has a getter
294+
// // we can't do anything about this, other than inform the user that this property cannot be set.
295+
// continue
296+
// } else if (e instanceof ReferenceError) {
297+
// // this may happen in non strict mode
298+
// continue
299+
// }
300+
// }
278301
}
279-
}
280302

281-
if (Object.getOwnPropertySymbols) {
282-
let symbols = Object.getOwnPropertySymbols(parent)
283-
for (let i = 0; i < symbols.length; i++) {
284-
// Don't need to worry about cloning a symbol because it is a primitive,
285-
// like a number or string.
286-
let symbol = symbols[i]
287-
let descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
288-
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
289-
continue
303+
if (Object.getOwnPropertySymbols) {
304+
let symbols = Object.getOwnPropertySymbols(parent)
305+
for (let i = 0; i < symbols.length; i++) {
306+
// Don't need to worry about cloning a symbol because it is a primitive,
307+
// like a number or string.
308+
const symbol = symbols[i]
309+
const descriptor = Object.getOwnPropertyDescriptor(parent, symbol)
310+
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
311+
continue
312+
}
313+
child[symbol] = _clone(parent[symbol], depth - 1)
314+
Object.defineProperty(child, symbol, descriptor)
290315
}
291-
child[symbol] = _clone(parent[symbol], depth - 1)
292-
Object.defineProperty(child, symbol, descriptor)
293316
}
294-
}
295317

296-
if (includeNonEnumerable) {
297-
let allPropertyNames = Object.getOwnPropertyNames(parent)
298-
for (let i = 0; i < allPropertyNames.length; i++) {
299-
const propertyName = allPropertyNames[i]
300-
let descriptor = Object.getOwnPropertyDescriptor(parent, propertyName)
301-
if (descriptor && descriptor.enumerable) {
302-
continue
318+
if (includeNonEnumerable) {
319+
let allPropertyNames = Object.getOwnPropertyNames(parent)
320+
for (let i = 0; i < allPropertyNames.length; i++) {
321+
const propertyName = allPropertyNames[i]
322+
const descriptor = Object.getOwnPropertyDescriptor(parent, propertyName)
323+
if (descriptor && descriptor.enumerable) {
324+
continue
325+
}
326+
child[propertyName] = _clone(parent[propertyName], depth - 1)
327+
Object.defineProperty(child, propertyName, descriptor)
303328
}
304-
child[propertyName] = _clone(parent[propertyName], depth - 1)
305-
Object.defineProperty(child, propertyName, descriptor)
306329
}
307330
}
308331

0 commit comments

Comments
 (0)