@exodus/react-native-webview 9.4.0-no-android.0 → 11.26.1-exodus.1

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 (49) hide show
  1. package/README.md +21 -18
  2. package/android/.editorconfig +6 -0
  3. package/android/build.gradle +137 -0
  4. package/android/gradle.properties +6 -0
  5. package/android/src/main/AndroidManifest.xml +15 -0
  6. package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewFileProvider.java +14 -0
  7. package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +1650 -0
  8. package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java +550 -0
  9. package/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewPackage.kt +15 -0
  10. package/android/src/main/java/com/reactnativecommunity/webview/WebViewConfig.java +12 -0
  11. package/android/src/main/java/com/reactnativecommunity/webview/events/TopHttpErrorEvent.kt +25 -0
  12. package/android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingErrorEvent.kt +25 -0
  13. package/android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingFinishEvent.kt +24 -0
  14. package/android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingProgressEvent.kt +24 -0
  15. package/android/src/main/java/com/reactnativecommunity/webview/events/TopLoadingStartEvent.kt +25 -0
  16. package/android/src/main/java/com/reactnativecommunity/webview/events/TopMessageEvent.kt +24 -0
  17. package/android/src/main/java/com/reactnativecommunity/webview/events/TopRenderProcessGoneEvent.kt +26 -0
  18. package/android/src/main/java/com/reactnativecommunity/webview/events/TopShouldStartLoadWithRequestEvent.kt +29 -0
  19. package/android/src/main/res/xml/file_provider_paths.xml +6 -0
  20. package/apple/RNCWKProcessPoolManager.h +15 -0
  21. package/apple/RNCWKProcessPoolManager.m +36 -0
  22. package/apple/RNCWebView.h +117 -0
  23. package/apple/RNCWebView.m +1532 -0
  24. package/apple/RNCWebViewManager.h +13 -0
  25. package/apple/RNCWebViewManager.m +288 -0
  26. package/index.d.ts +65 -0
  27. package/index.js +4 -0
  28. package/ios/RNCWebView.xcodeproj/project.pbxproj +2 -0
  29. package/lib/WebView.android.d.ts +7 -0
  30. package/lib/WebView.android.js +125 -1
  31. package/lib/WebView.d.ts +7 -0
  32. package/lib/WebView.ios.d.ts +7 -0
  33. package/lib/WebView.ios.js +148 -202
  34. package/lib/WebView.js +9 -2
  35. package/lib/WebView.styles.d.ts +12 -0
  36. package/lib/WebView.styles.js +7 -7
  37. package/lib/WebViewNativeComponent.android.d.ts +4 -0
  38. package/lib/WebViewNativeComponent.android.js +3 -0
  39. package/lib/WebViewNativeComponent.ios.d.ts +4 -0
  40. package/lib/WebViewNativeComponent.ios.js +3 -0
  41. package/lib/WebViewShared.d.ts +37 -0
  42. package/lib/WebViewShared.js +121 -24
  43. package/lib/WebViewTypes.d.ts +873 -0
  44. package/lib/WebViewTypes.js +31 -16
  45. package/lib/index.d.ts +4 -0
  46. package/lib/index.js +3 -0
  47. package/package.json +83 -87
  48. package/react-native-webview.podspec +4 -4
  49. package/react-native.config.js +37 -0
