• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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