• 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    public 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)[RefInfo.MAKE_OBSERVED_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    public static readonly arrayLengthChangingFunctions = new Set(['push', 'pop', 'shift', 'splice', 'unshift', 'shrinkTo', 'extendTo']);
129    public static readonly arrayMutatingFunctions = new Set(['copyWithin', 'fill', 'reverse', 'sort']);
130
131    // Note: The code of this function is duplicated with adaptation for enableV2Compatibility
132    // when making changes here, review of these changes are also needed in
133    // SubscribableArrayHandler.getV2Compatible function
134    get(target: Array<any>, key: string | symbol, receiver: Array<any>): any {
135
136        if (typeof key === 'symbol') {
137            if (key === Symbol.iterator) {
138                const conditionalTarget = this.getTarget(target);
139                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
140                return (...args): any => target[key](...args);
141            }
142            if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
143                return target;
144            }
145            if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) {
146                return true;
147            }
148            return target[key];
149        }
150
151        stateMgmtConsole.debug(`ArrayProxyHandler get key '${key}'`);
152        const conditionalTarget = this.getTarget(target);
153
154        if (key === 'length') {
155            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
156            return target[key];
157        }
158
159        // makeObserved logic adds wrapper proxy later
160        let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key);
161        if (typeof (ret) !== 'function') {
162            ObserveV2.getObserve().addRef(conditionalTarget, key);
163            return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret)[RefInfo.MAKE_OBSERVED_PROXY] : ret;
164        }
165
166        if (ArrayProxyHandler.arrayMutatingFunctions.has(key)) {
167            return function (...args): any {
168                ret.call(target, ...args);
169                ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
170                // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call
171                // operates on the proxied object.
172                return receiver;
173            };
174        } else if (ArrayProxyHandler.arrayLengthChangingFunctions.has(key)) {
175            return function (...args): any {
176                const result = ret.call(target, ...args);
177                ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
178                return result;
179            };
180        } else if (!SendableType.isArray(target)) {
181            return ret.bind(receiver);
182        } else if (key === 'forEach') {
183            // to make ForEach Component and its Item can addref
184            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
185            return function (callbackFn: (value: any, index: number, array: Array<any>) => void): any {
186                const result = ret.call(target, (value: any, index: number, array: Array<any>) => {
187                    // Collections.Array will report BusinessError: The foreach cannot be bound if call "receiver".
188                    // because the passed parameter is not the instance of the container class.
189                    // so we must call "target" here to deal with the collections situations.
190                    // But we also need to addref for each index.
191                    ObserveV2.getObserve().addRef(conditionalTarget, index.toString());
192                    callbackFn(typeof value === 'object' ? RefInfo.get(value)[RefInfo.MAKE_OBSERVED_PROXY] : value, index, receiver);
193                });
194                return result;
195            };
196        } else {
197            return ret.bind(target); // SendableArray can't be bound -> functions not observed
198        }
199    }
200
201    set(target: Array<any>, key: string | symbol, value: any): boolean {
202        if (typeof key === 'symbol') {
203            if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) {
204                target[key] = value;
205            }
206            return true;
207        }
208
209        if (target[key] === value) {
210            return true;
211        }
212
213        const originalLength = target.length;
214        target[key] = value;
215        const arrayLenChanged = target.length !== originalLength;
216        ObserveV2.getObserve().fireChange(this.getTarget(target), arrayLenChanged ? ObserveV2.OB_LENGTH : key.toString());
217        return true;
218    }
219};
220
221/**
222 * Common Proxy handler for Maps and Sets for both decorators and makeObserved
223 */
224class SetMapProxyHandler {
225
226    public static readonly OB_MAP_SET_ANY_PROPERTY = '___ob_map_set';
227
228    private isMakeObserved_: boolean;
229
230    constructor(isMakeObserved: boolean = false) {
231        this.isMakeObserved_ = isMakeObserved;
232    }
233
234    // decorators work on object that holds the dependencies directly
235    // makeObserved can't modify the object itself, so it creates a
236    // wrapper object around it and that will hold the references
237    //
238    // this function is used to get the correct object that can be observed
239    private getTarget(obj: any): any {
240        return this.isMakeObserved_ ? RefInfo.get(obj) : obj;
241    }
242
243    // Note: The code of this function is duplicated with adaptation for enableV2Compatibility
244    // when making changes here, review of these changes are also needed in
245    // SubscribableMapSetHandler.getV2Compatible function
246    get(target: any, key: string | symbol, receiver: any): any {
247        if (typeof key === 'symbol') {
248            if (key === Symbol.iterator) {
249                const conditionalTarget = this.getTarget(target);
250                ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
251                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
252                return (...args): any => target[key](...args);
253            }
254            if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
255                return target;
256            }
257            if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) {
258                return true;
259            }
260            return target[key];
261        }
262
263        stateMgmtConsole.debug(`SetMapProxyHandler get key '${key}'`);
264        const conditionalTarget = this.getTarget(target);
265
266        if (key === 'size') {
267            ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
268            return target[key];
269        }
270
271        // makeObserved logic adds wrapper proxy later
272        let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key);
273        if (typeof (ret) !== 'function') {
274            ObserveV2.getObserve().addRef(conditionalTarget, key);
275            return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret)[RefInfo.MAKE_OBSERVED_PROXY] : ret;
276        }
277
278        if (key === 'has') {
279            return (prop): boolean => {
280                const ret = target.has(prop);
281                if (ret) {
282                    ObserveV2.getObserve().addRef(conditionalTarget, prop);
283                } else {
284                    ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
285                }
286                return ret;
287            };
288        }
289        if (key === 'delete') {
290            return (prop): boolean => {
291                if (target.has(prop)) {
292                    const res: boolean = target.delete(prop)
293                    ObserveV2.getObserve().fireChange(conditionalTarget, prop);
294                    ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
295                    return res;
296                } else {
297                    return false;
298                }
299            };
300        }
301        if (key === 'clear') {
302            return (): void => {
303                if (target.size > 0) {
304                    target.forEach((_, prop) => {
305                        ObserveV2.getObserve().fireChange(conditionalTarget, prop.toString(), true);
306                    });
307                    target.clear();
308                    ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
309                    ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
310                }
311            };
312        }
313        if (key === 'keys' || key === 'values' || key === 'entries') {
314            return (): any => {
315                ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
316                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
317                return target[key]();
318            };
319        }
320
321        if (target instanceof Set || (this.isMakeObserved_ && SendableType.isSet(target))) {
322            if (key === 'add') {
323                return (val): any => {
324                    if (target.has(val)) {
325                        return receiver;
326                    }
327                    target.add(val);
328                    ObserveV2.getObserve().fireChange(conditionalTarget, val);
329                    ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
330                    ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
331                    return receiver;
332                };
333            }
334
335            if (key === 'forEach') {
336                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
337                return function (callbackFn: (value: any, value2: any, set: Set<any>) => void): any {
338                    // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
339                    // if necessary, addref for each item in Set and also wrap proxy for makeObserved if it is Object.
340                    // currently, just execute it in target because there is no Component need to iterate Set, only Array
341                    const result = ret.call(target, callbackFn);
342                    return result;
343                };
344            }
345            // Bind to receiver ==> functions are observed
346            return (typeof ret === 'function') ? ret.bind(receiver) : ret;
347        }
348
349        if (target instanceof Map || (this.isMakeObserved_ && SendableType.isMap(target))) {
350            if (key === 'get') {
351                return (prop): any => {
352                    if (target.has(prop)) {
353                        ObserveV2.getObserve().addRef(conditionalTarget, prop);
354                    } else {
355                        ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
356                    }
357                    let item = target.get(prop);
358                    return (typeof item === 'object' && this.isMakeObserved_) ? RefInfo.get(item)[RefInfo.MAKE_OBSERVED_PROXY] : item;
359                };
360            }
361            if (key === 'set') {
362                return (prop, val): any => {
363                    if (!target.has(prop)) {
364                        target.set(prop, val);
365                        ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
366                    } else if (target.get(prop) !== val) {
367                        target.set(prop, val);
368                        ObserveV2.getObserve().fireChange(conditionalTarget, prop);
369                    }
370                    ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
371                    return receiver;
372                };
373            }
374            if (key === 'forEach') {
375                ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH);
376                return function (callbackFn: (value: any, key: any, map: Map<any, any>) => void): any {
377                    // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
378                    // if necessary, addref for each item in Map and also wrap proxy for makeObserved if it is Object.
379                    // currently, just execute it in target because there is no Component need to iterate Map, only Array
380                    const result = ret.call(target, callbackFn);
381                    return result;
382                };
383            }
384        }
385        // Bind to receiver ==> functions are observed
386        return (typeof ret === 'function') ? ret.bind(receiver) : ret;
387    }
388
389    set(target: any, key: string | symbol, value: any): boolean {
390        if (typeof key === 'symbol') {
391            if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) {
392                target[key] = value;
393            }
394            return true;
395        }
396
397        if (target[key] === value) {
398            return true;
399        }
400        target[key] = value;
401        ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString());
402        return true;
403    }
404};