@@ -0,0 +1,1650 @@
1
+ package com.reactnativecommunity.webview;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.annotation.TargetApi;
5
+ import android.app.Activity;
6
+ import android.app.DownloadManager;
7
+ import android.content.Context;
8
+ import android.content.pm.ActivityInfo;
9
+ import android.content.pm.PackageManager;
10
+ import android.graphics.Bitmap;
11
+ import android.graphics.Color;
12
+ import android.Manifest;
13
+ import android.net.http.SslError;
14
+ import android.net.Uri;
15
+ import android.os.Build;
16
+ import android.os.Environment;
17
+ import android.os.Message;
18
+ import android.os.SystemClock;
19
+ import android.text.TextUtils;
20
+ import android.util.Log;
21
+ import android.view.Gravity;
22
+ import android.view.MotionEvent;
23
+ import android.view.View;
24
+ import android.view.ViewGroup;
25
+ import android.view.ViewGroup.LayoutParams;
26
+ import android.view.WindowManager;
27
+ import android.webkit.ConsoleMessage;
28
+ import android.webkit.CookieManager;
29
+ import android.webkit.DownloadListener;
30
+ import android.webkit.GeolocationPermissions;
31
+ import android.webkit.HttpAuthHandler;
32
+ import android.webkit.JavascriptInterface;
33
+ import android.webkit.RenderProcessGoneDetail;
34
+ import android.webkit.SslErrorHandler;
35
+ import android.webkit.PermissionRequest;
36
+ import android.webkit.ValueCallback;
37
+ import android.webkit.WebChromeClient;
38
+ import android.webkit.WebResourceRequest;
39
+ import android.webkit.WebResourceResponse;
40
+ import android.webkit.WebSettings;
41
+ import android.webkit.WebView;
42
+ import android.webkit.WebViewClient;
43
+ import android.widget.FrameLayout;
44
+
45
+ import androidx.annotation.NonNull;
46
+ import androidx.annotation.Nullable;
47
+ import androidx.annotation.RequiresApi;
48
+ import androidx.core.content.ContextCompat;
49
+ import androidx.core.util.Pair;
50
+ import androidx.webkit.WebSettingsCompat;
51
+ import androidx.webkit.WebViewFeature;
52
+
53
+ import com.facebook.common.logging.FLog;
54
+ import com.facebook.react.modules.core.PermissionAwareActivity;
55
+ import com.facebook.react.modules.core.PermissionListener;
56
+ import com.facebook.react.views.scroll.ScrollEvent;
57
+ import com.facebook.react.views.scroll.ScrollEventType;
58
+ import com.facebook.react.views.scroll.OnScrollDispatchHelper;
59
+ import com.facebook.react.bridge.Arguments;
60
+ import com.facebook.react.bridge.CatalystInstance;
61
+ import com.facebook.react.bridge.LifecycleEventListener;
62
+ import com.facebook.react.bridge.ReactContext;
63
+ import com.facebook.react.bridge.ReadableArray;
64
+ import com.facebook.react.bridge.ReadableMap;
65
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
66
+ import com.facebook.react.bridge.WritableMap;
67
+ import com.facebook.react.bridge.WritableNativeArray;
68
+ import com.facebook.react.bridge.WritableNativeMap;
69
+ import com.facebook.react.common.MapBuilder;
70
+ import com.facebook.react.common.build.ReactBuildConfig;
71
+ import com.facebook.react.module.annotations.ReactModule;
72
+ import com.facebook.react.uimanager.SimpleViewManager;
73
+ import com.facebook.react.uimanager.ThemedReactContext;
74
+ import com.facebook.react.uimanager.UIManagerModule;
75
+ import com.facebook.react.uimanager.annotations.ReactProp;
76
+ import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
77
+ import com.facebook.react.uimanager.events.Event;
78
+ import com.facebook.react.uimanager.events.EventDispatcher;
79
+ import com.reactnativecommunity.webview.RNCWebViewModule.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState;
80
+ import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
81
+ import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
82
+ import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
83
+ import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
84
+ import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
85
+ import com.reactnativecommunity.webview.events.TopMessageEvent;
86
+ import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
87
+ import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
88
+
89
+ import org.json.JSONException;
90
+ import org.json.JSONObject;
91
+
92
+ import java.io.UnsupportedEncodingException;
93
+ import java.lang.IllegalArgumentException;
94
+ import java.net.MalformedURLException;
95
+ import java.net.URL;
96
+ import java.net.URLEncoder;
97
+ import java.util.ArrayList;
98
+ import java.util.Collections;
99
+ import java.util.HashMap;
100
+ import java.util.List;
101
+ import java.util.Locale;
102
+ import java.util.Map;
103
+ import java.util.concurrent.atomic.AtomicReference;
104
+
105
+ /**
106
+ * Manages instances of {@link WebView}
107
+ * <p>
108
+ * Can accept following commands:
109
+ * - GO_BACK
110
+ * - GO_FORWARD
111
+ * - RELOAD
112
+ * - LOAD_URL
113
+ * <p>
114
+ * {@link WebView} instances could emit following direct events:
115
+ * - topLoadingFinish
116
+ * - topLoadingStart
117
+ * - topLoadingStart
118
+ * - topLoadingProgress
119
+ * - topShouldStartLoadWithRequest
120
+ * <p>
121
+ * Each event will carry the following properties:
122
+ * - target - view's react tag
123
+ * - url - url set for the webview
124
+ * - loading - whether webview is in a loading state
125
+ * - title - title of the current page
126
+ * - canGoBack - boolean, whether there is anything on a history stack to go back
127
+ * - canGoForward - boolean, whether it is possible to request GO_FORWARD command
128
+ */
129
+ @ReactModule(name = RNCWebViewManager.REACT_CLASS)
130
+ public class RNCWebViewManager extends SimpleViewManager<WebView> {
131
+ private static final String TAG = "RNCWebViewManager";
132
+
133
+ public static final int COMMAND_GO_BACK = 1;
134
+ public static final int COMMAND_GO_FORWARD = 2;
135
+ public static final int COMMAND_RELOAD = 3;
136
+ public static final int COMMAND_STOP_LOADING = 4;
137
+ public static final int COMMAND_POST_MESSAGE = 5;
138
+ public static final int COMMAND_INJECT_JAVASCRIPT = 6;
139
+ public static final int COMMAND_LOAD_URL = 7;
140
+ public static final int COMMAND_FOCUS = 8;
141
+
142
+ // android commands
143
+ public static final int COMMAND_CLEAR_FORM_DATA = 1000;
144
+ public static final int COMMAND_CLEAR_CACHE = 1001;
145
+ public static final int COMMAND_CLEAR_HISTORY = 1002;
146
+
147
+ protected static final String REACT_CLASS = "RNCWebView";
148
+ protected static final String HTML_ENCODING = "UTF-8";
149
+ protected static final String HTML_MIME_TYPE = "text/html";
150
+ protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
151
+ protected static final String HTTP_METHOD_POST = "POST";
152
+ // Use `webView.loadUrl("about:blank")` to reliably reset the view
153
+ // state and release page resources (including any running JavaScript).
154
+ protected static final String BLANK_URL = "about:blank";
155
+ protected static final int SHOULD_OVERRIDE_URL_LOADING_TIMEOUT = 250;
156
+ protected static final String DEFAULT_DOWNLOADING_MESSAGE = "Downloading";
157
+ protected static final String DEFAULT_LACK_PERMISSION_TO_DOWNLOAD_MESSAGE =
158
+ "Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files.";
159
+ protected WebViewConfig mWebViewConfig;
160
+
161
+ protected RNCWebChromeClient mWebChromeClient = null;
162
+ protected boolean mAllowsFullscreenVideo = false;
163
+ protected boolean mAllowsProtectedMedia = false;
164
+ protected @Nullable String mUserAgent = null;
165
+ protected @Nullable String mUserAgentWithApplicationName = null;
166
+ protected @Nullable String mDownloadingMessage = null;
167
+ protected @Nullable String mLackPermissionToDownloadMessage = null;
168
+
169
+ public RNCWebViewManager() {
170
+ mWebViewConfig = new WebViewConfig() {
171
+ public void configWebView(WebView webView) {
172
+ }
173
+ };
174
+ }
175
+
176
+ public RNCWebViewManager(WebViewConfig webViewConfig) {
177
+ mWebViewConfig = webViewConfig;
178
+ }
179
+
180
+ @Override
181
+ public String getName() {
182
+ return REACT_CLASS;
183
+ }
184
+
185
+ protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
186
+ return new RNCWebView(reactContext);
187
+ }
188
+
189
+ @Override
190
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
191
+ protected WebView createViewInstance(ThemedReactContext reactContext) {
192
+ RNCWebView webView = createRNCWebViewInstance(reactContext);
193
+ setupWebChromeClient(reactContext, webView);
194
+ reactContext.addLifecycleEventListener(webView);
195
+ mWebViewConfig.configWebView(webView);
196
+ WebSettings settings = webView.getSettings();
197
+ settings.setBuiltInZoomControls(true);
198
+ settings.setDisplayZoomControls(false);
199
+ settings.setDomStorageEnabled(true);
200
+ settings.setSupportMultipleWindows(true);
201
+
202
+ settings.setAllowFileAccess(false);
203
+ settings.setAllowContentAccess(false);
204
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
205
+ settings.setAllowFileAccessFromFileURLs(false);
206
+ setAllowUniversalAccessFromFileURLs(webView, false);
207
+ }
208
+ setMixedContentMode(webView, "never");
209
+
210
+ // Fixes broken full-screen modals/galleries due to body height being 0.
211
+ webView.setLayoutParams(
212
+ new LayoutParams(LayoutParams.MATCH_PARENT,
213
+ LayoutParams.MATCH_PARENT));
214
+
215
+ if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
216
+ WebView.setWebContentsDebuggingEnabled(true);
217
+ }
218
+
219
+ return webView;
220
+ }
221
+
222
+ private String getDownloadingMessage() {
223
+ return mDownloadingMessage == null ? DEFAULT_DOWNLOADING_MESSAGE : mDownloadingMessage;
224
+ }
225
+
226
+ private String getLackPermissionToDownloadMessage() {
227
+ return mDownloadingMessage == null ? DEFAULT_LACK_PERMISSION_TO_DOWNLOAD_MESSAGE : mLackPermissionToDownloadMessage;
228
+ }
229
+
230
+ @ReactProp(name = "javaScriptEnabled")
231
+ public void setJavaScriptEnabled(WebView view, boolean enabled) {
232
+ view.getSettings().setJavaScriptEnabled(enabled);
233
+ }
234
+
235
+ @ReactProp(name = "setBuiltInZoomControls")
236
+ public void setBuiltInZoomControls(WebView view, boolean enabled) {
237
+ view.getSettings().setBuiltInZoomControls(enabled);
238
+ }
239
+
240
+ @ReactProp(name = "setDisplayZoomControls")
241
+ public void setDisplayZoomControls(WebView view, boolean enabled) {
242
+ view.getSettings().setDisplayZoomControls(enabled);
243
+ }
244
+
245
+ @ReactProp(name = "setSupportMultipleWindows")
246
+ public void setSupportMultipleWindows(WebView view, boolean enabled){
247
+ view.getSettings().setSupportMultipleWindows(enabled);
248
+ }
249
+
250
+ @ReactProp(name = "showsHorizontalScrollIndicator")
251
+ public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
252
+ view.setHorizontalScrollBarEnabled(enabled);
253
+ }
254
+
255
+ @ReactProp(name = "showsVerticalScrollIndicator")
256
+ public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
257
+ view.setVerticalScrollBarEnabled(enabled);
258
+ }
259
+
260
+ @ReactProp(name = "downloadingMessage")
261
+ public void setDownloadingMessage(WebView view, String message) {
262
+ mDownloadingMessage = message;
263
+ }
264
+
265
+ @ReactProp(name = "lackPermissionToDownloadMessage")
266
+ public void setLackPermissionToDownlaodMessage(WebView view, String message) {
267
+ mLackPermissionToDownloadMessage = message;
268
+ }
269
+
270
+ @ReactProp(name = "cacheEnabled")
271
+ public void setCacheEnabled(WebView view, boolean enabled) {
272
+ view.getSettings().setCacheMode(enabled ? WebSettings.LOAD_DEFAULT : WebSettings.LOAD_NO_CACHE);
273
+ }
274
+
275
+ @ReactProp(name = "cacheMode")
276
+ public void setCacheMode(WebView view, String cacheModeString) {
277
+ Integer cacheMode;
278
+ switch (cacheModeString) {
279
+ case "LOAD_CACHE_ONLY":
280
+ cacheMode = WebSettings.LOAD_CACHE_ONLY;
281
+ break;
282
+ case "LOAD_CACHE_ELSE_NETWORK":
283
+ cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK;
284
+ break;
285
+ case "LOAD_NO_CACHE":
286
+ cacheMode = WebSettings.LOAD_NO_CACHE;
287
+ break;
288
+ case "LOAD_DEFAULT":
289
+ default:
290
+ cacheMode = WebSettings.LOAD_DEFAULT;
291
+ break;
292
+ }
293
+ view.getSettings().setCacheMode(cacheMode);
294
+ }
295
+
296
+ @ReactProp(name = "androidHardwareAccelerationDisabled")
297
+ public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
298
+ if (disabled) {
299
+ view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
300
+ }
301
+ }
302
+
303
+ @ReactProp(name = "androidLayerType")
304
+ public void setLayerType(WebView view, String layerTypeString) {
305
+ int layerType = View.LAYER_TYPE_NONE;
306
+ switch (layerTypeString) {
307
+ case "hardware":
308
+ layerType = View.LAYER_TYPE_HARDWARE;
309
+ break;
310
+ case "software":
311
+ layerType = View.LAYER_TYPE_SOFTWARE;
312
+ break;
313
+ }
314
+ view.setLayerType(layerType, null);
315
+ }
316
+
317
+
318
+ @ReactProp(name = "overScrollMode")
319
+ public void setOverScrollMode(WebView view, String overScrollModeString) {
320
+ Integer overScrollMode;
321
+ switch (overScrollModeString) {
322
+ case "never":
323
+ overScrollMode = View.OVER_SCROLL_NEVER;
324
+ break;
325
+ case "content":
326
+ overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
327
+ break;
328
+ case "always":
329
+ default:
330
+ overScrollMode = View.OVER_SCROLL_ALWAYS;
331
+ break;
332
+ }
333
+ view.setOverScrollMode(overScrollMode);
334
+ }
335
+
336
+ @ReactProp(name = "nestedScrollEnabled")
337
+ public void setNestedScrollEnabled(WebView view, boolean enabled) {
338
+ ((RNCWebView) view).setNestedScrollEnabled(enabled);
339
+ }
340
+
341
+ @ReactProp(name = "thirdPartyCookiesEnabled")
342
+ public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
343
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
344
+ CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
345
+ }
346
+ }
347
+
348
+ @ReactProp(name = "textZoom")
349
+ public void setTextZoom(WebView view, int value) {
350
+ view.getSettings().setTextZoom(value);
351
+ }
352
+
353
+ @ReactProp(name = "scalesPageToFit")
354
+ public void setScalesPageToFit(WebView view, boolean enabled) {
355
+ view.getSettings().setLoadWithOverviewMode(enabled);
356
+ view.getSettings().setUseWideViewPort(enabled);
357
+ }
358
+
359
+ @ReactProp(name = "domStorageEnabled")
360
+ public void setDomStorageEnabled(WebView view, boolean enabled) {
361
+ view.getSettings().setDomStorageEnabled(enabled);
362
+ }
363
+
364
+ @ReactProp(name = "userAgent")
365
+ public void setUserAgent(WebView view, @Nullable String userAgent) {
366
+ if (userAgent != null) {
367
+ mUserAgent = userAgent;
368
+ } else {
369
+ mUserAgent = null;
370
+ }
371
+ this.setUserAgentString(view);
372
+ }
373
+
374
+ @ReactProp(name = "applicationNameForUserAgent")
375
+ public void setApplicationNameForUserAgent(WebView view, @Nullable String applicationName) {
376
+ if(applicationName != null) {
377
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
378
+ String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
379
+ mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
380
+ }
381
+ } else {
382
+ mUserAgentWithApplicationName = null;
383
+ }
384
+ this.setUserAgentString(view);
385
+ }
386
+
387
+ protected void setUserAgentString(WebView view) {
388
+ if(mUserAgent != null) {
389
+ view.getSettings().setUserAgentString(mUserAgent);
390
+ } else if(mUserAgentWithApplicationName != null) {
391
+ view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
392
+ } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
393
+ // handle unsets of `userAgent` prop as long as device is >= API 17
394
+ view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
395
+ }
396
+ }
397
+
398
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
399
+ @ReactProp(name = "mediaPlaybackRequiresUserAction")
400
+ public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
401
+ view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
402
+ }
403
+
404
+ @ReactProp(name = "javaScriptCanOpenWindowsAutomatically")
405
+ public void setJavaScriptCanOpenWindowsAutomatically(WebView view, boolean enabled) {
406
+ view.getSettings().setJavaScriptCanOpenWindowsAutomatically(false);
407
+ }
408
+
409
+ @ReactProp(name = "allowFileAccessFromFileURLs")
410
+ public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
411
+ view.getSettings().setAllowFileAccessFromFileURLs(allow);
412
+ }
413
+
414
+ @ReactProp(name = "allowUniversalAccessFromFileURLs")
415
+ public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
416
+ view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
417
+ }
418
+
419
+ @ReactProp(name = "saveFormDataDisabled")
420
+ public void setSaveFormDataDisabled(WebView view, boolean disable) {
421
+ view.getSettings().setSaveFormData(!disable);
422
+ }
423
+
424
+ @ReactProp(name = "injectedJavaScript")
425
+ public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
426
+ ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
427
+ }
428
+
429
+ @ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
430
+ public void setInjectedJavaScriptBeforeContentLoaded(WebView view, @Nullable String injectedJavaScriptBeforeContentLoaded) {
431
+ ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoaded(injectedJavaScriptBeforeContentLoaded);
432
+ }
433
+
434
+ @ReactProp(name = "injectedJavaScriptForMainFrameOnly")
435
+ public void setInjectedJavaScriptForMainFrameOnly(WebView view, boolean enabled) {
436
+ ((RNCWebView) view).setInjectedJavaScriptForMainFrameOnly(enabled);
437
+ }
438
+
439
+ @ReactProp(name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly")
440
+ public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(WebView view, boolean enabled) {
441
+ ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(enabled);
442
+ }
443
+
444
+ @ReactProp(name = "messagingEnabled")
445
+ public void setMessagingEnabled(WebView view, boolean enabled) {
446
+ ((RNCWebView) view).setMessagingEnabled(enabled);
447
+ }
448
+
449
+ @ReactProp(name = "messagingModuleName")
450
+ public void setMessagingModuleName(WebView view, String moduleName) {
451
+ ((RNCWebView) view).setMessagingModuleName(moduleName);
452
+ }
453
+
454
+ @ReactProp(name = "incognito")
455
+ public void setIncognito(WebView view, boolean enabled) {
456
+ // Don't do anything when incognito is disabled
457
+ if (!enabled) {
458
+ return;
459
+ }
460
+
461
+ // Remove all previous cookies
462
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
463
+ CookieManager.getInstance().removeAllCookies(null);
464
+ } else {
465
+ CookieManager.getInstance().removeAllCookie();
466
+ }
467
+
468
+ // Disable caching
469
+ view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
470
+ view.clearHistory();
471
+ view.clearCache(true);
472
+
473
+ // No form data or autofill enabled
474
+ view.clearFormData();
475
+ view.getSettings().setSavePassword(false);
476
+ view.getSettings().setSaveFormData(false);
477
+ }
478
+
479
+ @ReactProp(name = "source")
480
+ public void setSource(WebView view, @Nullable ReadableMap source) {
481
+ if (source != null) {
482
+ if (source.hasKey("uri")) {
483
+ String url = source.getString("uri");
484
+ String previousUrl = view.getUrl();
485
+ if (previousUrl != null && previousUrl.equals(url)) {
486
+ return;
487
+ }
488
+ if (source.hasKey("method")) {
489
+ String method = source.getString("method");
490
+ if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
491
+ byte[] postData = null;
492
+ if (source.hasKey("body")) {
493
+ String body = source.getString("body");
494
+ try {
495
+ postData = body.getBytes("UTF-8");
496
+ } catch (UnsupportedEncodingException e) {
497
+ postData = body.getBytes();
498
+ }
499
+ }
500
+ if (postData == null) {
501
+ postData = new byte[0];
502
+ }
503
+ view.postUrl(url, postData);
504
+ return;
505
+ }
506
+ }
507
+ HashMap<String, String> headerMap = new HashMap<>();
508
+ if (source.hasKey("headers")) {
509
+ ReadableMap headers = source.getMap("headers");
510
+ ReadableMapKeySetIterator iter = headers.keySetIterator();
511
+ while (iter.hasNextKey()) {
512
+ String key = iter.nextKey();
513
+ if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
514
+ if (view.getSettings() != null) {
515
+ view.getSettings().setUserAgentString(headers.getString(key));
516
+ }
517
+ } else {
518
+ headerMap.put(key, headers.getString(key));
519
+ }
520
+ }
521
+ }
522
+ view.loadUrl(url, headerMap);
523
+ return;
524
+ }
525
+ }
526
+ view.loadUrl(BLANK_URL);
527
+ }
528
+
529
+ @ReactProp(name = "basicAuthCredential")
530
+ public void setBasicAuthCredential(WebView view, @Nullable ReadableMap credential) {
531
+ @Nullable BasicAuthCredential basicAuthCredential = null;
532
+ if (credential != null) {
533
+ if (credential.hasKey("username") && credential.hasKey("password")) {
534
+ String username = credential.getString("username");
535
+ String password = credential.getString("password");
536
+ basicAuthCredential = new BasicAuthCredential(username, password);
537
+ }
538
+ }
539
+ ((RNCWebView) view).setBasicAuthCredential(basicAuthCredential);
540
+ }
541
+
542
+ @ReactProp(name = "onContentSizeChange")
543
+ public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
544
+ ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
545
+ }
546
+
547
+ @ReactProp(name = "mixedContentMode")
548
+ public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
549
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
550
+ if (mixedContentMode == null || "never".equals(mixedContentMode)) {
551
+ view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
552
+ } else if ("always".equals(mixedContentMode)) {
553
+ view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
554
+ } else if ("compatibility".equals(mixedContentMode)) {
555
+ view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
556
+ }
557
+ }
558
+ }
559
+
560
+ @ReactProp(name = "urlPrefixesForDefaultIntent")
561
+ public void setUrlPrefixesForDefaultIntent(
562
+ WebView view,
563
+ @Nullable ReadableArray urlPrefixesForDefaultIntent) {
564
+ RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
565
+ if (client != null && urlPrefixesForDefaultIntent != null) {
566
+ client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
567
+ }
568
+ }
569
+
570
+ @ReactProp(name = "allowsFullscreenVideo")
571
+ public void setAllowsFullscreenVideo(
572
+ WebView view,
573
+ @Nullable Boolean allowsFullscreenVideo) {
574
+ mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
575
+ setupWebChromeClient((ReactContext)view.getContext(), view);
576
+ }
577
+
578
+ @ReactProp(name = "allowFileAccess")
579
+ public void setAllowFileAccess(
580
+ WebView view,
581
+ @Nullable Boolean allowFileAccess) {
582
+ view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
583
+ }
584
+
585
+ @ReactProp(name = "geolocationEnabled")
586
+ public void setGeolocationEnabled(
587
+ WebView view,
588
+ @Nullable Boolean isGeolocationEnabled) {
589
+ view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
590
+ }
591
+
592
+ @ReactProp(name = "onScroll")
593
+ public void setOnScroll(WebView view, boolean hasScrollEvent) {
594
+ ((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
595
+ }
596
+
597
+ @ReactProp(name = "forceDarkOn")
598
+ public void setForceDarkOn(WebView view, boolean enabled) {
599
+ // Only Android 10+ support dark mode
600
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
601
+ // Switch WebView dark mode
602
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
603
+ int forceDarkMode = enabled ? WebSettingsCompat.FORCE_DARK_ON : WebSettingsCompat.FORCE_DARK_OFF;
604
+ WebSettingsCompat.setForceDark(view.getSettings(), forceDarkMode);
605
+ }
606
+
607
+ // Set how WebView content should be darkened.
608
+ // PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING: checks for the "color-scheme" <meta> tag.
609
+ // If present, it uses media queries. If absent, it applies user-agent (automatic)
610
+ // More information about Force Dark Strategy can be found here:
611
+ // https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setForceDarkStrategy(android.webkit.WebSettings)
612
+ if (enabled && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
613
+ WebSettingsCompat.setForceDarkStrategy(view.getSettings(), WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
614
+ }
615
+ }
616
+ }
617
+
618
+ @ReactProp(name = "minimumFontSize")
619
+ public void setMinimumFontSize(WebView view, int fontSize) {
620
+ view.getSettings().setMinimumFontSize(fontSize);
621
+ }
622
+
623
+ @ReactProp(name = "allowsProtectedMedia")
624
+ public void setAllowsProtectedMedia(WebView view, boolean enabled) {
625
+ // This variable is used to keep consistency
626
+ // in case a new WebChromeClient is created
627
+ // (eg. when mAllowsFullScreenVideo changes)
628
+ mAllowsProtectedMedia = enabled;
629
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
630
+ WebChromeClient client = view.getWebChromeClient();
631
+ if (client != null && client instanceof RNCWebChromeClient) {
632
+ ((RNCWebChromeClient) client).setAllowsProtectedMedia(enabled);
633
+ }
634
+ }
635
+ }
636
+
637
+ @Override
638
+ protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
639
+ // Do not register default touch emitter and let WebView implementation handle touches
640
+ view.setWebViewClient(new RNCWebViewClient());
641
+ }
642
+
643
+ @Override
644
+ public Map getExportedCustomDirectEventTypeConstants() {
645
+ Map export = super.getExportedCustomDirectEventTypeConstants();
646
+ if (export == null) {
647
+ export = MapBuilder.newHashMap();
648
+ }
649
+ // Default events but adding them here explicitly for clarity
650
+ export.put(TopLoadingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingStart"));
651
+ export.put(TopLoadingFinishEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingFinish"));
652
+ export.put(TopLoadingErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingError"));
653
+ export.put(TopMessageEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMessage"));
654
+ // !Default events but adding them here explicitly for clarity
655
+
656
+ export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
657
+ export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
658
+ export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
659
+ export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
660
+ export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
661
+ return export;
662
+ }
663
+
664
+ @Override
665
+ public @Nullable
666
+ Map<String, Integer> getCommandsMap() {
667
+ return MapBuilder.<String, Integer>builder()
668
+ .put("goBack", COMMAND_GO_BACK)
669
+ .put("goForward", COMMAND_GO_FORWARD)
670
+ .put("reload", COMMAND_RELOAD)
671
+ .put("stopLoading", COMMAND_STOP_LOADING)
672
+ .put("postMessage", COMMAND_POST_MESSAGE)
673
+ .put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
674
+ .put("loadUrl", COMMAND_LOAD_URL)
675
+ .put("requestFocus", COMMAND_FOCUS)
676
+ .put("clearFormData", COMMAND_CLEAR_FORM_DATA)
677
+ .put("clearCache", COMMAND_CLEAR_CACHE)
678
+ .put("clearHistory", COMMAND_CLEAR_HISTORY)
679
+ .build();
680
+ }
681
+
682
+ @Override
683
+ public void receiveCommand(@NonNull WebView root, String commandId, @Nullable ReadableArray args) {
684
+ switch (commandId) {
685
+ case "goBack":
686
+ root.goBack();
687
+ break;
688
+ case "goForward":
689
+ root.goForward();
690
+ break;
691
+ case "reload":
692
+ root.reload();
693
+ break;
694
+ case "stopLoading":
695
+ root.stopLoading();
696
+ break;
697
+ case "postMessage":
698
+ try {
699
+ RNCWebView reactWebView = (RNCWebView) root;
700
+ JSONObject eventInitDict = new JSONObject();
701
+ eventInitDict.put("data", args.getString(0));
702
+ reactWebView.evaluateJavascriptWithFallback("(function () {" +
703
+ "var event;" +
704
+ "var data = " + eventInitDict.toString() + ";" +
705
+ "try {" +
706
+ "event = new MessageEvent('message', data);" +
707
+ "} catch (e) {" +
708
+ "event = document.createEvent('MessageEvent');" +
709
+ "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
710
+ "}" +
711
+ "document.dispatchEvent(event);" +
712
+ "})();");
713
+ } catch (JSONException e) {
714
+ throw new RuntimeException(e);
715
+ }
716
+ break;
717
+ case "injectJavaScript":
718
+ RNCWebView reactWebView = (RNCWebView) root;
719
+ reactWebView.evaluateJavascriptWithFallback(args.getString(0));
720
+ break;
721
+ case "loadUrl":
722
+ if (args == null) {
723
+ throw new RuntimeException("Arguments for loading an url are null!");
724
+ }
725
+ ((RNCWebView) root).progressChangedFilter.setWaitingForCommandLoadUrl(false);
726
+ root.loadUrl(args.getString(0));
727
+ break;
728
+ case "requestFocus":
729
+ root.requestFocus();
730
+ break;
731
+ case "clearFormData":
732
+ root.clearFormData();
733
+ break;
734
+ case "clearCache":
735
+ boolean includeDiskFiles = args != null && args.getBoolean(0);
736
+ root.clearCache(includeDiskFiles);
737
+ break;
738
+ case "clearHistory":
739
+ root.clearHistory();
740
+ break;
741
+ }
742
+ super.receiveCommand(root, commandId, args);
743
+ }
744
+
745
+ @Override
746
+ public void onDropViewInstance(WebView webView) {
747
+ super.onDropViewInstance(webView);
748
+ ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
749
+ ((RNCWebView) webView).cleanupCallbacksAndDestroy();
750
+ mWebChromeClient = null;
751
+ }
752
+
753
+ public static RNCWebViewModule getModule(ReactContext reactContext) {
754
+ return reactContext.getNativeModule(RNCWebViewModule.class);
755
+ }
756
+
757
+ protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
758
+ Activity activity = reactContext.getCurrentActivity();
759
+
760
+ if (mAllowsFullscreenVideo && activity != null) {
761
+ int initialRequestedOrientation = activity.getRequestedOrientation();
762
+
763
+ mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
764
+ @Override
765
+ public Bitmap getDefaultVideoPoster() {
766
+ return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
767
+ }
768
+
769
+ @Override
770
+ public void onShowCustomView(View view, CustomViewCallback callback) {
771
+ if (mVideoView != null) {
772
+ callback.onCustomViewHidden();
773
+ return;
774
+ }
775
+
776
+ mVideoView = view;
777
+ mCustomViewCallback = callback;
778
+
779
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
780
+
781
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
782
+ mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
783
+ activity.getWindow().setFlags(
784
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
785
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
786
+ );
787
+ }
788
+
789
+ mVideoView.setBackgroundColor(Color.BLACK);
790
+
791
+ // Since RN's Modals interfere with the View hierarchy
792
+ // we will decide which View to hide if the hierarchy
793
+ // does not match (i.e., the WebView is within a Modal)
794
+ // NOTE: We could use `mWebView.getRootView()` instead of `getRootView()`
795
+ // but that breaks the Modal's styles and layout, so we need this to render
796
+ // in the main View hierarchy regardless
797
+ ViewGroup rootView = getRootView();
798
+ rootView.addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
799
+
800
+ // Different root views, we are in a Modal
801
+ if (rootView.getRootView() != mWebView.getRootView()) {
802
+ mWebView.getRootView().setVisibility(View.GONE);
803
+ } else {
804
+ // Same view hierarchy (no Modal), just hide the WebView then
805
+ mWebView.setVisibility(View.GONE);
806
+ }
807
+
808
+ mReactContext.addLifecycleEventListener(this);
809
+ }
810
+
811
+ @Override
812
+ public void onHideCustomView() {
813
+ if (mVideoView == null) {
814
+ return;
815
+ }
816
+
817
+ // Same logic as above
818
+ ViewGroup rootView = getRootView();
819
+
820
+ if (rootView.getRootView() != mWebView.getRootView()) {
821
+ mWebView.getRootView().setVisibility(View.VISIBLE);
822
+ } else {
823
+ // Same view hierarchy (no Modal)
824
+ mWebView.setVisibility(View.VISIBLE);
825
+ }
826
+
827
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
828
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
829
+ }
830
+
831
+ rootView.removeView(mVideoView);
832
+ mCustomViewCallback.onCustomViewHidden();
833
+
834
+ mVideoView = null;
835
+ mCustomViewCallback = null;
836
+
837
+ activity.setRequestedOrientation(initialRequestedOrientation);
838
+
839
+ mReactContext.removeLifecycleEventListener(this);
840
+ }
841
+ };
842
+ } else {
843
+ if (mWebChromeClient != null) {
844
+ mWebChromeClient.onHideCustomView();
845
+ }
846
+
847
+ mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
848
+ @Override
849
+ public Bitmap getDefaultVideoPoster() {
850
+ return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
851
+ }
852
+ };
853
+ }
854
+ mWebChromeClient.setAllowsProtectedMedia(mAllowsProtectedMedia);
855
+ webView.setWebChromeClient(mWebChromeClient);
856
+ }
857
+
858
+ protected static class RNCWebViewClient extends WebViewClient {
859
+
860
+ protected boolean mLastLoadFailed = false;
861
+ protected @Nullable
862
+ ReadableArray mUrlPrefixesForDefaultIntent;
863
+ protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
864
+ protected @Nullable String ignoreErrFailedForThisURL = null;
865
+ protected @Nullable BasicAuthCredential basicAuthCredential = null;
866
+
867
+ public void setIgnoreErrFailedForThisURL(@Nullable String url) {
868
+ ignoreErrFailedForThisURL = url;
869
+ }
870
+
871
+ public void setBasicAuthCredential(@Nullable BasicAuthCredential credential) {
872
+ basicAuthCredential = credential;
873
+ }
874
+
875
+ @Override
876
+ public void onPageFinished(WebView webView, String url) {
877
+ super.onPageFinished(webView, url);
878
+
879
+ if (!mLastLoadFailed) {
880
+ RNCWebView reactWebView = (RNCWebView) webView;
881
+
882
+ reactWebView.callInjectedJavaScript();
883
+
884
+ emitFinishEvent(webView, url);
885
+ }
886
+ }
887
+
888
+ @Override
889
+ public void onPageStarted(WebView webView, String url, Bitmap favicon) {
890
+ super.onPageStarted(webView, url, favicon);
891
+ mLastLoadFailed = false;
892
+
893
+ RNCWebView reactWebView = (RNCWebView) webView;
894
+ reactWebView.callInjectedJavaScriptBeforeContentLoaded();
895
+
896
+ ((RNCWebView) webView).dispatchEvent(
897
+ webView,
898
+ new TopLoadingStartEvent(
899
+ webView.getId(),
900
+ createWebViewEvent(webView, url)));
901
+ }
902
+
903
+ @Override
904
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
905
+ final RNCWebView rncWebView = (RNCWebView) view;
906
+ final boolean isJsDebugging = ((ReactContext) view.getContext()).getJavaScriptContextHolder().get() == 0;
907
+
908
+ if (!isJsDebugging && rncWebView.mCatalystInstance != null) {
909
+ final Pair<Integer, AtomicReference<ShouldOverrideCallbackState>> lock = RNCWebViewModule.shouldOverrideUrlLoadingLock.getNewLock();
910
+ final int lockIdentifier = lock.first;
911
+ final AtomicReference<ShouldOverrideCallbackState> lockObject = lock.second;
912
+
913
+ final WritableMap event = createWebViewEvent(view, url);
914
+ event.putInt("lockIdentifier", lockIdentifier);
915
+ rncWebView.sendDirectMessage("onShouldStartLoadWithRequest", event);
916
+
917
+ try {
918
+ assert lockObject != null;
919
+ synchronized (lockObject) {
920
+ final long startTime = SystemClock.elapsedRealtime();
921
+ while (lockObject.get() == ShouldOverrideCallbackState.UNDECIDED) {
922
+ if (SystemClock.elapsedRealtime() - startTime > SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
923
+ FLog.w(TAG, "Did not receive response to shouldOverrideUrlLoading in time, defaulting to allow loading.");
924
+ RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
925
+ return false;
926
+ }
927
+ lockObject.wait(SHOULD_OVERRIDE_URL_LOADING_TIMEOUT);
928
+ }
929
+ }
930
+ } catch (InterruptedException e) {
931
+ FLog.e(TAG, "shouldOverrideUrlLoading was interrupted while waiting for result.", e);
932
+ RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
933
+ return false;
934
+ }
935
+
936
+ final boolean shouldOverride = lockObject.get() == ShouldOverrideCallbackState.SHOULD_OVERRIDE;
937
+ RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
938
+
939
+ return shouldOverride;
940
+ } else {
941
+ FLog.w(TAG, "Couldn't use blocking synchronous call for onShouldStartLoadWithRequest due to debugging or missing Catalyst instance, falling back to old event-and-load.");
942
+ progressChangedFilter.setWaitingForCommandLoadUrl(true);
943
+ ((RNCWebView) view).dispatchEvent(
944
+ view,
945
+ new TopShouldStartLoadWithRequestEvent(
946
+ view.getId(),
947
+ createWebViewEvent(view, url)));
948
+ return true;
949
+ }
950
+ }
951
+
952
+ @TargetApi(Build.VERSION_CODES.N)
953
+ @Override
954
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
955
+ final String url = request.getUrl().toString();
956
+ return this.shouldOverrideUrlLoading(view, url);
957
+ }
958
+
959
+ @Override
960
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
961
+ if (basicAuthCredential != null) {
962
+ handler.proceed(basicAuthCredential.username, basicAuthCredential.password);
963
+ return;
964
+ }
965
+ super.onReceivedHttpAuthRequest(view, handler, host, realm);
966
+ }
967
+
968
+ @Override
969
+ public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
970
+ // onReceivedSslError is called for most requests, per Android docs: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%2520android.webkit.SslErrorHandler,%2520android.net.http.SslError)
971
+ // WebView.getUrl() will return the top-level window URL.
972
+ // If a top-level navigation triggers this error handler, the top-level URL will be the failing URL (not the URL of the currently-rendered page).
973
+ // This is desired behavior. We later use these values to determine whether the request is a top-level navigation or a subresource request.
974
+ String topWindowUrl = webView.getUrl();
975
+ String failingUrl = error.getUrl();
976
+
977
+ // Cancel request after obtaining top-level URL.
978
+ // If request is cancelled before obtaining top-level URL, undesired behavior may occur.
979
+ // Undesired behavior: Return value of WebView.getUrl() may be the current URL instead of the failing URL.
980
+ handler.cancel();
981
+
982
+ if (!topWindowUrl.equalsIgnoreCase(failingUrl)) {
983
+ // If error is not due to top-level navigation, then do not call onReceivedError()
984
+ Log.w(TAG, "Resource blocked from loading due to SSL error. Blocked URL: "+failingUrl);
985
+ return;
986
+ }
987
+
988
+ int code = error.getPrimaryError();
989
+ String description = "";
990
+ String descriptionPrefix = "SSL error: ";
991
+
992
+ // https://developer.android.com/reference/android/net/http/SslError.html
993
+ switch (code) {
994
+ case SslError.SSL_DATE_INVALID:
995
+ description = "The date of the certificate is invalid";
996
+ break;
997
+ case SslError.SSL_EXPIRED:
998
+ description = "The certificate has expired";
999
+ break;
1000
+ case SslError.SSL_IDMISMATCH:
1001
+ description = "Hostname mismatch";
1002
+ break;
1003
+ case SslError.SSL_INVALID:
1004
+ description = "A generic error occurred";
1005
+ break;
1006
+ case SslError.SSL_NOTYETVALID:
1007
+ description = "The certificate is not yet valid";
1008
+ break;
1009
+ case SslError.SSL_UNTRUSTED:
1010
+ description = "The certificate authority is not trusted";
1011
+ break;
1012
+ default:
1013
+ description = "Unknown SSL Error";
1014
+ break;
1015
+ }
1016
+
1017
+ description = descriptionPrefix + description;
1018
+
1019
+ this.onReceivedError(
1020
+ webView,
1021
+ code,
1022
+ description,
1023
+ failingUrl
1024
+ );
1025
+ }
1026
+
1027
+ @Override
1028
+ public void onReceivedError(
1029
+ WebView webView,
1030
+ int errorCode,
1031
+ String description,
1032
+ String failingUrl) {
1033
+
1034
+ if (ignoreErrFailedForThisURL != null
1035
+ && failingUrl.equals(ignoreErrFailedForThisURL)
1036
+ && errorCode == -1
1037
+ && description.equals("net::ERR_FAILED")) {
1038
+
1039
+ // This is a workaround for a bug in the WebView.
1040
+ // See these chromium issues for more context:
1041
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
1042
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
1043
+ // This entire commit should be reverted once this bug is resolved in chromium.
1044
+ setIgnoreErrFailedForThisURL(null);
1045
+ return;
1046
+ }
1047
+
1048
+ super.onReceivedError(webView, errorCode, description, failingUrl);
1049
+ mLastLoadFailed = true;
1050
+
1051
+ // In case of an error JS side expect to get a finish event first, and then get an error event
1052
+ // Android WebView does it in the opposite way, so we need to simulate that behavior
1053
+ emitFinishEvent(webView, failingUrl);
1054
+
1055
+ WritableMap eventData = createWebViewEvent(webView, failingUrl);
1056
+ eventData.putDouble("code", errorCode);
1057
+ eventData.putString("description", description);
1058
+
1059
+ ((RNCWebView) webView).dispatchEvent(
1060
+ webView,
1061
+ new TopLoadingErrorEvent(webView.getId(), eventData));
1062
+ }
1063
+
1064
+ @RequiresApi(api = Build.VERSION_CODES.M)
1065
+ @Override
1066
+ public void onReceivedHttpError(
1067
+ WebView webView,
1068
+ WebResourceRequest request,
1069
+ WebResourceResponse errorResponse) {
1070
+ super.onReceivedHttpError(webView, request, errorResponse);
1071
+
1072
+ if (request.isForMainFrame()) {
1073
+ WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
1074
+ eventData.putInt("statusCode", errorResponse.getStatusCode());
1075
+ eventData.putString("description", errorResponse.getReasonPhrase());
1076
+
1077
+ ((RNCWebView) webView).dispatchEvent(
1078
+ webView,
1079
+ new TopHttpErrorEvent(webView.getId(), eventData));
1080
+ }
1081
+ }
1082
+
1083
+ @TargetApi(Build.VERSION_CODES.O)
1084
+ @Override
1085
+ public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
1086
+ // WebViewClient.onRenderProcessGone was added in O.
1087
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
1088
+ return false;
1089
+ }
1090
+ super.onRenderProcessGone(webView, detail);
1091
+
1092
+ if(detail.didCrash()){
1093
+ Log.e(TAG, "The WebView rendering process crashed.");
1094
+ }
1095
+ else{
1096
+ Log.w(TAG, "The WebView rendering process was killed by the system.");
1097
+ }
1098
+
1099
+ // if webView is null, we cannot return any event
1100
+ // since the view is already dead/disposed
1101
+ // still prevent the app crash by returning true.
1102
+ if(webView == null){
1103
+ return true;
1104
+ }
1105
+
1106
+ WritableMap event = createWebViewEvent(webView, webView.getUrl());
1107
+ event.putBoolean("didCrash", detail.didCrash());
1108
+
1109
+ ((RNCWebView) webView).dispatchEvent(
1110
+ webView,
1111
+ new TopRenderProcessGoneEvent(webView.getId(), event)
1112
+ );
1113
+
1114
+ // returning false would crash the app.
1115
+ return true;
1116
+ }
1117
+
1118
+ protected void emitFinishEvent(WebView webView, String url) {
1119
+ ((RNCWebView) webView).dispatchEvent(
1120
+ webView,
1121
+ new TopLoadingFinishEvent(
1122
+ webView.getId(),
1123
+ createWebViewEvent(webView, url)));
1124
+ }
1125
+
1126
+ protected WritableMap createWebViewEvent(WebView webView, String url) {
1127
+ WritableMap event = Arguments.createMap();
1128
+ event.putDouble("target", webView.getId());
1129
+ // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
1130
+ // like onPageFinished
1131
+ event.putString("url", url);
1132
+ event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
1133
+ event.putString("title", webView.getTitle());
1134
+ event.putBoolean("canGoBack", webView.canGoBack());
1135
+ event.putBoolean("canGoForward", webView.canGoForward());
1136
+ return event;
1137
+ }
1138
+
1139
+ public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
1140
+ mUrlPrefixesForDefaultIntent = specialUrls;
1141
+ }
1142
+
1143
+ public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
1144
+ progressChangedFilter = filter;
1145
+ }
1146
+ }
1147
+
1148
+ protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
1149
+ protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
1150
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
1151
+
1152
+ @RequiresApi(api = Build.VERSION_CODES.KITKAT)
1153
+ protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
1154
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
1155
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
1156
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1157
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
1158
+ View.SYSTEM_UI_FLAG_IMMERSIVE |
1159
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
1160
+
1161
+ protected static final int COMMON_PERMISSION_REQUEST = 3;
1162
+
1163
+ protected ReactContext mReactContext;
1164
+ protected View mWebView;
1165
+
1166
+ protected View mVideoView;
1167
+ protected WebChromeClient.CustomViewCallback mCustomViewCallback;
1168
+
1169
+ /*
1170
+ * - Permissions -
1171
+ * As native permissions are asynchronously handled by the PermissionListener, many fields have
1172
+ * to be stored to send permissions results to the webview
1173
+ */
1174
+
1175
+ // Webview camera & audio permission callback
1176
+ protected PermissionRequest permissionRequest;
1177
+ // Webview camera & audio permission already granted
1178
+ protected List<String> grantedPermissions;
1179
+
1180
+ // Webview geolocation permission callback
1181
+ protected GeolocationPermissions.Callback geolocationPermissionCallback;
1182
+ // Webview geolocation permission origin callback
1183
+ protected String geolocationPermissionOrigin;
1184
+
1185
+ // true if native permissions dialog is shown, false otherwise
1186
+ protected boolean permissionsRequestShown = false;
1187
+ // Pending Android permissions for the next request
1188
+ protected List<String> pendingPermissions = new ArrayList<>();
1189
+
1190
+ protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
1191
+
1192
+ // True if protected media should be allowed, false otherwise
1193
+ protected boolean mAllowsProtectedMedia = false;
1194
+
1195
+ public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
1196
+ this.mReactContext = reactContext;
1197
+ this.mWebView = webView;
1198
+ }
1199
+
1200
+ @Override
1201
+ public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
1202
+
1203
+ final WebView newWebView = new WebView(view.getContext());
1204
+ final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
1205
+ transport.setWebView(newWebView);
1206
+ resultMsg.sendToTarget();
1207
+
1208
+ return true;
1209
+ }
1210
+
1211
+ @Override
1212
+ public boolean onConsoleMessage(ConsoleMessage message) {
1213
+ if (ReactBuildConfig.DEBUG) {
1214
+ return super.onConsoleMessage(message);
1215
+ }
1216
+ // Ignore console logs in non debug builds.
1217
+ return true;
1218
+ }
1219
+
1220
+ @Override
1221
+ public void onProgressChanged(WebView webView, int newProgress) {
1222
+ super.onProgressChanged(webView, newProgress);
1223
+ final String url = webView.getUrl();
1224
+ if (progressChangedFilter.isWaitingForCommandLoadUrl()) {
1225
+ return;
1226
+ }
1227
+ WritableMap event = Arguments.createMap();
1228
+ event.putDouble("target", webView.getId());
1229
+ event.putString("title", webView.getTitle());
1230
+ event.putString("url", url);
1231
+ event.putBoolean("canGoBack", webView.canGoBack());
1232
+ event.putBoolean("canGoForward", webView.canGoForward());
1233
+ event.putDouble("progress", (float) newProgress / 100);
1234
+ ((RNCWebView) webView).dispatchEvent(
1235
+ webView,
1236
+ new TopLoadingProgressEvent(
1237
+ webView.getId(),
1238
+ event));
1239
+ }
1240
+
1241
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1242
+ @Override
1243
+ public void onPermissionRequest(final PermissionRequest request) {
1244
+ request.deny();
1245
+ }
1246
+
1247
+
1248
+ @Override
1249
+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
1250
+
1251
+ // Always deny
1252
+ callback.invoke(origin, true, false);
1253
+
1254
+ }
1255
+
1256
+ private PermissionAwareActivity getPermissionAwareActivity() {
1257
+ Activity activity = mReactContext.getCurrentActivity();
1258
+ if (activity == null) {
1259
+ throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
1260
+ } else if (!(activity instanceof PermissionAwareActivity)) {
1261
+ throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
1262
+ }
1263
+ return (PermissionAwareActivity) activity;
1264
+ }
1265
+
1266
+ protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
1267
+ getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
1268
+ }
1269
+
1270
+ protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
1271
+ getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
1272
+ }
1273
+
1274
+ protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
1275
+ getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
1276
+ }
1277
+
1278
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1279
+ @Override
1280
+ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1281
+ String[] acceptTypes = fileChooserParams.getAcceptTypes();
1282
+ boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
1283
+ return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptTypes, allowMultiple);
1284
+ }
1285
+
1286
+ @Override
1287
+ public void onHostResume() {
1288
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
1289
+ mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
1290
+ }
1291
+ }
1292
+
1293
+ @Override
1294
+ public void onHostPause() { }
1295
+
1296
+ @Override
1297
+ public void onHostDestroy() { }
1298
+
1299
+ protected ViewGroup getRootView() {
1300
+ return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
1301
+ }
1302
+
1303
+ public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
1304
+ progressChangedFilter = filter;
1305
+ }
1306
+
1307
+ /**
1308
+ * Set whether or not protected media should be allowed
1309
+ * /!\ Setting this to false won't revoke permission already granted to the current webpage.
1310
+ * In order to do so, you'd need to reload the page /!\
1311
+ */
1312
+ public void setAllowsProtectedMedia(boolean enabled) {
1313
+ mAllowsProtectedMedia = enabled;
1314
+ }
1315
+ }
1316
+
1317
+ /**
1318
+ * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
1319
+ * to call {@link WebView#destroy} on activity destroy event and also to clear the client
1320
+ */
1321
+ protected static class RNCWebView extends WebView implements LifecycleEventListener {
1322
+ protected @Nullable
1323
+ String injectedJS;
1324
+ protected @Nullable
1325
+ String injectedJSBeforeContentLoaded;
1326
+
1327
+ /**
1328
+ * android.webkit.WebChromeClient fundamentally does not support JS injection into frames other
1329
+ * than the main frame, so these two properties are mostly here just for parity with iOS & macOS.
1330
+ */
1331
+ protected boolean injectedJavaScriptForMainFrameOnly = true;
1332
+ protected boolean injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true;
1333
+
1334
+ protected boolean messagingEnabled = false;
1335
+ protected @Nullable
1336
+ String messagingModuleName;
1337
+ protected @Nullable
1338
+ RNCWebViewClient mRNCWebViewClient;
1339
+ protected @Nullable
1340
+ CatalystInstance mCatalystInstance;
1341
+ protected boolean sendContentSizeChangeEvents = false;
1342
+ private OnScrollDispatchHelper mOnScrollDispatchHelper;
1343
+ protected boolean hasScrollEvent = false;
1344
+ protected boolean nestedScrollEnabled = false;
1345
+ protected ProgressChangedFilter progressChangedFilter;
1346
+
1347
+ /**
1348
+ * WebView must be created with an context of the current activity
1349
+ * <p>
1350
+ * Activity Context is required for creation of dialogs internally by WebView
1351
+ * Reactive Native needed for access to ReactNative internal system functionality
1352
+ */
1353
+ public RNCWebView(ThemedReactContext reactContext) {
1354
+ super(reactContext);
1355
+ this.createCatalystInstance();
1356
+ progressChangedFilter = new ProgressChangedFilter();
1357
+ }
1358
+
1359
+ public void setIgnoreErrFailedForThisURL(String url) {
1360
+ mRNCWebViewClient.setIgnoreErrFailedForThisURL(url);
1361
+ }
1362
+
1363
+ public void setBasicAuthCredential(BasicAuthCredential credential) {
1364
+ mRNCWebViewClient.setBasicAuthCredential(credential);
1365
+ }
1366
+
1367
+ public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
1368
+ this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
1369
+ }
1370
+
1371
+ public void setHasScrollEvent(boolean hasScrollEvent) {
1372
+ this.hasScrollEvent = hasScrollEvent;
1373
+ }
1374
+
1375
+ public void setNestedScrollEnabled(boolean nestedScrollEnabled) {
1376
+ this.nestedScrollEnabled = nestedScrollEnabled;
1377
+ }
1378
+
1379
+ @Override
1380
+ public void onHostResume() {
1381
+ // do nothing
1382
+ }
1383
+
1384
+ @Override
1385
+ public void onHostPause() {
1386
+ // do nothing
1387
+ }
1388
+
1389
+ @Override
1390
+ public void onHostDestroy() {
1391
+ cleanupCallbacksAndDestroy();
1392
+ }
1393
+
1394
+ @Override
1395
+ public boolean onTouchEvent(MotionEvent event) {
1396
+ if (this.nestedScrollEnabled) {
1397
+ requestDisallowInterceptTouchEvent(true);
1398
+ }
1399
+ return super.onTouchEvent(event);
1400
+ }
1401
+
1402
+ @Override
1403
+ protected void onSizeChanged(int w, int h, int ow, int oh) {
1404
+ super.onSizeChanged(w, h, ow, oh);
1405
+
1406
+ if (sendContentSizeChangeEvents) {
1407
+ dispatchEvent(
1408
+ this,
1409
+ new ContentSizeChangeEvent(
1410
+ this.getId(),
1411
+ w,
1412
+ h
1413
+ )
1414
+ );
1415
+ }
1416
+ }
1417
+
1418
+ @Override
1419
+ public void setWebViewClient(WebViewClient client) {
1420
+ super.setWebViewClient(client);
1421
+ if (client instanceof RNCWebViewClient) {
1422
+ mRNCWebViewClient = (RNCWebViewClient) client;
1423
+ mRNCWebViewClient.setProgressChangedFilter(progressChangedFilter);
1424
+ }
1425
+ }
1426
+
1427
+ WebChromeClient mWebChromeClient;
1428
+ @Override
1429
+ public void setWebChromeClient(WebChromeClient client) {
1430
+ this.mWebChromeClient = client;
1431
+ super.setWebChromeClient(client);
1432
+ if (client instanceof RNCWebChromeClient) {
1433
+ ((RNCWebChromeClient) client).setProgressChangedFilter(progressChangedFilter);
1434
+ }
1435
+ }
1436
+
1437
+ public @Nullable
1438
+ RNCWebViewClient getRNCWebViewClient() {
1439
+ return mRNCWebViewClient;
1440
+ }
1441
+
1442
+ public void setInjectedJavaScript(@Nullable String js) {
1443
+ injectedJS = js;
1444
+ }
1445
+
1446
+ public void setInjectedJavaScriptBeforeContentLoaded(@Nullable String js) {
1447
+ injectedJSBeforeContentLoaded = js;
1448
+ }
1449
+
1450
+ public void setInjectedJavaScriptForMainFrameOnly(boolean enabled) {
1451
+ injectedJavaScriptForMainFrameOnly = enabled;
1452
+ }
1453
+
1454
+ public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(boolean enabled) {
1455
+ injectedJavaScriptBeforeContentLoadedForMainFrameOnly = enabled;
1456
+ }
1457
+
1458
+ protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
1459
+ return new RNCWebViewBridge(webView);
1460
+ }
1461
+
1462
+ protected void createCatalystInstance() {
1463
+ ReactContext reactContext = (ReactContext) this.getContext();
1464
+
1465
+ if (reactContext != null) {
1466
+ mCatalystInstance = reactContext.getCatalystInstance();
1467
+ }
1468
+ }
1469
+
1470
+ @SuppressLint("AddJavascriptInterface")
1471
+ public void setMessagingEnabled(boolean enabled) {
1472
+ if (messagingEnabled == enabled) {
1473
+ return;
1474
+ }
1475
+
1476
+ messagingEnabled = enabled;
1477
+
1478
+ if (enabled) {
1479
+ addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
1480
+ } else {
1481
+ removeJavascriptInterface(JAVASCRIPT_INTERFACE);
1482
+ }
1483
+ }
1484
+
1485
+ public void setMessagingModuleName(String moduleName) {
1486
+ messagingModuleName = moduleName;
1487
+ }
1488
+
1489
+ protected void evaluateJavascriptWithFallback(String script) {
1490
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
1491
+ evaluateJavascript(script, null);
1492
+ return;
1493
+ }
1494
+
1495
+ try {
1496
+ loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
1497
+ } catch (UnsupportedEncodingException e) {
1498
+ // UTF-8 should always be supported
1499
+ throw new RuntimeException(e);
1500
+ }
1501
+ }
1502
+
1503
+ public void callInjectedJavaScript() {
1504
+ if (getSettings().getJavaScriptEnabled() &&
1505
+ injectedJS != null &&
1506
+ !TextUtils.isEmpty(injectedJS)) {
1507
+ evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
1508
+ }
1509
+ }
1510
+
1511
+ public void callInjectedJavaScriptBeforeContentLoaded() {
1512
+ if (getSettings().getJavaScriptEnabled() &&
1513
+ injectedJSBeforeContentLoaded != null &&
1514
+ !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
1515
+ evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
1516
+ }
1517
+ }
1518
+
1519
+ public void onMessage(String message) {
1520
+ ReactContext reactContext = (ReactContext) this.getContext();
1521
+ RNCWebView mContext = this;
1522
+
1523
+ if (mRNCWebViewClient != null) {
1524
+ WebView webView = this;
1525
+ webView.post(new Runnable() {
1526
+ @Override
1527
+ public void run() {
1528
+ if (mRNCWebViewClient == null) {
1529
+ return;
1530
+ }
1531
+ WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
1532
+ data.putString("data", message);
1533
+
1534
+ if (mCatalystInstance != null) {
1535
+ mContext.sendDirectMessage("onMessage", data);
1536
+ } else {
1537
+ dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
1538
+ }
1539
+ }
1540
+ });
1541
+ } else {
1542
+ WritableMap eventData = Arguments.createMap();
1543
+ eventData.putString("data", message);
1544
+
1545
+ if (mCatalystInstance != null) {
1546
+ this.sendDirectMessage("onMessage", eventData);
1547
+ } else {
1548
+ dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
1549
+ }
1550
+ }
1551
+ }
1552
+
1553
+ protected void sendDirectMessage(final String method, WritableMap data) {
1554
+ WritableNativeMap event = new WritableNativeMap();
1555
+ event.putMap("nativeEvent", data);
1556
+
1557
+ WritableNativeArray params = new WritableNativeArray();
1558
+ params.pushMap(event);
1559
+
1560
+ mCatalystInstance.callFunction(messagingModuleName, method, params);
1561
+ }
1562
+
1563
+ protected void onScrollChanged(int x, int y, int oldX, int oldY) {
1564
+ super.onScrollChanged(x, y, oldX, oldY);
1565
+
1566
+ if (!hasScrollEvent) {
1567
+ return;
1568
+ }
1569
+
1570
+ if (mOnScrollDispatchHelper == null) {
1571
+ mOnScrollDispatchHelper = new OnScrollDispatchHelper();
1572
+ }
1573
+
1574
+ if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
1575
+ ScrollEvent event = ScrollEvent.obtain(
1576
+ this.getId(),
1577
+ ScrollEventType.SCROLL,
1578
+ x,
1579
+ y,
1580
+ mOnScrollDispatchHelper.getXFlingVelocity(),
1581
+ mOnScrollDispatchHelper.getYFlingVelocity(),
1582
+ this.computeHorizontalScrollRange(),
1583
+ this.computeVerticalScrollRange(),
1584
+ this.getWidth(),
1585
+ this.getHeight());
1586
+
1587
+ dispatchEvent(this, event);
1588
+ }
1589
+ }
1590
+
1591
+ protected void dispatchEvent(WebView webView, Event event) {
1592
+ ReactContext reactContext = (ReactContext) webView.getContext();
1593
+ EventDispatcher eventDispatcher =
1594
+ reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
1595
+ eventDispatcher.dispatchEvent(event);
1596
+ }
1597
+
1598
+ protected void cleanupCallbacksAndDestroy() {
1599
+ setWebViewClient(null);
1600
+ destroy();
1601
+ }
1602
+
1603
+ @Override
1604
+ public void destroy() {
1605
+ if (mWebChromeClient != null) {
1606
+ mWebChromeClient.onHideCustomView();
1607
+ }
1608
+ super.destroy();
1609
+ }
1610
+
1611
+ protected class RNCWebViewBridge {
1612
+ RNCWebView mContext;
1613
+
1614
+ RNCWebViewBridge(RNCWebView c) {
1615
+ mContext = c;
1616
+ }
1617
+
1618
+ /**
1619
+ * This method is called whenever JavaScript running within the web view calls:
1620
+ * - window[JAVASCRIPT_INTERFACE].postMessage
1621
+ */
1622
+ @JavascriptInterface
1623
+ public void postMessage(String message) {
1624
+ mContext.onMessage(message);
1625
+ }
1626
+ }
1627
+
1628
+ protected static class ProgressChangedFilter {
1629
+ private boolean waitingForCommandLoadUrl = false;
1630
+
1631
+ public void setWaitingForCommandLoadUrl(boolean isWaiting) {
1632
+ waitingForCommandLoadUrl = isWaiting;
1633
+ }
1634
+
1635
+ public boolean isWaitingForCommandLoadUrl() {
1636
+ return waitingForCommandLoadUrl;
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ class BasicAuthCredential {
1643
+ String username;
1644
+ String password;
1645
+
1646
+ BasicAuthCredential(String username, String password) {
1647
+ this.username = username;
1648
+ this.password = password;
1649
+ }
1650
+ }