@react-native-ohos/react-native-webview 13.16.1 → 13.16.2-beta.5

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 (80) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +20 -20
  3. package/README.OpenSource +10 -10
  4. package/README.md +396 -13
  5. package/harmony/rn_webview/.gitignore +6 -0
  6. package/harmony/rn_webview/BuildProfile.ets +17 -0
  7. package/harmony/rn_webview/OAT.xml +44 -44
  8. package/harmony/rn_webview/build-profile.json5 +28 -28
  9. package/harmony/rn_webview/index.ets +12 -12
  10. package/harmony/rn_webview/oh-package-lock.json5 +28 -18
  11. package/harmony/rn_webview/oh-package.json5 +13 -13
  12. package/harmony/rn_webview/src/main/cpp/CMakeLists.txt +9 -9
  13. package/harmony/rn_webview/src/main/cpp/WebViewPackage.h +16 -16
  14. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/BaseReactNativeWebviewPackage.h +96 -96
  15. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/components/RNCWebViewJSIBinder.h +121 -120
  16. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.cpp +18 -18
  17. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebView.h +16 -16
  18. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.cpp +17 -17
  19. package/harmony/rn_webview/src/main/cpp/generated/RNOH/generated/turbo_modules/RNCWebViewModule.h +16 -16
  20. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ComponentDescriptors.h +24 -24
  21. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.cpp +258 -258
  22. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/EventEmitters.h +277 -277
  23. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.cpp +104 -103
  24. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/Props.h +510 -509
  25. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.cpp +17 -17
  26. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/ShadowNodes.h +32 -32
  27. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.cpp +16 -16
  28. package/harmony/rn_webview/src/main/cpp/generated/react/renderer/components/react_native_webview/States.h +28 -28
  29. package/harmony/rn_webview/src/main/ets/CutomReference.ts +29 -29
  30. package/harmony/rn_webview/src/main/ets/Logger.ts +43 -43
  31. package/harmony/rn_webview/src/main/ets/Magic.ets +199 -184
  32. package/harmony/rn_webview/src/main/ets/RNCWebView.ets +824 -688
  33. package/harmony/rn_webview/src/main/ets/RNCWebViewPackage.ets +37 -37
  34. package/harmony/rn_webview/src/main/ets/ShouldRequestUrl.ts +47 -47
  35. package/harmony/rn_webview/src/main/ets/WebViewBaseOperate.ets +514 -501
  36. package/harmony/rn_webview/src/main/ets/WebViewTurboModule.ets +62 -62
  37. package/harmony/rn_webview/src/main/ets/generated/components/RNCWebView.ts +529 -525
  38. package/harmony/rn_webview/src/main/ets/generated/components/ts.ts +5 -5
  39. package/harmony/rn_webview/src/main/ets/generated/index.ets +5 -5
  40. package/harmony/rn_webview/src/main/ets/generated/ts.ts +6 -6
  41. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebView.ts +18 -18
  42. package/harmony/rn_webview/src/main/ets/generated/turboModules/RNCWebViewModule.ts +16 -16
  43. package/harmony/rn_webview/src/main/ets/generated/turboModules/ts.ts +6 -6
  44. package/harmony/rn_webview/src/main/module.json5 +10 -10
  45. package/harmony/rn_webview/src/main/resources/base/element/string.json +39 -39
  46. package/harmony/rn_webview/src/main/resources/en_US/element/string.json +39 -39
  47. package/harmony/rn_webview/src/main/resources/zh_CN/element/string.json +39 -39
  48. package/harmony/rn_webview/src/test/List.test.ets +4 -4
  49. package/harmony/rn_webview/src/test/LocalUnit.test.ets +32 -32
  50. package/harmony/rn_webview/ts.ets +8 -8
  51. package/harmony/rn_webview.har +0 -0
  52. package/lib/NativeRNCWebView.d.ts +10 -0
  53. package/lib/NativeRNCWebView.d.ts.map +1 -0
  54. package/lib/NativeRNCWebView.js +1 -0
  55. package/lib/NativeRNCWebViewModule.d.ts +9 -0
  56. package/lib/NativeRNCWebViewModule.d.ts.map +1 -0
  57. package/lib/NativeRNCWebViewModule.js +1 -0
  58. package/lib/RNCWebViewNativeComponent.d.ts +250 -0
  59. package/lib/RNCWebViewNativeComponent.d.ts.map +1 -0
  60. package/lib/RNCWebViewNativeComponent.js +1 -0
  61. package/lib/WebView.d.ts +4 -0
  62. package/lib/WebView.d.ts.map +1 -0
  63. package/lib/WebView.harmony.d.ts +16 -0
  64. package/lib/WebView.harmony.d.ts.map +1 -0
  65. package/lib/WebView.harmony.js +1 -0
  66. package/lib/WebView.js +1 -0
  67. package/lib/codegenUtils.d.ts +3 -0
  68. package/lib/codegenUtils.d.ts.map +1 -0
  69. package/lib/codegenUtils.js +0 -0
  70. package/lib/index.d.ts +4 -0
  71. package/lib/index.d.ts.map +1 -0
  72. package/lib/index.js +1 -0
  73. package/package.json +101 -99
  74. package/src/NativeRNCWebView.ts +21 -21
  75. package/src/NativeRNCWebViewModule.ts +19 -19
  76. package/src/RNCWebViewNativeComponent.ts +356 -355
  77. package/src/WebView.harmony.tsx +348 -346
  78. package/src/WebView.tsx +10 -10
  79. package/src/codegenUtils.ts +10 -10
  80. package/src/index.ts +10 -10
