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}