1/* 2 * Copyright (c) 2022-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/** 18 * 19 * This file includes only framework internal classes and functions 20 * non are part of SDK. Do not access from app. 21 * 22 * PUV2ViewBase is the common base class of ViewPU and ViewV2 23 * 24 */ 25 26/// <reference path="../../../../ark_theme/export/ark_theme_scope_manager.d.ts" /> 27/// <reference path="./puv2_view_native_base.d.ts" /> 28 29type ExtraInfo = { page: string, line: number, col: number }; 30type ProfileRecursionCounter = { total: number }; 31enum PrebuildPhase { 32 None = 0, 33 BuildPrebuildCmd = 1, 34 ExecutePrebuildCmd = 2, 35 PrebuildDone = 3, 36} 37 38//API Version 18 39const API_VERSION_ISOLATION_FOR_5_1: number = 18; 40 41// NativeView 42// implemented in C++ for release 43abstract class PUV2ViewBase extends ViewBuildNodeBase { 44 45 // List of inactive components used for Dfx 46 protected static readonly inactiveComponents_: Set<string> = new Set<string>(); 47 protected get isReusable_(): boolean { 48 // property getter function are in the prototype 49 // @Reusable and @ReusableV2 decorators modify the function 50 // in decorated class' prototype to return true 51 return false; 52 } 53 54 // Array.sort() converts array items to string to compare them! 55 static readonly compareNumber = (a: number, b: number): number => { 56 return (a < b) ? -1 : (a > b) ? 1 : 0; 57 }; 58 59 // indicates the currently rendered or rendered UINode's elmtIds 60 // or UINodeRegisterProxy.notRecordingDependencies if none is currently rendering 61 // isRenderInProgress == true always when currentlyRenderedElmtIdStack_ length >= 0 62 protected currentlyRenderedElmtIdStack_: Array<number> = new Array<number>(); 63 64 // Set of elmtIds that need re-render 65 protected dirtDescendantElementIds_: Set<number> = new Set<number>(); 66 67 // Set of elmtIds retaken by IF that need re-render 68 protected dirtRetakenElementIds_: Set<number> = new Set<number>(); 69 70 protected parent_: IView | undefined = undefined; 71 72 // static flag for paused rendering 73 // when paused, getCurrentlyRenderedElmtId() will return UINodeRegisterProxy.notRecordingDependencies 74 public static renderingPaused: boolean = false; 75 76 // greater than 0 means the node is active, otherwise node is inactive. 77 // inActive means updates are delayed 78 protected activeCount_: number = 1; 79 80 // flag if {aboutToBeDeletedInternal} is called and the instance of ViewPU/V2 has not been GC. 81 protected isDeleting_: boolean = false; 82 83 protected isCompFreezeAllowed_: boolean = false; 84 85 protected static prebuildFuncQueues: Map<number, Array<PrebuildFunc>> = new Map(); 86 87 protected static propertyChangedFuncQueues: Map<number, Array<PrebuildFunc>> = new Map(); 88 89 protected extraInfo_: ExtraInfo = undefined; 90 91 // used by view createdBy BuilderNode. Indicated weather need to block the recylce or reuse events called by parentView; 92 public __isBlockRecycleOrReuse__: boolean = false; 93 94 // Set of elements for delayed update 95 private elmtIdsDelayedUpdate_: Set<number> = new Set(); 96 97 protected static prebuildPhase_: PrebuildPhase = PrebuildPhase.None; 98 protected isPrebuilding_: boolean = false; 99 protected static prebuildingElmtId_: number = -1; 100 101 static readonly doRecycle: boolean = true; 102 static readonly doReuse: boolean = false; 103 104 private nativeViewPartialUpdate: NativeViewPartialUpdate; 105 106 constructor(parent: IView, elmtId: number = UINodeRegisterProxy.notRecordingDependencies, extraInfo: ExtraInfo = undefined) { 107 super(true); 108 this.nativeViewPartialUpdate = new NativeViewPartialUpdate(this); 109 // if set use the elmtId also as the ViewPU/V2 object's subscribable id. 110 // these matching is requirement for updateChildViewById(elmtId) being able to 111 // find the child ViewPU/V2 object by given elmtId 112 this.id_ = elmtId === UINodeRegisterProxy.notRecordingDependencies ? SubscriberManager.MakeId() : elmtId; 113 114 stateMgmtConsole.debug(`PUV2ViewBase constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}'`); 115 116 if (extraInfo) { 117 this.extraInfo_ = extraInfo; 118 } 119 120 if (parent) { 121 // this View is not a top-level View 122 this.setCardId(parent.getCardId()); 123 // Call below will set this parent_ to parent as well 124 parent.addChild(this as unknown as IView); // FIXME 125 } 126 127 this.isCompFreezeAllowed_ = this.isCompFreezeAllowed_ || (this.parent_ && this.parent_.isCompFreezeAllowed()); 128 this.__isBlockRecycleOrReuse__ = typeof globalThis.__CheckIsInBuilderNode__ === 'function' ? globalThis.__CheckIsInBuilderNode__(parent) : false; 129 stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: done`); 130 } 131 132 public static create(view: PUV2ViewBase): void { 133 return NativeViewPartialUpdate.create(view.nativeViewPartialUpdate); 134 } 135 136 static createRecycle(componentCall: object, isRecycling: boolean, reuseId: string, callback: () => void): void { 137 return NativeViewPartialUpdate.createRecycle(componentCall, isRecycling, reuseId, callback); 138 } 139 140 public markNeedUpdate(): void { 141 return this.nativeViewPartialUpdate.markNeedUpdate(); 142 } 143 144 public syncInstanceId(): void { 145 return this.nativeViewPartialUpdate.syncInstanceId(); 146 } 147 148 public restoreInstanceId(): void { 149 return this.nativeViewPartialUpdate.restoreInstanceId(); 150 } 151 152 public getInstanceId(): number { 153 return this.nativeViewPartialUpdate.getInstanceId(); 154 } 155 156 public markStatic(): void { 157 return this.nativeViewPartialUpdate.markStatic(); 158 } 159 160 public finishUpdateFunc(elmtId: number): void { 161 return this.nativeViewPartialUpdate.finishUpdateFunc(elmtId); 162 } 163 164 public setCardId(cardId: number): void { 165 return this.nativeViewPartialUpdate.setCardId(cardId); 166 } 167 168 public getCardId(): number { 169 return this.nativeViewPartialUpdate.getCardId(); 170 } 171 172 public elmtIdExists(elmtId: number): boolean { 173 return this.nativeViewPartialUpdate.elmtIdExists(elmtId); 174 } 175 176 public isLazyItemRender(elmtId: number): boolean { 177 return this.nativeViewPartialUpdate.isLazyItemRender(elmtId); 178 } 179 180 public isFirstRender(): boolean { 181 return this.nativeViewPartialUpdate.isFirstRender(); 182 } 183 184 public findChildByIdForPreview(viewId: number): object { 185 return this.nativeViewPartialUpdate.findChildByIdForPreview(viewId); 186 } 187 188 public resetRecycleCustomNode(): void { 189 return this.nativeViewPartialUpdate.resetRecycleCustomNode(); 190 } 191 192 public queryNavDestinationInfo(isInner: boolean | undefined): object { 193 return this.nativeViewPartialUpdate.queryNavDestinationInfo(isInner); 194 } 195 196 public queryNavigationInfo(): object { 197 return this.nativeViewPartialUpdate.queryNavigationInfo(); 198 } 199 200 public queryRouterPageInfo(): object { 201 return this.nativeViewPartialUpdate.queryRouterPageInfo(); 202 } 203 204 public getUIContext(): object { 205 if (typeof globalThis.__getUIContext__ === 'function') { 206 return globalThis.__getUIContext__(this.nativeViewPartialUpdate.getMainInstanceId()); 207 } 208 return this.nativeViewPartialUpdate.getUIContext(); 209 } 210 211 public sendStateInfo(stateInfo: string): void { 212 return this.nativeViewPartialUpdate.sendStateInfo(stateInfo); 213 } 214 215 public getUniqueId(): number { 216 return this.nativeViewPartialUpdate.getUniqueId(); 217 } 218 219 public setIsV2(isV2: boolean): void { 220 return this.nativeViewPartialUpdate.setIsV2(isV2); 221 } 222 223 public getDialogController(): object { 224 return this.nativeViewPartialUpdate.getDialogController(); 225 } 226 227 public allowReusableV2Descendant(): boolean { 228 return this.nativeViewPartialUpdate.allowReusableV2Descendant(); 229 } 230 231 // globally unique id, this is different from compilerAssignedUniqueChildId! 232 id__(): number { 233 return this.id_; 234 } 235 236 updateId(elmtId: number): void { 237 this.id_ = elmtId; 238 } 239 240 /* Adds the elmtId to elmtIdsDelayedUpdate for delayed update 241 once the view gets active 242 */ 243 public scheduleDelayedUpdate(elmtId: number) : void { 244 this.elmtIdsDelayedUpdate.add(elmtId); 245 } 246 247 public get elmtIdsDelayedUpdate(): Set<number> { 248 return this.elmtIdsDelayedUpdate_; 249 } 250 251 public setParent(parent: IView): void { 252 if (this.parent_ && parent) { 253 stateMgmtConsole.warn(`${this.debugInfo__()}: setChild: changing parent to '${parent?.debugInfo__()} (unsafe operation)`); 254 } 255 this.parent_ = parent; 256 } 257 258 public getParent(): IView | undefined { 259 return this.parent_; 260 } 261 262 /** 263 * remove given child and remove 'this' as its parent 264 * @param child child to add 265 * @returns returns false if child with given child's id does not exist 266 */ 267 public removeChild(child: IView): boolean { 268 const hasBeenDeleted = this.childrenWeakrefMap_.delete(child.id__()); 269 if (!hasBeenDeleted) { 270 stateMgmtConsole.warn(`${this.debugInfo__()}: removeChild '${child?.debugInfo__()}', child id ${child.id__()} not known. Internal error!`); 271 } else { 272 child.setParent(undefined); 273 } 274 return hasBeenDeleted; 275 } 276 277 // inform the subscribed property 278 // that the View and thereby all properties 279 // are about to be deleted 280 abstract aboutToBeDeleted(): void; 281 282 aboutToReuse(_: Object): void { } 283 aboutToRecycle(): void { } 284 285 public isDeleting(): boolean { 286 return this.isDeleting_; 287 } 288 289 public setDeleting(): void { 290 stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (self)`); 291 this.isDeleting_ = true; 292 } 293 294 public setDeleteStatusRecursively(): void { 295 if (!this.childrenWeakrefMap_.size) { 296 return; 297 } 298 stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (${this.childrenWeakrefMap_.size} children)`); 299 this.childrenWeakrefMap_.forEach((value: WeakRef<IView>) => { 300 let child: IView = value.deref(); 301 if (child) { 302 child.setDeleting(); 303 child.setDeleteStatusRecursively(); 304 } 305 }); 306 } 307 308 public isCompFreezeAllowed(): boolean { 309 return this.isCompFreezeAllowed_; 310 } 311 312 public setActiveCount(active: boolean): void { 313 // When the child node supports the Component freezing, the root node will definitely recurse to the child node. 314 // From API16, to prevent child node mistakenly activated by the parent node, reference counting is used to control node status. 315 // active + 1 means count +1, inactive -1 means count -1, Expect no more than 1 316 if (Utils.isApiVersionEQAbove(API_VERSION_ISOLATION_FOR_5_1)) { 317 this.activeCount_ += active ? 1 : -1; 318 } 319 else { 320 this.activeCount_ = active ? 1 : 0; 321 } 322 if (this.activeCount_ > 1) { 323 stateMgmtConsole.warn(`${this.debugInfo__()} activeCount_ error:${this.activeCount_}`); 324 } 325 } 326 327 public getChildViewV2ForElmtId(elmtId: number): ViewV2 | undefined { 328 const optComp = this.childrenWeakrefMap_.get(elmtId); 329 return optComp?.deref() && (optComp.deref() instanceof ViewV2) ? 330 optComp?.deref() as ViewV2 : undefined; 331 } 332 333 protected purgeVariableDependenciesOnElmtIdOwnFunc(elmtId: number): void { 334 // ViewPU overrides to unregister ViewPU from variables, 335 // not in use in ViewV2 336 } 337 338 // overwritten by sub classes 339 public debugInfo__(): string { 340 return `@Component '${this.constructor.name}'[${this.id__()}]`; 341 } 342 343 public debugInfoRegisteredElmtIds(): string { 344 return this.updateFuncByElmtId.debugInfoRegisteredElmtIds(); 345 } 346 347 // for given elmtIds look up their component name/type and format a string out of this info 348 // use function only for debug output and DFX. 349 public debugInfoElmtIds(elmtIds: Array<number>): string { 350 let result: string = ''; 351 let sepa: string = ''; 352 elmtIds.forEach((elmtId: number) => { 353 result += `${sepa}${this.debugInfoElmtId(elmtId)}`; 354 sepa = ', '; 355 }); 356 return result; 357 } 358 359 public dumpStateVars(): void { 360 stateMgmtConsole.debug(`${this.debugInfo__()}: State variables:\n ${this.debugInfoStateVars()}`); 361 } 362 363 protected abstract debugInfoStateVars(): string; 364 365 public isViewActive(): boolean { 366 return this.activeCount_ > 0; 367 } 368 369 // abstract functions to be implemented by application defined class / transpiled code 370 protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number); 371 protected abstract initialRender(): void; 372 protected abstract rerender(): void; 373 protected abstract get isViewV2(): boolean; 374 375 public abstract updateRecycleElmtId(oldElmtId: number, newElmtId: number): void; 376 public abstract updateStateVars(params: Object); 377 public abstract UpdateElement(elmtId: number): void; 378 379 public dumpReport(): void { 380 stateMgmtConsole.warn(`Printing profiler information`); 381 stateMgmtProfiler.report(); 382 } 383 384 /** 385 * force a complete rerender / update by executing all update functions 386 * exec a regular rerender first 387 * 388 * @param deep recurse all children as well 389 * 390 * framework internal functions, apps must not call 391 */ 392 public forceCompleteRerender(deep: boolean = false): void { 393 stateMgmtProfiler.begin('ViewPU/V2.forceCompleteRerender'); 394 stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - start.`); 395 396 // see which elmtIds are managed by this View 397 // and clean up all book keeping for them 398 this.purgeDeletedElmtIds(); 399 400 // forceCompleteRerender() might have been called externally, 401 // ensure all pending book keeping is finished to prevent unwanted element updates 402 ObserveV2.getObserve()?.runIdleTasks(); 403 404 Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId)); 405 406 if (!deep) { 407 stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`); 408 stateMgmtProfiler.end(); 409 return; 410 } 411 for (const child of this.childrenWeakrefMap_.values()) { 412 const childView: IView | undefined = child.deref(); 413 414 if (!childView) { 415 continue; 416 } 417 418 if (child instanceof ViewPU) { 419 if (!child.isRecycled()) { 420 child.forceCompleteRerender(true); 421 } else { 422 child.delayCompleteRerender(deep); 423 } 424 } else { 425 childView.forceCompleteRerender(true); 426 } 427 } 428 stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`); 429 stateMgmtProfiler.end(); 430 } 431 432 // clear all cached node 433 public __ClearAllRecyle__PUV2ViewBase__Internal(): void { 434 if (this instanceof ViewPU) { 435 this.hasRecycleManager() && this.getRecycleManager().purgeAllCachedRecycleNode(); 436 } else if (this instanceof ViewV2) { 437 this.hasRecyclePool() && this.getRecyclePool().purgeAllCachedRecycleElmtIds(); 438 } else { 439 stateMgmtConsole.error(`clearAllRecycle: this no instanceof ViewPU or ViewV2, error!`); 440 return; 441 } 442 for (const child of (this as PUV2ViewBase).childrenWeakrefMap_.values()) { 443 const childView: IView | undefined = child.deref(); 444 if (!childView) { 445 continue; 446 } else { 447 childView.__ClearAllRecyle__PUV2ViewBase__Internal(); 448 } 449 } 450 } 451 452 /** 453 * force a complete rerender / update on specific node by executing update function. 454 * 455 * @param elmtId which node needs to update. 456 * 457 * framework internal functions, apps must not call 458 */ 459 public forceRerenderNode(elmtId: number): void { 460 stateMgmtProfiler.begin('ViewPU/V2.forceRerenderNode'); 461 // see which elmtIds are managed by this View 462 // and clean up all book keeping for them 463 this.purgeDeletedElmtIds(); 464 this.UpdateElement(elmtId); 465 466 // remove elemtId from dirtDescendantElementIds. 467 this.dirtDescendantElementIds_.delete(elmtId); 468 stateMgmtProfiler.end(); 469 } 470 471 /** 472 * for C++ to judge whether a CustomNode has updateFunc with specified nodeId. 473 * use same judgement with UpdateElement, to make sure it can rerender if return true. 474 * 475 * @param elmtId query ID 476 * 477 * framework internal function 478 */ 479 public hasNodeUpdateFunc(elmtId: number): boolean { 480 const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId); 481 const updateFunc = entry ? entry.getUpdateFunc() : undefined; 482 // if this component does not have updateFunc for elmtId, return false. 483 return typeof updateFunc === 'function'; 484 } 485 486 public static pauseRendering(): void { 487 PUV2ViewBase.renderingPaused = true; 488 } 489 490 public static restoreRendering(): void { 491 PUV2ViewBase.renderingPaused = false; 492 } 493 494 /** 495 Partial updates for ForEach. 496 * @param elmtId ID of element. 497 * @param itemArray Array of items for use of itemGenFunc. 498 * @param itemGenFunc Item generation function to generate new elements. If index parameter is 499 * given set itemGenFuncUsesIndex to true. 500 * @param idGenFunc ID generation function to generate unique ID for each element. If index parameter is 501 * given set idGenFuncUsesIndex to true. 502 * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not. 503 * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not. 504 */ 505 public forEachUpdateFunction( 506 elmtId: number, 507 itemArray: Array<any>, 508 itemGenFunc: (item: any, index?: number) => void, 509 idGenFunc?: (item: any, index?: number) => string, 510 itemGenFuncUsesIndex: boolean = false, 511 idGenFuncUsesIndex: boolean = false 512 ): void { 513 514 stateMgmtProfiler.begin('ViewPU/V2.forEachUpdateFunction'); 515 stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render) start ...`); 516 517 if (itemArray === null || itemArray === undefined) { 518 stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): input array is null or undefined error. Application error!`); 519 stateMgmtProfiler.end(); 520 return; 521 } 522 523 if (typeof itemGenFunc !== 'function') { 524 stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): Item generation function missing. Application error!`); 525 stateMgmtProfiler.end(); 526 return; 527 } 528 529 if (idGenFunc !== undefined && typeof idGenFunc !== 'function') { 530 stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): id generator is not a function. Application error!`); 531 stateMgmtProfiler.end(); 532 return; 533 } 534 535 if (idGenFunc === undefined) { 536 stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: providing default id gen function `); 537 idGenFuncUsesIndex = true; 538 // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message 539 idGenFunc = (item: any, index: number): string => { 540 try { 541 return `${index}__${JSON.stringify(item)}`; 542 } catch (e) { 543 throw new Error(`${this.debugInfo__()}: ForEach id ${elmtId}: use of default id generator function not possible on provided data structure. Need to specify id generator function (ForEach 3rd parameter). Application Error!`); 544 } 545 }; 546 } 547 548 let diffIndexArray = []; // New indexes compared to old one. 549 let newIdArray = []; 550 let idDuplicates = []; 551 const arr = itemArray; // just to trigger a 'get' onto the array 552 553 // ID gen is with index. 554 if (idGenFuncUsesIndex || idGenFunc.length > 1) { 555 // Create array of new ids. 556 arr.forEach((item, indx) => { 557 newIdArray.push(idGenFunc(item, indx)); 558 }); 559 } 560 else { 561 // Create array of new ids. 562 arr.forEach((item, index) => { 563 newIdArray.push(`${itemGenFuncUsesIndex ? index + '_' : ''}` + idGenFunc(item)); 564 }); 565 } 566 567 // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to foreach change 568 let removedChildElmtIds = []; 569 // Set new array on C++ side. 570 // C++ returns array of indexes of newly added array items. 571 // these are indexes in new child list. 572 ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates, removedChildElmtIds); 573 574 // Its error if there are duplicate IDs. 575 if (idDuplicates.length > 0) { 576 idDuplicates.forEach((indx) => { 577 stateMgmtConsole.error(`Error: ForEach id generated for ${indx}${indx < 4 ? indx === 2 ? 'nd' : 'rd' : 'th'} array item is duplicated.`); 578 }); 579 stateMgmtConsole.applicationError(`${this.debugInfo__()}: Ids generated by the ForEach id gen function must be unique. Application error!`); 580 } 581 582 stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: diff indexes ${JSON.stringify(diffIndexArray)} . `); 583 584 // Item gen is with index. 585 stateMgmtConsole.debug(` ... item Gen ${itemGenFuncUsesIndex ? 'with' : 'without'} index`); 586 // Create new elements if any. 587 stateMgmtProfiler.begin('ViewPU/V2.forEachUpdateFunction (native)'); 588 diffIndexArray.forEach((indx) => { 589 ForEach.createNewChildStart(newIdArray[indx], this.nativeViewPartialUpdate); 590 if (itemGenFuncUsesIndex) { 591 itemGenFunc(arr[indx], indx); 592 } else { 593 itemGenFunc(arr[indx]); 594 } 595 ForEach.createNewChildFinish(newIdArray[indx], this.nativeViewPartialUpdate); 596 }); 597 598 // un-registers the removed child elementIDs using proxy 599 UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds); 600 601 // purging these elmtIds from state mgmt will make sure no more update function on any deleted child will be executed 602 stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: elmtIds need unregister after foreach key change: ${JSON.stringify(removedChildElmtIds)}`); 603 this.purgeDeletedElmtIds(); 604 605 stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render) - DONE.`); 606 stateMgmtProfiler.end(); 607 stateMgmtProfiler.end(); 608 } 609 610 /** 611 * getNodeById is used to get ArkComponent stored updateFuncByElmtId 612 * @param elmtId - the id of the component 613 * @returns ArkComponent | undefined 614 */ 615 public getNodeById(elmtId: number): ArkComponent | undefined { 616 const entry = this.updateFuncByElmtId.get(elmtId); 617 return entry ? entry.getNode() : undefined; 618 } 619 620 /** 621 * return its elmtId if currently rendering or re-rendering an UINode 622 * otherwise return UINodeRegisterProxy.notRecordingDependencies 623 * set in observeComponentCreation(2) 624 */ 625 public getCurrentlyRenderedElmtId() { 626 return PUV2ViewBase.renderingPaused || this.currentlyRenderedElmtIdStack_.length === 0 627 ? UINodeRegisterProxy.notRecordingDependencies 628 : this.currentlyRenderedElmtIdStack_[this.currentlyRenderedElmtIdStack_.length - 1]; 629 } 630 631 public debugInfoViewHierarchy(recursive: boolean = false): string { 632 return this.debugInfoViewHierarchyInternal(0, recursive); 633 } 634 635 public debugInfoViewHierarchyInternal(depth: number = 0, recursive: boolean = false): string { 636 let retVaL: string = `\n${' '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]`; 637 retVaL += (this instanceof ViewPU) ? 'ViewPU' : 'ViewV2'; 638 if (this.isCompFreezeAllowed()) { 639 retVaL += ` {freezeWhenInactive : ${this.isCompFreezeAllowed()}}`; 640 } 641 retVaL += ` {isViewActive: ${this.isViewActive()}, isDeleting_: ${this.isDeleting_}}`; 642 if (depth < 1 || recursive) { 643 this.childrenWeakrefMap_.forEach((weakChild: WeakRef<IView>) => { 644 retVaL += weakChild.deref()?.debugInfoViewHierarchyInternal(depth + 1, recursive); 645 }); 646 } 647 return retVaL; 648 } 649 650 public debugInfoUpdateFuncByElmtId(recursive: boolean = false): string { 651 return this.debugInfoUpdateFuncByElmtIdInternal({ total: 0 }, 0, recursive); 652 } 653 654 public debugInfoUpdateFuncByElmtIdInternal(counter: ProfileRecursionCounter, depth: number = 0, recursive: boolean = false): string { 655 let retVaL: string = `\n${' '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`; 656 this.updateFuncByElmtId.forEach((value, key, map) => { 657 retVaL += `\n${' '.repeat(depth + 2)}${value.getComponentName()}[${key}]`; 658 }); 659 counter.total += this.updateFuncByElmtId.size; 660 retVaL += `\n${' '.repeat(depth + 1)}}[${this.updateFuncByElmtId.size}]`; 661 if (recursive) { 662 this.childrenWeakrefMap_.forEach((value, key, map) => { 663 retVaL += value.deref()?.debugInfoUpdateFuncByElmtIdInternal(counter, depth + 1, recursive); 664 }); 665 } 666 if (recursive && depth === 0) { 667 retVaL += `\nTotal: ${counter.total}`; 668 } 669 return retVaL; 670 } 671 672 public debugInfoInactiveComponents(): string { 673 // As active status has been added to -viewHierarchy, 674 // it is more convenient to use -viewHierarchy instead of -inactiveComponents... 675 return Array.from(PUV2ViewBase.inactiveComponents_) 676 .map((component) => `- ${component}`).join('\n'); 677 } 678 679 /** 680 * on first render create a new Instance of Repeat 681 * on re-render connect to existing instance 682 * @param arr 683 * @returns 684 */ 685 abstract __mkRepeatAPI<I>(arr: Array<I>): RepeatAPI<I>; 686 687 public findViewInHierarchy(id: number): ViewPU | ViewV2 { 688 let weakChild = this.childrenWeakrefMap_.get(id); 689 if (weakChild) { 690 const child = weakChild.deref(); 691 // found child with id, is it a ViewPU? 692 return (child instanceof ViewPU || child instanceof ViewV2) ? child : undefined; 693 } 694 695 // did not find, continue searching 696 let retVal: ViewPU | ViewV2 = undefined; 697 for (const [key, value] of this.childrenWeakrefMap_.entries()) { 698 retVal = value.deref().findViewInHierarchy(id); 699 if (retVal) { 700 break; 701 } 702 } 703 return retVal; 704 } 705 /** 706 * onDumpInfo is used to process commands delivered by the hidumper process 707 * @param commands - list of commands provided in the shell 708 * @returns void 709 */ 710 protected onDumpInfo(commands: string[]): void { 711 712 let dfxCommands: DFXCommand[] = this.processOnDumpCommands(commands); 713 714 dfxCommands.forEach((command) => { 715 let view: ViewPU | ViewV2 = undefined; 716 if (command.viewId) { 717 view = this.findViewInHierarchy(command.viewId); 718 if (!view) { 719 DumpLog.print(0, `\nTarget view: ${command.viewId} not found for command: ${command.what}\n`); 720 return; 721 } 722 } else { 723 view = this as unknown as ViewPU | ViewV2; 724 command.viewId = view.id__(); 725 } 726 let headerStr: string = view instanceof ViewPU ? 'ViewPU' : 'ViewV2'; 727 switch (command.what) { 728 case '-dumpAll': 729 view.printDFXHeader(headerStr + 'Info', command); 730 DumpLog.print(0, view.debugInfoView(command.isRecursive)); 731 break; 732 case '-viewHierarchy': 733 view.printDFXHeader(headerStr + 'Hierarchy', command); 734 DumpLog.print(0, view.debugInfoViewHierarchy(command.isRecursive)); 735 break; 736 case '-stateVariables': 737 view.printDFXHeader(headerStr + 'State Variables', command); 738 DumpLog.print(0, view.debugInfoStateVars()); 739 break; 740 case '-registeredElementIds': 741 view.printDFXHeader(headerStr + 'Registered Element IDs', command); 742 DumpLog.print(0, view.debugInfoUpdateFuncByElmtId(command.isRecursive)); 743 break; 744 case '-dirtyElementIds': 745 view.printDFXHeader(headerStr + 'Dirty Registered Element IDs', command); 746 DumpLog.print(0, view.debugInfoDirtDescendantElementIds(command.isRecursive)); 747 break; 748 case '-inactiveComponents': 749 view.printDFXHeader('List of Inactive Components', command); 750 DumpLog.print(0, view.debugInfoInactiveComponents()); 751 break; 752 case '-profiler': 753 view.printDFXHeader('Profiler Info', command); 754 view.dumpReport(); 755 this.sendStateInfo('{}'); 756 break; 757 default: 758 DumpLog.print(0, `\nUnsupported JS DFX dump command: [${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`); 759 } 760 }); 761 } 762 763 private printDFXHeader(header: string, command: DFXCommand): void { 764 let length: number = 50; 765 let remainder: number = length - header.length < 0 ? 0 : length - header.length; 766 DumpLog.print(0, `\n${'-'.repeat(remainder / 2)}${header}${'-'.repeat(remainder / 2)}`); 767 DumpLog.print(0, `[${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`); 768 } 769 770 private processOnDumpCommands(commands: string[]): DFXCommand[] { 771 let isFlag: Function = (param: string): boolean => { 772 return '-r'.match(param) != null || param.startsWith('-viewId='); 773 }; 774 775 let dfxCommands: DFXCommand[] = []; 776 777 for (let i: number = 0; i < commands.length; i++) { 778 let command = commands[i]; 779 if (isFlag(command)) { 780 if (command.startsWith('-viewId=')) { 781 let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1]; 782 if (dfxCommand) { 783 let input: string[] = command.split('='); 784 if (input[1]) { 785 let viewId: number = Number.parseInt(input[1]); 786 dfxCommand.viewId = Number.isNaN(viewId) ? UINodeRegisterProxy.notRecordingDependencies : viewId; 787 } 788 } 789 } else if (command.match('-r')) { 790 let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1]; 791 if (dfxCommand) { 792 dfxCommand.isRecursive = true; 793 } 794 } 795 } else { 796 dfxCommands.push({ 797 what: command, 798 viewId: undefined, 799 isRecursive: false, 800 }); 801 } 802 } 803 return dfxCommands; 804 } 805 806 // dump state var for v1 and v2 and send the dump value to ide to show in inspector 807 public onDumpInspector(): string { 808 const dumpInfo: DumpInfo = new DumpInfo(); 809 dumpInfo.viewInfo = { 810 componentName: this.constructor.name, id: this.id__(), isV2: this.isViewV2, 811 isCompFreezeAllowed_:this.isCompFreezeAllowed_, isViewActive_: this.isViewActive() 812 }; 813 let resInfo: string = ''; 814 try { 815 stateMgmtDFX.getDecoratedVariableInfo(this, dumpInfo); 816 resInfo = JSON.stringify(dumpInfo); 817 } catch (error) { 818 stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in getInspector: ${(error as Error).message}`); 819 } 820 return resInfo; 821 } 822 823 public traverseChildDoRecycleOrReuse(recyleOrReuse: boolean): void { 824 this.childrenWeakrefMap_.forEach((weakRefChild) => { 825 const child = weakRefChild.deref(); 826 if ( 827 child && 828 (child instanceof ViewPU || child instanceof ViewV2) && 829 !child.hasBeenRecycled_ && 830 !child.__isBlockRecycleOrReuse__ 831 ) { 832 recyleOrReuse ? child.aboutToRecycleInternal() : child.aboutToReuseInternal(); 833 } // if child 834 }); 835 } 836 837 public processPropertyChangedFuncQueue(): void { 838 if (!PUV2ViewBase.propertyChangedFuncQueues.has(this.id__())) { 839 return; 840 } 841 let propertyChangedFuncQueue = PUV2ViewBase.propertyChangedFuncQueues.get(this.id__()); 842 if (!propertyChangedFuncQueue) { 843 PUV2ViewBase.propertyChangedFuncQueues.delete(this.id__()); 844 return; 845 } 846 for (const propertyChangedFunc of propertyChangedFuncQueue) { 847 if (propertyChangedFunc && typeof propertyChangedFunc === 'function') { 848 propertyChangedFunc(); 849 } 850 } 851 PUV2ViewBase.propertyChangedFuncQueues.delete(this.id__()); 852 } 853 854 public setPrebuildPhase(prebuildPhase: PrebuildPhase): void { 855 PUV2ViewBase.prebuildPhase_ = prebuildPhase; 856 if (PUV2ViewBase.prebuildPhase_ === PrebuildPhase.BuildPrebuildCmd) { 857 this.isPrebuilding_ = true; 858 PUV2ViewBase.prebuildingElmtId_ = this.id__(); 859 if (!PUV2ViewBase.prebuildFuncQueues.has(this.id__())) { 860 PUV2ViewBase.prebuildFuncQueues.set(this.id__(), new Array<PrebuildFunc>()); 861 } 862 } else if (PUV2ViewBase.prebuildPhase_ === PrebuildPhase.ExecutePrebuildCmd) { 863 this.isPrebuilding_ = true; 864 PUV2ViewBase.prebuildingElmtId_ = this.id__(); 865 } else if (PUV2ViewBase.prebuildPhase_ === PrebuildPhase.PrebuildDone) { 866 PUV2ViewBase.prebuildingElmtId_ = -1; 867 PUV2ViewBase.prebuildFuncQueues.delete(this.id__()); 868 this.isPrebuilding_ = false; 869 this.processPropertyChangedFuncQueue(); 870 } 871 } 872 873 public static isNeedBuildPrebuildCmd(): boolean { 874 const needBuild: boolean = PUV2ViewBase.prebuildPhase_ === PrebuildPhase.BuildPrebuildCmd; 875 return needBuild; 876 } 877 878 private prebuildComponent(): void { 879 let prebuildFuncQueue = PUV2ViewBase.prebuildFuncQueues.get(this.id__()); 880 if (!prebuildFuncQueue) { 881 stateMgmtConsole.error(`prebuildComponent: prebuildFuncQueue ${this.id__()} not in prebuildFuncQueues`); 882 return; 883 } 884 const prebuildFunc = prebuildFuncQueue.shift(); 885 if (prebuildFunc && typeof prebuildFunc === 'function') { 886 prebuildFunc(); 887 } 888 } 889 890 protected isEnablePrebuildInMultiFrame(): boolean { 891 return !this.isViewV2; 892 } 893 public ifElseBranchUpdateFunctionDirtyRetaken(): void { 894 let retakenElmtIds = new Array<number>(); 895 const res: boolean = If.getRetakenElmtIds(retakenElmtIds); 896 if (res) { 897 for (const retakenElmtId of retakenElmtIds) { 898 this.updateFuncByElmtId.get(retakenElmtId)?.setPending(false); 899 if (this.updateFuncByElmtId.get(retakenElmtId)?.isChanged() && !this.dirtDescendantElementIds_.has(retakenElmtId)) { 900 this.dirtRetakenElementIds_.add(retakenElmtId); 901 } 902 this.updateFuncByElmtId.get(retakenElmtId)?.setIsChanged(false); 903 } 904 } 905 } 906} // class PUV2ViewBase 907