@react-native-ohos/react-native-webview 13.16.2-beta.2 → 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 +29 -0
  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 -198
  32. package/harmony/rn_webview/src/main/ets/RNCWebView.ets +824 -818
  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 -514
  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 -101
  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,819 +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
- 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
- nestedScroll = 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 (this.url != this.source.uri) {
714
- if (this.source.uri != "") {
715
- Logger.debug(TAG, `[RNOH] newDescriptor props update uri: ` + this.source.uri);
716
- if (this.controllerAttached) {
717
- this.controller.loadUrl(this.descriptorWrapper.rawProps.newSource.uri, this.headers)
718
- }
719
- } else {
720
- this.loadHtmlData()
721
- }
722
- this.url = this.source.uri as string;
723
- }
724
- if (this.controllerAttached) {
725
- this.controller?.setScrollable(this.descriptorWrapper.rawProps.scrollEnabled)
726
- }
727
- }
728
-
729
- private initVariable() {
730
- this.descriptorWrapper = this.ctx.descriptorRegistry.getDescriptor<WebViewDescriptor>(this.tag)
731
- this.javaScriptEnable = this.descriptorWrapper.rawProps.javaScriptEnabled;
732
- this.minFontSize =
733
- this.descriptorWrapper.rawProps.minimumFontSize ? this.descriptorWrapper.rawProps.minimumFontSize : EIGHT;
734
- this.cacheMode =
735
- this.descriptorWrapper.rawProps.cacheEnabled ?
736
- this.webViewBaseOperate?.transCacheMode(this.descriptorWrapper.rawProps.cacheMode as CACHE_MODE) as number :
737
- CacheMode.Online;
738
- this.source = {
739
- uri: this.descriptorWrapper.rawProps.newSource.uri ?? "",
740
- method: this.descriptorWrapper.rawProps.newSource.method ?? "",
741
- body: this.descriptorWrapper.rawProps.newSource.body ?? "",
742
- html: this.descriptorWrapper.rawProps.newSource.html ?? "",
743
- baseUrl: this.descriptorWrapper.rawProps.newSource.baseUrl ?? "",
744
- headers: this.descriptorWrapper.rawProps.newSource.headers ?? []
745
- }
746
- if (this.source.headers) {
747
- this.source.headers.forEach(item => {
748
- if (item.name && item.value) {
749
- this.headers.push({ headerKey: item.name, headerValue: item.value })
750
- }
751
- })
752
- }
753
- // this.html = this.source.html
754
- const newWidth = this.descriptorWrapper.layoutMetrics.frame.size.width
755
- const newHeight = this.descriptorWrapper.layoutMetrics.frame.size.height
756
-
757
- if (this.debounceTimer !== -1) {
758
- clearTimeout(this.debounceTimer)
759
- this.debounceTimer = -1
760
- }
761
-
762
- if (this.isFirstDebounce) {
763
- this.webviewWidth = newWidth
764
- this.webviewHeight = newHeight
765
- this.isFirstDebounce = false
766
- } else {
767
- this.debounceTimer = setTimeout(() => {
768
- this.webviewWidth = newWidth
769
- this.webviewHeight = newHeight
770
- }, RNCWebView.DEBOUNCE_TIME)
771
- }
772
- this.scrollEnabled = this.descriptorWrapper.rawProps.scrollEnabled;
773
- // 当禁止滚动时,使用父组件滚动
774
- this.nestedScroll = this.scrollEnabled ? NestedScrollMode.SELF_FIRST : NestedScrollMode.PARENT_FIRST;
775
- if (this.source.uri && this.source.uri.toString().startsWith("asset://")) {
776
- this.source.uri = $rawfile(this.source.uri.toString().replace("asset://", this.ctx.rnInstance.getAssetsDest()));
777
- }
778
-
779
- if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.ALWAYS) {
780
- this.overScrollMode = OverScrollMode.ALWAYS
781
- } else if (this.descriptorWrapper.rawProps.overScrollMode === WebViewOverScrollMode.NEVER) {
782
- this.overScrollMode = OverScrollMode.NEVER
783
- }
784
-
785
- this.overScrollMode = this.descriptorWrapper.rawProps.bounces ? OverScrollMode.ALWAYS : OverScrollMode.NEVER;
786
-
787
- if (this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded) {
788
- this.injectedJavaScriptBeforeContentLoaded = [
789
- { script: this.descriptorWrapper.rawProps.injectedJavaScriptBeforeContentLoaded, scriptRules: ["*"] }
790
- ];
791
- }
792
- }
793
-
794
- private registerPostMessage() {
795
- if (this.messagingEnabled == this.descriptorWrapper.rawProps.messagingEnabled) {
796
- return;
797
- }
798
- this.messagingEnabled = this.descriptorWrapper.rawProps.messagingEnabled;
799
- if (this.messagingEnabled) {
800
- let bridge: RNCWebViewBridge = {
801
- postMessage: (data: string) => {
802
- Logger.debug(TAG, `[RNOH] bridge postMessage, ${JSON.stringify(data)}`);
803
- if (this.controller != null) {
804
- let result: WebViewEventParams = this.createWebViewEvent("onMessage")
805
- if (result) {
806
- result.data = data.toString()
807
- result.lockIdentifier = this.webViewBaseOperate?.getLockIdentifier()
808
- this.eventEmitter!.emit("message", result as ResultType);
809
- }
810
- }
811
- }
812
- };
813
- this.controller.registerJavaScriptProxy(bridge, JAVASCRIPT_INTERFACE, ["postMessage"])
814
- this.source.uri ?
815
- this.controller.loadUrl(this.source.uri, this.headers) : this.controller.refresh()
816
- this.hasRegisterJavaScriptProxy = true
817
- }
818
- }
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
+ }
819
825
  }