diff --git a/harmony/rn_webview.har b/harmony/rn_webview.har index 70da08c2b..e4537ef8d 100644 Binary files a/harmony/rn_webview.har and b/harmony/rn_webview.har differ diff --git a/harmony/rn_webview/src/main/ets/RNCWebView.ets b/harmony/rn_webview/src/main/ets/RNCWebView.ets index eb754266b..88fc25aec 100644 --- a/harmony/rn_webview/src/main/ets/RNCWebView.ets +++ b/harmony/rn_webview/src/main/ets/RNCWebView.ets @@ -22,17 +22,18 @@ * SOFTWARE. */ -import { Descriptor, RNOHContext, ViewBaseProps } from '@rnoh/react-native-openharmony'; +import { Descriptor, RNComponentContext, ViewBaseProps } from '@rnoh/react-native-openharmony'; import webview from '@ohos.web.webview'; -import { CallbackState, ShouldRequestUrl } from './ShouldRequestUrl'; import { RNC } from '@rnoh/react-native-openharmony/generated'; import Logger from './Logger'; +import { CallbackState, ShouldRequestUrl } from './ShouldRequestUrl'; export class WebViewNewSourceHeader { name?: string value?: string } + export class WebViewNewSource { uri?: string | Resource method?: string @@ -61,15 +62,18 @@ export interface WebViewProps extends ViewBaseProps { domStorageEnabled: boolean scalesPageToFit: boolean messagingModuleName: string - shouldStartLoadWithRequestEnabled: boolean webviewDebuggingEnabled: boolean - // nestedScrollEnabled: boolean scrollEnabled: boolean + incognito: boolean + userAgent: string + shouldStartLoadWithRequestEnabled: boolean } + export class RNCWebViewBridge { postMessage!: (data: string) => void; } + export class WebViewEventParams { type: string url?: string @@ -85,10 +89,15 @@ export class WebViewEventParams { } } -export enum CACHE_MODE { +export enum ShouldStartLoadWithRequestEnabledStatus { + START = 0, + END = 1, +} + +export enum CACHE_MODE { 'LOAD_DEFAULT' = 'LOAD_DEFAULT', - 'LOAD_CACHE_ELSE_NETWORK' = 'LOAD_CACHE_ELSE_NETWORK' , - 'LOAD_NO_CACHE' = 'LOAD_NO_CACHE' , + 'LOAD_CACHE_ELSE_NETWORK' = 'LOAD_CACHE_ELSE_NETWORK', + 'LOAD_NO_CACHE' = 'LOAD_NO_CACHE', 'LOAD_CACHE_ONLY' = 'LOAD_CACHE_ONLY', } @@ -101,7 +110,8 @@ export class ResultType { lockIdentifier: number data: string - constructor(url: string, loading: boolean, title: string, canGoBack: boolean, canGoForward: boolean, lockIdentifier: number, data: string) { + constructor(url: string, loading: boolean, title: string, canGoBack: boolean, canGoForward: boolean, + lockIdentifier: number, data: string) { this.url = url this.loading = loading this.title = title @@ -117,7 +127,7 @@ export type WebViewViewDescriptor = Descriptor<"RNCWebView", WebViewProps> @Component export struct RNCWebView { public static readonly NAME = RNC.RNCWebView.NAME - ctx!: RNOHContext + ctx!: RNComponentContext tag: number = 0 source: WebViewNewSource = { uri: "", @@ -126,10 +136,9 @@ export struct RNCWebView { html: "", baseUrl: "" } + @State descriptor: WebViewViewDescriptor = {} as WebViewViewDescriptor html: string | undefined = "" url: string | Resource = "" - private unregisterDescriptorChangesListener?: () => void = undefined - private cleanupCommandCallback?: () => void = undefined controller: webview.WebviewController = new webview.WebviewController(); javaScriptEnable: boolean = true overScrollMode: OverScrollMode = OverScrollMode.NEVER @@ -143,96 +152,91 @@ export struct RNCWebView { // nestedScrollFlag: boolean = false; renderMode: RenderMode = RenderMode.SYNC_RENDER; scrollEnabled = true; + @State onLoadInterceptStatus: number = ShouldStartLoadWithRequestEnabledStatus.START + private unregisterDescriptorChangesListener?: () => void = undefined + private cleanupCommandCallback?: () => void = undefined private eventEmitter: RNC.RNCWebView.EventEmitter | undefined = undefined private cleanUpCallbacks: (() => void)[] = [] private descriptorWrapper: RNC.RNCWebView.DescriptorWrapper = {} as RNC.RNCWebView.DescriptorWrapper - private onDescriptorWrapperChange(descriptorWrapper: RNC.RNCWebView.DescriptorWrapper) { - this.descriptorWrapper = descriptorWrapper - this.initVariable() - if (this.html != "" && this.html != this.source.html) { - Logger.debug(TAG, "[RNOH] html is update") - this.html = this.source.html - if (this.controllerAttached) { - try { - this.controller.loadData( - this.source.html, - "text/html", - "UTF-8", - this.source.baseUrl, - " " - ); - } catch (error) { - Logger.error(TAG, "error: " + error) - } - } - } else if (this.source.uri != "" && this.url != this.source.uri) { - Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri); - this.url = this.source.uri as string; - if (this.controllerAttached) { - this.controller.loadUrl(this.descriptorWrapper.props.newSource.uri) - } - } - if (this.controllerAttached) { - this.controller?.setScrollable(this.descriptorWrapper.props.scrollEnabled) - } + sendEvent(eventNmae: string, payload: Payload) { + this.ctx.rnInstance.emitDeviceEvent(eventNmae, payload) } aboutToAppear() { this.eventEmitter = new RNC.RNCWebView.EventEmitter(this.ctx.rnInstance, this.tag) - this.descriptorWrapper = this.ctx.descriptorRegistry.findDescriptorWrapperByTag(this.tag)! - - this.initVariable() + this.onDescriptorWrapperChange(this.ctx.descriptorRegistry.findDescriptorWrapperByTag(this.tag)!) this.cleanUpCallbacks.push(this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (_descriptor, newDescriptorWrapper) => { this.onDescriptorWrapperChange(newDescriptorWrapper! as RNC.RNCWebView.DescriptorWrapper) } )) + if (this.descriptorWrapper.props.overScrollMode === 'always') { + this.overScrollMode = OverScrollMode.ALWAYS + } else if (this.descriptorWrapper.props.overScrollMode === 'never') { + this.overScrollMode = OverScrollMode.NEVER + } - this.registerCommandCallback() - } - - private initVariable() { + // this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, + // (newDescriptor) => { + // this.descriptor = (newDescriptor as WebViewViewDescriptor) + // Logger.debug(TAG, `[RNOH] newDescriptor props uri, ${JSON.stringify(this.descriptor.props.newSource.uri)}`); + // this.cacheMode = + // this.descriptor.props.cacheEnabled ? this.transCacheMode(this.descriptor.props.cacheMode) : CacheMode.Online; + // this.javaScriptEnable = this.descriptor.props.javaScriptEnabled; + // this.source = this.descriptor.props.newSource + // if (this.html != "" && this.html != this.source.html) { + // Logger.debug(TAG, "[RNOH] html is update") + // this.html = this.source.html + // if (this.controllerAttached) { + // try { + // this.controller.loadData( + // this.source.html, + // "text/html", + // "UTF-8", + // this.source.baseUrl, + // " " + // ); + // } catch (error) { + // Logger.error(TAG, "error: " + error) + // } + // } + // } else if (this.source.uri != "" && this.url != this.source.uri) { + // Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri); + // this.url = this.source.uri + // if (this.controllerAttached) { + // this.controller.loadUrl(this.descriptor.props.newSource.uri) + // } + // } + // }) + // webview.WebviewController.setWebDebuggingAccess(this.descriptor.props.webviewDebuggingEnabled) + this.scrollEnabled = this.descriptorWrapper.props.scrollEnabled; this.javaScriptEnable = this.descriptorWrapper.props.javaScriptEnabled; this.cacheMode = - this.descriptorWrapper.props.cacheEnabled ? this.transCacheMode(this.descriptorWrapper.props.cacheMode as CACHE_MODE) : CacheMode.Online; + this.descriptorWrapper.props.cacheEnabled ? + this.transCacheMode(this.descriptorWrapper.props.cacheMode as CACHE_MODE) : CacheMode.Online; this.source = this.descriptorWrapper.props.newSource this.html = this.source.html if (this.source.uri && this.source.uri.toString().startsWith("asset://")) { this.source.uri = $rawfile(this.source.uri.toString().replace("asset://", this.ctx.rnInstance.getAssetsDest())); } this.url = this.source.uri as string; - - if(this.descriptorWrapper.props.overScrollMode === 'always') { - this.overScrollMode = OverScrollMode.ALWAYS - } else if(this.descriptorWrapper.props.overScrollMode === 'never') { - this.overScrollMode = OverScrollMode.NEVER + // this.nestedScrollFlag = this.descriptor.props.nestedScrollEnabled; + // nestedScrollEnabled true表示可嵌套滚动,不需要自适应和web统一渲染,默认是false + // this.renderMode = this.nestedScrollFlag?RenderMode.ASYNC_RENDER:RenderMode.SYNC_RENDER + this.overScrollMode = this.descriptorWrapper.props.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER + this.registerCommandCallback() + if (this.hasIncognito()) { + this.cacheMode = CacheMode.Online; + webview.WebCookieManager.putAcceptCookieEnabled(false); } - - this.overScrollMode = this.descriptorWrapper.props.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER; + setTimeout(() => { + console.log('descriptorWrapper===>' + JSON.stringify(this.descriptorWrapper.props)) + }, 0) } - private registerPostMessage() { - if (this.messagingEnabled == this.descriptorWrapper.props.messagingEnabled) { - return; - } - this.messagingEnabled = this.descriptorWrapper.props.messagingEnabled; - if (this.messagingEnabled) { - let bridge: RNCWebViewBridge = { - postMessage: (data: string) => { - Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`); - if (this.controller != null) { - let result: WebViewEventParams = this.createWebViewEvent("onMessage") - result.data = data - result.lockIdentifier = 0 - this.eventEmitter!.emit("message", result as ResultType); - } - } - }; - this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"]) - this.controller.refresh() - this.hasRegisterJavaScriptProxy = true - } + hasIncognito(): boolean { + return this.descriptorWrapper.props.incognito && this.descriptorWrapper.props.incognito === true } createWebViewEvent(param: string): WebViewEventParams { @@ -262,6 +266,9 @@ export struct RNCWebView { this.controller.deleteJavaScriptRegister(JAVASCRIPT_INTERFACE) this.controller.refresh() this.hasRegisterJavaScriptProxy = false + if (this.hasIncognito()) { + this.controller.clearHistory() + } } catch (error) { Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`); } @@ -432,7 +439,7 @@ export struct RNCWebView { }) } - onHttpError(code: number,description: string) { + onHttpError(code: number, description: string) { this.eventEmitter!.emit('httpError', { url: this.controller.getUrl(), loading: false, @@ -491,8 +498,9 @@ export struct RNCWebView { } build() { + //RNViewBase({ ctx: this.ctx, tag: this.tag }) { Stack() { - Web({ src: this.source.uri || '', controller: this.controller, renderMode: this.renderMode }) + Web({ src: this.source.uri, controller: this.controller, renderMode: this.renderMode }) .width(this.descriptorWrapper.width) .height(this.descriptorWrapper.height) .constraintSize({ minHeight: 1 }) @@ -506,11 +514,26 @@ export struct RNCWebView { .cacheMode(this.cacheMode) .domStorageAccess(this.descriptorWrapper.props.domStorageEnabled) .zoomAccess(this.descriptorWrapper.props.scalesPageToFit)// nestedScrollFlag 为true 表示可以在嵌套滚动中,web自己能滚动 - .overScrollMode(this.overScrollMode) + .overScrollMode(this.overScrollMode)// .layoutMode(this.nestedScrollFlag ? WebLayoutMode.NONE : WebLayoutMode.FIT_CONTENT) + // .userAgent(this.descriptorWrapper.props.userAgent ? this.descriptorWrapper.props.userAgent : + // '')// .nestedScroll(this.nestedScrollFlag ? { + // scrollForward: NestedScrollMode.SELF_ONLY, + // scrollBackward: NestedScrollMode.SELF_ONLY + // } : { scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST }) + // .overScrollMode(OverScrollMode.NEVER) + // .onSizeChange((event)=>{ + // Logger.debug(TAG, "[RNOH] event width: " + event.width + "[RNOH] event height: " + event.height) + // }) .onProgressChange((event) => { + console.log('onProgressChangeonProgressChange====>' + JSON.stringify(event)) if (event) { this.progress = event.newProgress - Logger.debug(TAG, "[RNOH] event progress: " + event.newProgress) + this.sendEvent('onLoadProgress', event.newProgress as number) + } + }) + .onScroll((event) => { + if (event) { + this.sendEvent('onScroll', event) } }) .onPageBegin(() => { @@ -526,30 +549,43 @@ export struct RNCWebView { if (event) { let errorInfo: string = event.error.getErrorInfo(); let errorCode: number = event.error.getErrorCode(); - if (!event.request.isMainFrame()) { - Logger.debug(TAG, "[RNOH] ERR_INTERNET_DISCONNECTED:OR ERR_CACHE_MISS") - return - } Logger.debug(TAG, "[RNOH] errorInfo:" + errorInfo) Logger.debug(TAG, "[RNOH] errorCode:" + errorCode) this.onLoadingError(errorCode, errorInfo) } }) .onHttpErrorReceive((event) => { - console.log('onHttpErrorReceiveevent',event) + console.log('onHttpErrorReceiveevent', event) if (event) { let errorInfo: string = event.response.getResponseData(); let code: number = event.response.getResponseCode() - console.log('onHttpErrorReceive',errorInfo) + console.log('onHttpErrorReceive', errorInfo) Logger.debug(TAG, "[RNOH] errorInfo:" + errorInfo) - this.onHttpError(code,errorInfo) + this.onHttpError(code, errorInfo) } }) .onLoadIntercept((event) => { - if (!this.descriptorWrapper.props.shouldStartLoadWithRequestEnabled) { - Logger.debug(TAG, - "[RNOH]:shouldStartLoadWithRequestEnabled:" + this.descriptorWrapper.props.shouldStartLoadWithRequestEnabled) - return false + if (event) { + if (this.onLoadInterceptStatus === ShouldStartLoadWithRequestEnabledStatus.START) { + this.sendEvent('onLoadIntercept', JSON.stringify({ + url: event.data.getRequestUrl(), + loading: this.progress != 100, + title: this.controller.getTitle(), + canGoBack: this.controller.accessBackward(), + canGoForward: this.controller.accessForward(), + lockIdentifier: 11, + navigationType: "other", + mainDocumentURL: "", + isTopFrame: event.data.isMainFrame(), + status: this.onLoadInterceptStatus + })) + this.onLoadInterceptStatus = ShouldStartLoadWithRequestEnabledStatus.END + setTimeout(() => { + if (this.descriptorWrapper.props.shouldStartLoadWithRequestEnabled) { + this.controller.refresh() + } + }) + } } if (this.source.html != undefined && this.source.html != '') { Logger.debug(TAG, "[RNOH]: load html not intercept") @@ -611,10 +647,81 @@ export struct RNCWebView { if (!this.hasRegisterJavaScriptProxy) { this.registerPostMessage() } + if (this.descriptorWrapper.props.userAgent) { + try { + let userAgent = this.controller.getUserAgent() + this.descriptorWrapper.props.userAgent; + this.controller.setCustomUserAgent(userAgent); + } catch (error) { + console.error(`ErrorCode: ${error.code}, Message: ${error.message}, userAgent: ${this.descriptorWrapper.props.userAgent}`); + } + } }) } .width(this.descriptorWrapper.width) .height(this.descriptorWrapper.height) - .position({ x: this.descriptorWrapper.positionRelativeToParent.x, y: this.descriptorWrapper.positionRelativeToParent.y }) + .position({ + x: this.descriptorWrapper.positionRelativeToParent.x, + y: this.descriptorWrapper.positionRelativeToParent.y + }) + } + + private onDescriptorWrapperChange(descriptorWrapper: RNC.RNCWebView.DescriptorWrapper) { + this.descriptorWrapper = descriptorWrapper + + Logger.debug(TAG, `[RNOH] newDescriptor props uri, ${JSON.stringify(this.descriptorWrapper.props.newSource.uri)}`); + this.cacheMode = + this.descriptorWrapper.props.cacheEnabled ? + this.transCacheMode(this.descriptorWrapper.props.cacheMode as CACHE_MODE) : CacheMode.Online; + this.javaScriptEnable = this.descriptorWrapper.props.javaScriptEnabled; + this.source = this.descriptorWrapper.props.newSource + if (this.html != "" && this.html != this.source.html) { + Logger.debug(TAG, "[RNOH] html is update") + this.html = this.source.html + if (this.controllerAttached) { + try { + this.controller.loadData( + this.source.html, + "text/html", + "UTF-8", + this.source.baseUrl, + " " + ); + } catch (error) { + Logger.error(TAG, "error: " + error) + } + } + } else if (this.source.uri != "" && this.url != this.source.uri) { + Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri); + this.url = this.source.uri as string; + if (this.controllerAttached) { + this.controller.loadUrl(this.descriptorWrapper.props.newSource.uri) + } + } + if (this.controllerAttached) { + this.controller?.setScrollable(this.descriptorWrapper.props.scrollEnabled) + } + } + + private registerPostMessage() { + if (this.messagingEnabled == this.descriptorWrapper.props.messagingEnabled) { + return; + } + this.messagingEnabled = this.descriptorWrapper.props.messagingEnabled; + if (this.messagingEnabled) { + let bridge: RNCWebViewBridge = { + postMessage: (data: string) => { + Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`); + if (this.controller != null) { + let result: WebViewEventParams = this.createWebViewEvent("onMessage") + result.data = data + result.lockIdentifier = 0 + this.eventEmitter!.emit("message", result as ResultType); + } + } + }; + this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"]) + this.controller.refresh() + this.hasRegisterJavaScriptProxy = true + } } } \ No newline at end of file