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

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 (56) hide show
  1. package/LICENSE +20 -20
  2. package/README.OpenSource +10 -10
  3. package/README.md +12 -12
  4. package/harmony/rn_webview/OAT.xml +44 -44
  5. package/harmony/rn_webview/build-profile.json5 +28 -28
  6. package/harmony/rn_webview/index.ets +12 -12
  7. package/harmony/rn_webview/oh-package.json5 +13 -13
  8. package/harmony/rn_webview/src/main/cpp/CMakeLists.txt +9 -9
  9. package/harmony/rn_webview/src/main/cpp/WebViewPackage.h +16 -16
  10. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/BaseReactNativeWebviewPackage.h +95 -95
  11. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/components/RNCWebViewJSIBinder.h +119 -119
  12. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.cpp +18 -18
  13. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.h +16 -16
  14. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.cpp +17 -17
  15. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.h +16 -16
  16. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ComponentDescriptors.h +24 -24
  17. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.cpp +241 -241
  18. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.h +263 -263
  19. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.cpp +103 -103
  20. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.h +509 -509
  21. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.cpp +17 -17
  22. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.h +32 -32
  23. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.cpp +16 -16
  24. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.h +28 -28
  25. package/harmony/rn_webview/src/main/ets/CutomReference.ts +29 -29
  26. package/harmony/rn_webview/src/main/ets/Logger.ts +43 -43
  27. package/harmony/rn_webview/src/main/ets/Magic.ets +184 -184
  28. package/harmony/rn_webview/src/main/ets/RNCWebView.ets +570 -570
  29. package/harmony/rn_webview/src/main/ets/RNCWebViewPackage.ets +37 -37
  30. package/harmony/rn_webview/src/main/ets/ShouldRequestUrl.ts +47 -47
  31. package/harmony/rn_webview/src/main/ets/WebViewBaseOperate.ets +457 -443
  32. package/harmony/rn_webview/src/main/ets/WebViewTurboModule.ets +57 -57
  33. package/harmony/rn_webview/src/main/ets/generated/components/RNCWebView.ts +524 -524
  34. package/harmony/rn_webview/src/main/ets/generated/components/ts.ts +5 -5
  35. package/harmony/rn_webview/src/main/ets/generated/index.ets +5 -5
  36. package/harmony/rn_webview/src/main/ets/generated/ts.ts +6 -6
  37. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebView.ts +18 -18
  38. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebViewModule.ts +16 -16
  39. package/harmony/rn_webview/src/main/ets/generated/turboModules/ts.ts +6 -6
  40. package/harmony/rn_webview/src/main/module.json5 +10 -10
  41. package/harmony/rn_webview/src/main/resources/base/element/string.json +39 -39
  42. package/harmony/rn_webview/src/main/resources/en_US/element/string.json +39 -39
  43. package/harmony/rn_webview/src/main/resources/zh_CN/element/string.json +39 -39
  44. package/harmony/rn_webview/src/test/List.test.ets +4 -4
  45. package/harmony/rn_webview/src/test/LocalUnit.test.ets +32 -32
  46. package/harmony/rn_webview/ts.ets +8 -8
  47. package/harmony/rn_webview.har +0 -0
  48. package/package.json +99 -99
  49. package/src/NativeRNCWebView.ts +21 -21
  50. package/src/NativeRNCWebViewModule.ts +19 -19
  51. package/src/RNCWebViewNativeComponent.ts +354 -354
  52. package/src/WebView.harmony.tsx +342 -342
  53. package/src/WebView.tsx +10 -10
  54. package/src/codegenUtils.ts +10 -10
  55. package/src/index.ts +10 -10
  56. package/harmony/rn_webview/BuildProfile.ets +0 -17
@@ -1,571 +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
- }
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, this.source.html, this.source.baseUrl)
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
571
  }