@react-native-ohos/react-native-webview 13.15.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.OpenSource +11 -0
  3. package/README.md +13 -0
  4. package/harmony/rn_webview/BuildProfile.ets +17 -0
  5. package/harmony/rn_webview/OAT.xml +45 -0
  6. package/harmony/rn_webview/build-profile.json5 +28 -0
  7. package/harmony/rn_webview/hvigorfile.ts +1 -0
  8. package/harmony/rn_webview/index.ets +13 -0
  9. package/harmony/rn_webview/oh-package.json5 +13 -0
  10. package/harmony/rn_webview/src/main/cpp/CMakeLists.txt +9 -0
  11. package/harmony/rn_webview/src/main/cpp/WebViewPackage.h +17 -0
  12. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/BaseReactNativeWebviewPackage.h +95 -0
  13. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/components/RNCWebViewJSIBinder.h +119 -0
  14. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.cpp +18 -0
  15. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.h +16 -0
  16. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.cpp +17 -0
  17. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.h +16 -0
  18. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ComponentDescriptors.h +24 -0
  19. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.cpp +241 -0
  20. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.h +263 -0
  21. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.cpp +103 -0
  22. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.h +509 -0
  23. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.cpp +17 -0
  24. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.h +32 -0
  25. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.cpp +16 -0
  26. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.h +29 -0
  27. package/harmony/rn_webview/src/main/ets/CutomReference.ts +30 -0
  28. package/harmony/rn_webview/src/main/ets/Logger.ts +44 -0
  29. package/harmony/rn_webview/src/main/ets/Magic.ets +184 -0
  30. package/harmony/rn_webview/src/main/ets/RNCWebView.ets +571 -0
  31. package/harmony/rn_webview/src/main/ets/RNCWebViewPackage.ets +38 -0
  32. package/harmony/rn_webview/src/main/ets/ShouldRequestUrl.ts +48 -0
  33. package/harmony/rn_webview/src/main/ets/WebViewBaseOperate.ets +443 -0
  34. package/harmony/rn_webview/src/main/ets/WebViewTurboModule.ets +58 -0
  35. package/harmony/rn_webview/src/main/ets/generated/components/RNCWebView.ts +524 -0
  36. package/harmony/rn_webview/src/main/ets/generated/components/ts.ts +5 -0
  37. package/harmony/rn_webview/src/main/ets/generated/index.ets +5 -0
  38. package/harmony/rn_webview/src/main/ets/generated/ts.ts +6 -0
  39. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebView.ts +18 -0
  40. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebViewModule.ts +16 -0
  41. package/harmony/rn_webview/src/main/ets/generated/turboModules/ts.ts +6 -0
  42. package/harmony/rn_webview/src/main/module.json5 +11 -0
  43. package/harmony/rn_webview/src/main/resources/base/element/string.json +40 -0
  44. package/harmony/rn_webview/src/main/resources/en_US/element/string.json +40 -0
  45. package/harmony/rn_webview/src/main/resources/zh_CN/element/string.json +40 -0
  46. package/harmony/rn_webview/src/test/List.test.ets +5 -0
  47. package/harmony/rn_webview/src/test/LocalUnit.test.ets +33 -0
  48. package/harmony/rn_webview/ts.ets +9 -0
  49. package/harmony/rn_webview.har +0 -0
  50. package/package.json +99 -0
  51. package/src/NativeRNCWebView.ts +22 -0
  52. package/src/NativeRNCWebViewModule.ts +19 -0
  53. package/src/RNCWebViewNativeComponent.ts +354 -0
  54. package/src/WebView.harmony.tsx +343 -0
  55. package/src/WebView.tsx +10 -0
  56. package/src/codegenUtils.ts +11 -0
  57. package/src/index.ts +10 -0
