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