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): Constructor { 62 stateMgmtConsole.debug(`@Observed class decorator: Overwriting constructor for '${BaseClass.name}', gets wrapped inside ObservableObject proxy.`); 63 64 // prevent use of V3 @track inside V2 @Observed class 65 if (BaseClass.prototype && Reflect.has(BaseClass.prototype, ObserveV2.SYMBOL_REFS)) { 66 const error = `'@Observed class ${BaseClass?.name}': invalid use of V3 @track decorator inside V2 @Observed 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 113 private owningProperties_: Set<number>; 114 private readCbFunc_?: PropertyReadCbFunc; 115 private obSelf_?: ObservedPropertyAbstractPU<any>; 116 117 constructor(owningProperty: IPropertySubscriber) { 118 this.owningProperties_ = new Set<number>(); 119 120 if (owningProperty) { 121 this.addOwningProperty(owningProperty); 122 } 123 stateMgmtConsole.debug(`SubscribableHandler: constructor done`); 124 } 125 126 private isPropertyTracked(obj: Object, property: string): boolean { 127 return Reflect.has(obj, `___TRACKED_${property}`) || 128 property === TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_PROP_PROPERTY || 129 property === TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY; 130 } 131 132 addOwningProperty(subscriber: IPropertySubscriber): void { 133 if (subscriber) { 134 stateMgmtConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id__()}'.`); 135 this.owningProperties_.add(subscriber.id__()); 136 } else { 137 stateMgmtConsole.warn(`SubscribableHandler: addOwningProperty: undefined subscriber.`); 138 } 139 } 140 141 /* 142 the inverse function of createOneWaySync or createTwoWaySync 143 */ 144 public removeOwningProperty(property: IPropertySubscriber): void { 145 return this.removeOwningPropertyById(property.id__()); 146 } 147 148 public removeOwningPropertyById(subscriberId: number): void { 149 stateMgmtConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`); 150 this.owningProperties_.delete(subscriberId); 151 } 152 153 protected notifyObjectPropertyHasChanged(propName: string, newValue: any) { 154 stateMgmtConsole.debug(`SubscribableHandler: notifyObjectPropertyHasChanged '${propName}'.`); 155 this.owningProperties_.forEach((subscribedId) => { 156 const owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId); 157 if (!owningProperty) { 158 stateMgmtConsole.warn(`SubscribableHandler: notifyObjectPropertyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 159 return; 160 } 161 162 // PU code path 163 if ('onTrackedObjectPropertyCompatModeHasChangedPU' in owningProperty) { 164 (owningProperty as unknown as ObservedObjectEventsPUReceiver<any>).onTrackedObjectPropertyCompatModeHasChangedPU(this, propName); 165 return; 166 } 167 168 // FU code path 169 if ('hasChanged' in owningProperty) { 170 (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue); 171 } 172 if ('propertyHasChanged' in owningProperty) { 173 (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName); 174 } 175 }); 176 } 177 178 protected notifyTrackedObjectPropertyHasChanged(propName: string): void { 179 stateMgmtConsole.debug(`SubscribableHandler: notifyTrackedObjectPropertyHasChanged '@Track ${propName}'.`); 180 this.owningProperties_.forEach((subscribedId) => { 181 const owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId); 182 if (owningProperty && 'onTrackedObjectPropertyHasChangedPU' in owningProperty) { 183 // PU code path with observed object property change tracking optimization 184 (owningProperty as unknown as ObservedObjectEventsPUReceiver<any>).onTrackedObjectPropertyHasChangedPU(this, propName); 185 } else { 186 stateMgmtConsole.warn(`SubscribableHandler: notifyTrackedObjectPropertyHasChanged: subscriber.'${subscribedId}' lacks method 'trackedObjectPropertyHasChangedPU' internal error!.`); 187 } 188 }); 189 // no need to support FU code path when app uses @Track 190 } 191 192 public has(target: Object, property: PropertyKey): boolean { 193 stateMgmtConsole.debug(`SubscribableHandler: has '${property.toString()}'.`); 194 return (property === ObservedObject.__IS_OBSERVED_OBJECT) ? true : Reflect.has(target, property); 195 } 196 197 public get(target: Object, property: PropertyKey, receiver?: any): any { 198 switch (property) { 199 case ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT: 200 return target; 201 break; 202 case SubscribableHandler.COUNT_SUBSCRIBERS: 203 return this.owningProperties_.size; 204 break; 205 case ObserveV2.SYMBOL_REFS: 206 case ObserveV2.V2_DECO_META: 207 case ObserveV2.SYMBOL_MAKE_OBSERVED: 208 // return result unmonitored 209 return Reflect.get(target, property, receiver); 210 break; 211 default: 212 const result = Reflect.get(target, property, receiver); 213 let propertyStr : string = String(property); 214 if (this.readCbFunc_ && typeof result !== 'function' && this.obSelf_ !== undefined) { 215 let isTracked = this.isPropertyTracked(target, propertyStr); 216 stateMgmtConsole.debug(`SubscribableHandler: get ObservedObject property '${isTracked ? '@Track ' : ''}${propertyStr}' notifying read.`); 217 this.readCbFunc_.call(this.obSelf_, receiver, propertyStr, isTracked); 218 } else { 219 // result is function or in compatibility mode (in compat mode cbFunc will never be set) 220 stateMgmtConsole.debug(`SubscribableHandler: get ObservedObject property '${propertyStr}' not notifying read.`); 221 } 222 return result; 223 break; 224 } 225 } 226 227 public set(target: Object, property: PropertyKey, newValue: any): boolean { 228 switch (property) { 229 case SubscribableHandler.SUBSCRIBE: 230 // assignment obsObj[SubscribableHandler.SUBSCRIBE] = subscriber 231 this.addOwningProperty(newValue as IPropertySubscriber); 232 return true; 233 break; 234 case SubscribableHandler.UNSUBSCRIBE: 235 // assignment obsObj[SubscribableHandler.UNSUBSCRIBE] = subscriber 236 this.removeOwningProperty(newValue as IPropertySubscriber); 237 return true; 238 break; 239 case SubscribableHandler.SET_ONREAD_CB: 240 // assignment obsObj[SubscribableHandler.SET_ONREAD_CB] = readCallbackFunc 241 stateMgmtConsole.debug(`SubscribableHandler: setReadingProperty: ${TrackedObject.isCompatibilityMode(target) ? 'not used in compatibility mode' : newValue ? 'set new cb function' : 'unset cb function'}.`); 242 this.readCbFunc_ = TrackedObject.isCompatibilityMode(target) ? undefined : (newValue as (PropertyReadCbFunc | undefined)); 243 return true; 244 break; 245 case SubscribableHandler.RAW_THIS: 246 this.obSelf_ = TrackedObject.isCompatibilityMode(target) ? undefined : newValue; 247 return true; 248 break; 249 default: 250 // this is added for stability test: Reflect.get target is not object 251 try { 252 if (Reflect.get(target, property) === newValue) { 253 return true; 254 } 255 } catch (error) { 256 ArkTools.print('SubscribableHandler: set', target); 257 stateMgmtConsole.error(`An error occurred in SubscribableHandler set, target type is: ${typeof target}, ${error.message}`); 258 throw error; 259 } 260 Reflect.set(target, property, newValue); 261 const propString = String(property); 262 if (TrackedObject.isCompatibilityMode(target)) { 263 stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '${propString}' (object property tracking compatibility mode).`); 264 this.notifyObjectPropertyHasChanged(propString, newValue); 265 } else { 266 if (this.isPropertyTracked(target, propString)) { 267 stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '@Track ${propString}'.`); 268 this.notifyTrackedObjectPropertyHasChanged(propString); 269 } else { 270 stateMgmtConsole.debug(`SubscribableHandler: set ObservedObject property '${propString}' (object property tracking mode) is NOT @Tracked!`); 271 } 272 } 273 return true; 274 break; 275 } 276 277 // unreachable 278 return false; 279 } 280} 281 282class SubscribableMapSetHandler extends SubscribableHandler { 283 constructor(owningProperty: IPropertySubscriber) { 284 super(owningProperty); 285 } 286 287 // In-place Map/Set modification functions 288 mutatingFunctions = new Set([ 289 /*Map functions*/ 290 'set', 'clear', 'delete', 291 /*Set functions*/ 292 'add', 'clear', 'delete', 293 ]); 294 proxiedFunctions = new Set([ 295 /*Map functions*/ 296 'set', 297 /*Set functions*/ 298 'add' 299 ]); 300 301 /** 302 * Get trap for Map/Set type proxy 303 * Functions that modify Map/Set in-place are intercepted and replaced with a function 304 * that executes the original function and notifies the handler of a change. 305 * @param target Original Map/Set object 306 * @param property 307 * @param receiver Proxied Map/Set object 308 * @returns 309 */ 310 get(target, property, receiver) { 311 if (property === ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT) { 312 return target; 313 } 314 315 //receiver will fail for internal slot methods of Set and Map 316 //So assign the target as receiver in this case. 317 if (property === Symbol.iterator || property === 'size') { 318 receiver = target; 319 } 320 321 let ret = super.get(target, property, receiver); 322 if (ret && typeof ret === 'function') { 323 const self = this; 324 return function () { 325 // execute original function with given arguments 326 const result = ret.apply(target, arguments); 327 if (self.mutatingFunctions.has(property)) { 328 self.notifyObjectPropertyHasChanged(property, target); 329 } 330 // Only calls to inserting items can be chained, so returning the 'proxiedObject' 331 // ensures that when chain calls also 2nd function call operates on the proxied object. 332 // Otherwise return the original result of the function. 333 return self.proxiedFunctions.has(property) ? receiver : result; 334 }.bind(receiver); 335 } 336 337 return ret; 338 } 339} 340 341class SubscribableDateHandler extends SubscribableHandler { 342 343 constructor(owningProperty: IPropertySubscriber) { 344 super(owningProperty); 345 } 346 347 dateSetFunctions = new Set(['setFullYear', 'setMonth', 'setDate', 'setHours', 'setMinutes', 'setSeconds', 348 'setMilliseconds', 'setTime', 'setUTCFullYear', 'setUTCMonth', 'setUTCDate', 'setUTCHours', 'setUTCMinutes', 349 'setUTCSeconds', 'setUTCMilliseconds']); 350 351 /** 352 * Get trap for Date type proxy 353 * Functions that modify Date in-place are intercepted and replaced with a function 354 * that executes the original function and notifies the handler of a change. 355 * @param target Original Date object 356 * @param property 357 * @returns 358 */ 359 public get(target, property): any { 360 let ret = super.get(target, property); 361 362 if (typeof ret === 'function') { 363 if (this.dateSetFunctions.has(property)) { 364 const self = this; 365 return function () { 366 // execute original function with given arguments 367 let result = ret.apply(this, arguments); 368 self.notifyObjectPropertyHasChanged(property.toString(), this); 369 return result; 370 // bind 'this' to target inside the function 371 }.bind(target) 372 } 373 return ret.bind(target); 374 } 375 return ret; 376 } 377} 378 379class SubscribableArrayHandler extends SubscribableHandler { 380 constructor(owningProperty: IPropertySubscriber) { 381 super(owningProperty); 382 } 383 384 // In-place array modification functions 385 mutatingFunctions = new Set(['splice', 'copyWithin', 'fill', 'reverse', 'sort']); 386 // 'splice' and 'pop' self modifies the array, returns deleted array items 387 // means, alike other self-modifying functions, splice does not return the array itself. 388 specialFunctions = new Set(['splice', 'pop']); 389 390 /** 391 * Get trap for Array type proxy 392 * Functions that modify Array in-place are intercepted and replaced with a function 393 * that executes the original function and notifies the handler of a change. 394 * @param target Original Array object 395 * @param property 396 * @param receiver Proxied Array object 397 * @returns 398 */ 399 get(target, property, receiver) { 400 if (property === ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT) { 401 return target; 402 } 403 404 let ret = super.get(target, property, receiver); 405 if (ret && typeof ret === 'function') { 406 const self = this; 407 const prop = property.toString(); 408 if (self.mutatingFunctions.has(prop)) { 409 return function () { 410 const result = ret.apply(target, arguments); 411 // prop is the function name here 412 // and result is the function return value 413 // function modifies none or more properties 414 self.notifyObjectPropertyHasChanged(prop, self.specialFunctions.has(prop) ? target : result); 415 // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call 416 // operates on the proxied object. 417 return self.specialFunctions.has(prop) ? result : receiver; 418 }.bind(receiver); 419 } 420 // binding the proxiedObject ensures that modifying functions like push() operate on the 421 // proxied array and each array change is notified. 422 return ret.bind(receiver); 423 } 424 return ret; 425 } 426} 427 428 429class ExtendableProxy { 430 constructor(obj: Object, handler: SubscribableHandler) { 431 return new Proxy(obj, handler); 432 } 433} 434 435class ObservedObject<T extends Object> extends ExtendableProxy { 436 437 /** 438 * Factory function for ObservedObjects / 439 * wrapping of objects for proxying 440 * 441 * @param rawObject unproxied Object or ObservedObject 442 * @param objOwner owner of this Object to sign uop for propertyChange 443 * notifications 444 * @returns the rawObject if object is already an ObservedObject, 445 * otherwise the newly created ObservedObject 446 */ 447 public static createNew<T extends Object>(rawObject: T, 448 owningProperty: IPropertySubscriber): T { 449 450 if (rawObject === null || rawObject === undefined) { 451 stateMgmtConsole.error(`ObservedObject.CreateNew, input object must not be null or undefined.`); 452 return rawObject; 453 } 454 455 if (ObservedObject.IsObservedObject(rawObject)) { 456 ObservedObject.addOwningProperty(rawObject, owningProperty); 457 return rawObject; 458 } 459 460 return ObservedObject.createNewInternal<T>(rawObject, owningProperty); 461 } 462 463 public static createNewInternal<T extends Object>(rawObject: T, 464 owningProperty: IPropertySubscriber): T { 465 let proxiedObject; 466 if (rawObject instanceof Map || rawObject instanceof Set) { 467 proxiedObject = new ObservedObject<T>(rawObject, new SubscribableMapSetHandler(owningProperty), owningProperty); 468 } 469 else if (rawObject instanceof Date) { 470 proxiedObject = new ObservedObject<T>(rawObject, new SubscribableDateHandler(owningProperty), owningProperty); 471 } 472 else if (Array.isArray(rawObject)) { 473 proxiedObject = new ObservedObject<T>(rawObject, new SubscribableArrayHandler(owningProperty), owningProperty); 474 } 475 else { 476 proxiedObject = new ObservedObject(rawObject, new SubscribableHandler(owningProperty), owningProperty); 477 } 478 return proxiedObject as T; 479 } 480 481 /* 482 Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy 483 no set observation, no notification of changes! 484 Use with caution, do not store any references 485 */ 486 static GetRawObject<T extends Object>(obj: T): T { 487 return !ObservedObject.IsObservedObject(obj) ? obj : obj[ObservedObject.__OBSERVED_OBJECT_RAW_OBJECT]; 488 } 489 490 /** 491 * 492 * @param obj anything 493 * @returns true if the parameter is an Object wrpped with a ObservedObject 494 * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use 495 * this static function instead. 496 */ 497 static IsObservedObject(obj: any): boolean { 498 return (obj && (typeof obj === 'object') && Reflect.has(obj, ObservedObject.__IS_OBSERVED_OBJECT)); 499 } 500 501 /** 502 * add a subscriber to given ObservedObject 503 * due to the proxy nature this static method approach needs to be used instead of a member 504 * function 505 * @param obj 506 * @param subscriber 507 * @returns false if given object is not an ObservedObject 508 */ 509 public static addOwningProperty(obj: Object, subscriber: IPropertySubscriber): boolean { 510 if (!ObservedObject.IsObservedObject(obj) || !subscriber) { 511 return false; 512 } 513 514 obj[SubscribableHandler.SUBSCRIBE] = subscriber; 515 return true; 516 } 517 518 /** 519 * remove a subscriber to given ObservedObject 520 * due to the proxy nature this static method approach needs to be used instead of a member 521 * function 522 * @param obj 523 * @param subscriber 524 * @returns false if given object is not an ObservedObject 525 */ 526 public static removeOwningProperty(obj: Object, 527 subscriber: IPropertySubscriber): boolean { 528 if (!ObservedObject.IsObservedObject(obj)) { 529 return false; 530 } 531 532 obj[SubscribableHandler.UNSUBSCRIBE] = subscriber; 533 return true; 534 } 535 536 /** 537 * 538 * @param obj any Object 539 * @returns return number of subscribers to the given ObservedObject 540 * or false if given object is not an ObservedObject 541 */ 542 public static countSubscribers(obj: Object): number | false { 543 return ObservedObject.IsObservedObject(obj) ? obj[SubscribableHandler.COUNT_SUBSCRIBERS] : false; 544 } 545 546 /* 547 set or unset callback function to be called when a property has been called 548 */ 549 public static registerPropertyReadCb(obj: Object, readPropCb: PropertyReadCbFunc, obSelf: ObservedPropertyAbstractPU<any>): boolean { 550 if (!ObservedObject.IsObservedObject(obj)) { 551 return false; 552 } 553 obj[SubscribableHandler.SET_ONREAD_CB] = readPropCb; 554 obj[SubscribableHandler.RAW_THIS] = obSelf; 555 return true; 556 } 557 558 public static unregisterPropertyReadCb(obj: Object): boolean { 559 if (!ObservedObject.IsObservedObject(obj)) { 560 return false; 561 } 562 obj[SubscribableHandler.SET_ONREAD_CB] = undefined; 563 obj[SubscribableHandler.RAW_THIS] = undefined; 564 return true; 565 } 566 567 568 /** 569 * Utility function for debugging the prototype chain of given Object 570 * The given object can be any Object, it is not required to be an ObservedObject 571 * @param object 572 * @returns multi-line string containing info about the prototype chain 573 * on class in class hiararchy per line 574 */ 575 public static tracePrototypeChainOfObject(object: Object | undefined): string { 576 let proto = Object.getPrototypeOf(object); 577 let result = ''; 578 let sepa = ''; 579 while (proto) { 580 result += `${sepa}${ObservedObject.tracePrototype(proto)}`; 581 proto = Object.getPrototypeOf(proto); 582 sepa = ',\n'; 583 } 584 585 return result; 586 } 587 588 /** 589 * Utility function for debugging all functions of given Prototype. 590 * @returns string containing containing names of all functions and members of given Prototype 591 */ 592 public static tracePrototype(proto: any) { 593 if (!proto) { 594 return ''; 595 } 596 597 let result = `${proto.constructor && proto.constructor.name ? proto.constructor.name : '<no class>'}: `; 598 let sepa = ''; 599 for (let name of Object.getOwnPropertyNames(proto)) { 600 result += `${sepa}${name}`; 601 sepa = ', '; 602 }; 603 return result; 604 } 605 606 607 /** 608 * @Observed decorator extends the decorated class. This function returns the prototype of the decorated class 609 * @param proto 610 * @returns prototype of the @Observed decorated class or 'proto' parameter if not @Observed decorated 611 */ 612 public static getPrototypeOfObservedClass(proto: Object): Object { 613 return (proto.constructor && proto.constructor.name === 'ObservedClass') 614 ? Object.getPrototypeOf(proto.constructor.prototype) 615 : proto; 616 } 617 618 619 /** 620 * To create a new ObservableObject use CreateNew function 621 * 622 * constructor create a new ObservableObject and subscribe its owner to propertyHasChanged 623 * notifications 624 * @param obj raw Object, if obj is a ObservableOject throws an error 625 * @param objectOwner 626 */ 627 private constructor(obj: T, handler: SubscribableHandler, objectOwningProperty: IPropertySubscriber) { 628 super(obj, handler); 629 630 if (ObservedObject.IsObservedObject(obj)) { 631 stateMgmtConsole.error('ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already'); 632 } 633 if (objectOwningProperty) { 634 this[SubscribableHandler.SUBSCRIBE] = objectOwningProperty; 635 } 636 } // end of constructor 637 638 public static readonly __IS_OBSERVED_OBJECT = Symbol('_____is_observed_object__'); 639 public static readonly __OBSERVED_OBJECT_RAW_OBJECT = Symbol('_____raw_object__'); 640} 641