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

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