import webview from '@ohos.web.webview';
import { TencentQianWebViewHandlers } from '../../TencentQianWebViewConfigurator';
/** 进度联动回调类型。 */
export type WebProgressListener = (progress: number, visible: boolean) => void;
/** 标题 / 可后退联动回调类型。 */
export type WebTitleListener = (title: string, canGoBack: boolean) => void;
/**
* 离线 Web 组件(`BuilderNode`)与容器 struct 之间的**共享状态 + 回调载体**。
*
* 由于 `@Builder` 是全局函数、无法访问 struct 的 `this`/`@State`,容器与 Web 组件
* 的联动(进度条、标题、canGoBack、渲染恢复)统一通过本 `@Observed` 对象完成:
* - Web 组件(在 `@Builder` 内)写入这些字段;
* - 容器 struct 以 `@ObjectLink`/`@State` 引用同一实例,响应式读取渲染顶栏/进度条。
*
* 复用关键:预热池与真实页共用同一 `@Builder`,通过替换本 model 的 `url`/`handlers`
* 即可把「预热态」切换为「真实签署态」,无需重建 Web 组件。
*/
@Observed
export class WebHostModel {
/** 当前主框架 URL。 */
public url: string = 'about:blank';
/** Web 组件 controller(与容器持有的同一实例)。 */
public controller: webview.WebviewController;
/** Configurator 生成的事件处理器集合(controller attach 前可为 null)。 */
public handlers: TencentQianWebViewHandlers | null = null;
// ── 联动状态(Page 顶栏/进度条读;Component 忽略)──
public progress: number = 0;
public progressVisible: boolean = false;
public title: string = '';
public canGoBack: boolean = false;
public isPageLoading: boolean = false;
/**
* 一次性「清历史」标志。池复用(HEAVY 预热)借出后,容器 `loadUrl(signingUrl)` 前置为 true;
* 首个 `onPageEnd`(signingUrl 加载完成)时执行一次 `clearHistory()`,清掉预热残留
* (warmupUrl / about:blank),使 signingUrl 成为唯一历史项 —— 第一次返回即触发退出而非回到预热页。
* 清后立即复位,保证不影响签署页内部的多级前进/后退。自建 node(首页即 signingUrl)无需置位。
*/
public clearHistoryOnce: boolean = false;
/** 最近一次主框架导航 URL,用于渲染进程异常兜底重载。 */
public lastVisitedUrl: string = '';
/**
* 渲染进程崩溃连续恢复计数,防止"必崩页面"死循环 reload。
* 每次崩溃恢复 +1,页面成功加载完成(onPageEnd)时清零;连续崩溃达
* `MAX_RENDER_RECOVERY` 次仍未成功 → 判定不可恢复,触发 onRenderUnrecoverable 关闭容器。
* 对齐 Android renderRecoveryCount。
*/
public renderRecoveryCount: number = 0;
/**
* 上次渲染进程崩溃的时间戳(毫秒)。用于冷却窗口重置:距上次崩溃超过
* `RENDER_RECOVERY_RESET_WINDOW` 视为页面已稳定运行过,本次按全新问题处理并重置
* `renderRecoveryCount`,避免长时间运行中的偶发(非连续)崩溃累计触顶被误判为不可恢复。
*/
public lastRenderTerminateAtMs: number = 0;
/**
* 本实例是否借自预热池。渲染进程崩溃时用于决定是否作废池实例
* (避免僵尸实例被后续 `obtain` 复用),对齐 Android `fromPool`。
*/
public fromPool: boolean = false;
/**
* 进度联动回调(容器 struct 设置,用于把 `@Builder` 内的进度事件驱动到 struct `@State`)。
* 采用回调而非 `@Observed` 深层响应式,规避跨 `BuilderNode` 属性观察的不确定性。
*/
public onProgressChanged: WebProgressListener | null = null;
/** 标题 / 可后退联动回调(同上)。 */
public onTitleChanged: WebTitleListener | null = null;
/**
* 渲染进程崩溃且无法恢复时的回调(容器 struct 设置)。
* `RenderRecovery` 的 `refresh()` + `loadUrl()` 均失败时触发,容器据此关闭页面,
* 避免用户停留在持久白屏。对齐 Android `recoverFromRenderProcessGone` 恢复失败时 `finish()`。
*/
public onRenderUnrecoverable: (() => void) | null = null;
/**
* 请求作废预热池实例的回调(容器在池复用时绑定为 `HarmonyWebViewPool.invalidate`)。
*
* 借出态实例渲染进程崩溃时,即便 `RenderRecovery` reload 成功,该实例也已是"渲染进程死过的"
* 僵尸,归还后会污染池、被后续 `open` 复用致白屏。故崩溃即作废池(避免僵尸复用),
* 关闭时不再归还。通过回调解耦,规避 `TencentQianWebNode` ↔ `HarmonyWebViewPool` 循环依赖。
* 对齐 Android 借出态 `invalidatePooledWebView`。
*/
public onPoolInvalidateRequest: (() => void) | null = null;
/**
* 待执行的延迟恢复 timer id(`RenderRecovery.recover` 返回),-1 表示无。
*
* 崩溃恢复延迟 1s 执行,若用户在此窗口内关闭页面(`aboutToDisappear`),必须
* `clearTimeout` 取消,否则回调会访问已销毁/已归还的 controller,造成无效 reload
* 或污染已归还的池实例(野指针)。
*/
public pendingRecoverTimerId: number = -1;
constructor(controller: webview.WebviewController) {
this.controller = controller;
}
/**
* 断开全部指向容器 struct 的联动回调(容器 `aboutToDisappear` 时调用)。
*
* **防泄漏关键**:池 model 生命周期跟随池单例(长),容器 struct 生命周期短。
* 归还池后若不清这些捕获了容器 `this` 的闭包,池 model 会一直持有已销毁容器的引用,
* 在"归还后 ~ 下次借出前"窗口内造成内存泄漏(上个 Page/Component 无法被回收)。
* 自建 node 归还前一并清理也无副作用(随后即 dispose)。
*/
public clearContainerListeners(): void {
// 取消未执行的延迟恢复,避免回调访问已销毁/已归还的 controller(野指针)。
this.cancelPendingRecover();
this.onProgressChanged = null;
this.onTitleChanged = null;
this.onRenderUnrecoverable = null;
this.onPoolInvalidateRequest = null;
}
/** 取消待执行的延迟恢复 timer(若有)。容器销毁/归还池、或恢复重新调度前调用。 */
public cancelPendingRecover(): void {
if (this.pendingRecoverTimerId !== -1) {
clearTimeout(this.pendingRecoverTimerId);
this.pendingRecoverTimerId = -1;
}
}
}
|