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 private 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 if (typeof (ret) !== 'function') { 65 ObserveV2.getObserve().addRef(conditionalTarget, key); 66 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret; 67 } 68 69 if (target instanceof Date) { 70 if (ObjectProxyHandler.dateSetFunctions.has(key)) { 71 return function (...args): any { 72 // execute original function with given arguments 73 let result = ret.call(this, ...args); 74 ObserveV2.getObserve().fireChange(conditionalTarget, ObjectProxyHandler.OB_DATE); 75 return result; 76 // bind 'this' to target inside the function 77 }.bind(target); 78 } else { 79 ObserveV2.getObserve().addRef(conditionalTarget, ObjectProxyHandler.OB_DATE); 80 } 81 return ret.bind(target); 82 } 83 84 // function 85 return ret.bind(receiver); 86 } 87 88 set(target: any, key: string | symbol, value: any): boolean { 89 if (typeof key === 'symbol') { 90 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 91 target[key] = value; 92 } 93 return true; 94 } 95 96 if (target[key] === value) { 97 return true; 98 } 99 target[key] = value; 100 ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString()); 101 return true; 102 } 103}; 104 105/** 106 * Common Proxy handler for Arrays for both decorators and makeObserved 107 */ 108class ArrayProxyHandler { 109 110 private isMakeObserved_: boolean; 111 112 constructor(isMakeObserved: boolean = false) { 113 this.isMakeObserved_ = isMakeObserved; 114 } 115 116 // decorators work on object that holds the dependencies directly 117 // makeObserved can't modify the object itself, so it creates a 118 // wrapper object around it and that will hold the references 119 // 120 // this function is used to get the correct object that can be observed 121 private getTarget(obj: any): any { 122 return this.isMakeObserved_ ? RefInfo.get(obj) : obj; 123 } 124 125 // shrinkTo and extendTo is collection.Array api. 126 private static readonly arrayLengthChangingFunctions = new Set(['push', 'pop', 'shift', 'splice', 'unshift', 'shrinkTo', 'extendTo']); 127 private static readonly arrayMutatingFunctions = new Set(['copyWithin', 'fill', 'reverse', 'sort']); 128 129 get(target: Array<any>, key: string | symbol, receiver: Array<any>): any { 130 131 if (typeof key === 'symbol') { 132 if (key === Symbol.iterator) { 133 const conditionalTarget = this.getTarget(target); 134 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 135 return (...args): any => target[key](...args); 136 } 137 if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) { 138 return target; 139 } 140 if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) { 141 return true; 142 } 143 return target[key]; 144 } 145 146 stateMgmtConsole.debug(`ArrayProxyHandler get key '${key}'`); 147 const conditionalTarget = this.getTarget(target); 148 149 if (key === 'length') { 150 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 151 return target[key]; 152 } 153 154 // makeObserved logic adds wrapper proxy later 155 let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key); 156 if (typeof (ret) !== 'function') { 157 ObserveV2.getObserve().addRef(conditionalTarget, key); 158 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret; 159 } 160 161 if (ArrayProxyHandler.arrayMutatingFunctions.has(key)) { 162 return function (...args): any { 163 ret.call(target, ...args); 164 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 165 // returning the 'receiver(proxied object)' ensures that when chain calls also 2nd function call 166 // operates on the proxied object. 167 return receiver; 168 }; 169 } else if (ArrayProxyHandler.arrayLengthChangingFunctions.has(key)) { 170 return function (...args): any { 171 const result = ret.call(target, ...args); 172 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 173 return result; 174 }; 175 } else if (!SendableType.isArray(target)) { 176 return ret.bind(receiver); 177 } else if (key === 'forEach') { 178 // to make ForEach Component and its Item can addref 179 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 180 return function (callbackFn: (value: any, index: number, array: Array<any>) => void): any { 181 const result = ret.call(target, (value: any, index: number, array: Array<any>) => { 182 // Collections.Array will report BusinessError: The foreach cannot be bound if call "receiver". 183 // because the passed parameter is not the instance of the container class. 184 // so we must call "target" here to deal with the collections situations. 185 // But we also need to addref for each index. 186 ObserveV2.getObserve().addRef(conditionalTarget, index.toString()); 187 callbackFn(typeof value == 'object' ? RefInfo.get(value).proxy : value, index, receiver); 188 }); 189 return result; 190 } 191 } else { 192 return ret.bind(target); // SendableArray can't be bound -> functions not observed 193 } 194 } 195 196 set(target: Array<any>, key: string | symbol, value: any): boolean { 197 if (typeof key === 'symbol') { 198 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 199 target[key] = value; 200 } 201 return true; 202 } 203 204 if (target[key] === value) { 205 return true; 206 } 207 208 const originalLength = target.length; 209 target[key] = value; 210 const arrayLenChanged = target.length !== originalLength; 211 ObserveV2.getObserve().fireChange(this.getTarget(target), arrayLenChanged ? ObserveV2.OB_LENGTH : key.toString()); 212 return true; 213 } 214}; 215 216/** 217 * Common Proxy handler for Maps and Sets for both decorators and makeObserved 218 */ 219class SetMapProxyHandler { 220 221 private static readonly OB_MAP_SET_ANY_PROPERTY = '___ob_map_set'; 222 223 private isMakeObserved_: boolean; 224 225 constructor(isMakeObserved: boolean = false) { 226 this.isMakeObserved_ = isMakeObserved; 227 } 228 229 // decorators work on object that holds the dependencies directly 230 // makeObserved can't modify the object itself, so it creates a 231 // wrapper object around it and that will hold the references 232 // 233 // this function is used to get the correct object that can be observed 234 private getTarget(obj: any): any { 235 return this.isMakeObserved_ ? RefInfo.get(obj) : obj; 236 } 237 238 get(target: any, key: string | symbol, receiver: any): any { 239 if (typeof key === 'symbol') { 240 if (key === Symbol.iterator) { 241 const conditionalTarget = this.getTarget(target); 242 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 243 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 244 return (...args): any => target[key](...args); 245 } 246 if (key === ObserveV2.SYMBOL_PROXY_GET_TARGET) { 247 return target; 248 } 249 if (this.isMakeObserved_ && key === ObserveV2.SYMBOL_MAKE_OBSERVED) { 250 return true; 251 } 252 return target[key]; 253 } 254 255 stateMgmtConsole.debug(`SetMapProxyHandler get key '${key}'`); 256 const conditionalTarget = this.getTarget(target); 257 258 if (key === 'size') { 259 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 260 return target[key]; 261 } 262 263 // makeObserved logic adds wrapper proxy later 264 let ret = this.isMakeObserved_ ? target[key] : ObserveV2.autoProxyObject(target, key); 265 if (typeof (ret) !== 'function') { 266 ObserveV2.getObserve().addRef(conditionalTarget, key); 267 return (typeof (ret) === 'object' && this.isMakeObserved_) ? RefInfo.get(ret).proxy : ret; 268 } 269 270 if (key === 'has') { 271 return (prop): boolean => { 272 const ret = target.has(prop); 273 if (ret) { 274 ObserveV2.getObserve().addRef(conditionalTarget, prop); 275 } else { 276 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 277 } 278 return ret; 279 }; 280 } 281 if (key === 'delete') { 282 return (prop): boolean => { 283 if (target.has(prop)) { 284 ObserveV2.getObserve().fireChange(conditionalTarget, prop); 285 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 286 return target.delete(prop); 287 } else { 288 return false; 289 } 290 }; 291 } 292 if (key === 'clear') { 293 return (): void => { 294 if (target.size > 0) { 295 target.forEach((_, prop) => { 296 ObserveV2.getObserve().fireChange(conditionalTarget, prop.toString()); 297 }); 298 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 299 ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 300 target.clear(); 301 } 302 }; 303 } 304 if (key === 'keys' || key === 'values' || key === 'entries') { 305 return (): any => { 306 ObserveV2.getObserve().addRef(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 307 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 308 return target[key](); 309 }; 310 } 311 312 if (target instanceof Set || (this.isMakeObserved_ && SendableType.isSet(target))) { 313 if (key === 'add') { 314 return (val): any => { 315 ObserveV2.getObserve().fireChange(conditionalTarget, val.toString()); 316 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 317 if (!target.has(val)) { 318 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 319 target.add(val); 320 } 321 return receiver; 322 } 323 } 324 325 if (key === 'forEach') { 326 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 327 return function (callbackFn: (value: any, value2: any, set: Set<any>) => void): any { 328 // need to execute it target because it is the internal function for build-in type, and proxy does not have such slot. 329 // if necessary, addref for each item in Set and also wrap proxy for makeObserved if it is Object. 330 // currently, just execute it in target because there is no Component need to iterate Set, only Array 331 const result = ret.call(target, callbackFn); 332 return result; 333 } 334 } 335 // Bind to receiver ==> functions are observed 336 return (typeof ret === 'function') ? ret.bind(receiver) : ret; 337 } 338 339 if (target instanceof Map || (this.isMakeObserved_ && SendableType.isMap(target))) { 340 if (key === 'get') { 341 return (prop): any => { 342 if (target.has(prop)) { 343 ObserveV2.getObserve().addRef(conditionalTarget, prop); 344 } else { 345 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 346 } 347 let item = target.get(prop); 348 return (typeof item === 'object' && this.isMakeObserved_) ? RefInfo.get(item).proxy : item; 349 }; 350 } 351 if (key === 'set') { 352 return (prop, val): any => { 353 if (!target.has(prop)) { 354 ObserveV2.getObserve().fireChange(conditionalTarget, ObserveV2.OB_LENGTH); 355 } else if (target.get(prop) !== val) { 356 ObserveV2.getObserve().fireChange(conditionalTarget, prop); 357 } 358 ObserveV2.getObserve().fireChange(conditionalTarget, SetMapProxyHandler.OB_MAP_SET_ANY_PROPERTY); 359 target.set(prop, val); 360 return receiver; 361 }; 362 } 363 if (key === 'forEach') { 364 ObserveV2.getObserve().addRef(conditionalTarget, ObserveV2.OB_LENGTH); 365 return function (callbackFn: (value: any, key: any, map: Map<any, 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 Map 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 Map, only Array 369 const result = ret.call(target, callbackFn); 370 return result; 371 } 372 } 373 } 374 // Bind to receiver ==> functions are observed 375 return (typeof ret === 'function') ? ret.bind(receiver) : ret; 376 } 377 378 set(target: any, key: string | symbol, value: any): boolean { 379 if (typeof key === 'symbol') { 380 if (!this.isMakeObserved_ && key !== ObserveV2.SYMBOL_PROXY_GET_TARGET) { 381 target[key] = value; 382 } 383 return true; 384 } 385 386 if (target[key] === value) { 387 return true; 388 } 389 target[key] = value; 390 ObserveV2.getObserve().fireChange(this.getTarget(target), key.toString()); 391 return true; 392 } 393};