1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16/** 17 * Common Proxy handler for objects and dates for both decorators and makeObserved 18 */ 19class ObjectProxyHandler { 20 21 public static readonly OB_DATE = '__date__'; 22 23 private isMakeObserved_: boolean; 24 25 constructor(isMakeObserved: boolean = false) { 26 this.isMakeObserved_ = isMakeObserved; 27 } 28 29 // decorators work on object that holds the dependencies directly 30 // makeObserved can't modify the object itself, so it creates a 31 // wrapper object around it and that will hold the references 32 // 33 // this function is used to get the correct object that can be observed 34 private getTarget(obj: any): any { 35 return this.isMakeObserved_ ? RefInfo.get(obj) : obj; 36 } 37 38 private static readonly dateSetFunctions = new Set(['setFullYear', 'setMonth', 'setDate', 'setHours', 'setMinutes', 39 'setSeconds', 'setMilliseconds', 'setTime', 'setUTCFullYear', 'setUTCMonth', 'setUTCDate', 'setUTCHours', 40 'setUTCMinutes', 'setUTCSeconds', 'setUTCMilliseconds']); 41 42 get(target: any, key: string | symbol, receiver: any): any { 43 44 if (typeof key === 'symbol') { 45 if (key === Symbol.iterator) { 46 const conditionalTarget = this.getTarget(target); 47 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 48 return (...args): any => target[key](...args); 49 } 50 if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) { 51 return target; 52 } 53 if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) { 54 return true; 55 } 56 return target[key]; 57 } 58 59 stateMgmtConsole.debug(`ObjectProxyHandler get key '${key}'`); 60 const conditionalTarget = this.getTarget(target); 61 62 // makeObserved logic adds wrapper proxy later 63 let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key); 64 // do not addref for function type, it will make such huge unnecessary dependency collection 65 // for some common function attributes, e.g. toString etc. 66 if (typeof (ret) !== 'function') { 67 ObserveV2.getObserve().addRef(conditionalTarget, key); 68 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret)[RefInfo.MAKE_OBSERVED_PROXY] : ret; 69 } 70 71 if (target instanceof Date) { 72 if (ObjectProxyHandler.dateSetFunctions.has(key)) { 73 return function (...args): any { 74 // execute original function with given arguments 75 let result = ret.call(this, ...args); 76 ObserveV2.getObserve().fireChange(conditionalTarget, ObjectProxyHandler.OB_DATE); 77 return result; 78 // bind 'this' to target inside the function 79 }.bind(target); 80 } else { 81 ObserveV2.getObserve().addRef(conditionalTarget, ObjectProxyHandler.OB_DATE); 82 } 83 return ret.bind(target); 84 } 85 86 // function 87 return ret.bind(receiver); 88 } 89 90 set(target: any, key: string | symbol, value: any): boolean { 91 if (typeof key === 'symbol') { 92 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 93 target[key] = value; 94 } 95 return true; 96 } 97 98 if (target[key] === value) { 99 return true; 100 } 101 target[key] = value; 102 ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString()); 103 return true; 104 } 105}; 106 107/** 108 * Common Proxy handler for Arrays for both decorators and makeObserved 109 */ 110class ArrayProxyHandler { 111 112 private isMakeObserved_: boolean; 113 114 constructor(isMakeObserved: boolean = false) { 115 this.isMakeObserved_ = isMakeObserved; 116 } 117 118 // decorators work on object that holds the dependencies directly 119 // makeObserved can't modify the object itself, so it creates a 120 // wrapper object around it and that will hold the references 121 // 122 // this function is used to get the correct object that can be observed 123 private getTarget(obj: any): any { 124 return this.isMakeObserved_ ? RefInfo.get(obj) : obj; 125 } 126 127 // shrinkTo and extendTo is collection.Array api. 128 public static readonly arrayLengthChangingFunctions = new Set(['push', 'pop', 'shift', 'splice', 'unshift', 'shrinkTo', 'extendTo']); 129 public static readonly arrayMutatingFunctions = new Set(['copyWithin', 'fill', 'reverse', 'sort']); 130 131 // Note: The code of this function is duplicated with adaptation for enableV2Compatibility 132 // when making changes here, review of these changes are also needed in 133 // SubscribableArrayHandler.getV2Compatible function 134 get(target: Array<any>, key: string | symbol, receiver: Array<any>): any { 135 136 if (typeof key === 'symbol') { 137 if (key === Symbol.iterator) { 138 const conditionalTarget = this.getTarget(target); 139 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 140 return (...args): any => target[key](...args); 141 } 142 if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) { 143 return target; 144 } 145 if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) { 146 return true; 147 } 148 return target[key]; 149 } 150 151 stateMgmtConsole.debug(`ArrayProxyHandler get key '${key}'`); 152 const conditionalTarget = this.getTarget(target); 153 154 if (key === 'length') { 155 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 156 return target[key]; 157 } 158 159 // makeObserved logic adds wrapper proxy later 160 let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key); 161 if (typeof (ret) !== 'function') { 162 ObserveV2.getObserve().addRef(conditionalTarget, key); 163 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret)[RefInfo.MAKE_OBSERVED_PROXY] : ret; 164 } 165 166 if (ArrayProxyHandler.arrayMutatingFunctions.has(key)) { 167 return function (...args): any { 168 ret.call(target, ...args); 169 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 170 // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call 171 // operates on the proxied object. 172 return receiver; 173 }; 174 } else if (ArrayProxyHandler.arrayLengthChangingFunctions.has(key)) { 175 return function (...args): any { 176 const result = ret.call(target, ...args); 177 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 178 return result; 179 }; 180 } else if (!SendableType.isArray(target)) { 181 return ret.bind(receiver); 182 } else if (key === 'forEach') { 183 // to make ForEach Component and its Item can addref 184 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 185 return function (callbackFn: (value: any, index: number, array: Array<any>) => void): any { 186 const result = ret.call(target, (value: any, index: number, array: Array<any>) => { 187 // Collections.Array will report BusinessError: The foreach cannot be bound if call "receiver". 188 // because the passed parameter is not the instance of the container class. 189 // so we must call "target" here to deal with the collections situations. 190 // But we also need to addref for each index. 191 ObserveV2.getObserve().addRef(conditionalTarget, index.toString()); 192 callbackFn(typeof value === 'object' ? RefInfo.get(value)[RefInfo.MAKE_OBSERVED_PROXY] : value, index, receiver); 193 }); 194 return result; 195 }; 196 } else { 197 return ret.bind(target); // SendableArray can't be bound -> functions not observed 198 } 199 } 200 201 set(target: Array<any>, key: string | symbol, value: any): boolean { 202 if (typeof key === 'symbol') { 203 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 204 target[key] = value; 205 } 206 return true; 207 } 208 209 if (target[key] === value) { 210 return true; 211 } 212 213 const originalLength = target.length; 214 target[key] = value; 215 const arrayLenChanged = target.length !== originalLength; 216 ObserveV2.getObserve().fireChange(this.getTarget(target), arrayLenChanged ? ObserveV2.OB_LENGTH : key.toString()); 217 return true; 218 } 219}; 220 221/** 222 * Common Proxy handler for Maps and Sets for both decorators and makeObserved 223 */ 224class SetMapProxyHandler { 225 226 public static readonly OB_MAP_SET_ANY_PROPERTY = '___ob_map_set'; 227 228 private isMakeObserved_: boolean; 229 230 constructor(isMakeObserved: boolean = false) { 231 this.isMakeObserved_ = isMakeObserved; 232 } 233 234 // decorators work on object that holds the dependencies directly 235 // makeObserved can't modify the object itself, so it creates a 236 // wrapper object around it and that will hold the references 237 // 238 // this function is used to get the correct object that can be observed 239 private getTarget(obj: any): any { 240 return this.isMakeObserved_ ? RefInfo.get(obj) : obj; 241 } 242 243 // Note: The code of this function is duplicated with adaptation for enableV2Compatibility 244 // when making changes here, review of these changes are also needed in 245 // SubscribableMapSetHandler.getV2Compatible function 246 get(target: any, key: string | symbol, receiver: any): any { 247 if (typeof key === 'symbol') { 248 if (key === Symbol.iterator) { 249 const conditionalTarget = this.getTarget(target); 250 ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 251 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 252 return (...args): any => target[key](...args); 253 } 254 if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) { 255 return target; 256 } 257 if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) { 258 return true; 259 } 260 return target[key]; 261 } 262 263 stateMgmtConsole.debug(`SetMapProxyHandler get key '${key}'`); 264 const conditionalTarget = this.getTarget(target); 265 266 if (key === 'size') { 267 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 268 return target[key]; 269 } 270 271 // makeObserved logic adds wrapper proxy later 272 let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key); 273 if (typeof (ret) !== 'function') { 274 ObserveV2.getObserve().addRef(conditionalTarget, key); 275 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret)[RefInfo.MAKE_OBSERVED_PROXY] : ret; 276 } 277 278 if (key === 'has') { 279 return (prop): boolean => { 280 const ret = target.has(prop); 281 if (ret) { 282 ObserveV2.getObserve().addRef(conditionalTarget, prop); 283 } else { 284 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 285 } 286 return ret; 287 }; 288 } 289 if (key === 'delete') { 290 return (prop): boolean => { 291 if (target.has(prop)) { 292 const res: boolean = target.delete(prop) 293 ObserveV2.getObserve().fireChange(conditionalTarget, prop); 294 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 295 return res; 296 } else { 297 return false; 298 } 299 }; 300 } 301 if (key === 'clear') { 302 return (): void => { 303 if (target.size > 0) { 304 target.forEach((_, prop) => { 305 ObserveV2.getObserve().fireChange(conditionalTarget, prop.toString(), true); 306 }); 307 target.clear(); 308 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 309 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 310 } 311 }; 312 } 313 if (key === 'keys' || key === 'values' || key === 'entries') { 314 return (): any => { 315 ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 316 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 317 return target[key](); 318 }; 319 } 320 321 if (target instanceof Set || (this.isMakeObserved_ && SendableType.isSet(target))) { 322 if (key === 'add') { 323 return (val): any => { 324 if (target.has(val)) { 325 return receiver; 326 } 327 target.add(val); 328 ObserveV2.getObserve().fireChange(conditionalTarget, val); 329 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 330 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 331 return receiver; 332 }; 333 } 334 335 if (key === 'forEach') { 336 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 337 return function (callbackFn: (value: any, value2: any, set: Set<any>) => void): any { 338 // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot. 339 // if necessary, addref for each item in Set and also wrap proxy for makeObserved if it is Object. 340 // currently, just execute it in target because there is no Component need to iterate Set, only Array 341 const result = ret.call(target, callbackFn); 342 return result; 343 }; 344 } 345 // Bind to receiver ==> functions are observed 346 return (typeof ret === 'function') ? ret.bind(receiver) : ret; 347 } 348 349 if (target instanceof Map || (this.isMakeObserved_ && SendableType.isMap(target))) { 350 if (key === 'get') { 351 return (prop): any => { 352 if (target.has(prop)) { 353 ObserveV2.getObserve().addRef(conditionalTarget, prop); 354 } else { 355 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 356 } 357 let item = target.get(prop); 358 return (typeof item === 'object' && this.isMakeObserved_) ? RefInfo.get(item)[RefInfo.MAKE_OBSERVED_PROXY] : item; 359 }; 360 } 361 if (key === 'set') { 362 return (prop, val): any => { 363 if (!target.has(prop)) { 364 target.set(prop, val); 365 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 366 } else if (target.get(prop) !== val) { 367 target.set(prop, val); 368 ObserveV2.getObserve().fireChange(conditionalTarget, prop); 369 } 370 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 371 return receiver; 372 }; 373 } 374 if (key === 'forEach') { 375 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 376 return function (callbackFn: (value: any, key: any, map: Map<any, any>) => void): any { 377 // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot. 378 // if necessary, addref for each item in Map and also wrap proxy for makeObserved if it is Object. 379 // currently, just execute it in target because there is no Component need to iterate Map, only Array 380 const result = ret.call(target, callbackFn); 381 return result; 382 }; 383 } 384 } 385 // Bind to receiver ==> functions are observed 386 return (typeof ret === 'function') ? ret.bind(receiver) : ret; 387 } 388 389 set(target: any, key: string | symbol, value: any): boolean { 390 if (typeof key === 'symbol') { 391 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 392 target[key] = value; 393 } 394 return true; 395 } 396 397 if (target[key] === value) { 398 return true; 399 } 400 target[key] = value; 401 ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString()); 402 return true; 403 } 404};