@react-native-ohos/react-native-webview 13.10.5 → 13.10.6-beta.6

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