@@ -0,0 +1,571 @@
1
+ // Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved
2
+ // Use of this source code is governed by a Apache-2.0 license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import { RNComponentContext } from '@rnoh/react-native-openharmony';
6
+ import webview from '@ohos.web.webview';
7
+ import { url as OSUrl } from '@kit.ArkTS';
8
+ import { RNC, TM } from './generated';
9
+ import Logger from './Logger';
10
+ import { BaseOperate } from './WebViewBaseOperate';
11
+ import {
12
+ AlertEvent,
13
+ CACHE_MODE,
14
+ JAVASCRIPT_INTERFACE,
15
+ ONE_HUNDRED,
16
+ EIGHT,
17
+ ResultType,
18
+ RNCWebViewBridge,
19
+ WebViewEventParams,
20
+ WebViewNewSource,
21
+ WebViewOverScrollMode,
22
+ ZERO,
23
+ COMMAND_NAME,
24
+ WebViewDescriptor,
25
+ REFRESH_OFFSET,
26
+ MINHEIGHT,
27
+ } from './Magic';
28
+
29
+ import { bundleManager } from '@kit.AbilityKit';
30
+ import { WebViewTurboModule, ShouldStartParams, ShouldOverrideCallbackState } from './WebViewTurboModule';
31
+ import { AnyThreadRNInstance, WorkerTaskRunnable } from '@rnoh/react-native-openharmony/src/main/ets/RNOH';
32
+ import buffer from '@ohos.buffer';
33
+
34
+ export const TAG = "WebView"
35
+
36
+ export const WEB_VIEW = "RNCWebView"
37
+
38
+ @Sendable
39
+ class WorkerRunnable implements WorkerTaskRunnable<ShouldStartParams> {
40
+ run(rnInstance: AnyThreadRNInstance, params: ShouldStartParams): void {
41
+ rnInstance.getTurboModule<WebViewTurboModule>(TM.RNCWebViewModule.NAME).setShouldStartParams(params)
42
+ }
43
+ }
44
+
45
+ @Component
46
+ export struct RNCWebView {
47
+ public static readonly NAME = RNC.RNCWebView.NAME
48
+ ctx!: RNComponentContext
49
+ tag: number = ZERO
50
+ source: WebViewNewSource = {
51
+ uri: "",
52
+ method: "",
53
+ body: "",
54
+ html: "",
55
+ baseUrl: ""
56
+ }
57
+ html: string | undefined = "";
58
+ url: string | Resource = "";
59
+ body: string | undefined = "";
60
+ controller: webview.WebviewController = new webview.WebviewController();
61
+ javaScriptEnable: boolean = true;
62
+ allowFileAccess: boolean = true;
63
+ forceDark: boolean = true;
64
+ minFontSize: number = EIGHT;
65
+ overScrollMode: OverScrollMode = OverScrollMode.NEVER;
66
+ progress: number = ZERO;
67
+ cacheMode: number = CacheMode.Default;
68
+ requestUrl: string = "";
69
+ bundleName: string = "";
70
+ messagingEnabled: boolean = false;
71
+ hasRegisterJavaScriptProxy: boolean = false;
72
+ controllerAttached: boolean = false;
73
+ renderMode: RenderMode = RenderMode.SYNC_RENDER;
74
+ scrollEnabled = true;
75
+ nestedScroll = NestedScrollMode.SELF_FIRST;
76
+ headers: Array<webview.WebHeader> = []
77
+ injectedJavaScriptBeforeContentLoaded: Array<ScriptItem> = [
78
+ { script: '', scriptRules: ["*"] }
79
+ ];
80
+ allowPageStartInProgress = true;
81
+ @State webviewWidth: number = ZERO
82
+ @State webviewHeight: number = ZERO
83
+ @State isRefreshing: boolean = false
84
+ @State mode: MixedMode = MixedMode.All;
85
+ private unregisterDescriptorChangesListener?: () => void = undefined
86
+ private cleanupCommandCallback?: () => void = undefined
87
+ private eventEmitter: RNC.RNCWebView.EventEmitter | undefined = undefined
88
+ private cleanUpCallbacks: (() => void)[] = []
89
+ private descriptorWrapper: WebViewDescriptor = Object() as WebViewDescriptor
90
+ private webViewBaseOperate: BaseOperate | null = null
91
+ private shouldInterceptLoad: boolean = true
92
+ private callLoadTimer: number = -1
93
+ static readonly SHOULD_OVERRIDE_URL_LOADING_TIMEOUT: number = 250
94
+
95
+ aboutToAppear() {
96
+ try {
97
+ this.initVariable()
98
+ this.getBundleName()
99
+ this.url = this.source.uri as string;
100
+ this.onDescriptorWrapperChange(this.descriptorWrapper)
101
+ this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag,
102
+ (newDescriptor) => {
103
+ this.onDescriptorWrapperChange(newDescriptor as WebViewDescriptor)
104
+ }
105
+ )
106
+ BaseOperate.setThirdPartyCookiesEnabled(this.descriptorWrapper.rawProps.thirdPartyCookiesEnabled)
107
+ webview.WebviewController.setWebDebuggingAccess(this.descriptorWrapper.rawProps.webviewDebuggingEnabled)
108
+ this.registerCommandCallback()
109
+ if (this.hasIncognito()) {
110
+ this.cacheMode = CacheMode.Online;
111
+ }
112
+ if (this.source.headers) {
113
+ this.source.headers.forEach(item => {
114
+ if (item.name && item.value) {
115
+ this.headers.push({ headerKey: item.name, headerValue: item.value })
116
+ }
117
+ })
118
+ }
119
+ } catch (error) {
120
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
121
+ }
122
+
123
+ }
124
+
125
+ hasIncognito(): boolean {
126
+ return this.descriptorWrapper.rawProps.incognito && this.descriptorWrapper.rawProps.incognito === true
127
+ }
128
+
129
+ createWebViewEvent(type: string): WebViewEventParams {
130
+ return this.webViewBaseOperate?.createWebViewEvent({ type, progress: this.progress }) as WebViewEventParams;
131
+ }
132
+
133
+ aboutToDisappear() {
134
+ this.getUIContext().getFocusController().clearFocus()
135
+ this.cleanupCommandCallback?.()
136
+ this.unregisterDescriptorChangesListener?.()
137
+ this.cleanUpCallbacks.forEach(cb => cb())
138
+ try {
139
+ if (this.hasIncognito()) {
140
+ this.webViewBaseOperate?.setIncognito()
141
+ }
142
+ this.controller.deleteJavaScriptRegister(JAVASCRIPT_INTERFACE)
143
+ this.controller.refresh()
144
+ this.hasRegisterJavaScriptProxy = false
145
+ this.webViewBaseOperate = null
146
+ } catch (error) {
147
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
148
+ }
149
+ }
150
+
151
+ registerCommandCallback() {
152
+ this.cleanupCommandCallback = this.ctx.componentCommandReceiver.registerCommandCallback(
153
+ this.tag,
154
+ (command: COMMAND_NAME, args: string[]) => this.webViewBaseOperate?.registerCommandCallback(command, args));
155
+ }
156
+
157
+ onLoadingStart() {
158
+ this.webViewBaseOperate?.emitLoadingStart({ progress: this.progress })
159
+ }
160
+
161
+ onProgressChange(event: OnProgressChangeEvent) {
162
+ this.progress = event.newProgress
163
+ // 修复单页面应用切换路由时 onPageBegin 未触发的问题
164
+ if (this.controller.getUrl() !== this.url && this.progress < ONE_HUNDRED && this.allowPageStartInProgress) {
165
+ this.allowPageStartInProgress = false;
166
+ this.onLoadingStart();
167
+ }
168
+ this.webViewBaseOperate?.emitProgressChange({ progress: this.progress })
169
+ if (this.progress === ONE_HUNDRED) {
170
+ this.allowPageStartInProgress = true;
171
+ }
172
+ }
173
+
174
+ onLoadingFinish() {
175
+ this.webViewBaseOperate?.emitLoadingFinish({ progress: this.progress })
176
+ this.isRefreshing = false
177
+ }
178
+
179
+ runInjectedJavaScript() {
180
+ let injectedJS = this.descriptorWrapper.rawProps.injectedJavaScript
181
+ if (this.javaScriptEnable && injectedJS != "" && this.controllerAttached) {
182
+ try {
183
+ this.controller.runJavaScript("(function() {\n" + injectedJS + ";\n})();")
184
+ .then((result) => {
185
+ Logger.debug(TAG, '[RNOH] result: ' + result);
186
+ })
187
+ .catch((error: string | Error) => {
188
+ Logger.error(TAG, "[RNOH] error: " + error);
189
+ })
190
+ } catch (error) {
191
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
192
+ }
193
+ }
194
+ }
195
+
196
+ getJsDialogTitle() {
197
+ const url = this.controller.getUrl()
198
+ // 对于 data: URL,只显示 'JavaScript',类似于 Chrome
199
+ let title = 'JavaScript';
200
+ const isDataUrl = (url !== null) && url.startsWith("data:");
201
+
202
+
203
+ if (!isDataUrl) {
204
+ try {
205
+ const dialogUrl = OSUrl.URL.parseURL(url);
206
+ title = `网址为”${dialogUrl.protocol}//${dialogUrl.host}“的网页显示:`;
207
+ } catch (ex) {
208
+ title = url;
209
+ }
210
+ }
211
+
212
+ return title;
213
+ }
214
+
215
+ onJavascriptAlert(event?: AlertEvent) {
216
+ if (event) {
217
+ AlertDialog.show({
218
+ title: this.getJsDialogTitle(),
219
+ message: event.message,
220
+ alignment: DialogAlignment.Center,
221
+ secondaryButton: {
222
+ value: this.resourceToString($r('app.string.determined')),
223
+ action: () => event.result.handleConfirm()
224
+ },
225
+ cancel: () => event.result.handleCancel(),
226
+ })
227
+ }
228
+ return true
229
+ }
230
+
231
+ onJavascriptConfirm(event?: AlertEvent) {
232
+ if (event) {
233
+ AlertDialog.show({
234
+ title: this.getJsDialogTitle(),
235
+ message: event.message,
236
+ alignment: DialogAlignment.Center,
237
+ secondaryButton: {
238
+ value: this.resourceToString($r('app.string.determined')),
239
+ action: () => event.result.handleConfirm()
240
+ },
241
+ cancel: () => event.result.handleCancel(),
242
+ primaryButton: {
243
+ value: this.resourceToString($r('app.string.cancel')),
244
+ action: () => event.result.handleCancel()
245
+ },
246
+ })
247
+ }
248
+ return true
249
+ }
250
+
251
+ ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch: boolean) {
252
+ this.webViewBaseOperate?.ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch)
253
+ }
254
+
255
+ loadHtmlData(html: string = '', url: string = '') {
256
+ try {
257
+ this.controller.loadData(
258
+ html,
259
+ "text/html",
260
+ "UTF-8",
261
+ url,
262
+ " "
263
+ );
264
+ } catch (error) {
265
+ Logger.error(TAG, "error:" + error)
266
+ }
267
+ }
268
+
269
+ controllerAttachedInit(): void {
270
+ this.controllerAttached = true;
271
+ this.eventEmitter = new RNC.RNCWebView.EventEmitter(this.ctx.rnInstance, this.tag)
272
+ this.webViewBaseOperate = new BaseOperate(this.eventEmitter, this.controller)
273
+ this.webViewBaseOperate.setCustomUserAgent(this.descriptorWrapper.rawProps.userAgent,
274
+ this.descriptorWrapper.rawProps.applicationNameForUserAgent)
275
+ this.webViewBaseOperate.setFraudulentWebsiteWarningEnabled(this.descriptorWrapper.rawProps.fraudulentWebsiteWarningEnabled)
276
+ let baseUrl = this.source.baseUrl
277
+ let uri = this.source.uri
278
+ if (this.source.html != undefined && this.source.html != "") {
279
+ this.loadHtmlData(this.source.html, baseUrl)
280
+ } else if (uri != undefined && uri != "") {
281
+ if (this.source.method && this.source.method.toUpperCase() === 'POST' && this.source.body) {
282
+ let postData = buffer.from(this.source.body);
283
+ let url = uri as string;
284
+ this.controller.postUrl(url, postData?.buffer);
285
+ } else {
286
+ this.controller.loadUrl(uri, this.headers);
287
+ }
288
+ } else {
289
+ this.loadHtmlData()
290
+ }
291
+ if (!this.hasRegisterJavaScriptProxy) {
292
+ this.registerPostMessage()
293
+ }
294
+ }
295
+
296
+ onPageBegin() {
297
+ try {
298
+ if (this.controller.getUrl() === this.url) {
299
+ this.onLoadingStart();
300
+ }
301
+ this.controller.setScrollable(this.scrollEnabled)
302
+ this.ignoreSilentHardwareSwitchMethods(this.descriptorWrapper.rawProps.ignoreSilentHardwareSwitch)
303
+ } catch (error) {
304
+ Logger.debug(TAG,
305
+ `[RNOH] setignoreSilentHardwareSwitch and setScrollable ErrorCode: ${error.code}, Message: ${error.message}, userAgent: ${this.descriptorWrapper.rawProps.userAgent}`);
306
+ }
307
+ }
308
+
309
+ getBundleName() {
310
+ let bundleFlags =
311
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY |
312
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
313
+
314
+ try {
315
+ bundleManager.getBundleInfoForSelf(bundleFlags, (error, data) => {
316
+ if (error) {
317
+ Logger.error(TAG,
318
+ `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
319
+ } else {
320
+ this.bundleName = JSON.stringify(data.name)
321
+ }
322
+ });
323
+ } catch (error) {
324
+ Logger.error(TAG,
325
+ `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
326
+ }
327
+ }
328
+
329
+ resourceToString(resource: Resource): string {
330
+ return getContext(this).resourceManager.getStringSync(resource)
331
+ }
332
+
333
+ onPageEnd() {
334
+ this.runInjectedJavaScript()
335
+ this.onLoadingFinish()
336
+ }
337
+
338
+ getPermissionDialogMessage(event: OnPermissionRequestEvent) {
339
+ let permissionDialogMessage = ''
340
+ permissionDialogMessage = event.request.getAccessibleResource().toString()
341
+ switch (permissionDialogMessage) {
342
+ case ProtectedResourceType.VIDEO_CAPTURE:
343
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera'))
344
+ break;
345
+ case ProtectedResourceType.AUDIO_CAPTURE:
346
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_microphone'))
347
+ break;
348
+ case ProtectedResourceType.MidiSysex:
349
+ permissionDialogMessage = this.resourceToString($r('app.string.midi_sysex_resources'))
350
+ break;
351
+ case ProtectedResourceType.VIDEO_CAPTURE + "," + ProtectedResourceType.AUDIO_CAPTURE:
352
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera_and_microphone'))
353
+ break;
354
+ }
355
+ return permissionDialogMessage;
356
+ }
357
+
358
+ onLoadIntercept(event: OnLoadInterceptEvent): boolean {
359
+ Logger.debug(TAG, `onLoadIntercept start`)
360
+ this.webViewBaseOperate?.setLockIdentifier(BaseOperate.generateLockIdentifier())
361
+
362
+ const params = new ShouldStartParams();
363
+ params.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier() || -1
364
+ this.ctx.runOnWorkerThread(new WorkerRunnable(), params);
365
+ this.webViewBaseOperate?.emitShouldStartLoadWithRequest(event)
366
+ const startTime = Date.now();
367
+ // 当lockState没变化并且小于超时时间,阻塞进程
368
+ while (params.lockState === ShouldOverrideCallbackState.UNDECIDED &&
369
+ Date.now() - startTime < RNCWebView.SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
370
+ // 空循环等待
371
+ }
372
+
373
+ Logger.debug('params.lockState res:' + params.lockState + `, wait time is ${Date.now() - startTime}`);
374
+ return params.lockState === ShouldOverrideCallbackState.SHOULD_OVERRIDE;
375
+ }
376
+
377
+ onOverrideUrlLoading(event: WebResourceRequest) {
378
+ this.webViewBaseOperate?.emitShouldStartLoadWithRequestOverrideUrlLoading(event)
379
+ return false
380
+ }
381
+
382
+ build() {
383
+ Stack() {
384
+ Web({ src: "", controller: this.controller, renderMode: this.renderMode })
385
+ .mixedMode(this.mode)
386
+ .onPermissionRequest((event: OnPermissionRequestEvent) => {
387
+ if (event && (this.getPermissionDialogMessage(event) != "TYPE_SENSOR")) {
388
+ AlertDialog.show({
389
+ title: this.resourceToString($r('app.string.on_confirm')) + event.request.getOrigin(),
390
+ message: this.getPermissionDialogMessage(event),
391
+ alignment: DialogAlignment.Center,
392
+ primaryButton: {
393
+ value: $r('app.string.deny'),
394
+ action: () => {
395
+ event.request.deny();
396
+ }
397
+ },
398
+ secondaryButton: {
399
+ value: $r('app.string.on_confirm'),
400
+ action: () => {
401
+ event.request.grant(event.request.getAccessibleResource());
402
+ }
403
+ },
404
+ cancel: () => {
405
+ event.request.deny();
406
+ }
407
+ })
408
+ }
409
+ })
410
+
411
+ .width(this.webviewWidth)
412
+ .height(this.webviewHeight)
413
+ .fileAccess(this.allowFileAccess)
414
+ .constraintSize({ minHeight: MINHEIGHT })
415
+ .overScrollMode(this.overScrollMode)
416
+ .javaScriptAccess(this.javaScriptEnable)
417
+ .javaScriptOnDocumentStart(this.injectedJavaScriptBeforeContentLoaded)
418
+ .horizontalScrollBarAccess(this.descriptorWrapper.rawProps.showsHorizontalScrollIndicator)
419
+ .verticalScrollBarAccess(this.descriptorWrapper.rawProps.showsVerticalScrollIndicator)
420
+ .overviewModeAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
421
+ .textZoomRatio(this.descriptorWrapper.rawProps.textZoom)
422
+ .backgroundColor(BaseOperate.getColorMode(this.ctx.uiAbilityContext,
423
+ this.descriptorWrapper.rawProps.forceDarkOn) === WebDarkMode.On ? Color.White : Color.Transparent)
424
+ .darkMode(BaseOperate.getColorMode(this.ctx.uiAbilityContext, this.descriptorWrapper.rawProps.forceDarkOn))
425
+ .forceDarkAccess(this.forceDark)
426
+ .cacheMode(this.cacheMode)
427
+ .minFontSize(this.minFontSize)
428
+ .domStorageAccess(this.descriptorWrapper.rawProps.domStorageEnabled)
429
+ .zoomAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
430
+ .overScrollMode(this.overScrollMode)
431
+ .onProgressChange((event: OnProgressChangeEvent) => this.onProgressChange(event))
432
+ .onScroll((event: OnScrollEvent) => this.webViewBaseOperate?.emitScroll(event))
433
+ .nestedScroll({
434
+ scrollForward: this.nestedScroll,
435
+ scrollBackward: this.nestedScroll,
436
+ })
437
+ .onPageBegin(() => this.onPageBegin())
438
+ .onPageEnd(() => this.onPageEnd())
439
+ .onErrorReceive((event: OnErrorReceiveEvent) => this.webViewBaseOperate?.emitLoadingError(event))
440
+ .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.webViewBaseOperate?.emitHttpError(event))
441
+ .onControllerAttached(() => this.controllerAttachedInit())
442
+ .onAlert((event: AlertEvent) => this.onJavascriptAlert(event))
443
+ .onConfirm((event: AlertEvent) => this.onJavascriptConfirm(event))
444
+ .onDownloadStart((event: OnDownloadStartEvent) => this.webViewBaseOperate?.onDownloadStart(event))
445
+ .geolocationAccess(this.descriptorWrapper.rawProps.geolocationEnabled)
446
+ .onGeolocationShow((event) => {
447
+ if (event && (this.descriptorWrapper.rawProps.geolocationEnabled != undefined)) {
448
+ event.geolocation.invoke(event.origin, this.descriptorWrapper.rawProps.geolocationEnabled, true);
449
+ }
450
+ })
451
+ .mediaPlayGestureAccess(this.descriptorWrapper.rawProps.mediaPlaybackRequiresUserAction)
452
+ .onRenderExited((event: OnRenderExitedEvent) => this.webViewBaseOperate?.onRenderExited(event))
453
+ .onLoadIntercept((event: OnLoadInterceptEvent) => this.onLoadIntercept(event))
454
+ .onTitleReceive((event: OnTitleReceiveEvent) => this.webViewBaseOperate?.onTitleReceive(event))
455
+ }
456
+ .width(this.webviewWidth)
457
+ .height(this.webviewHeight)
458
+ .position({
459
+ x: this.descriptorWrapper.layoutMetrics.frame.origin.x,
460
+ y: this.descriptorWrapper.layoutMetrics.frame.origin.y
461
+ })
462
+ }
463
+
464
+ private onDescriptorWrapperChange(descriptorWrapper: WebViewDescriptor) {
465
+ this.initVariable()
466
+ this.descriptorWrapper = descriptorWrapper
467
+ if (this.source.html != "" && this.html != this.source.html) {
468
+ if (this.controllerAttached) {
469
+ this.loadHtmlData(this.source.html, this.source.baseUrl)
470
+ Logger.debug(TAG, "[RNOH] html is update")
471
+ this.html = this.source.html
472
+ }
473
+ } else if (this.source.uri && this.source.method && this.source.method.toUpperCase() === 'POST' && this.source.body && (this.source.uri != this.url || this.body != this.source.body)) {
474
+ if (this.controllerAttached) {
475
+ let url = this.source.uri as string;
476
+ this.url = url;
477
+ this.body = this.source.body;
478
+ let postData = buffer.from(this.source.body);
479
+ this.controller.postUrl(url, postData?.buffer);
480
+ }
481
+ } else if (this.url != this.source.uri) {
482
+ if (this.source.uri != "") {
483
+ Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri);
484
+ if (this.controllerAttached) {
485
+ this.controller.loadUrl(this.descriptorWrapper.rawProps.newSource.uri, this.headers)
486
+ }
487
+ } else {
488
+ this.loadHtmlData()
489
+ }
490
+ this.url = this.source.uri as string;
491
+ }
492
+ if (this.controllerAttached) {
493
+ this.controller?.setScrollable(this.descriptorWrapper.rawProps.scrollEnabled)
494
+ }
495
+ }
496
+
497
+ private initVariable() {
498
+ this.descriptorWrapper = this.ctx.descriptorRegistry.getDescriptor<WebViewDescriptor>(this.tag)
499
+ this.javaScriptEnable = this.descriptorWrapper.rawProps.javaScriptEnabled;
500
+ this.minFontSize =
501
+ this.descriptorWrapper.rawProps.minimumFontSize ? this.descriptorWrapper.rawProps.minimumFontSize : EIGHT;
502
+ this.cacheMode =
503
+ this.descriptorWrapper.rawProps.cacheEnabled ?
504
+ this.webViewBaseOperate?.transCacheMode(this.descriptorWrapper.rawProps.cacheMode as CACHE_MODE) as number :
505
+ CacheMode.Online;
506
+ this.source = {
507
+ uri: this.descriptorWrapper.rawProps.newSource.uri ?? "",
508
+ method: this.descriptorWrapper.rawProps.newSource.method ?? "",
509
+ body: this.descriptorWrapper.rawProps.newSource.body ?? "",
510
+ html: this.descriptorWrapper.rawProps.newSource.html ?? "",
511
+ baseUrl: this.descriptorWrapper.rawProps.newSource.baseUrl ?? "",
512
+ headers: this.descriptorWrapper.rawProps.newSource.headers ?? []
513
+ }
514
+ if (this.source.headers) {
515
+ this.source.headers.forEach(item => {
516
+ if (item.name && item.value) {
517
+ this.headers.push({ headerKey: item.name, headerValue: item.value })
518
+ }
519
+ })
520
+ }
521
+ // this.html = this.source.html
522
+ this.webviewWidth = this.descriptorWrapper.layoutMetrics.frame.size.width
523
+ this.webviewHeight = this.descriptorWrapper.layoutMetrics.frame.size.height
524
+ this.scrollEnabled = this.descriptorWrapper.rawProps.scrollEnabled;
525
+ // 当禁止滚动时,使用父组件滚动
526
+ this.nestedScroll = this.scrollEnabled ? NestedScrollMode.SELF_FIRST : NestedScrollMode.PARENT_FIRST;
527
+ if (this.source.uri && this.source.uri.toString().startsWith("asset://")) {
528
+ this.source.uri = $rawfile(this.source.uri.toString().replace("asset://", this.ctx.rnInstance.getAssetsDest()));
529
+ }
530
+
531
+ if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.ALWAYS) {
532
+ this.overScrollMode = OverScrollMode.ALWAYS
533
+ } else if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.NEVER) {
534
+ this.overScrollMode = OverScrollMode.NEVER
535
+ }
536
+
537
+ this.overScrollMode = this.descriptorWrapper.rawProps.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER;
538
+
539
+ if (this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded) {
540
+ this.injectedJavaScriptBeforeContentLoaded = [
541
+ { script: this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded, scriptRules: ["*"] }
542
+ ];
543
+ }
544
+ }
545
+
546
+ private registerPostMessage() {
547
+ if (this.messagingEnabled == this.descriptorWrapper.rawProps.messagingEnabled) {
548
+ return;
549
+ }
550
+ this.messagingEnabled = this.descriptorWrapper.rawProps.messagingEnabled;
551
+ if (this.messagingEnabled) {
552
+ let bridge: RNCWebViewBridge = {
553
+ postMessage: (data: string) => {
554
+ Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`);
555
+ if (this.controller != null) {
556
+ let result: WebViewEventParams = this.createWebViewEvent("onMessage")
557
+ if (result) {
558
+ result.data = data.toString()
559
+ result.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier()
560
+ this.eventEmitter!.emit("message", result as ResultType);
561
+ }
562
+ }
563
+ }
564
+ };
565
+ this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"])
566
+ this.source.uri ?
567
+ this.controller.loadUrl(this.source.uri, this.headers) : this.controller.refresh()
568
+ this.hasRegisterJavaScriptProxy = true
569
+ }
570
+ }
571
+ }
@@ -0,0 +1,38 @@
1
+ // Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved
2
+ // Use of this source code is governed by a Apache-2.0 license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type {
6
+ DescriptorWrapperFactoryByDescriptorType,
7
+ DescriptorWrapperFactoryByDescriptorTypeCtx,
8
+ } from '@rnoh/react-native-openharmony/ts';
9
+ import { RNC, TM } from './generated/ts';
10
+ import { AnyThreadTurboModuleFactory, AnyThreadTurboModule } from '@rnoh/react-native-openharmony/ts';
11
+ import { RNOHPackage } from '@rnoh/react-native-openharmony';
12
+ import { WebViewTurboModule } from './WebViewTurboModule'
13
+ import { WorkerTurboModuleContext } from '@rnoh/react-native-openharmony/src/main/ets/RNOH/RNOHContext';
14
+
15
+ export class RNCWebViewPackage extends RNOHPackage {
16
+ createDescriptorWrapperFactoryByDescriptorType(ctx: DescriptorWrapperFactoryByDescriptorTypeCtx): DescriptorWrapperFactoryByDescriptorType {
17
+ return {
18
+ "RNCWebView": (ctx) => new RNC.RNCWebView.DescriptorWrapper(ctx.descriptor)
19
+ }
20
+ }
21
+
22
+ createAnyThreadTurboModuleFactory(ctx: WorkerTurboModuleContext): AnyThreadTurboModuleFactory {
23
+ return new WebViewTurboModulesFactory(ctx);
24
+ }
25
+ }
26
+
27
+ class WebViewTurboModulesFactory extends AnyThreadTurboModuleFactory {
28
+ createTurboModule(name: string): AnyThreadTurboModule | null {
29
+ if (name === TM.RNCWebViewModule.NAME) {
30
+ return new WebViewTurboModule(this.ctx);
31
+ }
32
+ return null;
33
+ }
34
+
35
+ hasTurboModule(name: string): boolean {
36
+ return name === TM.RNCWebViewModule.NAME;
37
+ }
38
+ }
@@ -0,0 +1,48 @@
1
+ // Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved
2
+ // Use of this source code is governed by a Apache-2.0 license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import { CustomReference } from './CutomReference';
6
+ import HashMap from '@ohos.util.HashMap';
7
+
8
+ export enum CallbackState {
9
+ UNDECIDED,
10
+ SHOULD_OVERRIDE,
11
+ DO_NOT_OVERRIDE,
12
+ }
13
+
14
+ export class ShouldRequestUrl {
15
+ private constructor() {
16
+ }
17
+
18
+ static nextLockIdentifier: number = 1;
19
+ static ShouldRequestUrlLocks: HashMap<string, CustomReference> = new HashMap();
20
+ static callBack: Function;
21
+
22
+ static setCallBack(lockIdentifier: string, callBack: Function): void {
23
+ let data: CustomReference = ShouldRequestUrl.ShouldRequestUrlLocks.get(lockIdentifier);
24
+ data.setCallBack(callBack)
25
+ }
26
+
27
+ public static getNewData(): string {
28
+ let lockIdentifier = ShouldRequestUrl.nextLockIdentifier++ + ''
29
+ let custom = new CustomReference(CallbackState.UNDECIDED)
30
+ ShouldRequestUrl.ShouldRequestUrlLocks.set(lockIdentifier, custom)
31
+ return lockIdentifier
32
+ }
33
+
34
+ public static setValue(lockIdentifier: number, stateParam: CallbackState): void {
35
+ let lock: string = lockIdentifier + '';
36
+ if (ShouldRequestUrl.ShouldRequestUrlLocks.hasKey(lock)) {
37
+ let data: CustomReference = ShouldRequestUrl.ShouldRequestUrlLocks.get(lock)
38
+ data.setValue(stateParam)
39
+ }
40
+ }
41
+
42
+ public static getValue(lockIdentifier: string): CallbackState {
43
+ return ShouldRequestUrl.ShouldRequestUrlLocks.get(lockIdentifier).getValue()
44
+ }
45
+ public static removeData(lockIdentifier: string): void {
46
+ ShouldRequestUrl.ShouldRequestUrlLocks.remove(lockIdentifier)
47
+ }
48
+ }