• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2023 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
18/**
19* @Observed class decorator
20*
21* usage:
22*    @Observed class ClassA { ... }
23*
24* Causes every instance of decorated clss to be automatically wrapped inside an ObservedObject.
25*
26* Implemented by extending the decroaetd class by class named 'ObservableObjectClass'.
27*
28* It is permisstable to decorate the base and the extended class like thisNote: I
29*   @Observed class ClassA { ...}
30*   @Observed class ClassB extends ClassA { ... }
31* and use
32*   a = new ClassA();
33*   b = new ClassB();
34* Only one ES6 Proxy is added.
35*
36*
37* Take note the decorator implementation extends the prototype chain.
38*
39* The prototype chain of a in above example is
40*  - ObservableObjectClass prototype
41*  - ClassA prototype
42*  - Object prototype
43*
44* Snd the prototype chain of b is
45*  - ObservableObjectClass prototype
46*  - ClassB prototype
47*  - ObservableObjectClass prototype
48*  - ClassA prototype
49*  - Object prototype
50*
51* The @Observed decorator is public, part of the SDK, starting from API 9.
52*
53*/
54
55
56// define just once to get just one Symbol
57const __IS_OBSERVED_PROXIED = Symbol('_____is_observed_proxied__');
58
59type Constructor = { new(...args: any[]): any };
60
61function Observed<T extends Constructor>(BaseClass: T): T {
62  stateMgmtConsole.debug(`@Observed class decorator: Overwriting constructor for '${BaseClass.name}', gets wrapped inside ObservableObject proxy.`);
63
64  // prevent use of V1 @Track inside V2 @ObservedV2 class
65  if (BaseClass.prototype && Reflect.has(BaseClass.prototype, ObserveV2.SYMBOL_REFS)) {
66    const error = `'@Observed class ${BaseClass?.name}': invalid use of V1 @Track decorator inside V2 @ObservedV2 class. Need to fix class definition to use @Track.`;
67    stateMgmtConsole.error(error);
68    throw new Error(error);
69  }
70
71  return class extends BaseClass {
72    constructor(...args: any) {
73      super(...args);
74      stateMgmtConsole.debug(`@Observed '${BaseClass.name}' modified constructor.`);
75      ConfigureStateMgmt.instance.usingPUObservedTrack(`@Observed`, BaseClass.name);
76      let isProxied = Reflect.has(this, __IS_OBSERVED_PROXIED);
77      Object.defineProperty(this, __IS_OBSERVED_PROXIED, {
78        value: true,
79        enumerable: false,
80        configurable: false,
81        writable: false
82      });
83      if (isProxied) {
84        stateMgmtConsole.debug(`   ... new '${BaseClass.name}', is proxied already`);
85        return this;
86      } else {
87        stateMgmtConsole.debug(`   ... new '${BaseClass.name}', wrapping inside ObservedObject proxy`);
88        return ObservedObject.createNewInternal(this, undefined);
89      }
90    }
91  };
92}
93
94/**
95 * class ObservedObject and supporting Handler classes,
96 * Extends from ES6 Proxy. In adding to 'get' and 'set'
97 * the clasess manage subscribers that receive notification
98 * about proxies object being 'read' or 'changed'.
99 *
100 * These classes are framework internal / non-SDK
101 *
102 */
103
104type PropertyReadCbFunc = (readObject: Object, readPropName: string, isTracked: boolean) => void;
105
106class SubscribableHandler {
107  static readonly SUBSCRIBE = Symbol('_____subscribe__');
108  static readonly UNSUBSCRIBE = Symbol('_____unsubscribe__');
109  static readonly COUNT_SUBSCRIBERS = Symbol('____count_subscribers__');
110  static readonly SET_ONREAD_CB = Symbol('_____set_onread_cb__');
111  static readonly RAW_THIS = Symbol('_____raw_this');
112  static readonly ENABLE_V2_COMPATIBLE = Symbol('_____enablev2_compatible');
113  static readonly MAKE_V1_OBSERVED = Symbol('___makev1_observed__');
114
115  private owningProperties_: Set<number>;
116  private readCbFunc_?: PropertyReadCbFunc;
117  private obSelf_?: ObservedPropertyAbstractPU<any>;
118  protected enableV2Compatible_ : boolean;
119
120  constructor(owningProperty: IPropertySubscriber) {
121    this.owningProperties_ = new Set<number>();
122
123    if (owningProperty) {
124      this.addOwningProperty(owningProperty);
125    }
126    this.enableV2Compatible_ = false;
127    stateMgmtConsole.debug(`SubscribableHandler: constructor done`);
128  }
129
130  protected isPropertyTracked(obj: Object, property: string): boolean {
131    return Reflect.has(obj, `___TRACKED_${property}`) ||
132      property === TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_PROP_PROPERTY ||
133      property === TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY;
134  }
135
136  addOwningProperty(subscriber: IPropertySubscriber): void {
137    if (subscriber) {
138      stateMgmtConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id__()}'.`);
139      this.owningProperties_.add(subscriber.id__());
140    } else {
141      stateMgmtConsole.warn(`SubscribableHandler: addOwningProperty: undefined subscriber.`);
142    }
143  }
144
145  /*
146    the inverse function of createOneWaySync or createTwoWaySync
147   */
148  public removeOwningProperty(property: IPropertySubscriber): void {
149    return this.removeOwningPropertyById(property.id__());
150  }
151
152  public removeOwningPropertyById(subscriberId: number): void {
153    stateMgmtConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`);
154    this.owningProperties_.delete(subscriberId);
155  }
156
157  protected notifyObjectPropertyHasChanged(propName: string, newValue: any) {
158    stateMgmtConsole.debug(`SubscribableHandler: notifyObjectPropertyHasChanged '${propName}'.`);
159    this.owningProperties_.forEach((subscribedId) => {
160      const owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId);
161      if (!owningProperty) {
162        stateMgmtConsole.warn(`SubscribableHandler: notifyObjectPropertyHasChanged: unknown subscriber.'${subscribedId}' error!.`);
163        return;
164      }
165
166      // PU code path
167      if ('onTrackedObjectPropertyCompatModeHasChangedPU' in owningProperty) {
168        (owningProperty as unknown as ObservedObjectEventsPUReceiver<any>).onTrackedObjectPropertyCompatModeHasChangedPU(this, propName);
169        return;
170      }
171
172      // FU code path
173      if ('hasChanged' in owningProperty) {
174        (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue);
175      }
176      if ('propertyHasChanged' in owningProperty) {
177        (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName);
178      }
179    });
180  }
181
182  protected notifyTrackedObjectPropertyHasChanged(propName: string): void {
183    stateMgmtConsole.debug(`SubscribableHandler: notifyTrackedObjectPropertyHasChanged '@Track ${propName}'.`);
184    this.owningProperties_.forEach((subscribedId) => {
185      const owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId);
186      if (owningProperty && 'onTrackedObjectPropertyHasChangedPU' in owningProperty) {
187        // PU code path with observed object property change tracking optimization
188        (owningProperty as unknown as ObservedObjectEventsPUReceiver<any>).onTrackedObjectPropertyHasChangedPU(this, propName);
189      } else {
190        stateMgmtConsole.warn(`SubscribableHandler: notifyTrackedObjectPropertyHasChanged: subscriber.'${subscribedId}' lacks method 'trackedObjectPropertyHasChangedPU' internal error!.`);
191      }
192    });
193    // no need to support FU code path when app uses @Track
194  }
195
196  public has(target: Object, property: PropertyKey): boolean {
197    stateMgmtConsole.debug(`SubscribableHandler: has '${property.toString()}'.`);
198    return (property === ObservedObject.__IS_OBSERVED_OBJECT) ? true : Reflect.has(target, property);
199  }
200
201  public get(target: Object, property: PropertyKey, receiver?: any): any {
202    // Optimizes get operations by handling symbol properties separately
203    // This allows non-symbol properties to bypass the switch block, improving performance
204      if (typeof property === 'symbol') {
205        switch (property) {
206          case ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT:
207            return target;
208          case SubscribableHandler.COUNT_SUBSCRIBERS:
209            return this.owningProperties_.size;
210          case ObserveV2.SYMBOL_REFS:
211          case ObserveV2.V2_DECO_META:
212          case ObserveV2.SYMBOL_MAKE_OBSERVED:
213            // return result unmonitored
214            return Reflect.get(target, property, receiver);
215          case ObserveV2.SYMBOL_PROXY_GET_TARGET:
216            return undefined;
217          case SubscribableHandler.ENABLE_V2_COMPATIBLE:
218            return this.enableV2Compatible_;
219          default:
220            break;
221        }
222      }
223      const result = Reflect.get(target, property, receiver);
224      let propertyStr: string = String(property);
225      if (this.readCbFunc_ && typeof result !== 'function' && this.obSelf_ !== undefined) {
226        let isTracked = this.isPropertyTracked(target, propertyStr);
227        stateMgmtConsole.debug(`SubscribableHandler: get ObservedObject property '${isTracked ? '@Track ' : ''}${propertyStr}' notifying read.`);
228        this.readCbFunc_.call(this.obSelf_, receiver, propertyStr, isTracked);
229
230        // If the property is tracked and V2 compatibility is enabled,
231        // add dependency view model object for V1V2 compatibility
232        if (isTracked && this.enableV2Compatible_) {
233          ObserveV2.getObserve().addRefV2Compatibility(target, propertyStr);
234
235          // do same as V2 proxy, call to autoProxyObject:
236          // Array, Set, Map length functions fireChange(object, OB_LENGTH)
237          if (typeof result === "object" && (Array.isArray(result) || result instanceof Set || result instanceof Map)) {
238            ObserveV2.getObserve().addRefV2Compatibility(result, ObserveV2.OB_LENGTH);
239          }
240        }
241      } else {
242        // result is function or in compatibility mode (in compat mode cbFunc will never be set)
243        stateMgmtConsole.debug(`SubscribableHandler: get ObservedObject property '${propertyStr}' not notifying read.`);
244
245        // add dependency view model object for V1V2 compatibility
246        if (this.enableV2Compatible_ && typeof result !== 'function') {
247          ObserveV2.getObserve().addRefV2Compatibility(target, propertyStr);
248
249          // do same as V2 proxy, call to autoProxyObject:
250          // Array, Set, Map length functions fireChange(object, OB_LENGTH)
251          if (typeof result === "object" && (Array.isArray(result) || result instanceof Set || result instanceof Map)) {
252            ObserveV2.getObserve().addRefV2Compatibility(result, ObserveV2.OB_LENGTH);
253          }
254        }
255      }
256      return result;
257  }
258
259  public set(target: Object, property: PropertyKey, newValue: any): boolean {
260    // Optimizes set operations by handling symbol properties separately
261    // This allows non-symbol properties to bypass the switch block, improving performance
262    if (typeof property === 'symbol') {
263      switch (property) {
264        case SubscribableHandler.SUBSCRIBE:
265          // assignment obsObj[SubscribableHandler.SUBSCRIBE] = subscriber
266          this.addOwningProperty(newValue as IPropertySubscriber);
267          return true;
268        case SubscribableHandler.UNSUBSCRIBE:
269          // assignment obsObj[SubscribableHandler.UNSUBSCRIBE] = subscriber
270          this.removeOwningProperty(newValue as IPropertySubscriber);
271          return true;
272        case SubscribableHandler.SET_ONREAD_CB:
273          // assignment obsObj[SubscribableHandler.SET_ONREAD_CB] = readCallbackFunc
274          stateMgmtConsole.debug(`SubscribableHandler: setReadingProperty: ${TrackedObject.isCompatibilityMode(target) ? 'not used in compatibility mode' : newValue ? 'set new cb function' : 'unset cb function'}.`);
275          this.readCbFunc_ = TrackedObject.isCompatibilityMode(target) ? undefined : (newValue as (PropertyReadCbFunc | undefined));
276          return true;
277        case SubscribableHandler.RAW_THIS:
278          this.obSelf_ = TrackedObject.isCompatibilityMode(target) ? undefined : newValue;
279          return true;
280        case SubscribableHandler.ENABLE_V2_COMPATIBLE:
281          this.enableV2Compatible_ = true;
282          return true;
283        case ObserveV2.SYMBOL_PROXY_GET_TARGET:
284          // Do nothing, just return
285          return true;
286        default:
287          break;
288      }
289    }
290
291    if (Reflect.get(target, property) === newValue) {
292      return true;
293    }
294
295    Reflect.set(target, property, newValue);
296    const propString = String(property);
297    if (TrackedObject.isCompatibilityMode(target)) {
298      stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '${propString}' (object property tracking compatibility mode).`);
299      this.notifyObjectPropertyHasChanged(propString, newValue);
300    } else {
301      if (this.isPropertyTracked(target, propString)) {
302        stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '@Track ${propString}'.`);
303        this.notifyTrackedObjectPropertyHasChanged(propString);
304
305      } else {
306        stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '${propString}' (object property tracking mode) is NOT @Tracked!`);
307        return true;
308      }
309    }
310
311    // mark view model object 'target' property 'propString' as changed
312    // Notify affected elements and ensure its nested objects are V2-compatible
313    if (this.enableV2Compatible_) {
314      ObserveV2.getObserve().fireChange(target, propString);
315      ObservedObject.enableV2CompatibleNoWarn(newValue);
316    }
317    return true;
318  }
319}
320
321
322class SubscribableMapSetHandler extends SubscribableHandler {
323  constructor(owningProperty: IPropertySubscriber) {
324    super(owningProperty);
325  }
326
327  // In-place Map/Set modification functions
328  mutatingFunctions = new Set([
329    /*Map functions*/
330    'set', 'clear', 'delete',
331    /*Set functions*/
332    'add', 'clear', 'delete',
333  ]);
334  proxiedFunctions = new Set([
335    /*Map functions*/
336    'set',
337    /*Set functions*/
338    'add'
339  ]);
340
341  /**
342   * Get trap for Map/Set type proxy
343   * Functions that modify Map/Set in-place are intercepted and replaced with a function
344   * that executes the original function and notifies the handler of a change.
345   * @param target Original Map/Set object
346   * @param property
347   * @param receiver Proxied Map/Set object
348   * @returns
349   */
350  get(target, property, receiver) {
351    if (property === ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT) {
352      return target;
353    }
354
355    if (this.enableV2Compatible_) {
356      return this.getV2Compatible(target, property, receiver);
357    }
358
359    //receiver will fail for internal slot methods of Set and Map
360    //So assign the target as receiver in this case.
361    if (property === Symbol.iterator || property === 'size') {
362      receiver = target;
363    }
364
365    let ret = super.get(target, property, receiver);
366    if (ret && typeof ret === 'function') {
367      const self = this;
368      return function () {
369        // execute original function with given arguments
370        const result = ret.apply(target, arguments);
371        if (self.mutatingFunctions.has(property)) {
372          self.notifyObjectPropertyHasChanged(property, target);
373        }
374        // Only calls to inserting items can be chained, so returning the 'proxiedObject'
375        // ensures that when chain calls also 2nd function call operates on the proxied object.
376        // Otherwise return the original result of the function.
377        return self.proxiedFunctions.has(property) ? receiver : result;
378      }.bind(receiver);
379    }
380
381    return ret;
382  }
383
384  // Note: The code of this function is duplicated with an adaptation for
385  // enableV2Compatibility from SetMapProxyHandler.get function
386  private getV2Compatible(target: any, key: string | symbol, receiver: any): any {
387    if (typeof key === 'symbol') {
388      if (key === Symbol.iterator) {
389        // this.getTarget not needed in V2 compat, always is target
390        const conditionalTarget = target;
391        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
392        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
393        return (...args): any => target[key](...args);
394      }
395      if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
396        return undefined;
397      }
398      if (key === SubscribableHandler.ENABLE_V2_COMPATIBLE) {
399        return this.enableV2Compatible_;
400      }
401      return target[key];
402    }
403
404    stateMgmtConsole.debug(`SetMapProxyHandler get key '${key}'`);
405    // this.getTarget not needed in V2 compat, always is target
406    const conditionalTarget = target;
407
408    if (key === 'size') {
409      ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
410      return target[key];
411    }
412
413    // same as in V1, do not like in V2, no V1 autoProxy with V2Compatibility
414    let ret = super.get(target, key, receiver);
415
416    if (typeof (ret) !== 'function') {
417      ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, key);
418      // change from V2 proxy: condition is never true in V2Compat:
419      return ret;
420    }
421
422    if (key === 'has') {
423      return (prop): boolean => {
424        const ret = target.has(prop);
425        if (ret) {
426          ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, prop);
427        } else {
428          ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
429        }
430        return ret;
431      };
432    }
433    if (key === 'delete') {
434      const self = this;
435      return (prop): boolean => {
436        if (target.has(prop)) {
437          const res: boolean = target.delete(prop);
438          ObserveV2.getObserve().fireChange(conditionalTarget, prop);
439          ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
440
441          // mutatingFunctions has 'delete'
442          // added for V1 notification
443          self.notifyObjectPropertyHasChanged(key, target);
444          return res;
445        } else {
446          return false;
447        }
448      };
449    }
450    if (key === 'clear') {
451      const self = this;
452      return (): void => {
453        if (target.size > 0) {
454          target.forEach((_, prop) => {
455             ObserveV2.getObserve().fireChange(conditionalTarget, prop.toString(), true);
456          });
457          target.clear();
458          ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
459          ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
460          // mutatingFunctions has 'clear'
461          // added for V1 notification
462          self.notifyObjectPropertyHasChanged(key, target);
463        }
464      };
465    }
466    if (key === 'keys' || key === 'values' || key === 'entries') {
467      return (): any => {
468        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
469        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
470        return target[key]();
471      };
472    }
473
474    // change from V2 proxy: Sendable types unsupported in V1
475    if (target instanceof Set) {
476      if (key === 'add') {
477        const self = this;
478        return (val): any => {
479          if (target.has(val)) {
480            return receiver;
481          }
482          target.add(val);
483          ObserveV2.getObserve().fireChange(conditionalTarget, val);
484          ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
485          ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
486
487          // mutatingFunctions has 'add'
488          // yes V1 notifies the function name!
489          self.notifyObjectPropertyHasChanged(key, target);
490          ObservedObject.enableV2CompatibleNoWarn(val);
491          return receiver;
492        };
493      }
494
495      if (key === 'forEach') {
496        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
497        return function (callbackFn: (value: any, value2: any, set: Set<any>) => void): any {
498          // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
499          // if necessary, addref for each item in Set and also wrap proxy for makeObserved if it is Object.
500          // currently, just execute it in target because there is no Component need to iterate Set, only Array
501          const result = ret.call(target, callbackFn);
502          return result;
503        };
504      }
505      // Bind to receiver ==> functions are observed
506      return (typeof ret === 'function') ? ret.bind(receiver) : ret;
507    }
508
509    // change from V2 proxy: Sendable types unsupported in V1
510    if (target instanceof Map) {
511      if (key === 'get') {
512        return (prop): any => {
513          if (target.has(prop)) {
514            ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, prop);
515          } else {
516            ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
517          }
518          let item = target.get(prop);
519          // change from V2 proxy, this condition is never true in V2Compat
520          // (typeof item === 'object' && this.isMakeObserved_) ? RefInfo.get(item)[RefInfo.MAKE_OBSERVED_PROXY] :
521
522
523          // do same as V2 proxy, call to autoProxyObject:
524          // Array, Set, Map length functions fireChange(object, OB_LENGTH)
525          if (typeof item === "object" && (Array.isArray(item) || item instanceof Set || item instanceof Map)) {
526            ObserveV2.getObserve().addRefV2Compatibility(item, ObserveV2.OB_LENGTH);
527          }
528          return item;
529        };
530      }
531      if (key === 'set') {
532        const self = this;
533        return (prop, val): any => {
534          if (!target.has(prop)) {
535            target.set(prop, val);
536            ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
537          } else if (target.get(prop) !== val) {
538            target.set(prop, val);
539            ObserveV2.getObserve().fireChange(conditionalTarget, prop);
540          }
541          ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY);
542
543          // mutatingFunctions has 'set'
544          // added for V1 notification
545          self.notifyObjectPropertyHasChanged(key, target);
546          ObservedObject.enableV2CompatibleNoWarn(val);
547          return receiver;
548        };
549      }
550      if (key === 'forEach') {
551        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
552        return function (callbackFn: (value: any, key: any, map: Map<any, any>) => void): any {
553          // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot.
554          // if necessary, addref for each item in Map and also wrap proxy for makeObserved if it is Object.
555          // currently, just execute it in target because there is no Component need to iterate Map, only Array
556          const result = ret.call(target, callbackFn);
557          return result;
558        };
559      }
560    }
561    // Bind to receiver ==> functions are observed
562    return (typeof ret === 'function') ? ret.bind(receiver) : ret;
563  }
564
565}
566
567class SubscribableDateHandler extends SubscribableHandler {
568
569  constructor(owningProperty: IPropertySubscriber) {
570    super(owningProperty);
571  }
572
573  dateSetFunctions = new Set(['setFullYear', 'setMonth', 'setDate', 'setHours', 'setMinutes', 'setSeconds',
574    'setMilliseconds', 'setTime', 'setUTCFullYear', 'setUTCMonth', 'setUTCDate', 'setUTCHours', 'setUTCMinutes',
575    'setUTCSeconds', 'setUTCMilliseconds']);
576
577  /**
578   * Get trap for Date type proxy
579   * Functions that modify Date in-place are intercepted and replaced with a function
580   * that executes the original function and notifies the handler of a change.
581   * @param target Original Date object
582   * @param property
583   * @returns
584   */
585  public get(target, property): any {
586    let ret = super.get(target, property);
587
588    if (typeof ret === 'function') {
589      const self = this;
590      if (this.dateSetFunctions.has(property)) {
591        return function () {
592          // execute original function with given arguments
593          let result = ret.apply(this, arguments);
594          self.notifyObjectPropertyHasChanged(property.toString(), this);
595          // enableV2Compatibility handling to fire Date change
596          if (self.enableV2Compatible_) {
597            ObserveV2.getObserve().fireChange(target, ObjectProxyHandler.OB_DATE);
598          }
599
600          return result;
601          // bind 'this' to target inside the function
602        }.bind(target)
603      } else if (self.enableV2Compatible_) {
604        ObserveV2.getObserve().addRefV2Compatibility(target, ObjectProxyHandler.OB_DATE);
605      }
606      return ret.bind(target);
607    }
608    return ret;
609  }
610}
611
612class SubscribableArrayHandler extends SubscribableHandler {
613  constructor(owningProperty: IPropertySubscriber) {
614    super(owningProperty);
615  }
616
617  // In-place array modification functions
618  mutatingFunctions = new Set(['splice', 'copyWithin', 'fill', 'reverse', 'sort']);
619  // 'splice' and 'pop' self modifies the array, returns deleted array items
620  // means, alike other self-modifying functions, splice does not return the array itself.
621  specialFunctions = new Set(['splice', 'pop']);
622
623  /**
624   * Get trap for Array type proxy
625   * Functions that modify Array in-place are intercepted and replaced with a function
626   * that executes the original function and notifies the handler of a change.
627   * @param target Original Array object
628   * @param property
629   * @param receiver Proxied Array object
630   * @returns
631   */
632  get(target, property, receiver) {
633    if (property === ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT) {
634      return target;
635    }
636
637    if (this.enableV2Compatible_) {
638      return this.getV2Compatible(target, property, receiver);
639    }
640
641    let ret = super.get(target, property, receiver);
642    if (ret && typeof ret === 'function') {
643      const self = this;
644      const prop = property.toString();
645      if (self.mutatingFunctions.has(prop)) {
646        return function () {
647          const result = ret.apply(target, arguments);
648          // prop is the function name here
649          // and result is the function return value
650          // function modifies none or more properties
651          self.notifyObjectPropertyHasChanged(prop, self.specialFunctions.has(prop) ? target : result);
652          // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call
653          // operates on the proxied object.
654          return self.specialFunctions.has(prop) ? result : receiver;
655        }.bind(receiver);
656      }
657      // binding the proxiedObject ensures that modifying functions like push() operate on the
658      // proxied array and each array change is notified.
659      return ret.bind(receiver);
660    }
661    return ret;
662  }
663
664  // Note: This function's implementation is similar to ArrayProxyHandler.get method
665  // to support the enableV2Compatibility for arrays of observed objects.
666  private getV2Compatible(target: any, key: string | symbol, receiver: any): any {
667    if (typeof key === 'symbol') {
668      if (key === Symbol.iterator) {
669        const conditionalTarget = target;
670        ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
671        return (...args): any => target[key](...args);
672      }
673      if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) {
674        return undefined;
675      }
676      if (key === SubscribableHandler.ENABLE_V2_COMPATIBLE) {
677        return this.enableV2Compatible_;
678      }
679      return target[key];
680    }
681
682    const conditionalTarget = target;
683
684    let ret = super.get(target, key, receiver);
685
686    if (key === 'length') {
687      ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
688      return ret;
689    }
690
691    if (typeof ret !== 'function') {
692      ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, key);
693
694      // do same as V2 proxy, call to autoProxyObject:
695      // Array, Set, Map length functions fireChange(object, OB_LENGTH)
696      if (typeof ret === "object" && (Array.isArray(ret) || ret instanceof Set || ret instanceof Map)) {
697        ObserveV2.getObserve().addRefV2Compatibility(ret, ObserveV2.OB_LENGTH);
698      }
699
700      return ret;
701    }
702
703    if (ArrayProxyHandler.arrayMutatingFunctions.has(key)) {
704      const self = this;
705      return function (...args): any {
706        // potential compat issue with pure V1
707        // get above uses bind(receiver) for specific functions
708        // causes array changes made by the function are noticed by the proxy
709        const result = ret.call(target, ...args);
710        ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
711
712        // addRefV2Compatibility on newly added V1 observed objects
713        args.forEach(arg => {
714          ObservedObject.enableV2CompatibleNoWarn(arg);
715        });
716
717        // v1 handling to notify property change in pure V1 case
718        self.notifyObjectPropertyHasChanged(key, self.specialFunctions.has(key) ? target : result);
719
720        // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call
721        // operates on the proxied object.
722        return receiver;
723      };
724    } else if (ArrayProxyHandler.arrayLengthChangingFunctions.has(key)) {
725      const self = this;
726      return function (...args): any {
727        // get above 'get' uses bind(receiver) which causes array changes made by the
728        // function to be noticed by the proxy. Is this causing compat issue with
729        // pure V1?
730        const result = ret.call(target, ...args);
731        ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH);
732
733        // apply enableV2CompatibleNoWarn on newly added V1 observed objects.
734        args.forEach(arg => {
735          ObservedObject.enableV2CompatibleNoWarn(arg);
736        });
737
738        // v1 handling to notify property change in pure V1 case
739        self.notifyObjectPropertyHasChanged(key, self.specialFunctions.has(key) ? target : result);
740        return result;
741      };
742    } else if (!SendableType.isArray(target)) {
743      return ret.bind(receiver);
744    } else if (key === 'forEach') {
745      // V1 does not support Sendable
746      // the following seems dead code
747      ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, ObserveV2.OB_LENGTH);
748      return function (callbackFn: (value: any, index: number, array: Array<any>) => void): any {
749        const result = ret.call(target, (value: any, index: number, array: Array<any>) => {
750          // Collections.Array will report BusinessError: The foreach cannot be bound if call "receiver".
751          // because the passed parameter is not the instance of the container class.
752          // so we must call "target" here to deal with the collections situations.
753          // But we also need to addref for each index.
754          ObserveV2.getObserve().addRefV2Compatibility(conditionalTarget, index.toString());
755          callbackFn(typeof value === 'object' ? RefInfo.get(value).proxy : value, index, receiver);
756        });
757        return result;
758      };
759    } else {
760      return ret.bind(target);
761    }
762  }
763
764  // Introduced a separate set function in ArrayHandlers to reduce multiple condition checks, specifically
765  // for handling fireChange on array length changes.
766  // If property is a symbol, super.set gets called used to manage all symbol-related cases.
767  public set(target: Array<any>, property: PropertyKey, newValue: any): boolean {
768
769    if (typeof property === 'symbol') {
770      // Handle symbols using the parent class
771      return super.set(target, property, newValue);
772    }
773
774    let oldArrayLength: number | undefined;
775
776    if (Reflect.get(target, property) === newValue) {
777      return true;
778    }
779    if (this.enableV2Compatible_) {
780      oldArrayLength = target.length;
781    }
782
783    Reflect.set(target, property, newValue);
784    const propString = String(property);
785
786    if (TrackedObject.isCompatibilityMode(target)) {
787      stateMgmtConsole.debug(`SubscribableArrayHandler: set ObservedObject property '${propString}' (object property tracking compatibility mode).`);
788      this.notifyObjectPropertyHasChanged(propString, newValue);
789    } else {
790      if (this.isPropertyTracked(target, propString)) {
791        stateMgmtConsole.debug(`SubscribableArrayHandler: set ObservedObject property '@Track ${propString}'.`);
792        this.notifyTrackedObjectPropertyHasChanged(propString);
793      } else {
794        stateMgmtConsole.debug(`SubscribableArrayHandler: set ObservedObject property '${propString}' (object property tracking mode) is NOT @Tracked!`);
795        return true;
796      }
797    }
798
799    if (this.enableV2Compatible_) {
800      const arrayLenChanged = target.length !== oldArrayLength;
801      ObserveV2.getObserve().fireChange(target, arrayLenChanged ? ObserveV2.OB_LENGTH : propString);
802      ObservedObject.enableV2CompatibleNoWarn(newValue);
803    }
804    //
805    return true;
806  }
807}
808
809
810class ExtendableProxy {
811  constructor(obj: Object, handler: SubscribableHandler) {
812    return new Proxy(obj, handler);
813  }
814}
815
816class ObservedObject<T extends Object> extends ExtendableProxy {
817
818  /**
819   * Factory function for ObservedObjects /
820   *  wrapping of objects for proxying
821   *
822   * @param rawObject unproxied Object or ObservedObject
823   * @param objOwner owner of this Object to sign uop for propertyChange
824   *          notifications
825   * @returns the rawObject if object is already an ObservedObject,
826   *          otherwise the newly created ObservedObject
827   */
828  public static createNew<T extends Object>(rawObject: T,
829    owningProperty: IPropertySubscriber): T {
830
831    if (rawObject === null || rawObject === undefined) {
832      stateMgmtConsole.error(`ObservedObject.CreateNew, input object must not be null or undefined.`);
833      return rawObject;
834    }
835
836    if (ObservedObject.IsObservedObject(rawObject)) {
837      ObservedObject.addOwningProperty(rawObject, owningProperty);
838      return rawObject;
839    }
840
841    return ObservedObject.createNewInternal<T>(rawObject, owningProperty);
842  }
843
844  public static createNewInternal<T extends Object>(rawObject: T,
845    owningProperty: IPropertySubscriber): T {
846    let proxiedObject;
847    if (rawObject instanceof Map || rawObject instanceof Set) {
848      proxiedObject = new ObservedObject<T>(rawObject, new SubscribableMapSetHandler(owningProperty), owningProperty);
849    }
850    else if (rawObject instanceof Date) {
851      proxiedObject = new ObservedObject<T>(rawObject, new SubscribableDateHandler(owningProperty), owningProperty);
852    }
853    else if (Array.isArray(rawObject)) {
854      proxiedObject = new ObservedObject<T>(rawObject, new SubscribableArrayHandler(owningProperty), owningProperty);
855    }
856    else {
857      proxiedObject = new ObservedObject(rawObject, new SubscribableHandler(owningProperty), owningProperty);
858    }
859    return proxiedObject as T;
860  }
861
862  /*
863    Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy
864    no set observation, no notification of changes!
865    Use with caution, do not store any references
866  */
867  static GetRawObject<T extends Object>(obj: T): T {
868    return !ObservedObject.IsObservedObject(obj) ? obj : obj[ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT];
869  }
870
871  /**
872   *
873   * @param obj anything
874   * @returns true if the parameter is an Object wrpped with a ObservedObject
875   * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use
876   * this static function instead.
877   */
878  static IsObservedObject(obj: any): boolean {
879    return (obj && (typeof obj === 'object') && Reflect.has(obj, ObservedObject.__IS_OBSERVED_OBJECT));
880  }
881
882  /**
883   * add a subscriber to given ObservedObject
884   * due to the proxy nature this static method approach needs to be used instead of a member
885   * function
886   * @param obj
887   * @param subscriber
888   * @returns false if given object is not an ObservedObject
889   */
890  public static addOwningProperty(obj: Object, subscriber: IPropertySubscriber): boolean {
891    if (!ObservedObject.IsObservedObject(obj) || !subscriber) {
892      return false;
893    }
894
895    obj[SubscribableHandler.SUBSCRIBE] = subscriber;
896    return true;
897  }
898
899  /**
900   * remove a subscriber to given ObservedObject
901   * due to the proxy nature this static method approach needs to be used instead of a member
902   * function
903   * @param obj
904   * @param subscriber
905   * @returns false if given object is not an ObservedObject
906   */
907  public static removeOwningProperty(obj: Object,
908    subscriber: IPropertySubscriber): boolean {
909    if (!ObservedObject.IsObservedObject(obj)) {
910      return false;
911    }
912
913    obj[SubscribableHandler.UNSUBSCRIBE] = subscriber;
914    return true;
915  }
916
917
918  /**
919   * Function called from sdk UIUtilsImpl to enable V2 compatibility with V1 component
920   * Marks an observed object as V2-compatible and recursively processes its nested properties
921   *
922   * @param obj - The observed object to be made V2-compatible.
923  */
924  public static enableV2Compatible(obj: Object) : void {
925    // Return if the object is a simple type
926    if (obj === null || obj === undefined || typeof obj !== 'object') {
927      stateMgmtConsole.warn(`enableV2Compatibility: input object must not be null or undefined.`);
928      return;
929    }
930
931    if (!ObservedObject.IsObservedObject(obj)) {
932      stateMgmtConsole.warn(`enableV2Compatibility cannot be applied for an object without V1 observation.`);
933      return;
934    }
935    if (ObserveV2.IsObservedObjectV2(obj) || ObserveV2.IsMakeObserved(obj) || ObserveV2.IsProxiedObservedV2(obj)) {
936      stateMgmtConsole.warn(`enableV2Compatibility cannot be applied for an object with V2 observation already enabled.`);
937      return;
938    }
939
940    this.enableV2CompatibleInternal(obj);
941  }
942
943  // This function behaves like `enableV2Compatible`, but suppresses warnings for non-observed objects,
944  // allowing nested non-observed objects to be processed without triggering logs.
945  public static enableV2CompatibleNoWarn(obj: Object, visitedObjects: Set<Object> = new Set()): void {
946    if (!ObservedObject.IsObservedObject(obj)) {
947      return;
948    }
949
950    if (ObserveV2.IsObservedObjectV2(obj) || ObserveV2.IsMakeObserved(obj) || ObserveV2.IsProxiedObservedV2(obj)) {
951      return;
952    }
953
954    this.enableV2CompatibleInternal(obj, visitedObjects);
955  }
956
957
958 /**
959  * Recursively enables V2 compatibility on the given object and its nested properties.
960  *  If compatibility mode is enabled, it recursively processes the nested object
961  * else, it checks if the object's properties are tracked and recursively processes only those.
962  *
963  * @param obj - The object to be observed for V1 changes.
964  * optional @param visitedObjects: Set object to record if the object is already processed or not
965  *
966  * @returns void
967  *
968  */
969  public static enableV2CompatibleInternal(obj: Object, visitedObjects: Set<Object> = new Set()): void {
970    // If the object has already been visited, return to avoid circular reference issues
971    if (visitedObjects.has(obj)) {
972      return;
973    }
974
975    // Get the unproxied/raw object
976    const rawObj = ObservedObject.GetRawObject(obj);
977
978    // Early return if rawObj is null or not an object
979    if (!rawObj || typeof rawObj !== 'object') {
980      return;
981    }
982    stateMgmtConsole.debug(`enableV2CompatibleInternal object of class '${obj?.constructor?.name}'`)
983    // Mark the object as visited to prevent circular references in future calls
984    visitedObjects.add(obj);
985
986    obj[SubscribableHandler.ENABLE_V2_COMPATIBLE] = true;
987
988    // Recursively process Array elements
989    if (Array.isArray(rawObj)) {
990      rawObj.forEach(item => this.enableV2CompatibleNoWarn(item));
991    } else if ((rawObj instanceof Map) || (rawObj instanceof Set)) { // Recursively process nested Map values
992      for (const item of rawObj.values()) {
993        this.enableV2CompatibleNoWarn(item);
994      }
995    } else { // If the object is a plain object, process its values recursively
996      Object.values(rawObj).forEach(value => this.enableV2CompatibleNoWarn(value));
997    }
998  }
999
1000  // return is given object V1 proxies and V2 compatibility has been enabled on it
1001  public static isEnableV2CompatibleInternal(obj: Object): boolean {
1002    return ObservedObject.IsObservedObject(obj) && (obj[SubscribableHandler.ENABLE_V2_COMPATIBLE] == true);
1003  }
1004
1005
1006  /**
1007   * Enables V1 change observation on the given object, unless it already has V1 or V2 observation enabled.
1008   *
1009   * This function is intended for use inside a @ComponentV2 or plain ArkTS to prepare a viewmodel object
1010   * before passing it to a @Component (V1). If the object is already observed (either via the @Observed decorator,
1011   * V1 observation, or V2 observation), no further observation is applied.
1012   * If the object is an instance of collection set of Array/Map/Set, no further observation is applied.
1013   *
1014   * @param obj - The object to be observed for V1 changes.
1015   * @returns The observed object, or the original object if it is already observed.
1016   *
1017   * report an application warning Throws an error if the object is incompatible with V1 change observation.
1018   */
1019  public static makeV1Observed<T extends Object>(obj: T) : T {
1020    if (obj === null || typeof obj !== 'object') {
1021      stateMgmtConsole.error(`makeV1Observed: input object must not be null or undefined.`);
1022      return obj;
1023    }
1024
1025    if (ObservedObject.IsObservedObject(obj)) {
1026      stateMgmtConsole.warn('makeV1Observed: object is already V1 observed. Nothing to do.');
1027      return obj;
1028    }
1029    if (ObserveV2.IsObservedObjectV2(obj) || ObserveV2.IsMakeObserved(obj) || ObserveV2.IsProxiedObservedV2(obj)) {
1030      stateMgmtConsole.applicationWarn('makeV1Observed: object is V2 observed. makeV1Observed cannot be applied.');
1031      return obj;
1032    }
1033
1034    if (SendableType.isContainer(obj)) {
1035      stateMgmtConsole.applicationWarn('makeV1Observed: Cannot be applied to Map, Set or Array collections.');
1036      return obj;
1037    }
1038
1039    obj[SubscribableHandler.MAKE_V1_OBSERVED] = true;
1040
1041    return ObservedObject.createNew(obj, undefined);
1042  }
1043
1044
1045  // return is given object V1 proxies
1046  public static isMakeV1Observed(obj: Object): boolean {
1047    return (obj[SubscribableHandler.MAKE_V1_OBSERVED] == true);
1048  }
1049
1050  /**
1051   *
1052   * @param obj any Object
1053   * @returns return number of subscribers to the given ObservedObject
1054   * or false if given object is not an ObservedObject
1055   */
1056  public static countSubscribers(obj: Object): number | false {
1057    return ObservedObject.IsObservedObject(obj) ? obj[SubscribableHandler.COUNT_SUBSCRIBERS] : false;
1058  }
1059
1060  /*
1061    set or unset callback function to be called when a property has been called
1062  */
1063  public static registerPropertyReadCb(obj: Object, readPropCb: PropertyReadCbFunc, obSelf: ObservedPropertyAbstractPU<any>): boolean {
1064    if (!ObservedObject.IsObservedObject(obj)) {
1065      return false;
1066    }
1067    obj[SubscribableHandler.SET_ONREAD_CB] = readPropCb;
1068    obj[SubscribableHandler.RAW_THIS] = obSelf;
1069    return true;
1070  }
1071
1072  public static unregisterPropertyReadCb(obj: Object): boolean {
1073    if (!ObservedObject.IsObservedObject(obj)) {
1074      return false;
1075    }
1076    obj[SubscribableHandler.SET_ONREAD_CB] = undefined;
1077    obj[SubscribableHandler.RAW_THIS] = undefined;
1078    return true;
1079  }
1080
1081
1082  /**
1083   * Utility function for debugging the prototype chain of given Object
1084   * The given object can be any Object, it is not required to be an ObservedObject
1085   * @param object
1086   * @returns multi-line string containing info about the prototype chain
1087   * on class in class hiararchy per line
1088   */
1089  public static tracePrototypeChainOfObject(object: Object | undefined): string {
1090    let proto = Object.getPrototypeOf(object);
1091    let result = '';
1092    let sepa = '';
1093    while (proto) {
1094      result += `${sepa}${ObservedObject.tracePrototype(proto)}`;
1095      proto = Object.getPrototypeOf(proto);
1096      sepa = ',\n';
1097    }
1098
1099    return result;
1100  }
1101
1102  /**
1103   * Utility function for debugging all functions of given Prototype.
1104   * @returns string containing containing names of all functions and members of given Prototype
1105   */
1106  public static tracePrototype(proto: any) {
1107    if (!proto) {
1108      return '';
1109    }
1110
1111    let result = `${proto.constructor && proto.constructor.name ? proto.constructor.name : '<no class>'}: `;
1112    let sepa = '';
1113    for (let name of Object.getOwnPropertyNames(proto)) {
1114      result += `${sepa}${name}`;
1115      sepa = ', ';
1116    };
1117    return result;
1118  }
1119
1120
1121  /**
1122   * @Observed  decorator extends the decorated class. This function returns the prototype of the decorated class
1123   * @param proto
1124   * @returns prototype of the @Observed decorated class or 'proto' parameter if not  @Observed decorated
1125   */
1126  public static getPrototypeOfObservedClass(proto: Object): Object {
1127    return (proto.constructor && proto.constructor.name === 'ObservedClass')
1128      ? Object.getPrototypeOf(proto.constructor.prototype)
1129      : proto;
1130  }
1131
1132
1133  /**
1134   * To create a new ObservableObject use CreateNew function
1135   *
1136   * constructor create a new ObservableObject and subscribe its owner to propertyHasChanged
1137   * notifications
1138   * @param obj  raw Object, if obj is a ObservableOject throws an error
1139   * @param objectOwner
1140   */
1141  private constructor(obj: T, handler: SubscribableHandler, objectOwningProperty: IPropertySubscriber) {
1142    super(obj, handler);
1143
1144    if (ObservedObject.IsObservedObject(obj)) {
1145      stateMgmtConsole.error('ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already');
1146    }
1147    if (objectOwningProperty) {
1148      this[SubscribableHandler.SUBSCRIBE] = objectOwningProperty;
1149    }
1150  } // end of constructor
1151
1152  public static readonly __IS_OBSERVED_OBJECT = Symbol('_____is_observed_object__');
1153  public static readonly __OBSERVED_OBJECT_RAW_OBJECT = Symbol('_____raw_object__');
1154}
1155