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