• 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//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