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