From f7432f732e512ee9653517d57645fcdf8df91856 Mon Sep 17 00:00:00 2001 From: richerfu Date: Wed, 22 Apr 2026 17:45:06 +0800 Subject: [PATCH 1/3] feat: support embedded webview --- crates/ability/src/helper/webview.rs | 13 + native_ability/src/main/ets/ability/type.ets | 3 + .../main/ets/components/DefaultXComponent.ets | 77 ++- .../src/main/ets/webview/DefaultWebview.ets | 484 ++++++++++++++---- native_ability/src/main/ets/webview/Utils.ets | 8 + 5 files changed, 483 insertions(+), 102 deletions(-) diff --git a/crates/ability/src/helper/webview.rs b/crates/ability/src/helper/webview.rs index 3272246..5cff8d2 100644 --- a/crates/ability/src/helper/webview.rs +++ b/crates/ability/src/helper/webview.rs @@ -261,6 +261,19 @@ impl Webview { } } + pub fn dispose(&self) -> Result<()> { + if let Some(env) = get_main_thread_env().borrow().as_ref() { + let dispose_js_function = self + .inner + .get_value(env)? + .get_named_property::>("dispose")?; + dispose_js_function.call(())?; + Ok(()) + } else { + Err(Error::from_reason("Failed to get main thread env")) + } + } + pub fn clear_all_browsing_data(&self) -> Result<()> { if let Some(env) = get_main_thread_env().borrow().as_ref() { let clear_all_browsing_data_js_function = self diff --git a/native_ability/src/main/ets/ability/type.ets b/native_ability/src/main/ets/ability/type.ets index 7e82db0..ebd8f6c 100644 --- a/native_ability/src/main/ets/ability/type.ets +++ b/native_ability/src/main/ets/ability/type.ets @@ -60,6 +60,8 @@ export interface WebViewInitData { export interface WebViewStyle { x?: number | string; y?: number | string; + visible?: boolean | string; + backgroundColor?: string | Color; } export interface AbilityInitContext { @@ -79,6 +81,7 @@ export interface Module { export interface ArkHelper { exit: (code: number) => void; createWebview: (data: WebViewInitData) => Object; + createEmbeddedWebview: (data: WebViewInitData) => Object; requestPermission: (permission: string | string[]) => Promise; getWindowAvoidArea: (type: number) => WindowAvoidAreaInfo | undefined; } diff --git a/native_ability/src/main/ets/components/DefaultXComponent.ets b/native_ability/src/main/ets/components/DefaultXComponent.ets index 9fd810e..50767ad 100644 --- a/native_ability/src/main/ets/components/DefaultXComponent.ets +++ b/native_ability/src/main/ets/components/DefaultXComponent.ets @@ -4,12 +4,13 @@ import { WebViewInitData as NativeWebViewInitData, WindowAvoidAreaInfo, } from "../ability/type"; -import { exit } from "../helper"; +import { exit, objectAssign } from "../helper"; import { Loadable } from "../helper/loadable"; import { requestPermission } from "../helper/permission"; import common from "@ohos.app.ability.common"; import window from "@ohos.window"; import { + EmbeddedWebviewManager, RustWebviewNodeController, WebviewStyle, WebviewInitData, @@ -21,6 +22,7 @@ export struct DefaultXComponent { moduleName: string = ""; private rootSlot = new NodeContent(); private webviewController = new RustWebviewNodeController(this.getUIContext()); + private embeddedWebviewManager = new EmbeddedWebviewManager(this.getUIContext()); private nativeModule: ESObject; private helper: ArkHelper = { exit, @@ -66,25 +68,82 @@ export struct DefaultXComponent { onTitleChange: data?.onTitleChange, } as WebviewInitData; - if (data?.transparent && !init.style?.backgroundColor) { - init.style!.backgroundColor = Color.Transparent; + init.style = init.style || {}; + + if (data?.transparent && !init.style.backgroundColor) { + init.style.backgroundColor = Color.Transparent; } const ret = this.webviewController.addWebview(init); + const applyStyle = (style: WebviewStyle) => { + objectAssign(init.style as Record, style); + this.webviewController.updateWebviewStyle(ret.webTag, init.style as WebviewStyle); + }; ret.controller.setBackgroundColor = (color: string) => { - init.style!.backgroundColor = color; - const node = this.webviewController.getWebviewNode(ret.webTag); - node?.update(init); + applyStyle({ backgroundColor: color }); }; ret.controller.setVisible = (visible: boolean) => { - init.style!.visible = visible ? "visible" : "hidden"; - const node = this.webviewController.getWebviewNode(ret.webTag); - node?.update(init); + applyStyle({ visible }); + }; + + ret.controller.dispose = () => { + this.webviewController.removeWebview(ret.webTag); }; return ret.controller; }, + createEmbeddedWebview: (data: NativeWebViewInitData) => { + const initScripts: ScriptItem[] = (data?.initializationScripts || []).map((i) => { + return { + script: i, + scriptRules: ["*"], + } as ScriptItem; + }); + const init: WebviewInitData = { + webTag: data?.id, + url: data?.url, + html: data?.html, + headers: data?.headers, + style: (data.style || {}) as WebviewStyle, + javascriptEnable: data?.javascriptEnable ?? true, + userAgent: data?.userAgent, + devtools: data?.devtools, + autoplay: data?.autoplay, + initializationScripts: initScripts, + onDragAndDrop: data?.onDragAndDrop, + onDownloadStart: data?.onDownloadStart, + onDownloadEnd: data?.onDownloadEnd, + onNavigationRequest: data?.onNavigationRequest, + onTitleChange: data?.onTitleChange, + } as WebviewInitData; + + init.style = init.style || {}; + + if (data?.transparent && !init.style.backgroundColor) { + init.style.backgroundColor = Color.Transparent; + } + + const ret = this.embeddedWebviewManager.createWebview(init); + const applyStyle = (style: WebviewStyle) => { + objectAssign(init.style as Record, style); + this.embeddedWebviewManager.updateWebviewStyle(ret.webTag, init.style as WebviewStyle); + }; + + ret.controller.setBackgroundColor = (color: string) => { + applyStyle({ backgroundColor: color }); + }; + + ret.controller.setVisible = (visible: boolean) => { + applyStyle({ visible }); + }; + + ret.controller.dispose = () => { + this.embeddedWebviewManager.removeWebview(ret.webTag); + }; + + return ret; + }, }; @StorageProp("loadMode") loadMode: "async" | "sync" = "async"; diff --git a/native_ability/src/main/ets/webview/DefaultWebview.ets b/native_ability/src/main/ets/webview/DefaultWebview.ets index 40f9030..7a42bf5 100644 --- a/native_ability/src/main/ets/webview/DefaultWebview.ets +++ b/native_ability/src/main/ets/webview/DefaultWebview.ets @@ -1,16 +1,19 @@ import { UIContext } from "@ohos.arkui.UIContext"; import web_webview from "@ohos.web.webview"; -import { NodeController, BuilderNode, FrameNode } from "@ohos.arkui.node"; +import { ComponentContent, NodeController, BuilderNode, FrameNode } from "@ohos.arkui.node"; +import hilog from "@ohos.hilog"; import { randomString } from "../helper"; import { OnDownloadStartResult } from "../ability/type"; import { getCookies, JsHelper } from "./Utils"; import { WebHeader } from "@kit.ArkUI"; +const TAG = "OHRSWebview"; + export interface WebviewStyle { x?: number | string; y?: number | string; backgroundColor?: string | Color; - visible?: string; + visible?: boolean | string; } export interface WebviewInitData { @@ -34,6 +37,35 @@ export interface WebviewInitData { interface WebviewNodeData extends WebviewInitData { controller: WebviewController; + didInitialLoad?: boolean; +} + +function describeWebviewError(error: ESObject): string { + const message: string = Reflect.get(error, "message"); + if (typeof message === "string" && message.length > 0) { + return message; + } + + try { + return JSON.stringify(error); + } catch (_) { + return `${error}`; + } +} + +function describeStyleValue(value?: number | string): string { + if (typeof value === "number" || typeof value === "string") { + return `${value}`; + } + return "undefined"; +} + +function describeWebviewStyle(style: WebviewStyle): string { + return `x=${describeStyleValue(style.x)}, y=${describeStyleValue(style.y)}, visible=${style.visible}, backgroundColor=${style.backgroundColor}`; +} + +function logWebviewError(stage: string, detail: string) { + hilog.error(0x0000, TAG, `${stage}: ${detail}`); } @Builder @@ -43,11 +75,15 @@ function WebBuilder(data: WebviewNodeData) { .width("100%") .height("100%") .position({ - x: data.style?.x || 0, - y: data.style?.y || 0, + x: data.style?.x ?? 0, + y: data.style?.y ?? 0, }) .backgroundColor(data?.style?.backgroundColor) - .visibility(data?.style?.visible === "hidden" ? Visibility.Hidden : Visibility.Visible) + .visibility( + data?.style?.visible === false || data?.style?.visible === "hidden" + ? Visibility.Hidden + : Visibility.Visible, + ) .javaScriptAccess(data?.javascriptEnable) .mediaPlayGestureAccess( typeof data?.autoplay === "boolean" && data.autoplay === true ? false : true, @@ -83,32 +119,154 @@ function WebBuilder(data: WebviewNodeData) { }); } -const webViewWrap = wrapBuilder(WebBuilder); +@Builder +function EmbeddedWebBuilder(data: WebviewNodeData) { + // The embedded path needs a bounded ArkTS root so hit testing stays clipped + // to the host slot owned by native. A bare `Web` component can render in the + // right place while still participating in input as an unbounded layer. + Stack() { + Web({ src: "", controller: data.controller as web_webview.WebviewController }) + .width("100%") + .height("100%") + .backgroundColor(data?.style?.backgroundColor) + .javaScriptAccess(data?.javascriptEnable) + .mediaPlayGestureAccess( + typeof data?.autoplay === "boolean" && data.autoplay === true ? false : true, + ) + .javaScriptOnDocumentStart(data?.initializationScripts) + .onControllerAttached(() => { + if (data.didInitialLoad === true) { + return; + } + data.didInitialLoad = true; + const ctrl = data.controller; + if (data?.url) { + const header: WebHeader[] = Object.keys( + (data?.headers || {}) as Record, + ).reduce((t: WebHeader[], i) => { + t.push({ headerKey: i, headerValue: data.headers![i] } as WebHeader); + return t; + }, []); + ctrl.loadUrl(data.url, header); + } else { + ctrl.loadData(data!.html, "text/html", "UTF-8", " ", " "); + } + }) + .onLoadIntercept((event) => { + if (typeof data?.onNavigationRequest === "function") { + const url = event.data.getRequestUrl(); + const ret = data.onNavigationRequest(url); + return ret; + } + return false; + }) + .onTitleReceive((e) => { + if (typeof data?.onTitleChange === "function") { + data.onTitleChange(e.title); + } + }) + } + .width("100%") + .height("100%") + .clip(true) + .visibility( + data?.style?.visible === false || data?.style?.visible === "hidden" + ? Visibility.Hidden + : Visibility.Visible, + ) + .hitTestBehavior(HitTestMode.Default); +} + +const webViewWrap = wrapBuilder<[WebviewNodeData]>(WebBuilder); +const embeddedWebViewWrap = wrapBuilder<[WebviewNodeData]>(EmbeddedWebBuilder); interface AddWebviewMethod { webTag: string; controller: JsHelper; } -export class RustWebviewNodeController extends NodeController { - private rootNode: FrameNode | null = null; - private webviewList: Map> = new Map(); - private webviewData: Map = new Map(); - private uiContext: UIContext | null = null; +export interface EmbeddedWebviewHandle extends AddWebviewMethod { + content: ComponentContent; +} - constructor(uiContext: UIContext) { - super(); - this.uiContext = uiContext; +interface EmbeddedWebviewEntry { + data: WebviewNodeData; + content: ComponentContent; + controller: JsHelper; +} + +function ensureWebviewNodeData(data: WebviewInitData): WebviewNodeData { + if (!data.webTag) { + data.webTag = randomString(); } + if (!data.controller) { + data.controller = new web_webview.WebviewController(data.webTag) as WebviewController; + } + data.style = data.style || {}; + if (typeof data.javascriptEnable !== "boolean") { + data.javascriptEnable = true; + } + const prepared = data as WebviewNodeData; + prepared.didInitialLoad = prepared.didInitialLoad === true; - private buildData(controller: WebviewController) { - const getUrl = () => { - return controller.getUrl(); - }; - const getCookiesHelper = (url: string) => { - return getCookies(url) as string; - }; - const loadUrl = (url: string, header?: Record) => { + if (prepared?.devtools) { + web_webview.WebviewController.setWebDebuggingAccess(true); + } + + return prepared; +} + +function setupDownloadDelegate(data: WebviewNodeData) { + if (typeof data?.onDownloadStart !== "function" && typeof data?.onDownloadEnd !== "function") { + return; + } + + const download = new web_webview.WebDownloadDelegate(); + + if (typeof data?.onDownloadStart === "function") { + download.onBeforeDownload((e) => { + const url = e.getUrl(); + const tempPath = e.getFullPath(); + const ret = data.onDownloadStart!(url, tempPath); + + if (ret.allow) { + e.start(ret.tempPath || tempPath); + } else { + e.cancel(); + } + }); + } + + if (typeof data?.onDownloadEnd === "function") { + download.onDownloadFinish((e) => { + const url = e.getUrl(); + const tempPath = e.getFullPath(); + data.onDownloadEnd!(url, tempPath, true); + }); + download.onDownloadFailed((e) => { + const url = e.getUrl(); + data.onDownloadEnd!(url, undefined, false); + }); + } + + try { + data.controller.setDownloadDelegate(download); + } catch (error) { + const detail = `setDownloadDelegate(${data.webTag}) failed: ${describeWebviewError(error)}`; + logWebviewError("webview/downloadDelegate", detail); + throw new Error(detail); + } +} + +function buildJsHelper(controller: WebviewController): JsHelper { + const getUrl = () => { + return controller.getUrl(); + }; + const getCookiesHelper = (url: string) => { + return getCookies(url) as string; + }; + const loadUrl = (url: string, header?: Record) => { + try { const headers = Object.keys((header || {}) as Record).reduce( (t, i) => { if (!!header![i]) { @@ -119,50 +277,122 @@ export class RustWebviewNodeController extends NodeController { [] as Array, ); controller.loadUrl(url, headers); - }; + } catch (error) { + const detail = `loadUrl(${url}) failed: ${describeWebviewError(error)}`; + logWebviewError("loadUrl", detail); + throw new Error(detail); + } + }; - const loadHtml = (html: string) => { + const loadHtml = (html: string) => { + try { controller.loadData(html, "text/html", "UTF-8", " ", " "); - }; + } catch (error) { + const detail = `loadHtml failed: ${describeWebviewError(error)}`; + logWebviewError("loadHtml", detail); + throw new Error(detail); + } + }; - const zoom = (scale: number) => { + const zoom = (scale: number) => { + try { controller.zoom(scale); - }; + } catch (error) { + const detail = `zoom(${scale}) failed: ${describeWebviewError(error)}`; + logWebviewError("zoom", detail); + throw new Error(detail); + } + }; - const refresh = () => { + const refresh = () => { + try { controller.refresh(); - }; + } catch (error) { + const detail = `refresh failed: ${describeWebviewError(error)}`; + logWebviewError("refresh", detail); + throw new Error(detail); + } + }; - const requestFocus = () => { + const requestFocus = () => { + try { controller.requestFocus(); - }; + } catch (error) { + const detail = `requestFocus failed: ${describeWebviewError(error)}`; + logWebviewError("requestFocus", detail); + throw new Error(detail); + } + }; - // clear browsing data - const clearAllBrowsingData = () => { + const clearAllBrowsingData = () => { + try { web_webview.WebStorage.deleteAllData(true); web_webview.WebDataBase.deleteHttpAuthCredentials(); controller.removeCache(true); controller.clearHistory(); - }; + } catch (error) { + const detail = `clearAllBrowsingData failed: ${describeWebviewError(error)}`; + logWebviewError("clearAllBrowsingData", detail); + throw new Error(detail); + } + }; - const runJavaScript = (code: string, cb: (result?: string) => void) => { - controller.runJavaScript(code).then((ret) => { + const runJavaScript = (code: string, cb: (result?: string) => void) => { + controller + .runJavaScript(code) + .then((ret) => { cb(ret); + }) + .catch((error: ESObject) => { + const detail = `runJavaScript failed: ${describeWebviewError(error)}`; + logWebviewError("runJavaScript", detail); + throw new Error(detail); }); - }; + }; + + return { + getUrl, + getCookies: getCookiesHelper, + loadUrl, + loadHtml, + zoom, + refresh, + requestFocus, + clearAllBrowsingData, + runJavaScript, + } as JsHelper; +} + +export class RustWebviewNodeController extends NodeController { + private rootNode: FrameNode | null = null; + private webviewEntries: Map = new Map(); + private webviewList: Map> = new Map(); + private uiContext: UIContext | null = null; - const data: JsHelper = { - getUrl, - getCookies: getCookiesHelper, - loadUrl, - loadHtml, - zoom, - refresh, - requestFocus, - clearAllBrowsingData, - runJavaScript, - } as JsHelper; - return data; + constructor(uiContext: UIContext) { + super(); + this.uiContext = uiContext; + } + + updateWebviewStyle(webTag: string, style: WebviewStyle) { + const entry = this.webviewEntries.get(webTag); + const node = this.webviewList.get(webTag); + if (!entry || !node) { + return; + } + + entry.style = style; + try { + node.update(entry); + } catch (error) { + const detail = `updateWebviewStyle(${webTag}) failed: ${describeWebviewError(error)}; style={${describeWebviewStyle(style)}}`; + logWebviewError("overlay/update", detail); + throw new Error(detail); + } + } + + private buildData(controller: WebviewController) { + return buildJsHelper(controller); } makeNode(uiContext: UIContext): FrameNode { @@ -173,75 +403,143 @@ export class RustWebviewNodeController extends NodeController { } addWebview(data: WebviewInitData): AddWebviewMethod { - if (!data.webTag) { - data.webTag = randomString(); + const prepared = ensureWebviewNodeData(data); + const webTag = prepared.webTag!; + if (this.rootNode === null) { + this.rootNode = new FrameNode(this.uiContext!); } - if (!data.controller) { - data.controller = new web_webview.WebviewController(data.webTag) as WebviewController; + + const node = new BuilderNode<[WebviewNodeData]>(this.uiContext!); + try { + node.build(webViewWrap, prepared); + } catch (error) { + const detail = `addWebview(${webTag}) build failed: ${describeWebviewError(error)}`; + logWebviewError("overlay/build", detail); + throw new Error(detail); } - if (this.rootNode === null) { - this.rootNode = new FrameNode(this.uiContext!); + const controller = this.buildData(prepared.controller); + setupDownloadDelegate(prepared); + this.webviewEntries.set(webTag, prepared); + this.webviewList.set(webTag, node); + + try { + this.rootNode?.appendChild(node.getFrameNode()); + } catch (error) { + const detail = `addWebview(${webTag}) append failed: ${describeWebviewError(error)}`; + logWebviewError("overlay/append", detail); + throw new Error(detail); } - // Enabled devtools - data?.devtools && web_webview.WebviewController.setWebDebuggingAccess(true); + return { + webTag, + controller, + }; + } - const node: BuilderNode = new BuilderNode(this.uiContext!); - node.build(webViewWrap, data); - this.webviewList.set(data.webTag, node); + removeWebview(webTag: string) { + const node = this.webviewList.get(webTag); + if (!node) { + return; + } - const controller = this.buildData(data.controller); - // intercept download task - if (typeof data?.onDownloadStart === "function" || typeof data?.onDownloadEnd === "function") { - const download = new web_webview.WebDownloadDelegate(); + this.webviewEntries.delete(webTag); + this.webviewList.delete(webTag); - if (typeof data?.onDownloadStart === "function") { - download.onBeforeDownload((e) => { - const url = e.getUrl(); - const tempPath = e.getFullPath(); - const ret = data.onDownloadStart!(url, tempPath); + try { + this.rootNode?.removeChild(node.getFrameNode()); + } catch (error) { + const detail = `removeWebview(${webTag}) failed: ${describeWebviewError(error)}`; + logWebviewError("overlay/remove", detail); + throw new Error(detail); + } + } - if (ret.allow) { - e.start(ret.tempPath || tempPath); - } else { - e.cancel(); - } - }); - } + getWebviewNode(webTag: string) { + return this.webviewList.get(webTag); + } +} - if (typeof data?.onDownloadEnd === "function") { - download.onDownloadFinish((e) => { - const url = e.getUrl(); - const tempPath = e.getFullPath(); - data.onDownloadEnd!(url, tempPath, true); - }); - download.onDownloadFailed((e) => { - const url = e.getUrl(); - data.onDownloadEnd!(url, undefined, false); - }); - } +export class EmbeddedWebviewManager { + private uiContext: UIContext; + private entries: Map = new Map(); + + constructor(uiContext: UIContext) { + this.uiContext = uiContext; + } - data.controller.setDownloadDelegate(download); + createWebview(data: WebviewInitData): EmbeddedWebviewHandle { + const prepared = ensureWebviewNodeData(data); + const webTag = prepared.webTag!; + setupDownloadDelegate(prepared); + + const content = new ComponentContent( + this.uiContext, + embeddedWebViewWrap, + prepared, + ); + + try { + content.update(prepared); + } catch (error) { + const detail = `createEmbeddedWebview(${webTag}) build failed: ${describeWebviewError(error)}`; + logWebviewError("embedded/build", detail); + throw new Error(detail); } - this.webviewData.set(data.webTag, controller); - this.rootNode?.appendChild(node.getFrameNode()); + const controller = buildJsHelper(prepared.controller); + this.entries.set(webTag, { + data: prepared, + content, + controller, + }); + return { - webTag: data.webTag, + webTag, controller, + content, }; } - getWebviewNode(webTag: string) { - return this.webviewList.get(webTag); + updateWebviewStyle(webTag: string, style: WebviewStyle) { + const entry = this.entries.get(webTag); + if (!entry) { + return; + } + + entry.data.style = style; + try { + entry.content.update(entry.data); + } catch (error) { + const detail = `updateEmbeddedWebviewStyle(${webTag}) failed: ${describeWebviewError(error)}; style={${describeWebviewStyle(style)}}`; + logWebviewError("embedded/update", detail); + throw new Error(detail); + } + } + + removeWebview(webTag: string) { + const entry = this.entries.get(webTag); + if (!entry) { + return; + } + + this.entries.delete(webTag); + try { + entry.content.dispose(); + } catch (error) { + const detail = `disposeEmbeddedWebview(${webTag}) failed: ${describeWebviewError(error)}`; + logWebviewError("embedded/dispose", detail); + throw new Error(detail); + } } } // extend WebviewController method declare class WebviewController extends web_webview.WebviewController { getCookies: (url: string) => string; + setStyle: (style: WebviewStyle) => void; setBackgroundColor: (color: string) => void; setVisible: (visible: boolean) => void; + dispose: () => void; clearAllBrowsingData: () => void; } diff --git a/native_ability/src/main/ets/webview/Utils.ets b/native_ability/src/main/ets/webview/Utils.ets index 2195484..c6356ac 100644 --- a/native_ability/src/main/ets/webview/Utils.ets +++ b/native_ability/src/main/ets/webview/Utils.ets @@ -4,6 +4,13 @@ export const getCookies = (url: string): string => { return webview.WebCookieManager.fetchCookieSync(url); }; +export interface JsWebviewStyle { + x?: number | string; + y?: number | string; + backgroundColor?: string | Color; + visible?: boolean | string; +} + export interface JsHelper { getCookies: (url: string) => string; getUrl: () => string; @@ -15,5 +22,6 @@ export interface JsHelper { runJavaScript: (code: string, callback: (result?: string) => void) => void; setBackgroundColor: (color: string) => void; setVisible: (visible: boolean) => void; + dispose: () => void; clearAllBrowsingData: () => void; } From 29ad8003a296294c87e518fef726a0f1e4bb0237 Mon Sep 17 00:00:00 2001 From: richerfu Date: Wed, 22 Apr 2026 19:47:09 +0800 Subject: [PATCH 2/3] feat: remove useless code --- native_ability/src/main/ets/ability/type.ets | 2 +- .../src/main/ets/webview/DefaultWebview.ets | 201 +++--------------- native_ability/src/main/ets/webview/Utils.ets | 2 +- 3 files changed, 34 insertions(+), 171 deletions(-) diff --git a/native_ability/src/main/ets/ability/type.ets b/native_ability/src/main/ets/ability/type.ets index ebd8f6c..2a7bd67 100644 --- a/native_ability/src/main/ets/ability/type.ets +++ b/native_ability/src/main/ets/ability/type.ets @@ -60,7 +60,7 @@ export interface WebViewInitData { export interface WebViewStyle { x?: number | string; y?: number | string; - visible?: boolean | string; + visible?: boolean; backgroundColor?: string | Color; } diff --git a/native_ability/src/main/ets/webview/DefaultWebview.ets b/native_ability/src/main/ets/webview/DefaultWebview.ets index 7a42bf5..03f06c3 100644 --- a/native_ability/src/main/ets/webview/DefaultWebview.ets +++ b/native_ability/src/main/ets/webview/DefaultWebview.ets @@ -1,19 +1,16 @@ import { UIContext } from "@ohos.arkui.UIContext"; import web_webview from "@ohos.web.webview"; import { ComponentContent, NodeController, BuilderNode, FrameNode } from "@ohos.arkui.node"; -import hilog from "@ohos.hilog"; import { randomString } from "../helper"; import { OnDownloadStartResult } from "../ability/type"; import { getCookies, JsHelper } from "./Utils"; import { WebHeader } from "@kit.ArkUI"; -const TAG = "OHRSWebview"; - export interface WebviewStyle { x?: number | string; y?: number | string; backgroundColor?: string | Color; - visible?: boolean | string; + visible?: boolean; } export interface WebviewInitData { @@ -40,34 +37,6 @@ interface WebviewNodeData extends WebviewInitData { didInitialLoad?: boolean; } -function describeWebviewError(error: ESObject): string { - const message: string = Reflect.get(error, "message"); - if (typeof message === "string" && message.length > 0) { - return message; - } - - try { - return JSON.stringify(error); - } catch (_) { - return `${error}`; - } -} - -function describeStyleValue(value?: number | string): string { - if (typeof value === "number" || typeof value === "string") { - return `${value}`; - } - return "undefined"; -} - -function describeWebviewStyle(style: WebviewStyle): string { - return `x=${describeStyleValue(style.x)}, y=${describeStyleValue(style.y)}, visible=${style.visible}, backgroundColor=${style.backgroundColor}`; -} - -function logWebviewError(stage: string, detail: string) { - hilog.error(0x0000, TAG, `${stage}: ${detail}`); -} - @Builder function WebBuilder(data: WebviewNodeData) { // init with empty url and reload with loadUrl or loadData with onControllerAttach @@ -79,11 +48,7 @@ function WebBuilder(data: WebviewNodeData) { y: data.style?.y ?? 0, }) .backgroundColor(data?.style?.backgroundColor) - .visibility( - data?.style?.visible === false || data?.style?.visible === "hidden" - ? Visibility.Hidden - : Visibility.Visible, - ) + .visibility(data?.style?.visible === false ? Visibility.Hidden : Visibility.Visible) .javaScriptAccess(data?.javascriptEnable) .mediaPlayGestureAccess( typeof data?.autoplay === "boolean" && data.autoplay === true ? false : true, @@ -169,11 +134,7 @@ function EmbeddedWebBuilder(data: WebviewNodeData) { .width("100%") .height("100%") .clip(true) - .visibility( - data?.style?.visible === false || data?.style?.visible === "hidden" - ? Visibility.Hidden - : Visibility.Visible, - ) + .visibility(data?.style?.visible === false ? Visibility.Hidden : Visibility.Visible) .hitTestBehavior(HitTestMode.Default); } @@ -192,7 +153,6 @@ export interface EmbeddedWebviewHandle extends AddWebviewMethod { interface EmbeddedWebviewEntry { data: WebviewNodeData; content: ComponentContent; - controller: JsHelper; } function ensureWebviewNodeData(data: WebviewInitData): WebviewNodeData { @@ -249,13 +209,7 @@ function setupDownloadDelegate(data: WebviewNodeData) { }); } - try { - data.controller.setDownloadDelegate(download); - } catch (error) { - const detail = `setDownloadDelegate(${data.webTag}) failed: ${describeWebviewError(error)}`; - logWebviewError("webview/downloadDelegate", detail); - throw new Error(detail); - } + data.controller.setDownloadDelegate(download); } function buildJsHelper(controller: WebviewController): JsHelper { @@ -266,88 +220,45 @@ function buildJsHelper(controller: WebviewController): JsHelper { return getCookies(url) as string; }; const loadUrl = (url: string, header?: Record) => { - try { - const headers = Object.keys((header || {}) as Record).reduce( - (t, i) => { - if (!!header![i]) { - t.push({ headerKey: i, headerValue: header![i] }); - } - return t; - }, - [] as Array, - ); - controller.loadUrl(url, headers); - } catch (error) { - const detail = `loadUrl(${url}) failed: ${describeWebviewError(error)}`; - logWebviewError("loadUrl", detail); - throw new Error(detail); - } + const headers = Object.keys((header || {}) as Record).reduce( + (t, i) => { + if (!!header![i]) { + t.push({ headerKey: i, headerValue: header![i] }); + } + return t; + }, + [] as Array, + ); + controller.loadUrl(url, headers); }; const loadHtml = (html: string) => { - try { - controller.loadData(html, "text/html", "UTF-8", " ", " "); - } catch (error) { - const detail = `loadHtml failed: ${describeWebviewError(error)}`; - logWebviewError("loadHtml", detail); - throw new Error(detail); - } + controller.loadData(html, "text/html", "UTF-8", " ", " "); }; const zoom = (scale: number) => { - try { - controller.zoom(scale); - } catch (error) { - const detail = `zoom(${scale}) failed: ${describeWebviewError(error)}`; - logWebviewError("zoom", detail); - throw new Error(detail); - } + controller.zoom(scale); }; const refresh = () => { - try { - controller.refresh(); - } catch (error) { - const detail = `refresh failed: ${describeWebviewError(error)}`; - logWebviewError("refresh", detail); - throw new Error(detail); - } + controller.refresh(); }; const requestFocus = () => { - try { - controller.requestFocus(); - } catch (error) { - const detail = `requestFocus failed: ${describeWebviewError(error)}`; - logWebviewError("requestFocus", detail); - throw new Error(detail); - } + controller.requestFocus(); }; const clearAllBrowsingData = () => { - try { - web_webview.WebStorage.deleteAllData(true); - web_webview.WebDataBase.deleteHttpAuthCredentials(); - controller.removeCache(true); - controller.clearHistory(); - } catch (error) { - const detail = `clearAllBrowsingData failed: ${describeWebviewError(error)}`; - logWebviewError("clearAllBrowsingData", detail); - throw new Error(detail); - } + web_webview.WebStorage.deleteAllData(true); + web_webview.WebDataBase.deleteHttpAuthCredentials(); + controller.removeCache(true); + controller.clearHistory(); }; const runJavaScript = (code: string, cb: (result?: string) => void) => { - controller - .runJavaScript(code) - .then((ret) => { - cb(ret); - }) - .catch((error: ESObject) => { - const detail = `runJavaScript failed: ${describeWebviewError(error)}`; - logWebviewError("runJavaScript", detail); - throw new Error(detail); - }); + controller.runJavaScript(code).then((ret) => { + cb(ret); + }); }; return { @@ -382,13 +293,7 @@ export class RustWebviewNodeController extends NodeController { } entry.style = style; - try { - node.update(entry); - } catch (error) { - const detail = `updateWebviewStyle(${webTag}) failed: ${describeWebviewError(error)}; style={${describeWebviewStyle(style)}}`; - logWebviewError("overlay/update", detail); - throw new Error(detail); - } + node.update(entry); } private buildData(controller: WebviewController) { @@ -410,26 +315,14 @@ export class RustWebviewNodeController extends NodeController { } const node = new BuilderNode<[WebviewNodeData]>(this.uiContext!); - try { - node.build(webViewWrap, prepared); - } catch (error) { - const detail = `addWebview(${webTag}) build failed: ${describeWebviewError(error)}`; - logWebviewError("overlay/build", detail); - throw new Error(detail); - } + node.build(webViewWrap, prepared); const controller = this.buildData(prepared.controller); setupDownloadDelegate(prepared); this.webviewEntries.set(webTag, prepared); this.webviewList.set(webTag, node); - try { - this.rootNode?.appendChild(node.getFrameNode()); - } catch (error) { - const detail = `addWebview(${webTag}) append failed: ${describeWebviewError(error)}`; - logWebviewError("overlay/append", detail); - throw new Error(detail); - } + this.rootNode?.appendChild(node.getFrameNode()); return { webTag, @@ -446,17 +339,7 @@ export class RustWebviewNodeController extends NodeController { this.webviewEntries.delete(webTag); this.webviewList.delete(webTag); - try { - this.rootNode?.removeChild(node.getFrameNode()); - } catch (error) { - const detail = `removeWebview(${webTag}) failed: ${describeWebviewError(error)}`; - logWebviewError("overlay/remove", detail); - throw new Error(detail); - } - } - - getWebviewNode(webTag: string) { - return this.webviewList.get(webTag); + this.rootNode?.removeChild(node.getFrameNode()); } } @@ -479,19 +362,12 @@ export class EmbeddedWebviewManager { prepared, ); - try { - content.update(prepared); - } catch (error) { - const detail = `createEmbeddedWebview(${webTag}) build failed: ${describeWebviewError(error)}`; - logWebviewError("embedded/build", detail); - throw new Error(detail); - } + content.update(prepared); const controller = buildJsHelper(prepared.controller); this.entries.set(webTag, { data: prepared, content, - controller, }); return { @@ -508,13 +384,7 @@ export class EmbeddedWebviewManager { } entry.data.style = style; - try { - entry.content.update(entry.data); - } catch (error) { - const detail = `updateEmbeddedWebviewStyle(${webTag}) failed: ${describeWebviewError(error)}; style={${describeWebviewStyle(style)}}`; - logWebviewError("embedded/update", detail); - throw new Error(detail); - } + entry.content.update(entry.data); } removeWebview(webTag: string) { @@ -524,20 +394,13 @@ export class EmbeddedWebviewManager { } this.entries.delete(webTag); - try { - entry.content.dispose(); - } catch (error) { - const detail = `disposeEmbeddedWebview(${webTag}) failed: ${describeWebviewError(error)}`; - logWebviewError("embedded/dispose", detail); - throw new Error(detail); - } + entry.content.dispose(); } } // extend WebviewController method declare class WebviewController extends web_webview.WebviewController { getCookies: (url: string) => string; - setStyle: (style: WebviewStyle) => void; setBackgroundColor: (color: string) => void; setVisible: (visible: boolean) => void; dispose: () => void; diff --git a/native_ability/src/main/ets/webview/Utils.ets b/native_ability/src/main/ets/webview/Utils.ets index c6356ac..892668c 100644 --- a/native_ability/src/main/ets/webview/Utils.ets +++ b/native_ability/src/main/ets/webview/Utils.ets @@ -8,7 +8,7 @@ export interface JsWebviewStyle { x?: number | string; y?: number | string; backgroundColor?: string | Color; - visible?: boolean | string; + visible?: boolean; } export interface JsHelper { From a0ad7e0606de15a2dfcfa3a30c22789d89b64d93 Mon Sep 17 00:00:00 2001 From: richerfu Date: Wed, 22 Apr 2026 22:40:35 +0800 Subject: [PATCH 3/3] fix: clippy lint --- crates/ability/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ability/src/app.rs b/crates/ability/src/app.rs index d33a30d..cebae34 100644 --- a/crates/ability/src/app.rs +++ b/crates/ability/src/app.rs @@ -541,7 +541,7 @@ impl OpenHarmonyApp { Ok(requested_permissions .into_iter() - .zip(codes.into_iter()) + .zip(codes) .map(|(permission, code)| PermissionRequestCode { permission, code }) .collect()) }