• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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 * ViewBuildNodeBase is the common base class of PUV2ViewBase and BaseNode
23 * The common methods for state management and BuilderNode are implemented in this base class.
24 */
25abstract class ViewBuildNodeBase {
26    protected isView_: boolean;
27    protected childrenWeakrefMap_ = new Map<number, WeakRef<IView>>();
28    // Tracks all child BuilderNodes of this ViewBuildNodeBase instance using WeakRefs.
29    protected builderNodeWeakrefMap_ = new Map<number, WeakRef<ViewBuildNodeBase>>();
30    protected updateFuncByElmtId = new UpdateFuncsByElmtId();
31    protected id_: number;
32    protected shareLocalStorage_: LocalStorage = undefined;
33
34    // Refer to the buildNode parent if it exists.
35    // It is undefined, if current node is created in view.
36    // It is not undefined, if current node is created in buildNode.
37    protected __parentViewBuildNode__: ViewBuildNodeBase = undefined;
38
39    // will find provide for consume only when __enableBuilderNodeConsume__ is true
40    // to avoid the affect the performance for builderNode
41    protected __enableBuilderNodeConsume__: boolean = false;
42
43    // Map elmtId -> Repeat instance in ViewPU or ViewV2
44    protected elmtId2Repeat_: Map<number, RepeatAPI<Object | null | undefined>> =
45        new Map<number, RepeatAPI<Object | null | undefined>>();
46    protected static arkThemeScopeManager: ArkThemeScopeManager | undefined = undefined;
47
48    public abstract ifElseBranchUpdateFunctionDirtyRetaken(): void;
49    public abstract forceCompleteRerender(deep: boolean): void;
50    public abstract forceRerenderNode(elmtId: number): void;
51    public abstract purgeDeleteElmtId(rmElmtId: number): boolean;
52
53    public abstract findProvidePU__(providePropName: string): ObservedPropertyAbstractPU<any> | undefined;
54
55    constructor(isView: boolean) {
56        this.isView_ = isView;
57        this.childrenWeakrefMap_ = new Map();
58        this.builderNodeWeakrefMap_ = new Map();
59    }
60    // globally unique id, this is different from compilerAssignedUniqueChildId!
61    id__(): number {
62        return this.id_;
63    }
64    // overwritten by sub classes
65    public debugInfo__(): string {
66        return `ViewBuildNodeBase '${this.constructor.name}'[${this.id__()}]`;
67    }
68
69    public debugInfoElmtId(elmtId: number, isProfiler: boolean = false): string | ElementType {
70        return isProfiler ? {
71            elementId: elmtId,
72            elementTag: this.updateFuncByElmtId.get(elmtId).getComponentName(),
73            isCustomNode: this.childrenWeakrefMap_.has(elmtId)
74        } : this.updateFuncByElmtId.debugInfoElmtId(elmtId);
75    }
76    /**
77     * Retrieve child by given id
78     * @param id
79     * @returns child if in map and weak ref resolves to IView object
80     */
81    public getChildById(id: number): IView | undefined {
82        const childWeakRef = this.childrenWeakrefMap_.get(id);
83        return childWeakRef ? childWeakRef.deref() : undefined;
84    }
85    public addChild(child: IView): boolean {
86        if (this.childrenWeakrefMap_.has(child.id__())) {
87            stateMgmtConsole.warn(`${this.debugInfo__()}: addChild '${child?.debugInfo__()}' elmtId already exists ${child.id__()}. Internal error!`);
88            return false;
89        }
90        this.childrenWeakrefMap_.set(child.id__(), new WeakRef(child));
91
92        // if current this is view
93        if (this.isView_) {
94            child.setParent(this as unknown as IView); // FIXME
95        } else {
96            child.setParentBuilderNode__(this);
97        }
98        return true;
99    }
100
101    public setParentBuilderNode__(node: ViewBuildNodeBase): void {
102        this.__parentViewBuildNode__ = node;
103    }
104    /**
105     * Adds a child BuilderNode to this view
106     * Invoke by buildNode when it attach to the view.
107     * @param child - The child node to add
108     * @returns True if added successfully, false if ID already exists
109     */
110    public addChildBuilderNode(child: ViewBuildNodeBase): boolean {
111        stateMgmtConsole.debug(`BuildNode ${child?.debugInfo__()} is added to the ${this.debugInfo__()}`);
112        if (this.builderNodeWeakrefMap_.has(child.id__())) {
113            stateMgmtConsole.warn(`${this.debugInfo__()}: addChildBuilderNode '${child?.debugInfo__()}' elmtId already exists ${child.id__()}. Internal error!`);
114            return false;
115        }
116        this.builderNodeWeakrefMap_.set(child.id__(), new WeakRef(child));
117        // recursively check children for buildNode and view
118        // if it has the default consume needs to reconnect the provide
119        if (child.__enableBuilderNodeConsume__) {
120            child.propagateToChildrenToConnected();
121        }
122        return true;
123    }
124
125    /**
126     * Recursively check all the buildNode and View children
127     * if it has the default consume, need to check if it has the new provide can be connected
128     * Invoke by buildNode when it attach to the view.
129     */
130    propagateToChildrenToConnected(): void {
131        if (this instanceof ViewPU && this.defaultConsume_.size > 0) {
132            this.reconnectToConsume()
133        }
134
135        this.childrenWeakrefMap_.forEach((weakRefChild) => {
136            const child = weakRefChild?.deref();
137            if (child instanceof ViewPU) {
138                child.propagateToChildrenToConnected();
139            }
140        })
141        this.builderNodeWeakrefMap_.forEach((weakRefChild) => {
142            const child = weakRefChild?.deref();
143            if (child instanceof ViewBuildNodeBase && child.__enableBuilderNodeConsume__) {
144                child.propagateToChildrenToConnected();
145            }
146        })
147    }
148    /**
149     * Removes a child BuilderNode from this view by elmtId.
150     * Invoke by buildNode when it detaches to the view.
151     * @param elmtId - The ID of the child node to remove
152     */
153    public removeChildBuilderNode(elmtId: number): void {
154        stateMgmtConsole.debug(`BuildNode ${elmtId} is removed from the ${this.debugInfo__()}`);
155        if (!this.builderNodeWeakrefMap_.has(elmtId)) {
156            stateMgmtConsole.warn(`${this.debugInfo__()}: removeChildBuilderNode(${elmtId}) no child with this elmtId. Internal error!`);
157            return;
158        }
159
160        const buildNode: ViewBuildNodeBase = this.builderNodeWeakrefMap_.get(elmtId)?.deref();
161        // recursively check children for buildNode and view
162        // if it has the default consume needs to reconnect the provide
163        if (buildNode && buildNode.__enableBuilderNodeConsume__) {
164            buildNode.propagateToChildrenToDisconnected();
165        }
166        this.builderNodeWeakrefMap_.delete(elmtId);
167    }
168    /**
169     * Clears all child BuilderNodes from this view
170     */
171    public clearChildBuilderNode(): void {
172        this.builderNodeWeakrefMap_.clear();
173    }
174
175    /**
176     * Recursively check all the buildNode and View children
177     * if it has the reconnected consume,
178     * Invoke by buildNode when it attach to the view.
179     */
180    public propagateToChildrenToDisconnected(): void {
181        if (this instanceof ViewPU && this.reconnectConsume_.size > 0) {
182            this.disconnectedConsume();
183        }
184        this.childrenWeakrefMap_.forEach((weakRefChild) => {
185            const child = weakRefChild?.deref();
186            if (child instanceof ViewPU) {
187                child.propagateToChildrenToDisconnected();
188            }
189        })
190        this.builderNodeWeakrefMap_.forEach((weakRefChild) => {
191            const child = weakRefChild?.deref();
192            if (child instanceof ViewBuildNodeBase && child.__enableBuilderNodeConsume__) {
193                child.propagateToChildrenToDisconnected();
194            }
195        })
196    }
197
198    protected purgeDeletedElmtIds(): void {
199        stateMgmtConsole.debug(`purgeDeletedElmtIds ViewBuildNodeBase '${this.constructor.name}' (id: ${this.id__()}) start ...`);
200        UINodeRegisterProxy.obtainDeletedElmtIds();
201        UINodeRegisterProxy.unregisterElmtIdsFromIViews();
202        stateMgmtConsole.debug(`purgeDeletedElmtIds ViewBuildNodeBase '${this.constructor.name}' (id: ${this.id__()}) end... `);
203    }
204    public updateStateVarsOfChildByElmtId(elmtId, params: Object): void {
205        stateMgmtProfiler.begin('ViewBuildNodeBase.updateStateVarsOfChildByElmtId');
206        stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - start`);
207
208        if (elmtId < 0) {
209            stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - invalid elmtId - internal error!`);
210            stateMgmtProfiler.end();
211            return;
212        }
213        let iChild: IView = this.getChildById(elmtId);
214        if (!iChild || !((iChild instanceof ViewPU) || (iChild instanceof ViewV2))) {
215            stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - no child with this elmtId - internal error!`);
216            stateMgmtProfiler.end();
217            return;
218        }
219        const child = iChild as ViewPU | ViewV2;
220        if ('updateStateVars' in child) {
221            child.updateStateVars(params);
222        }
223        if (!this.isView_) {
224            if ('updateDirtyElements' in child) {
225                child.updateDirtyElements();
226            }
227        }
228        stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - end`);
229        stateMgmtProfiler.end();
230    }
231    public createOrGetNode(elmtId: number, builder: () => ArkComponent): object {
232        const entry = this.updateFuncByElmtId.get(elmtId);
233        if (entry === undefined) {
234            stateMgmtConsole.warn(`${this.debugInfo__()} fail to create node, elmtId is illegal`);
235            return builder();
236        }
237        let nodeInfo = entry.getNode();
238        if (nodeInfo === undefined) {
239            nodeInfo = builder();
240            entry.setNode(nodeInfo);
241        }
242        return nodeInfo;
243    }
244    // performs the update on a branch within if() { branch } else if (..) { branch } else { branch }
245    public ifElseBranchUpdateFunction(branchId: number, branchfunc: () => void): void {
246        const oldBranchid: number = If.getBranchId();
247
248        if (branchId === oldBranchid) {
249            stateMgmtConsole.debug(`${this.debugInfo__()}: ifElseBranchUpdateFunction: IfElse branch unchanged, no work to do.`);
250            return;
251        }
252        if (this.isView_ || (!this.isView_ && Utils.isApiVersionEQAbove(16))) {
253            ViewBuildNodeBase.arkThemeScopeManager?.onIfElseBranchUpdateEnter();
254        }
255        // branchid identifies uniquely the if .. <1> .. else if .<2>. else .<3>.branch
256        // ifElseNode stores the most recent branch, so we can compare
257        // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to if .. else change
258        let removedChildElmtIds = new Array<number>();
259        let reservedChildElmtIds = new Array<number>();
260        If.branchId(branchId, removedChildElmtIds, reservedChildElmtIds);
261
262        for (const reservedChildElmtId of reservedChildElmtIds) {
263            this.updateFuncByElmtId.get(reservedChildElmtId)?.setPending(true);
264        }
265
266        removedChildElmtIds.forEach((id) => {
267            this.elmtId2Repeat_.delete(id);
268        });
269
270        //un-registers the removed child elementIDs using proxy
271        UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
272
273        // purging these elmtIds from state mgmt will make sure no more update function on any deleted child wi;ll be executed
274        stateMgmtConsole.debug(`${this.debugInfo__()}: ifElseBranchUpdateFunction: elmtIds need unregister after if/else branch switch: ${JSON.stringify(removedChildElmtIds)}`);
275        this.purgeDeletedElmtIds();
276
277        branchfunc();
278        this.ifElseBranchUpdateFunctionDirtyRetaken();
279        if (this.isView_ || (!this.isView_ && Utils.isApiVersionEQAbove(16))) {
280            ViewBuildNodeBase.arkThemeScopeManager?.onIfElseBranchUpdateExit(removedChildElmtIds);
281        }
282    }
283    public static setArkThemeScopeManager(mgr: ArkThemeScopeManager): void {
284        ViewBuildNodeBase.arkThemeScopeManager = mgr;
285    }
286    public onWillApplyThemeInternally(): void {
287        const theme = ViewBuildNodeBase.arkThemeScopeManager?.getFinalTheme(this);
288        if (theme) {
289            this.onWillApplyTheme(theme);
290        }
291    }
292    onWillApplyTheme(theme: Theme): void { }
293    onGlobalThemeChanged(): void {
294        this.onWillApplyThemeInternally();
295        this.forceCompleteRerender(false);
296        this.childrenWeakrefMap_.forEach((weakRefChild) => {
297            const child = weakRefChild.deref();
298            if (child) {
299                child.onGlobalThemeChanged();
300            }
301        });
302    }
303    public getShareLocalStorage(): LocalStorage {
304        return this.shareLocalStorage_;
305    }
306    public setShareLocalStorage(localStorage: LocalStorage): void {
307        this.shareLocalStorage_ = localStorage;
308    }
309    /**
310     * Propagates the active state to all child nodes in the given weak reference map
311     * This generic method can handle child View or child BuilderNode
312     *
313     * @param weakRefMap - The map containing WeakRefs to children
314     * @param active - Whether the child nodes should be activated (true) or frozen (false)
315     * @param isReuse - Related to component reuse
316     */
317    protected propagateToChildren(weakRefMap: Map<number, WeakRef<IView | ViewBuildNodeBase>> | undefined,
318        active: boolean, isReuse: boolean): void {
319        if (weakRefMap) {
320            for (const child of weakRefMap.values()) {
321                child.deref()?.setActiveInternal(active, isReuse);
322            }
323        }
324    }
325    // overwritten by sub classes
326    public setActiveInternal(active: boolean, isReuse = false): void {
327        // Propagate state to all child View
328        this.propagateToChildren(this.childrenWeakrefMap_, active, isReuse);
329        // Propagate state to all child BuilderNode
330        this.propagateToChildren(this.builderNodeWeakrefMap_, active, isReuse);
331    }
332}