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