@@ -1,689 +1,825 @@
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 ,BusinessError} 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 { abilityAccessCtrl, 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
- params.lockState = ShouldOverrideCallbackState.UNDECIDED
43
- rnInstance.getTurboModule<WebViewTurboModule>(TM.RNCWebViewModule.NAME).setShouldStartParams(params)
44
- }
45
- }
46
-
47
- let atManager = abilityAccessCtrl.createAtManager();
48
-
49
- @Component
50
- export struct RNCWebView {
51
- public static readonly NAME = RNC.RNCWebView.NAME
52
- ctx!: RNComponentContext
53
- tag: number = ZERO
54
- source: WebViewNewSource = {
55
- uri: "",
56
- method: "",
57
- body: "",
58
- html: "",
59
- baseUrl: ""
60
- }
61
- html: string | undefined = "";
62
- url: string | Resource = "";
63
- body: string | undefined = "";
64
- controller: webview.WebviewController = new webview.WebviewController();
65
- javaScriptEnable: boolean = true;
66
- allowFileAccess: boolean = true;
67
- forceDark: boolean = true;
68
- minFontSize: number = EIGHT;
69
- overScrollMode: OverScrollMode = OverScrollMode.NEVER;
70
- progress: number = ZERO;
71
- cacheMode: number = CacheMode.Default;
72
- requestUrl: string = "";
73
- bundleName: string = "";
74
- messagingEnabled: boolean = false;
75
- hasRegisterJavaScriptProxy: boolean = false;
76
- controllerAttached: boolean = false;
77
- renderMode: RenderMode = RenderMode.SYNC_RENDER;
78
- scrollEnabled = true;
79
- nestedScroll = NestedScrollMode.SELF_FIRST;
80
- headers: Array<webview.WebHeader> = []
81
- injectedJavaScriptBeforeContentLoaded: Array<ScriptItem> = [
82
- { script: '', scriptRules: ["*"] }
83
- ];
84
- allowPageStartInProgress = true;
85
- @State webviewWidth: number = ZERO
86
- @State webviewHeight: number = ZERO
87
- @State isRefreshing: boolean = false
88
- @State mode: MixedMode = MixedMode.All;
89
- private unregisterDescriptorChangesListener?: () => void = undefined
90
- private cleanupCommandCallback?: () => void = undefined
91
- private eventEmitter: RNC.RNCWebView.EventEmitter | undefined = undefined
92
- private cleanUpCallbacks: (() => void)[] = []
93
- private descriptorWrapper: WebViewDescriptor = Object() as WebViewDescriptor
94
- private webViewBaseOperate: BaseOperate | null = null
95
- private shouldInterceptLoad: boolean = true
96
- private callLoadTimer: number = -1
97
- static readonly SHOULD_OVERRIDE_URL_LOADING_TIMEOUT: number = 250
98
- private debounceTimer: number = -1
99
- static readonly DEBOUNCE_TIME: number = 100
100
- private isFirstDebounce: boolean = true
101
-
102
- aboutToAppear() {
103
- try {
104
- this.initVariable()
105
- this.getBundleName()
106
- this.url = this.source.uri as string;
107
- this.onDescriptorWrapperChange(this.descriptorWrapper)
108
- this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag,
109
- (newDescriptor) => {
110
- this.onDescriptorWrapperChange(newDescriptor as WebViewDescriptor)
111
- }
112
- )
113
- BaseOperate.setThirdPartyCookiesEnabled(this.descriptorWrapper.rawProps.thirdPartyCookiesEnabled)
114
- webview.WebviewController.setWebDebuggingAccess(this.descriptorWrapper.rawProps.webviewDebuggingEnabled)
115
- this.registerCommandCallback()
116
- if (this.hasIncognito()) {
117
- this.cacheMode = CacheMode.Online;
118
- }
119
- if (this.source.headers) {
120
- this.source.headers.forEach(item => {
121
- if (item.name && item.value) {
122
- this.headers.push({ headerKey: item.name, headerValue: item.value })
123
- }
124
- })
125
- }
126
- if (this.descriptorWrapper.rawProps.geolocationEnabled) {
127
- atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, ["ohos.permission.APPROXIMATELY_LOCATION"]).then((data) => {
128
- }).catch((error: BusinessError) => {
129
- console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
130
- })
131
- }
132
- } catch (error) {
133
- Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
134
- }
135
-
136
- }
137
-
138
- hasIncognito(): boolean {
139
- return this.descriptorWrapper.rawProps.incognito && this.descriptorWrapper.rawProps.incognito === true
140
- }
141
-
142
- createWebViewEvent(type: string): WebViewEventParams {
143
- return this.webViewBaseOperate?.createWebViewEvent({ type, progress: this.progress }) as WebViewEventParams;
144
- }
145
-
146
- aboutToDisappear() {
147
- this.getUIContext().getFocusController().clearFocus()
148
- this.cleanupCommandCallback?.()
149
- this.unregisterDescriptorChangesListener?.()
150
- this.cleanUpCallbacks.forEach(cb => cb())
151
- try {
152
- if (this.hasIncognito()) {
153
- this.webViewBaseOperate?.setIncognito()
154
- }
155
- this.controller.deleteJavaScriptRegister(JAVASCRIPT_INTERFACE)
156
- this.controller.refresh()
157
- this.hasRegisterJavaScriptProxy = false
158
- this.webViewBaseOperate = null
159
- } catch (error) {
160
- Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
161
- }
162
- }
163
-
164
- registerCommandCallback() {
165
- this.cleanupCommandCallback = this.ctx.componentCommandReceiver.registerCommandCallback(
166
- this.tag,
167
- (command: COMMAND_NAME, args: string[]) => this.webViewBaseOperate?.registerCommandCallback(command, args));
168
- }
169
-
170
- onLoadingStart() {
171
- this.webViewBaseOperate?.emitLoadingStart({ progress: this.progress })
172
- }
173
-
174
- onProgressChange(event: OnProgressChangeEvent) {
175
- this.progress = event.newProgress
176
- // 修复单页面应用切换路由时 onPageBegin 未触发的问题
177
- if (this.controller.getUrl() !== this.url && this.progress < ONE_HUNDRED && this.allowPageStartInProgress) {
178
- this.allowPageStartInProgress = false;
179
- this.onLoadingStart();
180
- }
181
- this.webViewBaseOperate?.emitProgressChange({ progress: this.progress })
182
- if (this.progress === ONE_HUNDRED) {
183
- this.allowPageStartInProgress = true;
184
- }
185
- }
186
-
187
- onLoadingFinish() {
188
- this.webViewBaseOperate?.emitLoadingFinish({ progress: this.progress })
189
- this.isRefreshing = false
190
- }
191
-
192
- runInjectedJavaScript() {
193
- let injectedJS = this.descriptorWrapper.rawProps.injectedJavaScript
194
- if (this.javaScriptEnable && injectedJS != "" && this.controllerAttached) {
195
- try {
196
- this.controller.runJavaScript("(function() {\n" + injectedJS + ";\n})();")
197
- .then((result) => {
198
- Logger.debug(TAG, '[RNOH] result: ' + result);
199
- })
200
- .catch((error: string | Error) => {
201
- Logger.error(TAG, "[RNOH] error: " + error);
202
- })
203
- } catch (error) {
204
- Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
205
- }
206
- }
207
- }
208
-
209
- getJsDialogTitle() {
210
- const url = this.controller.getUrl()
211
- // 对于 data: URL,只显示 'JavaScript',类似于 Chrome
212
- let title = 'JavaScript';
213
- const isDataUrl = (url !== null) && url.startsWith("data:");
214
-
215
-
216
- if (!isDataUrl) {
217
- try {
218
- const dialogUrl = OSUrl.URL.parseURL(url);
219
- title = `网址为”${dialogUrl.protocol}//${dialogUrl.host}“的网页显示:`;
220
- } catch (ex) {
221
- title = url;
222
- }
223
- }
224
-
225
- return title;
226
- }
227
-
228
- onJavascriptAlert(event?: AlertEvent) {
229
- if (event) {
230
- AlertDialog.show({
231
- title: this.getJsDialogTitle(),
232
- message: event.message,
233
- alignment: DialogAlignment.Center,
234
- secondaryButton: {
235
- value: this.resourceToString($r('app.string.determined')),
236
- action: () => event.result.handleConfirm()
237
- },
238
- cancel: () => event.result.handleCancel(),
239
- })
240
- }
241
- return true
242
- }
243
-
244
- onJavascriptConfirm(event?: AlertEvent) {
245
- if (event) {
246
- AlertDialog.show({
247
- title: this.getJsDialogTitle(),
248
- message: event.message,
249
- alignment: DialogAlignment.Center,
250
- secondaryButton: {
251
- value: this.resourceToString($r('app.string.determined')),
252
- action: () => event.result.handleConfirm()
253
- },
254
- cancel: () => event.result.handleCancel(),
255
- primaryButton: {
256
- value: this.resourceToString($r('app.string.cancel')),
257
- action: () => event.result.handleCancel()
258
- },
259
- })
260
- }
261
- return true
262
- }
263
-
264
- ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch: boolean) {
265
- this.webViewBaseOperate?.ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch)
266
- }
267
-
268
- loadHtmlData(html: string = '', url: string = '') {
269
- try {
270
- this.controller.loadData(
271
- html,
272
- "text/html",
273
- "UTF-8",
274
- url,
275
- " "
276
- );
277
- } catch (error) {
278
- Logger.error(TAG, "error:" + error)
279
- }
280
- }
281
-
282
- controllerAttachedInit(): void {
283
- this.controllerAttached = true;
284
- this.eventEmitter = new RNC.RNCWebView.EventEmitter(this.ctx.rnInstance, this.tag)
285
- this.webViewBaseOperate = new BaseOperate(this.eventEmitter, this.controller)
286
- this.webViewBaseOperate.setCustomUserAgent(this.descriptorWrapper.rawProps.userAgent,
287
- this.descriptorWrapper.rawProps.applicationNameForUserAgent)
288
- this.webViewBaseOperate.setFraudulentWebsiteWarningEnabled(this.descriptorWrapper.rawProps.fraudulentWebsiteWarningEnabled)
289
-
290
- let baseUrl = this.source.baseUrl
291
- let uri = this.source.uri
292
- if (this.source.html != undefined && this.source.html != "") {
293
- this.loadHtmlData(this.source.html, baseUrl)
294
- } else if (uri != undefined && uri != "") {
295
- if (this.source.method && this.source.method.toUpperCase() === 'POST' && this.source.body) {
296
- let postData = buffer.from(this.source.body);
297
- let url = uri as string;
298
- this.controller.postUrl(url, postData?.buffer);
299
- } else {
300
- this.controller.loadUrl(uri, this.headers);
301
- }
302
- } else {
303
- this.loadHtmlData()
304
- }
305
- if (!this.hasRegisterJavaScriptProxy) {
306
- this.registerPostMessage()
307
- }
308
- }
309
-
310
- onPageBegin() {
311
- try {
312
- if (this.controller.getUrl() === this.url) {
313
- this.onLoadingStart();
314
- }
315
- this.controller.setScrollable(this.scrollEnabled)
316
- this.ignoreSilentHardwareSwitchMethods(this.descriptorWrapper.rawProps.ignoreSilentHardwareSwitch)
317
- } catch (error) {
318
- Logger.debug(TAG,
319
- `[RNOH] setignoreSilentHardwareSwitch and setScrollable ErrorCode: ${error.code}, Message: ${error.message}, userAgent: ${this.descriptorWrapper.rawProps.userAgent}`);
320
- }
321
- }
322
-
323
- getBundleName() {
324
- let bundleFlags =
325
- bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY |
326
- bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
327
-
328
- try {
329
- bundleManager.getBundleInfoForSelf(bundleFlags, (error, data) => {
330
- if (error) {
331
- Logger.error(TAG,
332
- `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
333
- } else {
334
- this.bundleName = JSON.stringify(data.name)
335
- }
336
- });
337
- } catch (error) {
338
- Logger.error(TAG,
339
- `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
340
- }
341
- }
342
-
343
- resourceToString(resource: Resource): string {
344
- return getContext(this).resourceManager.getStringSync(resource)
345
- }
346
-
347
- onLoadFinished() {
348
- this.onLoadingFinish()
349
- }
350
-
351
- onPageEndExecuteRunInjectedJavaScript(){
352
- this.runInjectedJavaScript()
353
- }
354
-
355
- onPageEnd() {
356
- this.runInjectedJavaScript()
357
- this.onLoadingFinish()
358
- }
359
-
360
- getPermissionDialogMessage(event: OnPermissionRequestEvent) {
361
- let permissionDialogMessage = ''
362
- permissionDialogMessage = event.request.getAccessibleResource().toString()
363
- switch (permissionDialogMessage) {
364
- case ProtectedResourceType.VIDEO_CAPTURE:
365
- permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera'))
366
- break;
367
- case ProtectedResourceType.AUDIO_CAPTURE:
368
- permissionDialogMessage = this.resourceToString($r('app.string.use_your_microphone'))
369
- break;
370
- case ProtectedResourceType.MidiSysex:
371
- permissionDialogMessage = this.resourceToString($r('app.string.midi_sysex_resources'))
372
- break;
373
- case ProtectedResourceType.VIDEO_CAPTURE + "," + ProtectedResourceType.AUDIO_CAPTURE:
374
- permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera_and_microphone'))
375
- break;
376
- }
377
- return permissionDialogMessage;
378
- }
379
-
380
- onLoadIntercept(event: OnLoadInterceptEvent): boolean {
381
- Logger.debug(TAG, `onLoadIntercept start`)
382
- this.webViewBaseOperate?.setLockIdentifier(BaseOperate.generateLockIdentifier())
383
-
384
- const params = new ShouldStartParams();
385
- params.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier() || -1
386
- this.ctx.runOnWorkerThread(new WorkerRunnable(), params);
387
- this.webViewBaseOperate?.emitShouldStartLoadWithRequest(event, this.source.html, this.source.baseUrl)
388
- const startTime = Date.now();
389
- // 当lockState没变化并且小于超时时间,阻塞进程
390
- while (params.lockState === ShouldOverrideCallbackState.UNDECIDED &&
391
- Date.now() - startTime < RNCWebView.SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
392
- // 空循环等待
393
- }
394
-
395
- Logger.debug('params.lockState res:' + params.lockState + `, wait time is ${Date.now() - startTime}`);
396
- return params.lockState === ShouldOverrideCallbackState.SHOULD_OVERRIDE;
397
- }
398
-
399
- onOverrideUrlLoading(event: WebResourceRequest) {
400
- this.webViewBaseOperate?.emitShouldStartLoadWithRequestOverrideUrlLoading(event)
401
- return false
402
- }
403
-
404
- onSslErrorEventCallback(event: SslErrorEvent){
405
- this.webViewBaseOperate?.emitOnSslErrorEventCallback(event);
406
- }
407
-
408
- build() {
409
- Stack() {
410
- if ( deviceInfo.sdkApiVersion >= 20) {
411
- Web({ src: "", controller: this.controller, renderMode: this.renderMode })
412
- .mixedMode(this.mode)
413
- .onPermissionRequest((event: OnPermissionRequestEvent) => {
414
- if (event && (this.getPermissionDialogMessage(event) != "TYPE_SENSOR")) {
415
- AlertDialog.show({
416
- title: this.resourceToString($r('app.string.on_confirm')) + event.request.getOrigin(),
417
- message: this.getPermissionDialogMessage(event),
418
- alignment: DialogAlignment.Center,
419
- primaryButton: {
420
- value: $r('app.string.deny'),
421
- action: () => {
422
- event.request.deny();
423
- }
424
- },
425
- secondaryButton: {
426
- value: $r('app.string.on_confirm'),
427
- action: () => {
428
- event.request.grant(event.request.getAccessibleResource());
429
- }
430
- },
431
- cancel: () => {
432
- event.request.deny();
433
- }
434
- })
435
- }
436
- })
437
- .width(this.webviewWidth)
438
- .height(this.webviewHeight)
439
- .fileAccess(this.allowFileAccess)
440
- .constraintSize({ minHeight: MINHEIGHT })
441
- .overScrollMode(this.overScrollMode)
442
- .javaScriptAccess(this.javaScriptEnable)
443
- .javaScriptOnDocumentStart(this.injectedJavaScriptBeforeContentLoaded)
444
- .horizontalScrollBarAccess(this.descriptorWrapper.rawProps.showsHorizontalScrollIndicator)
445
- .verticalScrollBarAccess(this.descriptorWrapper.rawProps.showsVerticalScrollIndicator)
446
- .overviewModeAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
447
- .textZoomRatio(this.descriptorWrapper.rawProps.textZoom)
448
- .backgroundColor(BaseOperate.getColorMode(this.ctx.uiAbilityContext,
449
- this.descriptorWrapper.rawProps.forceDarkOn) === WebDarkMode.On ? Color.White : Color.Transparent)
450
- .darkMode(BaseOperate.getColorMode(this.ctx.uiAbilityContext, this.descriptorWrapper.rawProps.forceDarkOn))
451
- .forceDarkAccess(this.forceDark)
452
- .cacheMode(this.cacheMode)
453
- .minFontSize(this.minFontSize)
454
- .domStorageAccess(this.descriptorWrapper.rawProps.domStorageEnabled)
455
- .zoomAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
456
- .overScrollMode(this.overScrollMode)
457
- .onProgressChange((event: OnProgressChangeEvent) => this.onProgressChange(event))
458
- .onScroll((event: OnScrollEvent) => this.webViewBaseOperate?.emitScroll(event))
459
- .nestedScroll({
460
- scrollForward: this.nestedScroll,
461
- scrollBackward: this.nestedScroll,
462
- })
463
- .onLoadStarted(() => this.onPageBegin())
464
- .onLoadFinished(() => this.onLoadFinished())
465
- .onPageEnd(() => {this.onPageEndExecuteRunInjectedJavaScript()})
466
- .onErrorReceive((event: OnErrorReceiveEvent) => this.webViewBaseOperate?.emitLoadingError(event))
467
- .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.webViewBaseOperate?.emitHttpError(event))
468
- .onControllerAttached(() => this.controllerAttachedInit())
469
- .onAlert((event: AlertEvent) => this.onJavascriptAlert(event))
470
- .onConfirm((event: AlertEvent) => this.onJavascriptConfirm(event))
471
- .onDownloadStart((event: OnDownloadStartEvent) => this.webViewBaseOperate?.onDownloadStart(event))
472
- .geolocationAccess(this.descriptorWrapper.rawProps.geolocationEnabled)
473
- .onGeolocationShow((event) => {
474
- if (event && (this.descriptorWrapper.rawProps.geolocationEnabled != undefined)) {
475
- event.geolocation.invoke(event.origin, this.descriptorWrapper.rawProps.geolocationEnabled, true);
476
- }
477
- })
478
- .mediaPlayGestureAccess(this.descriptorWrapper.rawProps.mediaPlaybackRequiresUserAction)
479
- .onRenderExited((event: OnRenderExitedEvent) => this.webViewBaseOperate?.onRenderExited(event))
480
- .onLoadIntercept((event: OnLoadInterceptEvent) => this.onLoadIntercept(event))
481
- .onTitleReceive((event: OnTitleReceiveEvent) => this.webViewBaseOperate?.onTitleReceive(event))
482
- .onSslErrorEvent((event: SslErrorEvent) => this.onSslErrorEventCallback(event))
483
- } else {
484
- Web({ src: "", controller: this.controller, renderMode: this.renderMode })
485
- .mixedMode(this.mode)
486
- .onPermissionRequest((event: OnPermissionRequestEvent) => {
487
- if (event && (this.getPermissionDialogMessage(event) != "TYPE_SENSOR")) {
488
- AlertDialog.show({
489
- title: this.resourceToString($r('app.string.on_confirm')) + event.request.getOrigin(),
490
- message: this.getPermissionDialogMessage(event),
491
- alignment: DialogAlignment.Center,
492
- primaryButton: {
493
- value: $r('app.string.deny'),
494
- action: () => {
495
- event.request.deny();
496
- }
497
- },
498
- secondaryButton: {
499
- value: $r('app.string.on_confirm'),
500
- action: () => {
501
- event.request.grant(event.request.getAccessibleResource());
502
- }
503
- },
504
- cancel: () => {
505
- event.request.deny();
506
- }
507
- })
508
- }
509
- })
510
-
511
- .width(this.webviewWidth)
512
- .height(this.webviewHeight)
513
- .fileAccess(this.allowFileAccess)
514
- .constraintSize({ minHeight: MINHEIGHT })
515
- .overScrollMode(this.overScrollMode)
516
- .javaScriptAccess(this.javaScriptEnable)
517
- .javaScriptOnDocumentStart(this.injectedJavaScriptBeforeContentLoaded)
518
- .horizontalScrollBarAccess(this.descriptorWrapper.rawProps.showsHorizontalScrollIndicator)
519
- .verticalScrollBarAccess(this.descriptorWrapper.rawProps.showsVerticalScrollIndicator)
520
- .overviewModeAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
521
- .textZoomRatio(this.descriptorWrapper.rawProps.textZoom)
522
- .backgroundColor(BaseOperate.getColorMode(this.ctx.uiAbilityContext,
523
- this.descriptorWrapper.rawProps.forceDarkOn) === WebDarkMode.On ? Color.White : Color.Transparent)
524
- .darkMode(BaseOperate.getColorMode(this.ctx.uiAbilityContext, this.descriptorWrapper.rawProps.forceDarkOn))
525
- .forceDarkAccess(this.forceDark)
526
- .cacheMode(this.cacheMode)
527
- .minFontSize(this.minFontSize)
528
- .domStorageAccess(this.descriptorWrapper.rawProps.domStorageEnabled)
529
- .zoomAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
530
- .overScrollMode(this.overScrollMode)
531
- .onProgressChange((event: OnProgressChangeEvent) => this.onProgressChange(event))
532
- .onScroll((event: OnScrollEvent) => this.webViewBaseOperate?.emitScroll(event))
533
- .nestedScroll({
534
- scrollForward: this.nestedScroll,
535
- scrollBackward: this.nestedScroll,
536
- })
537
- .onPageBegin(() => this.onPageBegin())
538
- .onPageEnd(() => this.onPageEnd())
539
- .onErrorReceive((event: OnErrorReceiveEvent) => this.webViewBaseOperate?.emitLoadingError(event))
540
- .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.webViewBaseOperate?.emitHttpError(event))
541
- .onControllerAttached(() => this.controllerAttachedInit())
542
- .onAlert((event: AlertEvent) => this.onJavascriptAlert(event))
543
- .onConfirm((event: AlertEvent) => this.onJavascriptConfirm(event))
544
- .onDownloadStart((event: OnDownloadStartEvent) => this.webViewBaseOperate?.onDownloadStart(event))
545
- .geolocationAccess(this.descriptorWrapper.rawProps.geolocationEnabled)
546
- .onGeolocationShow((event) => {
547
- if (event && (this.descriptorWrapper.rawProps.geolocationEnabled != undefined)) {
548
- event.geolocation.invoke(event.origin, this.descriptorWrapper.rawProps.geolocationEnabled, true);
549
- }
550
- })
551
- .mediaPlayGestureAccess(this.descriptorWrapper.rawProps.mediaPlaybackRequiresUserAction)
552
- .onRenderExited((event: OnRenderExitedEvent) => this.webViewBaseOperate?.onRenderExited(event))
553
- .onLoadIntercept((event: OnLoadInterceptEvent) => this.onLoadIntercept(event))
554
- .onTitleReceive((event: OnTitleReceiveEvent) => this.webViewBaseOperate?.onTitleReceive(event))
555
- .onSslErrorEvent((event: SslErrorEvent) => this.onSslErrorEventCallback(event))
556
- }
557
- }
558
- .width(this.webviewWidth)
559
- .height(this.webviewHeight)
560
- .position({
561
- x: this.descriptorWrapper.layoutMetrics.frame.origin.x,
562
- y: this.descriptorWrapper.layoutMetrics.frame.origin.y
563
- })
564
- }
565
-
566
- private onDescriptorWrapperChange(descriptorWrapper: WebViewDescriptor) {
567
- this.initVariable()
568
- this.descriptorWrapper = descriptorWrapper
569
- if (this.source.html != "" && this.html != this.source.html) {
570
- if (this.controllerAttached) {
571
- this.loadHtmlData(this.source.html, this.source.baseUrl)
572
- Logger.debug(TAG, "[RNOH] html is update")
573
- this.html = this.source.html
574
- }
575
- } 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)) {
576
- if (this.controllerAttached) {
577
- let url = this.source.uri as string;
578
- this.url = url;
579
- this.body = this.source.body;
580
- let postData = buffer.from(this.source.body);
581
- this.controller.postUrl(url, postData?.buffer);
582
- }
583
- } else if (this.url != this.source.uri) {
584
- if (this.source.uri != "") {
585
- Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri);
586
- if (this.controllerAttached) {
587
- this.controller.loadUrl(this.descriptorWrapper.rawProps.newSource.uri, this.headers)
588
- }
589
- } else {
590
- this.loadHtmlData()
591
- }
592
- this.url = this.source.uri as string;
593
- }
594
- if (this.controllerAttached) {
595
- this.controller?.setScrollable(this.descriptorWrapper.rawProps.scrollEnabled)
596
- }
597
- }
598
-
599
- private initVariable() {
600
- this.descriptorWrapper = this.ctx.descriptorRegistry.getDescriptor<WebViewDescriptor>(this.tag)
601
- this.javaScriptEnable = this.descriptorWrapper.rawProps.javaScriptEnabled;
602
- this.minFontSize =
603
- this.descriptorWrapper.rawProps.minimumFontSize ? this.descriptorWrapper.rawProps.minimumFontSize : EIGHT;
604
- this.cacheMode =
605
- this.descriptorWrapper.rawProps.cacheEnabled ?
606
- this.webViewBaseOperate?.transCacheMode(this.descriptorWrapper.rawProps.cacheMode as CACHE_MODE) as number :
607
- CacheMode.Online;
608
- this.source = {
609
- uri: this.descriptorWrapper.rawProps.newSource.uri ?? "",
610
- method: this.descriptorWrapper.rawProps.newSource.method ?? "",
611
- body: this.descriptorWrapper.rawProps.newSource.body ?? "",
612
- html: this.descriptorWrapper.rawProps.newSource.html ?? "",
613
- baseUrl: this.descriptorWrapper.rawProps.newSource.baseUrl ?? "",
614
- headers: this.descriptorWrapper.rawProps.newSource.headers ?? []
615
- }
616
- if (this.source.headers) {
617
- this.source.headers.forEach(item => {
618
- if (item.name && item.value) {
619
- this.headers.push({ headerKey: item.name, headerValue: item.value })
620
- }
621
- })
622
- }
623
- // this.html = this.source.html
624
- const newWidth = this.descriptorWrapper.layoutMetrics.frame.size.width
625
- const newHeight = this.descriptorWrapper.layoutMetrics.frame.size.height
626
-
627
- if (this.debounceTimer !== -1) {
628
- clearTimeout(this.debounceTimer)
629
- this.debounceTimer = -1
630
- }
631
-
632
- if (this.isFirstDebounce) {
633
- this.webviewWidth = newWidth
634
- this.webviewHeight = newHeight
635
- this.isFirstDebounce = false
636
- } else {
637
- this.debounceTimer = setTimeout(() => {
638
- this.webviewWidth = newWidth
639
- this.webviewHeight = newHeight
640
- }, RNCWebView.DEBOUNCE_TIME)
641
- }
642
- this.scrollEnabled = this.descriptorWrapper.rawProps.scrollEnabled;
643
- // 当禁止滚动时,使用父组件滚动
644
- this.nestedScroll = this.scrollEnabled ? NestedScrollMode.SELF_FIRST : NestedScrollMode.PARENT_FIRST;
645
- if (this.source.uri && this.source.uri.toString().startsWith("asset://")) {
646
- this.source.uri = $rawfile(this.source.uri.toString().replace("asset://", this.ctx.rnInstance.getAssetsDest()));
647
- }
648
-
649
- if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.ALWAYS) {
650
- this.overScrollMode = OverScrollMode.ALWAYS
651
- } else if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.NEVER) {
652
- this.overScrollMode = OverScrollMode.NEVER
653
- }
654
-
655
- this.overScrollMode = this.descriptorWrapper.rawProps.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER;
656
-
657
- if (this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded) {
658
- this.injectedJavaScriptBeforeContentLoaded = [
659
- { script: this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded, scriptRules: ["*"] }
660
- ];
661
- }
662
- }
663
-
664
- private registerPostMessage() {
665
- if (this.messagingEnabled == this.descriptorWrapper.rawProps.messagingEnabled) {
666
- return;
667
- }
668
- this.messagingEnabled = this.descriptorWrapper.rawProps.messagingEnabled;
669
- if (this.messagingEnabled) {
670
- let bridge: RNCWebViewBridge = {
671
- postMessage: (data: string) => {
672
- Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`);
673
- if (this.controller != null) {
674
- let result: WebViewEventParams = this.createWebViewEvent("onMessage")
675
- if (result) {
676
- result.data = data.toString()
677
- result.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier()
678
- this.eventEmitter!.emit("message", result as ResultType);
679
- }
680
- }
681
- }
682
- };
683
- this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"])
684
- this.source.uri ?
685
- this.controller.loadUrl(this.source.uri, this.headers) : this.controller.refresh()
686
- this.hasRegisterJavaScriptProxy = true
687
- }
688
- }
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 ,BusinessError} 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
+ WebMediaCapturePermissionGrantType,
29
+ MenuItem
30
+ } from './Magic';
31
+
32
+ import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
33
+ import { WebViewTurboModule, ShouldStartParams, ShouldOverrideCallbackState } from './WebViewTurboModule';
34
+ import { AnyThreadRNInstance, WorkerTaskRunnable } from '@rnoh/react-native-openharmony/src/main/ets/RNOH';
35
+ import buffer from '@ohos.buffer';
36
+
37
+ export const TAG = "WebView"
38
+
39
+ export const WEB_VIEW = "RNCWebView"
40
+
41
+ @Sendable
42
+ class WorkerRunnable implements WorkerTaskRunnable<ShouldStartParams> {
43
+ run(rnInstance: AnyThreadRNInstance, params: ShouldStartParams): void {
44
+ params.lockState = ShouldOverrideCallbackState.UNDECIDED
45
+ rnInstance.getTurboModule<WebViewTurboModule>(TM.RNCWebViewModule.NAME).setShouldStartParams(params)
46
+ }
47
+ }
48
+
49
+ let atManager = abilityAccessCtrl.createAtManager();
50
+
51
+ @Component
52
+ export struct RNCWebView {
53
+ public static readonly NAME = RNC.RNCWebView.NAME
54
+ ctx!: RNComponentContext
55
+ tag: number = ZERO
56
+ source: WebViewNewSource = {
57
+ uri: "",
58
+ method: "",
59
+ body: "",
60
+ html: "",
61
+ baseUrl: ""
62
+ }
63
+ html: string | undefined = "";
64
+ url: string | Resource = "";
65
+ body: string | undefined = "";
66
+ controller: webview.WebviewController = new webview.WebviewController();
67
+ javaScriptEnable: boolean = true;
68
+ allowFileAccess: boolean = true;
69
+ forceDark: boolean = true;
70
+ minFontSize: number = EIGHT;
71
+ overScrollMode: OverScrollMode = OverScrollMode.NEVER;
72
+ progress: number = ZERO;
73
+ cacheMode: number = CacheMode.Default;
74
+ requestUrl: string = "";
75
+ bundleName: string = "";
76
+ messagingEnabled: boolean = false;
77
+ hasRegisterJavaScriptProxy: boolean = false;
78
+ controllerAttached: boolean = false;
79
+ renderMode: RenderMode = RenderMode.SYNC_RENDER;
80
+ scrollEnabled = true;
81
+ @State nestedScroll: NestedScrollMode = NestedScrollMode.SELF_FIRST;
82
+ headers: Array<webview.WebHeader> = []
83
+ injectedJavaScriptBeforeContentLoaded: Array<ScriptItem> = [
84
+ { script: '', scriptRules: ["*"] }
85
+ ];
86
+ allowPageStartInProgress = true;
87
+ @State webviewWidth: number = ZERO
88
+ @State webviewHeight: number = ZERO
89
+ @State isRefreshing: boolean = false
90
+ @State mode: MixedMode = MixedMode.All;
91
+ private unregisterDescriptorChangesListener?: () => void = undefined
92
+ private cleanupCommandCallback?: () => void = undefined
93
+ private eventEmitter: RNC.RNCWebView.EventEmitter | undefined = undefined
94
+ private cleanUpCallbacks: (() => void)[] = []
95
+ private descriptorWrapper: WebViewDescriptor = Object() as WebViewDescriptor
96
+ private webViewBaseOperate: BaseOperate | null = null
97
+ private shouldInterceptLoad: boolean = true
98
+ private callLoadTimer: number = -1
99
+ static readonly SHOULD_OVERRIDE_URL_LOADING_TIMEOUT: number = 250
100
+ private debounceTimer: number = -1
101
+ static readonly DEBOUNCE_TIME: number = 100
102
+ private isFirstDebounce: boolean = true
103
+
104
+ aboutToAppear() {
105
+ try {
106
+ this.initVariable()
107
+ this.getBundleName()
108
+ this.url = this.source.uri as string;
109
+ this.onDescriptorWrapperChange(this.descriptorWrapper)
110
+ this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag,
111
+ (newDescriptor) => {
112
+ this.onDescriptorWrapperChange(newDescriptor as WebViewDescriptor)
113
+ }
114
+ )
115
+ BaseOperate.setThirdPartyCookiesEnabled(this.descriptorWrapper.rawProps.thirdPartyCookiesEnabled)
116
+ webview.WebviewController.setWebDebuggingAccess(this.descriptorWrapper.rawProps.webviewDebuggingEnabled)
117
+ this.registerCommandCallback()
118
+ if (this.hasIncognito()) {
119
+ this.cacheMode = CacheMode.Online;
120
+ }
121
+ if (this.source.headers) {
122
+ this.source.headers.forEach(item => {
123
+ if (item.name && item.value) {
124
+ this.headers.push({ headerKey: item.name, headerValue: item.value })
125
+ }
126
+ })
127
+ }
128
+ if (this.descriptorWrapper.rawProps.geolocationEnabled) {
129
+ atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, ["ohos.permission.APPROXIMATELY_LOCATION"]).then((data) => {
130
+ }).catch((error: BusinessError) => {
131
+ console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
132
+ })
133
+ }
134
+ } catch (error) {
135
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
136
+ }
137
+
138
+ }
139
+
140
+ hasIncognito(): boolean {
141
+ return this.descriptorWrapper.rawProps.incognito && this.descriptorWrapper.rawProps.incognito === true
142
+ }
143
+
144
+ createWebViewEvent(type: string): WebViewEventParams {
145
+ return this.webViewBaseOperate?.createWebViewEvent({ type, progress: this.progress }) as WebViewEventParams;
146
+ }
147
+
148
+ aboutToDisappear() {
149
+ this.getUIContext().getFocusController().clearFocus()
150
+ this.cleanupCommandCallback?.()
151
+ this.unregisterDescriptorChangesListener?.()
152
+ this.cleanUpCallbacks.forEach(cb => cb())
153
+ try {
154
+ if (this.hasIncognito()) {
155
+ this.webViewBaseOperate?.setIncognito()
156
+ }
157
+ this.controller.deleteJavaScriptRegister(JAVASCRIPT_INTERFACE)
158
+ this.controller.refresh()
159
+ this.hasRegisterJavaScriptProxy = false
160
+ this.webViewBaseOperate = null
161
+ } catch (error) {
162
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
163
+ }
164
+ }
165
+
166
+ registerCommandCallback() {
167
+ this.cleanupCommandCallback = this.ctx.componentCommandReceiver.registerCommandCallback(
168
+ this.tag,
169
+ (command: COMMAND_NAME, args: string[]) => this.webViewBaseOperate?.registerCommandCallback(command, args));
170
+ }
171
+
172
+ onLoadingStart() {
173
+ this.webViewBaseOperate?.emitLoadingStart({ progress: this.progress })
174
+ }
175
+
176
+ onProgressChange(event: OnProgressChangeEvent) {
177
+ this.progress = event.newProgress
178
+ // 修复单页面应用切换路由时 onPageBegin 未触发的问题
179
+ if (this.controller.getUrl() !== this.url && this.progress < ONE_HUNDRED && this.allowPageStartInProgress) {
180
+ this.allowPageStartInProgress = false;
181
+ this.onLoadingStart();
182
+ }
183
+ this.webViewBaseOperate?.emitProgressChange({ progress: this.progress })
184
+ if (this.progress === ONE_HUNDRED) {
185
+ this.allowPageStartInProgress = true;
186
+ }
187
+ }
188
+
189
+ onLoadingFinish() {
190
+ this.webViewBaseOperate?.emitLoadingFinish({ progress: this.progress })
191
+ this.isRefreshing = false
192
+ }
193
+
194
+ runInjectedJavaScript() {
195
+ let injectedJS = this.descriptorWrapper.rawProps.injectedJavaScript
196
+ if (this.javaScriptEnable && injectedJS != "" && this.controllerAttached) {
197
+ try {
198
+ this.controller.runJavaScript("(function() {\n" + injectedJS + ";\n})();")
199
+ .then((result) => {
200
+ Logger.debug(TAG, '[RNOH] result: ' + result);
201
+ })
202
+ .catch((error: string | Error) => {
203
+ Logger.error(TAG, "[RNOH] error: " + error);
204
+ })
205
+ } catch (error) {
206
+ Logger.error(TAG, `[RNOH]Errorcode: ${error.code}, Message: ${error.message}`);
207
+ }
208
+ }
209
+ }
210
+
211
+ getJsDialogTitle() {
212
+ const url = this.controller.getUrl()
213
+ // 对于 data: URL,只显示 'JavaScript',类似于 Chrome
214
+ let title = 'JavaScript';
215
+ const isDataUrl = (url !== null) && url.startsWith("data:");
216
+
217
+
218
+ if (!isDataUrl) {
219
+ try {
220
+ const dialogUrl = OSUrl.URL.parseURL(url);
221
+ title = `网址为”${dialogUrl.protocol}//${dialogUrl.host}“的网页显示:`;
222
+ } catch (ex) {
223
+ title = url;
224
+ }
225
+ }
226
+
227
+ return title;
228
+ }
229
+
230
+ onJavascriptAlert(event?: AlertEvent) {
231
+ if (event) {
232
+ AlertDialog.show({
233
+ title: this.getJsDialogTitle(),
234
+ message: event.message,
235
+ alignment: DialogAlignment.Center,
236
+ secondaryButton: {
237
+ value: this.resourceToString($r('app.string.determined')),
238
+ action: () => event.result.handleConfirm()
239
+ },
240
+ cancel: () => event.result.handleCancel(),
241
+ })
242
+ }
243
+ return true
244
+ }
245
+
246
+ onJavascriptConfirm(event?: AlertEvent) {
247
+ if (event) {
248
+ AlertDialog.show({
249
+ title: this.getJsDialogTitle(),
250
+ message: event.message,
251
+ alignment: DialogAlignment.Center,
252
+ secondaryButton: {
253
+ value: this.resourceToString($r('app.string.determined')),
254
+ action: () => event.result.handleConfirm()
255
+ },
256
+ cancel: () => event.result.handleCancel(),
257
+ primaryButton: {
258
+ value: this.resourceToString($r('app.string.cancel')),
259
+ action: () => event.result.handleCancel()
260
+ },
261
+ })
262
+ }
263
+ return true
264
+ }
265
+
266
+ ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch: boolean) {
267
+ this.webViewBaseOperate?.ignoreSilentHardwareSwitchMethods(ignoreSilentHardwareSwitch)
268
+ }
269
+
270
+ loadHtmlData(html: string = '', url: string = '') {
271
+ try {
272
+ this.controller.loadData(
273
+ html,
274
+ "text/html",
275
+ "UTF-8",
276
+ url,
277
+ " "
278
+ );
279
+ } catch (error) {
280
+ Logger.error(TAG, "error:" + error)
281
+ }
282
+ }
283
+
284
+ controllerAttachedInit(): void {
285
+ this.controllerAttached = true;
286
+ this.eventEmitter = new RNC.RNCWebView.EventEmitter(this.ctx.rnInstance, this.tag)
287
+ this.webViewBaseOperate = new BaseOperate(this.eventEmitter, this.controller)
288
+ this.webViewBaseOperate.setCustomUserAgent(this.descriptorWrapper.rawProps.userAgent,
289
+ this.descriptorWrapper.rawProps.applicationNameForUserAgent)
290
+ this.webViewBaseOperate.setFraudulentWebsiteWarningEnabled(this.descriptorWrapper.rawProps.fraudulentWebsiteWarningEnabled)
291
+
292
+ let baseUrl = this.source.baseUrl
293
+ let uri = this.source.uri
294
+ if (this.source.html != undefined && this.source.html != "") {
295
+ this.loadHtmlData(this.source.html, baseUrl)
296
+ } else if (uri != undefined && uri != "") {
297
+ if (this.source.method && this.source.method.toUpperCase() === 'POST' && this.source.body) {
298
+ let postData = buffer.from(this.source.body);
299
+ let url = uri as string;
300
+ this.controller.postUrl(url, postData?.buffer);
301
+ } else {
302
+ this.controller.loadUrl(uri, this.headers);
303
+ }
304
+ } else {
305
+ this.loadHtmlData()
306
+ }
307
+ if (!this.hasRegisterJavaScriptProxy) {
308
+ this.registerPostMessage()
309
+ }
310
+ }
311
+
312
+ onPageBegin() {
313
+ try {
314
+ if (this.controller.getUrl() === this.url) {
315
+ this.onLoadingStart();
316
+ }
317
+ this.controller.setScrollable(this.scrollEnabled)
318
+ this.ignoreSilentHardwareSwitchMethods(this.descriptorWrapper.rawProps.ignoreSilentHardwareSwitch)
319
+ } catch (error) {
320
+ Logger.debug(TAG,
321
+ `[RNOH] setignoreSilentHardwareSwitch and setScrollable ErrorCode: ${error.code}, Message: ${error.message}, userAgent: ${this.descriptorWrapper.rawProps.userAgent}`);
322
+ }
323
+ }
324
+
325
+ getBundleName() {
326
+ let bundleFlags =
327
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY |
328
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
329
+
330
+ try {
331
+ bundleManager.getBundleInfoForSelf(bundleFlags, (error, data) => {
332
+ if (error) {
333
+ Logger.error(TAG,
334
+ `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
335
+ } else {
336
+ this.bundleName = JSON.stringify(data.name)
337
+ }
338
+ });
339
+ } catch (error) {
340
+ Logger.error(TAG,
341
+ `[RNOH] getBundleInfoForSelf failed ErrorCode: ${error.code}, Message: ${error.message}`);
342
+ }
343
+ }
344
+
345
+ resourceToString(resource: Resource): string {
346
+ return getContext(this).resourceManager.getStringSync(resource)
347
+ }
348
+
349
+ onLoadFinished() {
350
+ this.onLoadingFinish()
351
+ }
352
+
353
+ onPageEndExecuteRunInjectedJavaScript(){
354
+ this.runInjectedJavaScript()
355
+ }
356
+
357
+ onPageEnd() {
358
+ this.runInjectedJavaScript()
359
+ this.onLoadingFinish()
360
+ }
361
+
362
+ getPermissionDialogMessage(event: OnPermissionRequestEvent) {
363
+ let permissionDialogMessage = ''
364
+ permissionDialogMessage = event.request.getAccessibleResource().toString()
365
+ switch (permissionDialogMessage) {
366
+ case ProtectedResourceType.VIDEO_CAPTURE:
367
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera'))
368
+ break;
369
+ case ProtectedResourceType.AUDIO_CAPTURE:
370
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_microphone'))
371
+ break;
372
+ case ProtectedResourceType.MidiSysex:
373
+ permissionDialogMessage = this.resourceToString($r('app.string.midi_sysex_resources'))
374
+ break;
375
+ case ProtectedResourceType.VIDEO_CAPTURE + "," + ProtectedResourceType.AUDIO_CAPTURE:
376
+ permissionDialogMessage = this.resourceToString($r('app.string.use_your_camera_and_microphone'))
377
+ break;
378
+ }
379
+ return permissionDialogMessage;
380
+ }
381
+
382
+ onLoadIntercept(event: OnLoadInterceptEvent): boolean {
383
+ Logger.debug(TAG, `onLoadIntercept start`)
384
+ this.webViewBaseOperate?.setLockIdentifier(BaseOperate.generateLockIdentifier())
385
+
386
+ const params = new ShouldStartParams();
387
+ params.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier() || -1
388
+ this.ctx.runOnWorkerThread(new WorkerRunnable(), params);
389
+ this.webViewBaseOperate?.emitShouldStartLoadWithRequest(event, this.source.html, this.source.baseUrl)
390
+ const startTime = Date.now();
391
+ // 当lockState没变化并且小于超时时间,阻塞进程
392
+ while (params.lockState === ShouldOverrideCallbackState.UNDECIDED &&
393
+ Date.now() - startTime < RNCWebView.SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
394
+ // 空循环等待
395
+ }
396
+
397
+ Logger.debug('params.lockState res:' + params.lockState + `, wait time is ${Date.now() - startTime}`);
398
+ return params.lockState === ShouldOverrideCallbackState.SHOULD_OVERRIDE;
399
+ }
400
+
401
+ onOverrideUrlLoading(event: WebResourceRequest) {
402
+ this.webViewBaseOperate?.emitShouldStartLoadWithRequestOverrideUrlLoading(event)
403
+ return false
404
+ }
405
+
406
+ onSslErrorEventCallback(event: SslErrorEvent){
407
+ this.webViewBaseOperate?.emitOnSslErrorEventCallback(event);
408
+ }
409
+
410
+ onCreateMenu = (menuItems: Array<TextMenuItem>) => {
411
+ let customMenuItems = this.descriptorWrapper.rawProps.menuItems
412
+ if (customMenuItems == undefined) {
413
+ return menuItems;
414
+ } else if (customMenuItems.length == 0) {
415
+ return [];
416
+ } else {
417
+ let menuItemsArray: Array<TextMenuItem> = [];
418
+ for (let index = 0; index < customMenuItems.length; index++) {
419
+ let element: MenuItem = customMenuItems[index];
420
+ let customItem: TextMenuItem = {
421
+ content: element.label,
422
+ id: TextMenuItemId.of(element.key)
423
+ };
424
+ menuItemsArray.push(customItem)
425
+ }
426
+ return menuItemsArray;
427
+ }
428
+ }
429
+
430
+ onMenuItemClick(menuItem: TextMenuItem): boolean {
431
+ if (this.descriptorWrapper.rawProps.menuItems != undefined) {
432
+ this.controller.runJavaScript("(function(){return {selection: window.getSelection().toString()} })()")
433
+ .then((result: string) => {
434
+ this.webViewBaseOperate?.customMenuSelection(menuItem, result)
435
+ })
436
+ .catch((error: string | Error) => {
437
+ Logger.info('[RNOH] function onMenuItemClick error: ' + error);
438
+ })
439
+ // 禁用系统默认行为
440
+ return true;
441
+ }
442
+ return false;
443
+ }
444
+
445
+ onPermissionRequestAlert(event: OnPermissionRequestEvent){
446
+ AlertDialog.show({
447
+ title: this.resourceToString($r('app.string.on_confirm')) + event.request.getOrigin(),
448
+ message: this.getPermissionDialogMessage(event),
449
+ alignment: DialogAlignment.Center,
450
+ primaryButton: {
451
+ value: $r('app.string.deny'),
452
+ action: () => {
453
+ event.request.deny();
454
+ }
455
+ },
456
+ secondaryButton: {
457
+ value: $r('app.string.on_confirm'),
458
+ action: () => {
459
+ event.request.grant(event.request.getAccessibleResource());
460
+ }
461
+ },
462
+ cancel: () => {
463
+ event.request.deny();
464
+ }
465
+ })
466
+ }
467
+
468
+ async handleMediaCapturePermissionGrantType(resources: string[], domain: string) {
469
+ let permissionList: Array<Permissions> = [];
470
+ resources.forEach(resource => {
471
+ switch (resource.toString()) {
472
+ case 'TYPE_VIDEO_CAPTURE':
473
+ permissionList.push('ohos.permission.CAMERA');
474
+ break;
475
+ case 'TYPE_AUDIO_CAPTURE':
476
+ permissionList.push('ohos.permission.MICROPHONE');
477
+ break;
478
+ }
479
+ });
480
+
481
+ await atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, permissionList);
482
+ }
483
+
484
+ build() {
485
+ Stack() {
486
+ if ( deviceInfo.sdkApiVersion >= 20) {
487
+ Web({ src: "", controller: this.controller, renderMode: this.renderMode })
488
+ .mixedMode(this.mode)
489
+ .onPermissionRequest(async (event: OnPermissionRequestEvent) => {
490
+ if (event && (this.getPermissionDialogMessage(event) != "TYPE_SENSOR")) {
491
+ const resources: string[] = event.request.getAccessibleResource();
492
+ if (resources.indexOf('TYPE_AUDIO_CAPTURE') >= 0 || resources.indexOf('TYPE_VIDEO_CAPTURE') >= 0 ) {
493
+ const originUrl = this.webViewBaseOperate?.verifyURLFormat(event.request.getOrigin(), this.source.html,
494
+ this.source.baseUrl);
495
+ const currentUrl = this.webViewBaseOperate?.verifyURLFormat(this.controller.getUrl(), this.source.html,
496
+ this.source.baseUrl);
497
+ const originDomain: string = OSUrl.URL.parseURL(originUrl).hostname;
498
+ const currentDomain: string = OSUrl.URL.parseURL(currentUrl).hostname;
499
+ let mediaCapturePermissionGrantType: string = WebMediaCapturePermissionGrantType.PROMPT
500
+ if (this.descriptorWrapper.rawProps.mediaCapturePermissionGrantType != undefined) {
501
+ mediaCapturePermissionGrantType = this.descriptorWrapper.rawProps.mediaCapturePermissionGrantType;
502
+ }
503
+ if (mediaCapturePermissionGrantType != WebMediaCapturePermissionGrantType.DENY) {
504
+ await this.handleMediaCapturePermissionGrantType(resources, originDomain)
505
+ }
506
+ switch (mediaCapturePermissionGrantType) {
507
+ case WebMediaCapturePermissionGrantType.GRANT_IF_SAME_HOST_ELSE_PROMPT:
508
+ if (originDomain != currentDomain) {
509
+ this.onPermissionRequestAlert(event)
510
+ } else {
511
+ event.request.grant(resources);
512
+ }
513
+ break;
514
+ case WebMediaCapturePermissionGrantType.GRANT_IF_SAME_HOST_ELSE_DENY:
515
+ if (originDomain != currentDomain) {
516
+ event.request.deny();
517
+ } else {
518
+ event.request.grant(resources);
519
+ }
520
+ break;
521
+ case WebMediaCapturePermissionGrantType.DENY:
522
+ event.request.deny();
523
+ break;
524
+ case WebMediaCapturePermissionGrantType.GRANT:
525
+ event.request.grant(resources);
526
+ break;
527
+ case WebMediaCapturePermissionGrantType.PROMPT:
528
+ this.onPermissionRequestAlert(event)
529
+ break;
530
+ }
531
+ } else {
532
+ this.onPermissionRequestAlert(event)
533
+ }
534
+ }
535
+ })
536
+ .width(this.webviewWidth)
537
+ .height(this.webviewHeight)
538
+ .fileAccess(this.allowFileAccess)
539
+ .constraintSize({ minHeight: MINHEIGHT })
540
+ .overScrollMode(this.overScrollMode)
541
+ .javaScriptAccess(this.javaScriptEnable)
542
+ .javaScriptOnDocumentStart(this.injectedJavaScriptBeforeContentLoaded)
543
+ .horizontalScrollBarAccess(this.descriptorWrapper.rawProps.showsHorizontalScrollIndicator)
544
+ .verticalScrollBarAccess(this.descriptorWrapper.rawProps.showsVerticalScrollIndicator)
545
+ .overviewModeAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
546
+ .textZoomRatio(this.descriptorWrapper.rawProps.textZoom)
547
+ .backgroundColor(BaseOperate.getColorMode(this.ctx.uiAbilityContext,
548
+ this.descriptorWrapper.rawProps.forceDarkOn) === WebDarkMode.On ? Color.White : Color.Transparent)
549
+ .darkMode(BaseOperate.getColorMode(this.ctx.uiAbilityContext, this.descriptorWrapper.rawProps.forceDarkOn))
550
+ .forceDarkAccess(this.forceDark)
551
+ .cacheMode(this.cacheMode)
552
+ .minFontSize(this.minFontSize)
553
+ .domStorageAccess(this.descriptorWrapper.rawProps.domStorageEnabled)
554
+ .zoomAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
555
+ .overScrollMode(this.overScrollMode)
556
+ .onProgressChange((event: OnProgressChangeEvent) => this.onProgressChange(event))
557
+ .onScroll((event: OnScrollEvent) => this.webViewBaseOperate?.emitScroll(event))
558
+ .nestedScroll({
559
+ scrollForward: this.nestedScroll,
560
+ scrollBackward: this.nestedScroll,
561
+ })
562
+ .onLoadStarted(() => this.onPageBegin())
563
+ .onLoadFinished(() => this.onLoadFinished())
564
+ .onPageEnd(() => {this.onPageEndExecuteRunInjectedJavaScript()})
565
+ .onErrorReceive((event: OnErrorReceiveEvent) => this.webViewBaseOperate?.emitLoadingError(event))
566
+ .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.webViewBaseOperate?.emitHttpError(event))
567
+ .onControllerAttached(() => this.controllerAttachedInit())
568
+ .onAlert((event: AlertEvent) => this.onJavascriptAlert(event))
569
+ .onConfirm((event: AlertEvent) => this.onJavascriptConfirm(event))
570
+ .onDownloadStart((event: OnDownloadStartEvent) => this.webViewBaseOperate?.onDownloadStart(event))
571
+ .geolocationAccess(this.descriptorWrapper.rawProps.geolocationEnabled)
572
+ .onGeolocationShow((event) => {
573
+ if (event && (this.descriptorWrapper.rawProps.geolocationEnabled != undefined)) {
574
+ event.geolocation.invoke(event.origin, this.descriptorWrapper.rawProps.geolocationEnabled, true);
575
+ }
576
+ })
577
+ .mediaPlayGestureAccess(this.descriptorWrapper.rawProps.mediaPlaybackRequiresUserAction)
578
+ .onRenderExited((event: OnRenderExitedEvent) => this.webViewBaseOperate?.onRenderExited(event))
579
+ .onLoadIntercept((event: OnLoadInterceptEvent) => this.onLoadIntercept(event))
580
+ .onTitleReceive((event: OnTitleReceiveEvent) => this.webViewBaseOperate?.onTitleReceive(event))
581
+ .onSslErrorEvent((event: SslErrorEvent) => this.onSslErrorEventCallback(event))
582
+ .editMenuOptions({
583
+ onCreateMenu: this.onCreateMenu.bind(this),
584
+ onMenuItemClick: this.onMenuItemClick.bind(this),
585
+ })
586
+ } else {
587
+ Web({ src: "", controller: this.controller, renderMode: this.renderMode })
588
+ .mixedMode(this.mode)
589
+ .onPermissionRequest(async (event: OnPermissionRequestEvent) => {
590
+ if (event && (this.getPermissionDialogMessage(event) != "TYPE_SENSOR")) {
591
+ const resources: string[] = event.request.getAccessibleResource();
592
+ if (resources.indexOf('TYPE_AUDIO_CAPTURE') >= 0 || resources.indexOf('TYPE_VIDEO_CAPTURE') >= 0 ) {
593
+ const originUrl = this.webViewBaseOperate?.verifyURLFormat(event.request.getOrigin(), this.source.html,
594
+ this.source.baseUrl);
595
+ const currentUrl = this.webViewBaseOperate?.verifyURLFormat(this.controller.getUrl(), this.source.html,
596
+ this.source.baseUrl);
597
+ const originDomain: string = OSUrl.URL.parseURL(originUrl).hostname;
598
+ const currentDomain: string = OSUrl.URL.parseURL(currentUrl).hostname;
599
+ let mediaCapturePermissionGrantType: string = WebMediaCapturePermissionGrantType.PROMPT
600
+ if (this.descriptorWrapper.rawProps.mediaCapturePermissionGrantType != undefined) {
601
+ mediaCapturePermissionGrantType = this.descriptorWrapper.rawProps.mediaCapturePermissionGrantType;
602
+ }
603
+ if (mediaCapturePermissionGrantType != WebMediaCapturePermissionGrantType.DENY) {
604
+ await this.handleMediaCapturePermissionGrantType(resources, originDomain)
605
+ }
606
+ switch (mediaCapturePermissionGrantType) {
607
+ case WebMediaCapturePermissionGrantType.GRANT_IF_SAME_HOST_ELSE_PROMPT:
608
+ if (originDomain != currentDomain) {
609
+ this.onPermissionRequestAlert(event)
610
+ } else {
611
+ event.request.grant(resources);
612
+ }
613
+ break;
614
+ case WebMediaCapturePermissionGrantType.GRANT_IF_SAME_HOST_ELSE_DENY:
615
+ if (originDomain != currentDomain) {
616
+ event.request.deny();
617
+ } else {
618
+ event.request.grant(resources);
619
+ }
620
+ break;
621
+ case WebMediaCapturePermissionGrantType.DENY:
622
+ event.request.deny();
623
+ break;
624
+ case WebMediaCapturePermissionGrantType.GRANT:
625
+ event.request.grant(resources);
626
+ break;
627
+ case WebMediaCapturePermissionGrantType.PROMPT:
628
+ this.onPermissionRequestAlert(event)
629
+ break;
630
+ }
631
+ } else {
632
+ this.onPermissionRequestAlert(event)
633
+ }
634
+ }
635
+ })
636
+
637
+ .width(this.webviewWidth)
638
+ .height(this.webviewHeight)
639
+ .fileAccess(this.allowFileAccess)
640
+ .constraintSize({ minHeight: MINHEIGHT })
641
+ .overScrollMode(this.overScrollMode)
642
+ .javaScriptAccess(this.javaScriptEnable)
643
+ .javaScriptOnDocumentStart(this.injectedJavaScriptBeforeContentLoaded)
644
+ .horizontalScrollBarAccess(this.descriptorWrapper.rawProps.showsHorizontalScrollIndicator)
645
+ .verticalScrollBarAccess(this.descriptorWrapper.rawProps.showsVerticalScrollIndicator)
646
+ .overviewModeAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
647
+ .textZoomRatio(this.descriptorWrapper.rawProps.textZoom)
648
+ .backgroundColor(BaseOperate.getColorMode(this.ctx.uiAbilityContext,
649
+ this.descriptorWrapper.rawProps.forceDarkOn) === WebDarkMode.On ? Color.White : Color.Transparent)
650
+ .darkMode(BaseOperate.getColorMode(this.ctx.uiAbilityContext, this.descriptorWrapper.rawProps.forceDarkOn))
651
+ .forceDarkAccess(this.forceDark)
652
+ .cacheMode(this.cacheMode)
653
+ .minFontSize(this.minFontSize)
654
+ .domStorageAccess(this.descriptorWrapper.rawProps.domStorageEnabled)
655
+ .zoomAccess(this.descriptorWrapper.rawProps.scalesPageToFit)
656
+ .overScrollMode(this.overScrollMode)
657
+ .onProgressChange((event: OnProgressChangeEvent) => this.onProgressChange(event))
658
+ .onScroll((event: OnScrollEvent) => this.webViewBaseOperate?.emitScroll(event))
659
+ .nestedScroll({
660
+ scrollForward: this.nestedScroll,
661
+ scrollBackward: this.nestedScroll,
662
+ })
663
+ .onPageBegin(() => this.onPageBegin())
664
+ .onPageEnd(() => this.onPageEnd())
665
+ .onErrorReceive((event: OnErrorReceiveEvent) => this.webViewBaseOperate?.emitLoadingError(event))
666
+ .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.webViewBaseOperate?.emitHttpError(event))
667
+ .onControllerAttached(() => this.controllerAttachedInit())
668
+ .onAlert((event: AlertEvent) => this.onJavascriptAlert(event))
669
+ .onConfirm((event: AlertEvent) => this.onJavascriptConfirm(event))
670
+ .onDownloadStart((event: OnDownloadStartEvent) => this.webViewBaseOperate?.onDownloadStart(event))
671
+ .geolocationAccess(this.descriptorWrapper.rawProps.geolocationEnabled)
672
+ .onGeolocationShow((event) => {
673
+ if (event && (this.descriptorWrapper.rawProps.geolocationEnabled != undefined)) {
674
+ event.geolocation.invoke(event.origin, this.descriptorWrapper.rawProps.geolocationEnabled, true);
675
+ }
676
+ })
677
+ .mediaPlayGestureAccess(this.descriptorWrapper.rawProps.mediaPlaybackRequiresUserAction)
678
+ .onRenderExited((event: OnRenderExitedEvent) => this.webViewBaseOperate?.onRenderExited(event))
679
+ .onLoadIntercept((event: OnLoadInterceptEvent) => this.onLoadIntercept(event))
680
+ .onTitleReceive((event: OnTitleReceiveEvent) => this.webViewBaseOperate?.onTitleReceive(event))
681
+ .onSslErrorEvent((event: SslErrorEvent) => this.onSslErrorEventCallback(event))
682
+ .editMenuOptions({
683
+ onCreateMenu: this.onCreateMenu.bind(this),
684
+ onMenuItemClick: this.onMenuItemClick.bind(this),
685
+ })
686
+ }
687
+ }
688
+ .width(this.webviewWidth)
689
+ .height(this.webviewHeight)
690
+ .position({
691
+ x: this.descriptorWrapper.layoutMetrics.frame.origin.x,
692
+ y: this.descriptorWrapper.layoutMetrics.frame.origin.y
693
+ })
694
+ }
695
+
696
+ private onDescriptorWrapperChange(descriptorWrapper: WebViewDescriptor) {
697
+ this.initVariable()
698
+ this.descriptorWrapper = descriptorWrapper
699
+ if (this.source.html != "" && this.html != this.source.html) {
700
+ if (this.controllerAttached) {
701
+ this.loadHtmlData(this.source.html, this.source.baseUrl)
702
+ Logger.debug(TAG, "[RNOH] html is update")
703
+ this.html = this.source.html
704
+ }
705
+ } 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)) {
706
+ if (this.controllerAttached) {
707
+ let url = this.source.uri as string;
708
+ this.url = url;
709
+ this.body = this.source.body;
710
+ let postData = buffer.from(this.source.body);
711
+ this.controller.postUrl(url, postData?.buffer);
712
+ }
713
+ } else if (typeof this.url !== typeof this.source.uri
714
+ || (typeof this.source.uri === "string" && this.url !== this.source.uri)
715
+ || (typeof this.source.uri === "object" && (this.url as Resource)?.params !== (this.source.uri as Resource)?.params)) {
716
+ if (this.source.uri != "") {
717
+ Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri);
718
+ if (this.controllerAttached) {
719
+ this.controller.loadUrl(this.source.uri, this.headers)
720
+ }
721
+ } else {
722
+ this.loadHtmlData()
723
+ }
724
+ this.url = this.source.uri as string;
725
+ }
726
+ if (this.controllerAttached) {
727
+ this.controller?.setScrollable(this.descriptorWrapper.rawProps.scrollEnabled)
728
+ }
729
+ }
730
+
731
+ private initVariable() {
732
+ this.descriptorWrapper = this.ctx.descriptorRegistry.getDescriptor<WebViewDescriptor>(this.tag)
733
+ this.javaScriptEnable = this.descriptorWrapper.rawProps.javaScriptEnabled;
734
+ this.minFontSize =
735
+ this.descriptorWrapper.rawProps.minimumFontSize ? this.descriptorWrapper.rawProps.minimumFontSize : EIGHT;
736
+ this.cacheMode =
737
+ this.descriptorWrapper.rawProps.cacheEnabled ?
738
+ this.webViewBaseOperate?.transCacheMode(this.descriptorWrapper.rawProps.cacheMode as CACHE_MODE) as number :
739
+ CacheMode.Online;
740
+ this.source = {
741
+ uri: this.descriptorWrapper.rawProps.newSource.uri ?? "",
742
+ method: this.descriptorWrapper.rawProps.newSource.method ?? "",
743
+ body: this.descriptorWrapper.rawProps.newSource.body ?? "",
744
+ html: this.descriptorWrapper.rawProps.newSource.html ?? "",
745
+ baseUrl: this.descriptorWrapper.rawProps.newSource.baseUrl ?? "",
746
+ headers: this.descriptorWrapper.rawProps.newSource.headers ?? []
747
+ }
748
+ if (this.source.headers) {
749
+ this.source.headers.forEach(item => {
750
+ if (item.name && item.value) {
751
+ this.headers.push({ headerKey: item.name, headerValue: item.value })
752
+ }
753
+ })
754
+ }
755
+ // this.html = this.source.html
756
+ const newWidth = this.descriptorWrapper.layoutMetrics.frame.size.width
757
+ const newHeight = this.descriptorWrapper.layoutMetrics.frame.size.height
758
+
759
+ if (this.debounceTimer !== -1) {
760
+ clearTimeout(this.debounceTimer)
761
+ this.debounceTimer = -1
762
+ }
763
+
764
+ if (this.isFirstDebounce) {
765
+ this.webviewWidth = newWidth
766
+ this.webviewHeight = newHeight
767
+ this.isFirstDebounce = false
768
+ } else {
769
+ this.debounceTimer = setTimeout(() => {
770
+ this.webviewWidth = newWidth
771
+ this.webviewHeight = newHeight
772
+ }, RNCWebView.DEBOUNCE_TIME)
773
+ }
774
+ this.scrollEnabled = this.descriptorWrapper.rawProps.scrollEnabled;
775
+ // 当禁止滚动时,使用父组件滚动
776
+ this.nestedScroll = this.scrollEnabled ? NestedScrollMode.SELF_FIRST : NestedScrollMode.PARENT_FIRST;
777
+ if (this.source.uri && this.source.uri.toString().startsWith("asset://")) {
778
+ this.source.uri = $rawfile(this.source.uri.toString().replace("asset://", this.ctx.rnInstance.getAssetsDest()));
779
+ }
780
+
781
+ if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.ALWAYS) {
782
+ this.overScrollMode = OverScrollMode.ALWAYS
783
+ } else if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.NEVER) {
784
+ this.overScrollMode = OverScrollMode.NEVER
785
+ }
786
+
787
+ this.overScrollMode = this.descriptorWrapper.rawProps.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER;
788
+
789
+ if (this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded) {
790
+ this.injectedJavaScriptBeforeContentLoaded = [
791
+ { script: this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded, scriptRules: ["*"] }
792
+ ];
793
+ }
794
+
795
+ const renderModeStr = this.descriptorWrapper.rawProps.renderMode || "SYNC_RENDER"
796
+ this.renderMode = (renderModeStr === "ASYNC_RENDER") ? RenderMode.ASYNC_RENDER : RenderMode.SYNC_RENDER
797
+
798
+ }
799
+
800
+ private registerPostMessage() {
801
+ if (this.messagingEnabled == this.descriptorWrapper.rawProps.messagingEnabled) {
802
+ return;
803
+ }
804
+ this.messagingEnabled = this.descriptorWrapper.rawProps.messagingEnabled;
805
+ if (this.messagingEnabled) {
806
+ let bridge: RNCWebViewBridge = {
807
+ postMessage: (data: string) => {
808
+ Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`);
809
+ if (this.controller != null) {
810
+ let result: WebViewEventParams = this.createWebViewEvent("onMessage")
811
+ if (result) {
812
+ result.data = data.toString()
813
+ result.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier()
814
+ this.eventEmitter!.emit("message", result as ResultType);
815
+ }
816
+ }
817
+ }
818
+ };
819
+ this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"])
820
+ this.source.uri ?
821
+ this.controller.loadUrl(this.source.uri, this.headers) : this.controller.refresh()
822
+ this.hasRegisterJavaScriptProxy = true
823
+ }
824
+ }
689
825
  }