• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/**
17 * Common Proxy handler for objects and dates for both decorators and makeObserved
18 */
19class ObjectProxyHandler {
20
21    private static readonly OB_DATE = '__date__';
22
23    private isMakeObserved_: boolean;
24
25    constructor(isMakeObserved: boolean = false) {
26        this.isMakeObserved_ = isMakeObserved;
27    }
28
29    // decorators work on object that holds the dependencies directly
30    // makeObserved can't modify the object itself, so it creates a
31    // wrapper object around it and that will hold the references
32    //
33    // this function is used to get the correct object that can be observed
34    private getTarget(obj: any): any {
35        return this.isMakeObserved_ ? RefInfo.get(obj) : obj;
36    }
37
38    private static readonly dateSetFunctions = new Set(['setFullYear', 'setMonth', 'setDate', 'setHours', 'setMinutes',
39        'setSeconds', 'setMilliseconds', 'setTime', 'setUTCFullYear', 'setUTCMonth', 'setUTCDate', 'setUTCHours',
40        'setUTCMinutes', 'setUTCSeconds', 'setUTCMilliseconds']);
41
42    get(target: any, key: string | symbol, receiver: any): any {
43
44        if (typeof key === 'symbol') {
45            if (key === Symbol.iterator) {
46                const conditionalTarget = this.getTarget(target);
47                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
48                return (...args): any => target[key](...args);
49            }
50            if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
51                return target;
52            }
53            if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) {
54                return true;
55            }
56            return target[key];
57        }
58
59        stateMgmtConsole.debug(`ObjectProxyHandler get key '${key}'`);
60        const conditionalTarget = this.getTarget(target);
61
62        // makeObserved logic adds wrapper proxy later
63        let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key);
64        // do not addref for function type, it will make such huge unnecessary dependency collection
65        // for some common function attributes, e.g. toString etc.
66        if (typeof (ret) !== 'function') {
67            ObserveV2.getObserve().addRef(conditionalTarget, key);
68            return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret;
69        }
70
71        if (target instanceof Date) {
72            if (ObjectProxyHandler.dateSetFunctions.has(key)) {
73                return function (...args): any {
74                    // execute original function with given arguments
75                    let result = ret.call(this, ...args);
76                    ObserveV2.getObserve().fireChange(conditionalTarget, ObjectProxyHandler.OB_DATE);
77                    return result;
78                    // bind 'this' to target inside the function
79                }.bind(target);
80            } else {
81                ObserveV2.getObserve().addRef(conditionalTarget, ObjectProxyHandler.OB_DATE);
82            }
83            return ret.bind(target);
84        }
85
86        // function
87        return ret.bind(receiver);
88    }
89
90    set(target: any, key: string | symbol, value: any): boolean {
91        if (typeof key === 'symbol') {
92            if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) {
93                target[key] = value;
94            }
95            return true;
96        }
97
98        if (target[key] === value) {
99            return true;
100        }
101        target[key] = value;
102        ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString());
103        return true;
104    }
105};
106
107/**
108 * Common Proxy handler for Arrays for both decorators and makeObserved
109 */
110class ArrayProxyHandler {
111
112    private isMakeObserved_: boolean;
113
114    constructor(isMakeObserved: boolean = false) {
115        this.isMakeObserved_ = isMakeObserved;
116    }
117
118    // decorators work on object that holds the dependencies directly
119    // makeObserved can't modify the object itself, so it creates a
120    // wrapper object around it and that will hold the references
121    //
122    // this function is used to get the correct object that can be observed
123    private getTarget(obj: any): any {
124        return this.isMakeObserved_ ? RefInfo.get(obj) : obj;
125    }
126
127    // shrinkTo and extendTo is collection.Array api.
128    private static readonly arrayLengthChangingFunctions = new Set(['push', 'pop', 'shift', 'splice', 'unshift', 'shrinkTo', 'extendTo']);
129    private static readonly arrayMutatingFunctions = new Set(['copyWithin', 'fill', 'reverse', 'sort']);
130
131    get(target: Array<any>, key: string | symbol, receiver: Array<any>): any {
132
133        if (typeof key === 'symbol') {
134            if (key === Symbol.iterator) {
135                const conditionalTarget = this.getTarget(target);
136                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
137                return (...args): any => target[key](...args);
138            }
139            if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
140                return target;
141            }
142            if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) {
143                return true;
144            }
145            return target[key];
146        }
147
148        stateMgmtConsole.debug(`ArrayProxyHandler get key '${key}'`);
149        const conditionalTarget = this.getTarget(target);
150
151        if (key === 'length') {
152            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
153            return target[key];
154        }
155
156        // makeObserved logic adds wrapper proxy later
157        let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key);
158        if (typeof (ret) !== 'function') {
159            ObserveV2.getObserve().addRef(conditionalTarget, key);
160            return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret;
161        }
162
163        if (ArrayProxyHandler.arrayMutatingFunctions.has(key)) {
164            return function (...args): any {
165                ret.call(target, ...args);
166                ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
167                // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call
168                // operates on the proxied object.
169                return receiver;
170            };
171        } else if (ArrayProxyHandler.arrayLengthChangingFunctions.has(key)) {
172            return function (...args): any {
173                const result = ret.call(target, ...args);
174                ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
175                return result;
176            };
177        } else if (!SendableType.isArray(target)) {
178            return ret.bind(receiver);
179        } else if (key === 'forEach') {
180            // to make ForEach Component and its Item can addref
181            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
182            return function (callbackFn: (value: any, index: number, array: Array<any>) => void): any {
183                const result = ret.call(target, (value: any, index: number, array: Array<any>) => {
184                    // Collections.Array will report BusinessError: The foreach cannot be bound if call "receiver".
185                    // because the passed parameter is not the instance of the container class.
186                    // so we must call "target" here to deal with the collections situations.
187                    // But we also need to addref for each index.
188                    ObserveV2.getObserve().addRef(conditionalTarget, index.toString());
189                    callbackFn(typeof value === 'object' ? RefInfo.get(value).proxy : value, index, receiver);
190                });
191                return result;
192            };
193        } else {
194            return ret.bind(target); // SendableArray can't be bound -> functions not observed
195        }
196    }
197
198    set(target: Array<any>, key: string | symbol, value: any): boolean {
199        if (typeof key === 'symbol') {
200            if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) {
201                target[key] = value;
202            }
203            return true;
204        }
205
206        if (target[key] === value) {
207            return true;
208        }
209
210        const originalLength = target.length;
211        target[key] = value;
212        const arrayLenChanged = target.length !== originalLength;
213        ObserveV2.getObserve().fireChange(this.getTarget(target), arrayLenChanged ? ObserveV2.OB_LENGTH : key.toString());
214        return true;
215    }
216};
217
218/**
219 * Common Proxy handler for Maps and Sets for both decorators and makeObserved
220 */
221class SetMapProxyHandler {
222
223    private static readonly OB_MAP_SET_ANY_PROPERTY = '___ob_map_set';
224
225    private isMakeObserved_: boolean;
226
227    constructor(isMakeObserved: boolean = false) {
228        this.isMakeObserved_ = isMakeObserved;
229    }
230
231    // decorators work on object that holds the dependencies directly
232    // makeObserved can't modify the object itself, so it creates a
233    // wrapper object around it and that will hold the references
234    //
235    // this function is used to get the correct object that can be observed
236    private getTarget(obj: any): any {
237        return this.isMakeObserved_ ? RefInfo.get(obj) : obj;
238    }
239
240    get(target: any, key: string | symbol, receiver: any): any {
241        if (typeof key === 'symbol') {
242            if (key === Symbol.iterator) {
243                const conditionalTarget = this.getTarget(target);
244                ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
245                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
246                return (...args): any => target[key](...args);
247            }
248            if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
249                return target;
250            }
251            if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) {
252                return true;
253            }
254            return target[key];
255        }
256
257        stateMgmtConsole.debug(`SetMapProxyHandler get key '${key}'`);
258        const conditionalTarget = this.getTarget(target);
259
260        if (key === 'size') {
261            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
262            return target[key];
263        }
264
265        // makeObserved logic adds wrapper proxy later
266        let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key);
267        if (typeof (ret) !== 'function') {
268            ObserveV2.getObserve().addRef(conditionalTarget, key);
269            return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret;
270        }
271
272        if (key === 'has') {
273            return (prop): boolean => {
274                const ret = target.has(prop);
275                if (ret) {
276                    ObserveV2.getObserve().addRef(conditionalTarget, prop);
277                } else {
278                    ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
279                }
280                return ret;
281            };
282        }
283        if (key === 'delete') {
284            return (prop): boolean => {
285                if (target.has(prop)) {
286                    ObserveV2.getObserve().fireChange(conditionalTarget, prop);
287                    ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
288                    return target.delete(prop);
289                } else {
290                    return false;
291                }
292            };
293        }
294        if (key === 'clear') {
295            return (): void => {
296                if (target.size > 0) {
297                    target.forEach((_, prop) => {
298                        ObserveV2.getObserve().fireChange(conditionalTarget, prop.toString());
299                    });
300                    ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
301                    ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
302                    target.clear();
303                }
304            };
305        }
306        if (key === 'keys' || key === 'values' || key === 'entries') {
307            return (): any => {
308                ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
309                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
310                return target[key]();
311            };
312        }
313
314        if (target instanceof Set || (this.isMakeObserved_ && SendableType.isSet(target))) {
315            if (key === 'add') {
316                return (val): any => {
317                    ObserveV2.getObserve().fireChange(conditionalTarget, val.toString());
318                    ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
319                    if (!target.has(val)) {
320                        ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
321                        target.add(val);
322                    }
323                    return receiver;
324                };
325            }
326
327            if (key === 'forEach') {
328                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
329                return function (callbackFn: (value: any, value2: any, set: Set<any>) => void): any {
330                    // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
331                    // if necessary, addref for each item in Set and also wrap proxy for makeObserved if it is Object.
332                    // currently, just execute it in target because there is no Component need to iterate Set, only Array
333                    const result = ret.call(target, callbackFn);
334                    return result;
335                };
336            }
337            // Bind to receiver ==> functions are observed
338            return (typeof ret === 'function') ? ret.bind(receiver) : ret;
339        }
340
341        if (target instanceof Map || (this.isMakeObserved_ && SendableType.isMap(target))) {
342            if (key === 'get') {
343                return (prop): any => {
344                    if (target.has(prop)) {
345                        ObserveV2.getObserve().addRef(conditionalTarget, prop);
346                    } else {
347                        ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
348                    }
349                    let item = target.get(prop);
350                    return (typeof item === 'object' && this.isMakeObserved_) ? RefInfo.get(item).proxy : item;
351                };
352            }
353            if (key === 'set') {
354                return (prop, val): any => {
355                    if (!target.has(prop)) {
356                        ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
357                    } else if (target.get(prop) !== val) {
358                        ObserveV2.getObserve().fireChange(conditionalTarget, prop);
359                    }
360                    ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
361                    target.set(prop, val);
362                    return receiver;
363                };
364            }
365            if (key === 'forEach') {
366                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
367                return function (callbackFn: (value: any, key: any, map: Map<any, any>) => void): any {
368                    // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
369                    // if necessary, addref for each item in Map and also wrap proxy for makeObserved if it is Object.
370                    // currently, just execute it in target because there is no Component need to iterate Map, only Array
371                    const result = ret.call(target, callbackFn);
372                    return result;
373                };
374            }
375        }
376        // Bind to receiver ==> functions are observed
377        return (typeof ret === 'function') ? ret.bind(receiver) : ret;
378    }
379
380    set(target: any, key: string | symbol, value: any): boolean {
381        if (typeof key === 'symbol') {
382            if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) {
383                target[key] = value;
384            }
385            return true;
386        }
387
388        if (target[key] === value) {
389            return true;
390        }
391        target[key] = value;
392        ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString());
393        return true;
394    }
395};