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};