Browse Source

Merge branch 'typscript-migration' of https://github.com/react-native-community/react-native-webview into typscript-migration

Thibault Malbranche 5 years ago
parent
commit
52c6530305

+ 2
- 3
.eslintrc.js View File

@@ -22,8 +22,9 @@ module.exports = {
22 22
     // Remove this rule because we only destructure props, but never state
23 23
     'react/destructuring-assignment': 'off',
24 24
 
25
+    'no-unused-vars': 'error',
26
+    'typescript/no-unused-vars': 'error',
25 27
     'typescript/adjacent-overload-signatures': 'error',
26
-    'typescript/explicit-function-return-type': 'error',
27 28
     'typescript/no-angle-bracket-type-assertion': 'error',
28 29
     'typescript/no-empty-interface': 'error',
29 30
     'typescript/no-explicit-any': 'error',
@@ -33,8 +34,6 @@ module.exports = {
33 34
     'typescript/no-triple-slash-reference': 'error',
34 35
     'typescript/prefer-namespace-keyword': 'error',
35 36
     'typescript/type-annotation-spacing': 'error',
36
-
37
-    'no-unused-vars': 'off',
38 37
   },
39 38
   settings: {
40 39
     'import/resolver': {

+ 1
- 0
android/build.gradle View File

@@ -48,6 +48,7 @@ android {
48 48
 
49 49
 repositories {
50 50
     mavenCentral()
51
+    jcenter()
51 52
     maven {
52 53
         url 'https://maven.google.com/'
53 54
         name 'Google'

+ 33
- 63
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

@@ -30,6 +30,7 @@ import android.webkit.GeolocationPermissions;
30 30
 import android.webkit.JavascriptInterface;
31 31
 import android.webkit.ValueCallback;
32 32
 import android.webkit.WebChromeClient;
33
+import android.webkit.WebResourceRequest;
33 34
 import android.webkit.WebSettings;
34 35
 import android.webkit.WebView;
35 36
 import android.webkit.WebViewClient;
@@ -51,11 +52,19 @@ import com.facebook.react.uimanager.annotations.ReactProp;
51 52
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
52 53
 import com.facebook.react.uimanager.events.Event;
53 54
 import com.facebook.react.uimanager.events.EventDispatcher;
55
+import com.facebook.react.uimanager.events.RCTEventEmitter;
54 56
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
55 57
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
56 58
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
57 59
 import com.reactnativecommunity.webview.events.TopMessageEvent;
58 60
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
61
+import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
62
+import java.io.UnsupportedEncodingException;
63
+import java.util.ArrayList;
64
+import java.util.HashMap;
65
+import java.util.Locale;
66
+import java.util.Map;
67
+import javax.annotation.Nullable;
59 68
 import org.json.JSONException;
60 69
 import org.json.JSONObject;
61 70
 
@@ -66,12 +75,14 @@ import org.json.JSONObject;
66 75
  *  - GO_BACK
67 76
  *  - GO_FORWARD
68 77
  *  - RELOAD
78
+ *  - LOAD_URL
69 79
  *
70 80
  * {@link WebView} instances could emit following direct events:
71 81
  *  - topLoadingFinish
72 82
  *  - topLoadingStart
73 83
  *  - topLoadingStart
74 84
  *  - topLoadingProgress
85
+ *  - topShouldStartLoadWithRequest
75 86
  *
76 87
  * Each event will carry the following properties:
77 88
  *  - target - view's react tag
@@ -99,6 +110,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
99 110
   public static final int COMMAND_STOP_LOADING = 4;
100 111
   public static final int COMMAND_POST_MESSAGE = 5;
101 112
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
113
+  public static final int COMMAND_LOAD_URL = 7;
102 114
 
103 115
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
104 116
   // state and release page resources (including any running JavaScript).
@@ -111,7 +123,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
111 123
 
112 124
     protected boolean mLastLoadFailed = false;
113 125
     protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
114
-    protected @Nullable List<Pattern> mOriginWhitelist;
115 126
 
116 127
     @Override
117 128
     public void onPageFinished(WebView webView, String url) {
@@ -139,50 +150,16 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
139 150
 
140 151
     @Override
141 152
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
142
-      if (url.equals(BLANK_URL)) return false;
143
-
144
-      // url blacklisting
145
-      if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) {
146
-        ArrayList<Object> urlPrefixesForDefaultIntent =
147
-            mUrlPrefixesForDefaultIntent.toArrayList();
148
-        for (Object urlPrefix : urlPrefixesForDefaultIntent) {
149
-          if (url.startsWith((String) urlPrefix)) {
150
-            launchIntent(view.getContext(), url);
151
-            return true;
152
-          }
153
-        }
154
-      }
155
-
156
-      if (mOriginWhitelist != null && shouldHandleURL(mOriginWhitelist, url)) {
157
-        return false;
158
-      }
159
-
160
-      launchIntent(view.getContext(), url);
153
+      dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), url));
161 154
       return true;
162 155
     }
163 156
 
164
-    private void launchIntent(Context context, String url) {
165
-      try {
166
-        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
167
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
168
-        intent.addCategory(Intent.CATEGORY_BROWSABLE);
169
-        context.startActivity(intent);
170
-      } catch (ActivityNotFoundException e) {
171
-        FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
172
-      }
173
-    }
174 157
 
175
-    private boolean shouldHandleURL(List<Pattern> originWhitelist, String url) {
176
-      Uri uri = Uri.parse(url);
177
-      String scheme = uri.getScheme() != null ? uri.getScheme() : "";
178
-      String authority = uri.getAuthority() != null ? uri.getAuthority() : "";
179
-      String urlToCheck = scheme + "://" + authority;
180
-      for (Pattern pattern : originWhitelist) {
181
-        if (pattern.matcher(urlToCheck).matches()) {
182
-          return true;
183
-        }
184
-      }
185
-      return false;
158
+    @TargetApi(Build.VERSION_CODES.N)
159
+    @Override
160
+    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
161
+      dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), request.getUrl().toString()));
162
+      return true;
186 163
     }
187 164
 
188 165
     @Override
@@ -231,10 +208,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
231 208
     public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
232 209
       mUrlPrefixesForDefaultIntent = specialUrls;
233 210
     }
234
-
235
-    public void setOriginWhitelist(List<Pattern> originWhitelist) {
236
-      mOriginWhitelist = originWhitelist;
237
-    }
238 211
   }
239 212
 
240 213
   /**
@@ -656,20 +629,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
656 629
     view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
657 630
   }
658 631
 
659
-  @ReactProp(name = "originWhitelist")
660
-  public void setOriginWhitelist(
661
-    WebView view,
662
-    @Nullable ReadableArray originWhitelist) {
663
-    RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
664
-    if (client != null && originWhitelist != null) {
665
-      List<Pattern> whiteList = new LinkedList<>();
666
-      for (int i = 0 ; i < originWhitelist.size() ; i++) {
667
-        whiteList.add(Pattern.compile(originWhitelist.getString(i)));
668
-      }
669
-      client.setOriginWhitelist(whiteList);
670
-    }
671
-  }
672
-
673 632
   @Override
674 633
   protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
675 634
     // Do not register default touch emitter and let WebView implementation handle touches
@@ -678,9 +637,13 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
678 637
 
679 638
   @Override
680 639
   public Map getExportedCustomDirectEventTypeConstants() {
681
-    MapBuilder.Builder builder = MapBuilder.builder();
682
-    builder.put("topLoadingProgress", MapBuilder.of("registrationName", "onLoadingProgress"));
683
-    return builder.build();
640
+    Map export = super.getExportedCustomDirectEventTypeConstants();
641
+    if (export == null) {
642
+      export = MapBuilder.newHashMap();
643
+    }
644
+    export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
645
+    export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
646
+    return export;
684 647
   }
685 648
 
686 649
   @Override
@@ -691,7 +654,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
691 654
         "reload", COMMAND_RELOAD,
692 655
         "stopLoading", COMMAND_STOP_LOADING,
693 656
         "postMessage", COMMAND_POST_MESSAGE,
694
-        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT
657
+        "injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
658
+        "loadUrl", COMMAND_LOAD_URL
695 659
       );
696 660
   }
697 661
 
@@ -734,6 +698,12 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
734 698
         RNCWebView reactWebView = (RNCWebView) root;
735 699
         reactWebView.evaluateJavascriptWithFallback(args.getString(0));
736 700
         break;
701
+      case COMMAND_LOAD_URL:
702
+        if (args == null) {
703
+          throw new RuntimeException("Arguments for loading an url are null!");
704
+        }
705
+        root.loadUrl(args.getString(0));
706
+        break;
737 707
     }
738 708
   }
739 709
 

+ 40
- 0
android/src/main/java/com/reactnativecommunity/webview/events/TopShouldStartLoadWithRequestEvent.java View File

@@ -0,0 +1,40 @@
1
+package com.reactnativecommunity.webview.events;
2
+
3
+import com.facebook.react.bridge.Arguments;
4
+import com.facebook.react.bridge.WritableMap;
5
+import com.facebook.react.uimanager.events.Event;
6
+import com.facebook.react.uimanager.events.RCTEventEmitter;
7
+
8
+public class TopShouldStartLoadWithRequestEvent extends Event<TopMessageEvent> {
9
+    public static final String EVENT_NAME = "topShouldStartLoadWithRequest";
10
+    private final String mUrl;
11
+
12
+    public TopShouldStartLoadWithRequestEvent(int viewId, String url) {
13
+        super(viewId);
14
+        mUrl = url;
15
+    }
16
+
17
+    @Override
18
+    public String getEventName() {
19
+        return EVENT_NAME;
20
+    }
21
+
22
+    @Override
23
+    public boolean canCoalesce() {
24
+        return false;
25
+    }
26
+
27
+    @Override
28
+    public short getCoalescingKey() {
29
+        // All events for a given view can be coalesced.
30
+        return 0;
31
+    }
32
+
33
+    @Override
34
+    public void dispatch(RCTEventEmitter rctEventEmitter) {
35
+        WritableMap data = Arguments.createMap();
36
+        data.putString("url", mUrl);
37
+        data.putString("navigationType", "other");
38
+        rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
39
+    }
40
+}

+ 1
- 1
docs/Reference.md View File

@@ -102,7 +102,7 @@ Controls whether to adjust the content inset for web views that are placed behin
102 102
 
103 103
 ### `injectedJavaScript`
104 104
 
105
-Set this to provide JavaScript that will be injected into the web page when the view loads.
105
+Set this to provide JavaScript that will be injected into the web page when the view loads. Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
106 106
 
107 107
 | Type   | Required |
108 108
 | ------ | -------- |

+ 89
- 37
ios/RNCWKWebView.m View File

@@ -8,10 +8,10 @@
8 8
 #import "RNCWKWebView.h"
9 9
 #import <React/RCTConvert.h>
10 10
 #import <React/RCTAutoInsetsProtocol.h>
11
+#import <UIKit/UIKit.h>
11 12
 
12 13
 #import "objc/runtime.h"
13 14
 
14
-static NSTimer *keyboardTimer;
15 15
 static NSString *const MessageHanderName = @"ReactNative";
16 16
 
17 17
 // runtime trick to remove WKWebView keyboard default toolbar
@@ -70,19 +70,6 @@ static NSString *const MessageHanderName = @"ReactNative";
70 70
     _automaticallyAdjustContentInsets = YES;
71 71
     _contentInset = UIEdgeInsetsZero;
72 72
   }
73
-
74
-  // Workaround for a keyboard dismissal bug present in iOS 12
75
-  // https://openradar.appspot.com/radar?id=5018321736957952
76
-  if (@available(iOS 12.0, *)) {
77
-    [[NSNotificationCenter defaultCenter]
78
-      addObserver:self
79
-      selector:@selector(keyboardWillHide)
80
-      name:UIKeyboardWillHideNotification object:nil];
81
-    [[NSNotificationCenter defaultCenter]
82
-      addObserver:self
83
-      selector:@selector(keyboardWillShow)
84
-      name:UIKeyboardWillShowNotification object:nil];
85
-  }
86 73
   return self;
87 74
 }
88 75
 
@@ -144,27 +131,6 @@ static NSString *const MessageHanderName = @"ReactNative";
144 131
     [super removeFromSuperview];
145 132
 }
146 133
 
147
--(void)keyboardWillHide
148
-{
149
-    keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
150
-    [[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes];
151
-}
152
-
153
--(void)keyboardWillShow
154
-{
155
-    if (keyboardTimer != nil) {
156
-        [keyboardTimer invalidate];
157
-    }
158
-}
159
-
160
--(void)keyboardDisplacementFix
161
-{
162
-    // https://stackoverflow.com/a/9637807/824966
163
-    [UIView animateWithDuration:.25 animations:^{
164
-        self.webView.scrollView.contentOffset = CGPointMake(0, 0);
165
-    }];
166
-}
167
-
168 134
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
169 135
     if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
170 136
         if(_onLoadingProgress){
@@ -339,6 +305,88 @@ static NSString *const MessageHanderName = @"ReactNative";
339 305
 
340 306
 #pragma mark - WKNavigationDelegate methods
341 307
 
308
+/**
309
+* alert
310
+*/
311
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler 
312
+{ 
313
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
314
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
315
+        completionHandler();
316
+    }]];
317
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
318
+
319
+}
320
+
321
+/**
322
+* confirm
323
+*/
324
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
325
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
326
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
327
+        completionHandler(YES);
328
+    }]];
329
+    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
330
+        completionHandler(NO);
331
+    }]];
332
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
333
+}
334
+
335
+/**
336
+* prompt
337
+*/
338
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
339
+    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
340
+    [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
341
+        textField.textColor = [UIColor lightGrayColor];
342
+        textField.placeholder = defaultText;
343
+    }];
344
+    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
345
+        completionHandler([[alert.textFields lastObject] text]);
346
+    }]];
347
+    [[self topViewController] presentViewController:alert animated:YES completion:NULL];
348
+}
349
+
350
+/**
351
+ * topViewController
352
+ */
353
+-(UIViewController *)topViewController{
354
+   UIViewController *controller = [self topViewControllerWithRootViewController:[self getCurrentWindow].rootViewController];
355
+   return controller;
356
+}
357
+
358
+/**
359
+ * topViewControllerWithRootViewController
360
+ */
361
+-(UIViewController *)topViewControllerWithRootViewController:(UIViewController *)viewController{
362
+  if (viewController==nil) return nil;
363
+  if (viewController.presentedViewController!=nil) {
364
+    return [self topViewControllerWithRootViewController:viewController.presentedViewController];
365
+  } else if ([viewController isKindOfClass:[UITabBarController class]]){
366
+    return [self topViewControllerWithRootViewController:[(UITabBarController *)viewController selectedViewController]];
367
+  } else if ([viewController isKindOfClass:[UINavigationController class]]){
368
+    return [self topViewControllerWithRootViewController:[(UINavigationController *)viewController visibleViewController]];
369
+  } else {
370
+    return viewController;
371
+  }
372
+}
373
+/**
374
+ * getCurrentWindow
375
+ */
376
+-(UIWindow *)getCurrentWindow{
377
+  UIWindow *window = [UIApplication sharedApplication].keyWindow;
378
+  if (window.windowLevel!=UIWindowLevelNormal) {
379
+    for (UIWindow *wid in [UIApplication sharedApplication].windows) {
380
+      if (window.windowLevel==UIWindowLevelNormal) {
381
+        window = wid;
382
+        break;
383
+      }
384
+    }
385
+  }
386
+  return window;
387
+}
388
+
389
+
342 390
 /**
343 391
  * Decides whether to allow or cancel a navigation.
344 392
  * @see https://fburl.com/42r9fxob
@@ -429,8 +477,12 @@ static NSString *const MessageHanderName = @"ReactNative";
429 477
           thenCall: (void (^)(NSString*)) callback
430 478
 {
431 479
   [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
432
-    if (error == nil && callback != nil) {
433
-      callback([NSString stringWithFormat:@"%@", result]);
480
+    if (error == nil) {
481
+      if (callback != nil) {
482
+        callback([NSString stringWithFormat:@"%@", result]);
483
+      }
484
+    } else {
485
+      RCTLogError(@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string.");
434 486
     }
435 487
   }];
436 488
 }

+ 2
- 2
package.json View File

@@ -7,7 +7,7 @@
7 7
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
8 8
   ],
9 9
   "license": "MIT",
10
-  "version": "2.13.0",
10
+  "version": "2.14.3",
11 11
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
12 12
   "scripts": {
13 13
     "ci:publish": "yarn semantic-release",
@@ -43,7 +43,7 @@
43 43
     "react-native": "0.57.5",
44 44
     "semantic-release": "15.10.3",
45 45
     "typescript": "3.1.6",
46
-    "typescript-eslint-parser": "21.0.1"
46
+    "typescript-eslint-parser": "21.0.2"
47 47
   },
48 48
   "repository": {
49 49
     "type": "git",

+ 49
- 38
src/WebView.android.tsx View File

@@ -2,30 +2,35 @@ import React from 'react';
2 2
 
3 3
 import {
4 4
   ActivityIndicator,
5
+  Image,
6
+  requireNativeComponent,
5 7
   StyleSheet,
6 8
   UIManager,
7 9
   View,
8
-  requireNativeComponent,
9 10
   NativeModules,
10
-  Image,
11
-  NativeSyntheticEvent,
12 11
   findNodeHandle,
12
+  NativeSyntheticEvent,
13 13
 } from 'react-native';
14 14
 
15 15
 import invariant from 'invariant';
16 16
 
17
-import WebViewShared from './WebViewShared';
17
+import {
18
+  defaultOriginWhitelist,
19
+  createOnShouldStartLoadWithRequest,
20
+} from './WebViewShared';
18 21
 import {
19 22
   WebViewSourceUri,
20 23
   WebViewError,
21 24
   WebViewErrorEvent,
22 25
   WebViewMessageEvent,
23 26
   WebViewNavigationEvent,
27
+  WebViewProgressEvent,
24 28
   WebViewSharedProps,
25 29
   WebViewSource,
26
-  WebViewProgressEvent,
27 30
 } from './types/WebViewTypes';
28 31
 
32
+const RNCWebView = requireNativeComponent('RNCWebView');
33
+
29 34
 const styles = StyleSheet.create({
30 35
   container: {
31 36
     flex: 1,
@@ -59,7 +64,7 @@ const isWebViewUriSource = (
59 64
 ): source is WebViewSourceUri =>
60 65
   typeof source !== 'number' && !('html' in source);
61 66
 
62
-const defaultRenderLoading = (): React.ReactNode => (
67
+const defaultRenderLoading = () => (
63 68
   <View style={styles.loadingView}>
64 69
     <ActivityIndicator style={styles.loadingProgressBar} />
65 70
   </View>
@@ -70,15 +75,10 @@ type State = {
70 75
   lastErrorEvent: WebViewError | null;
71 76
 };
72 77
 
73
-const RNCWebView = requireNativeComponent('RNCWebView');
74
-
75 78
 /**
76 79
  * Renders a native WebView.
77 80
  */
78
-export default class WebView extends React.Component<
79
-  WebViewSharedProps,
80
-  State
81
-> {
81
+class WebView extends React.Component<WebViewSharedProps, State> {
82 82
   static defaultProps = {
83 83
     overScrollMode: 'always',
84 84
     javaScriptEnabled: true,
@@ -86,10 +86,10 @@ export default class WebView extends React.Component<
86 86
     scalesPageToFit: true,
87 87
     allowFileAccess: false,
88 88
     saveFormDataDisabled: false,
89
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
89
+    originWhitelist: defaultOriginWhitelist,
90 90
   };
91 91
 
92
-  static isFileUploadSupported = async (): Promise<boolean> =>
92
+  static isFileUploadSupported = async () =>
93 93
     // native implementation should return "true" only for Android 5+
94 94
     NativeModules.RNCWebView.isFileUploadSupported();
95 95
 
@@ -102,7 +102,7 @@ export default class WebView extends React.Component<
102 102
 
103 103
   webViewRef = React.createRef<React.ComponentClass>();
104 104
 
105
-  goForward = (): void => {
105
+  goForward = () => {
106 106
     UIManager.dispatchViewManagerCommand(
107 107
       this.getWebViewHandle(),
108 108
       UIManager.RNCWebView.Commands.goForward,
@@ -110,7 +110,7 @@ export default class WebView extends React.Component<
110 110
     );
111 111
   };
112 112
 
113
-  goBack = (): void => {
113
+  goBack = () => {
114 114
     UIManager.dispatchViewManagerCommand(
115 115
       this.getWebViewHandle(),
116 116
       UIManager.RNCWebView.Commands.goBack,
@@ -118,7 +118,7 @@ export default class WebView extends React.Component<
118 118
     );
119 119
   };
120 120
 
121
-  reload = (): void => {
121
+  reload = () => {
122 122
     this.setState({
123 123
       viewState: WebViewState.LOADING,
124 124
     });
@@ -129,7 +129,7 @@ export default class WebView extends React.Component<
129 129
     );
130 130
   };
131 131
 
132
-  stopLoading = (): void => {
132
+  stopLoading = () => {
133 133
     UIManager.dispatchViewManagerCommand(
134 134
       this.getWebViewHandle(),
135 135
       UIManager.RNCWebView.Commands.stopLoading,
@@ -137,7 +137,7 @@ export default class WebView extends React.Component<
137 137
     );
138 138
   };
139 139
 
140
-  postMessage = (data: string): void => {
140
+  postMessage = (data: string) => {
141 141
     UIManager.dispatchViewManagerCommand(
142 142
       this.getWebViewHandle(),
143 143
       UIManager.RNCWebView.Commands.postMessage,
@@ -151,7 +151,7 @@ export default class WebView extends React.Component<
151 151
    * on pages with a Content Security Policy that disallows eval(). If you need that
152 152
    * functionality, look into postMessage/onMessage.
153 153
    */
154
-  injectJavaScript = (data: string): void => {
154
+  injectJavaScript = (data: string) => {
155 155
     UIManager.dispatchViewManagerCommand(
156 156
       this.getWebViewHandle(),
157 157
       UIManager.RNCWebView.Commands.injectJavaScript,
@@ -163,7 +163,7 @@ export default class WebView extends React.Component<
163 163
    * We return an event with a bunch of fields including:
164 164
    *  url, title, loading, canGoBack, canGoForward
165 165
    */
166
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
166
+  updateNavigationState = (event: WebViewNavigationEvent) => {
167 167
     if (this.props.onNavigationStateChange) {
168 168
       this.props.onNavigationStateChange(event.nativeEvent);
169 169
     }
@@ -172,7 +172,7 @@ export default class WebView extends React.Component<
172 172
   getWebViewHandle = (): number | null =>
173 173
     findNodeHandle(this.webViewRef.current);
174 174
 
175
-  onLoadingStart = (event: WebViewNavigationEvent): void => {
175
+  onLoadingStart = (event: WebViewNavigationEvent) => {
176 176
     const { onLoadStart } = this.props;
177 177
     if (onLoadStart) {
178 178
       onLoadStart(event);
@@ -180,7 +180,7 @@ export default class WebView extends React.Component<
180 180
     this.updateNavigationState(event);
181 181
   };
182 182
 
183
-  onLoadingError = (event: WebViewErrorEvent): void => {
183
+  onLoadingError = (event: WebViewErrorEvent) => {
184 184
     event.persist(); // persist this event because we need to store it
185 185
     const { onError, onLoadEnd } = this.props;
186 186
     if (onError) {
@@ -191,14 +191,13 @@ export default class WebView extends React.Component<
191 191
     }
192 192
     // eslint-disable-next-line no-console
193 193
     console.warn('Encountered an error loading page', event.nativeEvent);
194
-
195 194
     this.setState({
196 195
       lastErrorEvent: event.nativeEvent,
197 196
       viewState: WebViewState.ERROR,
198 197
     });
199 198
   };
200 199
 
201
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
200
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
202 201
     const { onLoad, onLoadEnd } = this.props;
203 202
     if (onLoad) {
204 203
       onLoad(event);
@@ -212,23 +211,34 @@ export default class WebView extends React.Component<
212 211
     this.updateNavigationState(event);
213 212
   };
214 213
 
215
-  onMessage = (event: WebViewMessageEvent): void => {
214
+  onMessage = (event: WebViewMessageEvent) => {
216 215
     const { onMessage } = this.props;
217 216
     if (onMessage) {
218 217
       onMessage(event);
219 218
     }
220 219
   };
221 220
 
222
-  onLoadingProgress = (
223
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
224
-  ): void => {
221
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
225 222
     const { onLoadProgress } = this.props;
226 223
     if (onLoadProgress) {
227 224
       onLoadProgress(event);
228 225
     }
229 226
   };
230 227
 
231
-  render(): React.ReactNode {
228
+  onShouldStartLoadWithRequestCallback = (
229
+    shouldStart: boolean,
230
+    url: string,
231
+  ) => {
232
+    if (shouldStart) {
233
+      UIManager.dispatchViewManagerCommand(
234
+        this.getWebViewHandle(),
235
+        UIManager.RNCWebView.Commands.loadUrl,
236
+        [String(url)],
237
+      );
238
+    }
239
+  };
240
+
241
+  render() {
232 242
     let otherView = null;
233 243
 
234 244
     if (this.state.viewState === WebViewState.LOADING) {
@@ -247,7 +257,6 @@ export default class WebView extends React.Component<
247 257
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
248 258
       }
249 259
     } else if (this.state.viewState !== WebViewState.IDLE) {
250
-      // eslint-disable-next-line no-console
251 260
       console.error(
252 261
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
253 262
       );
@@ -271,24 +280,24 @@ export default class WebView extends React.Component<
271 280
 
272 281
     if (isWebViewUriSource(source)) {
273 282
       if (source.method === 'POST' && source.headers) {
274
-        // eslint-disable-next-line no-console
275 283
         console.warn(
276 284
           'WebView: `source.headers` is not supported when using POST.',
277 285
         );
278 286
       } else if (source.method === 'GET' && source.body) {
279
-        // eslint-disable-next-line no-console
280 287
         console.warn('WebView: `source.body` is not supported when using GET.');
281 288
       }
282 289
     }
283 290
 
284 291
     const nativeConfig = this.props.nativeConfig || {};
285 292
 
286
-    const originWhitelist = (this.props.originWhitelist || []).map(
287
-      WebViewShared.originWhitelistToRegex,
288
-    );
289
-
290 293
     const NativeWebView = nativeConfig.component || RNCWebView;
291 294
 
295
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
296
+      this.onShouldStartLoadWithRequestCallback,
297
+      this.props.originWhitelist,
298
+      this.props.onShouldStartLoadWithRequest,
299
+    );
300
+
292 301
     const webView = (
293 302
       <NativeWebView
294 303
         ref={this.webViewRef}
@@ -309,6 +318,7 @@ export default class WebView extends React.Component<
309 318
         automaticallyAdjustContentInsets={
310 319
           this.props.automaticallyAdjustContentInsets
311 320
         }
321
+        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
312 322
         onContentSizeChange={this.props.onContentSizeChange}
313 323
         onLoadingStart={this.onLoadingStart}
314 324
         onLoadingFinish={this.onLoadingFinish}
@@ -322,7 +332,6 @@ export default class WebView extends React.Component<
322 332
         allowUniversalAccessFromFileURLs={
323 333
           this.props.allowUniversalAccessFromFileURLs
324 334
         }
325
-        originWhitelist={originWhitelist}
326 335
         mixedContentMode={this.props.mixedContentMode}
327 336
         saveFormDataDisabled={this.props.saveFormDataDisabled}
328 337
         urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
@@ -338,3 +347,5 @@ export default class WebView extends React.Component<
338 347
     );
339 348
   }
340 349
 }
350
+
351
+export default WebView;

+ 101
- 117
src/WebView.ios.tsx View File

@@ -1,7 +1,6 @@
1 1
 import React from 'react';
2 2
 import {
3 3
   ActivityIndicator,
4
-  Linking,
5 4
   StyleSheet,
6 5
   Text,
7 6
   UIManager,
@@ -15,11 +14,14 @@ import {
15 14
 
16 15
 import invariant from 'invariant';
17 16
 
18
-import WebViewShared from './WebViewShared';
17
+import {
18
+  defaultOriginWhitelist,
19
+  createOnShouldStartLoadWithRequest,
20
+} from './WebViewShared';
21
+
19 22
 import {
20 23
   WebViewSourceUri,
21 24
   WebViewError,
22
-  WebViewIOSLoadRequestEvent,
23 25
   WebViewErrorEvent,
24 26
   WebViewMessageEvent,
25 27
   WebViewNavigationEvent,
@@ -28,23 +30,6 @@ import {
28 30
   WebViewProgressEvent,
29 31
 } from './types/WebViewTypes';
30 32
 
31
-type DecelerationRate = number | 'normal' | 'fast';
32
-
33
-// Imported from https://github.com/facebook/react-native/blob/master/Libraries/Components/ScrollView/processDecelerationRate.js
34
-function processDecelerationRate(
35
-  decelerationRate?: DecelerationRate,
36
-): number | undefined {
37
-  if (decelerationRate === 'normal') {
38
-    return 0.998;
39
-  }
40
-  if (decelerationRate === 'fast') {
41
-    return 0.99;
42
-  }
43
-  return decelerationRate;
44
-}
45
-
46
-const { RNCWKWebViewManager, RNCUIWebViewManager } = NativeModules;
47
-
48 33
 const BGWASH = 'rgba(255,255,255,0.8)';
49 34
 
50 35
 const styles = StyleSheet.create({
@@ -83,6 +68,25 @@ const styles = StyleSheet.create({
83 68
   },
84 69
 });
85 70
 
71
+type DecelerationRate = number | 'normal' | 'fast';
72
+
73
+// Imported from https://github.com/facebook/react-native/blob/master/Libraries/Components/ScrollView/processDecelerationRate.js
74
+function processDecelerationRate(
75
+  decelerationRate?: DecelerationRate,
76
+): number | undefined {
77
+  if (decelerationRate === 'normal') {
78
+    return 0.998;
79
+  }
80
+  if (decelerationRate === 'fast') {
81
+    return 0.99;
82
+  }
83
+  return decelerationRate;
84
+}
85
+
86
+const { RNCWKWebViewManager, RNCUIWebViewManager } = NativeModules;
87
+const RNCUIWebView = requireNativeComponent('RNCUIWebView');
88
+const RNCWKWebView = requireNativeComponent('RNCWKWebView');
89
+
86 90
 const WebViewState: {
87 91
   IDLE: 'IDLE';
88 92
   LOADING: 'LOADING';
@@ -109,7 +113,19 @@ type State = {
109 113
   lastErrorEvent: WebViewError | null;
110 114
 };
111 115
 
112
-const defaultRenderLoading = (): React.ReactNode => (
116
+const DataDetectorTypes = [
117
+  'phoneNumber',
118
+  'link',
119
+  'address',
120
+  'calendarEvent',
121
+  'trackingNumber',
122
+  'flightNumber',
123
+  'lookupSuggestion',
124
+  'none',
125
+  'all',
126
+];
127
+
128
+const defaultRenderLoading = () => (
113 129
   <View style={styles.loadingView}>
114 130
     <ActivityIndicator />
115 131
   </View>
@@ -118,7 +134,7 @@ const defaultRenderError = (
118 134
   errorDomain: string | undefined,
119 135
   errorCode: number,
120 136
   errorDesc: string,
121
-): React.ReactNode => (
137
+) => (
122 138
   <View style={styles.errorContainer}>
123 139
     <Text style={styles.errorTextTitle}>Error loading page</Text>
124 140
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
@@ -127,9 +143,6 @@ const defaultRenderError = (
127 143
   </View>
128 144
 );
129 145
 
130
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
131
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
132
-
133 146
 /**
134 147
  * `WebView` renders web content in a native view.
135 148
  *
@@ -152,20 +165,17 @@ const RNCWKWebView = requireNativeComponent('RNCWKWebView');
152 165
  * You can use this component to navigate back and forth in the web view's
153 166
  * history and configure various properties for the web content.
154 167
  */
155
-export default class WebView extends React.Component<
156
-  WebViewSharedProps,
157
-  State
158
-> {
168
+class WebView extends React.Component<WebViewSharedProps, State> {
159 169
   static JSNavigationScheme = JSNavigationScheme;
160 170
 
161 171
   static NavigationType = NavigationType;
162 172
 
163 173
   static defaultProps = {
164 174
     useWebKit: true,
165
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
175
+    originWhitelist: defaultOriginWhitelist,
166 176
   };
167 177
 
168
-  static isFileUploadSupported = async (): Promise<boolean> =>
178
+  static isFileUploadSupported = async () =>
169 179
     // no native implementation for iOS, depends only on permissions
170 180
     true;
171 181
 
@@ -179,12 +189,11 @@ export default class WebView extends React.Component<
179 189
   webViewRef = React.createRef<React.ComponentClass>();
180 190
 
181 191
   // eslint-disable-next-line camelcase, react/sort-comp
182
-  UNSAFE_componentWillMount(): void {
192
+  UNSAFE_componentWillMount() {
183 193
     if (
184 194
       this.props.useWebKit === true
185 195
       && this.props.scalesPageToFit !== undefined
186 196
     ) {
187
-      // eslint-disable-next-line no-console
188 197
       console.warn(
189 198
         'The scalesPageToFit property is not supported when useWebKit = true',
190 199
       );
@@ -193,14 +202,13 @@ export default class WebView extends React.Component<
193 202
       !this.props.useWebKit
194 203
       && this.props.allowsBackForwardNavigationGestures
195 204
     ) {
196
-      // eslint-disable-next-line no-console
197 205
       console.warn(
198 206
         'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
199 207
       );
200 208
     }
201 209
   }
202 210
 
203
-  componentDidUpdate(prevProps: WebViewSharedProps): void {
211
+  componentDidUpdate(prevProps: WebViewSharedProps) {
204 212
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
205 213
       return;
206 214
     }
@@ -210,17 +218,24 @@ export default class WebView extends React.Component<
210 218
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
211 219
 
212 220
     if (this.props.scalesPageToFit !== undefined) {
213
-      // eslint-disable-next-line no-console
214 221
       console.warn(
215 222
         'The scalesPageToFit property is not supported when useWebKit = true',
216 223
       );
217 224
     }
218 225
   }
219 226
 
227
+  getCommands() {
228
+    if (!this.props.useWebKit) {
229
+      return UIManager.RNCUIWebView.Commands;
230
+    }
231
+
232
+    return UIManager.RNCWKWebView.Commands;
233
+  }
234
+
220 235
   /**
221 236
    * Go forward one page in the web view's history.
222 237
    */
223
-  goForward = (): void => {
238
+  goForward = () => {
224 239
     UIManager.dispatchViewManagerCommand(
225 240
       this.getWebViewHandle(),
226 241
       this.getCommands().goForward,
@@ -228,25 +243,10 @@ export default class WebView extends React.Component<
228 243
     );
229 244
   };
230 245
 
231
-  getCommands(): {
232
-    goForward: () => void;
233
-    goBack: () => void;
234
-    reload: () => void;
235
-    stopLoading: () => void;
236
-    postMessage: () => void;
237
-    injectJavaScript: () => void;
238
-  } {
239
-    if (!this.props.useWebKit) {
240
-      return UIManager.RNCUIWebView.Commands;
241
-    }
242
-
243
-    return UIManager.RNCWKWebView.Commands;
244
-  }
245
-
246 246
   /**
247 247
    * Go back one page in the web view's history.
248 248
    */
249
-  goBack = (): void => {
249
+  goBack = () => {
250 250
     UIManager.dispatchViewManagerCommand(
251 251
       this.getWebViewHandle(),
252 252
       this.getCommands().goBack,
@@ -257,7 +257,7 @@ export default class WebView extends React.Component<
257 257
   /**
258 258
    * Reloads the current page.
259 259
    */
260
-  reload = (): void => {
260
+  reload = () => {
261 261
     this.setState({ viewState: WebViewState.LOADING });
262 262
     UIManager.dispatchViewManagerCommand(
263 263
       this.getWebViewHandle(),
@@ -269,7 +269,7 @@ export default class WebView extends React.Component<
269 269
   /**
270 270
    * Stop loading the current page.
271 271
    */
272
-  stopLoading = (): void => {
272
+  stopLoading = () => {
273 273
     UIManager.dispatchViewManagerCommand(
274 274
       this.getWebViewHandle(),
275 275
       this.getCommands().stopLoading,
@@ -287,7 +287,7 @@ export default class WebView extends React.Component<
287 287
    * document.addEventListener('message', e => { document.title = e.data; });
288 288
    * ```
289 289
    */
290
-  postMessage = (data: string): void => {
290
+  postMessage = (data: string) => {
291 291
     UIManager.dispatchViewManagerCommand(
292 292
       this.getWebViewHandle(),
293 293
       this.getCommands().postMessage,
@@ -301,7 +301,7 @@ export default class WebView extends React.Component<
301 301
    * on pages with a Content Security Policy that disallows eval(). If you need that
302 302
    * functionality, look into postMessage/onMessage.
303 303
    */
304
-  injectJavaScript = (data: string): void => {
304
+  injectJavaScript = (data: string) => {
305 305
     UIManager.dispatchViewManagerCommand(
306 306
       this.getWebViewHandle(),
307 307
       this.getCommands().injectJavaScript,
@@ -313,7 +313,7 @@ export default class WebView extends React.Component<
313 313
    * We return an event with a bunch of fields including:
314 314
    *  url, title, loading, canGoBack, canGoForward
315 315
    */
316
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
316
+  updateNavigationState = (event: WebViewNavigationEvent) => {
317 317
     if (this.props.onNavigationStateChange) {
318 318
       this.props.onNavigationStateChange(event.nativeEvent);
319 319
     }
@@ -322,10 +322,9 @@ export default class WebView extends React.Component<
322 322
   /**
323 323
    * Returns the native `WebView` node.
324 324
    */
325
-  getWebViewHandle = (): number | null =>
326
-    findNodeHandle(this.webViewRef.current);
325
+  getWebViewHandle = () => findNodeHandle(this.webViewRef.current);
327 326
 
328
-  onLoadingStart = (event: WebViewNavigationEvent): void => {
327
+  onLoadingStart = (event: WebViewNavigationEvent) => {
329 328
     const { onLoadStart } = this.props;
330 329
     if (onLoadStart) {
331 330
       onLoadStart(event);
@@ -333,7 +332,7 @@ export default class WebView extends React.Component<
333 332
     this.updateNavigationState(event);
334 333
   };
335 334
 
336
-  onLoadingError = (event: WebViewErrorEvent): void => {
335
+  onLoadingError = (event: WebViewErrorEvent) => {
337 336
     event.persist(); // persist this event because we need to store it
338 337
     const { onError, onLoadEnd } = this.props;
339 338
     if (onError) {
@@ -344,14 +343,13 @@ export default class WebView extends React.Component<
344 343
     }
345 344
     // eslint-disable-next-line no-console
346 345
     console.warn('Encountered an error loading page', event.nativeEvent);
347
-
348 346
     this.setState({
349 347
       lastErrorEvent: event.nativeEvent,
350 348
       viewState: WebViewState.ERROR,
351 349
     });
352 350
   };
353 351
 
354
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
352
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
355 353
     const { onLoad, onLoadEnd } = this.props;
356 354
     if (onLoad) {
357 355
       onLoad(event);
@@ -365,38 +363,50 @@ export default class WebView extends React.Component<
365 363
     this.updateNavigationState(event);
366 364
   };
367 365
 
368
-  onMessage = (event: WebViewMessageEvent): void => {
366
+  onMessage = (event: WebViewMessageEvent) => {
369 367
     const { onMessage } = this.props;
370 368
     if (onMessage) {
371 369
       onMessage(event);
372 370
     }
373 371
   };
374 372
 
375
-  onLoadingProgress = (
376
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
377
-  ): void => {
373
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
378 374
     const { onLoadProgress } = this.props;
379 375
     if (onLoadProgress) {
380 376
       onLoadProgress(event);
381 377
     }
382 378
   };
383 379
 
384
-  showRedboxOnPropChanges(
380
+  onShouldStartLoadWithRequestCallback = (
381
+    shouldStart: boolean,
382
+    url: string,
383
+    lockIdentifier: number,
384
+  ) => {
385
+    const nativeConfig = this.props.nativeConfig || {};
386
+
387
+    let { viewManager } = nativeConfig;
388
+
389
+    if (this.props.useWebKit) {
390
+      viewManager = viewManager || RNCWKWebViewManager;
391
+    } else {
392
+      viewManager = viewManager || RNCUIWebViewManager;
393
+    }
394
+    invariant(viewManager != null, 'viewManager expected to be non-null');
395
+    viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
396
+  };
397
+
398
+  showRedboxOnPropChanges = (
385 399
     prevProps: WebViewSharedProps,
386
-    propName:
387
-      | 'allowsInlineMediaPlayback'
388
-      | 'mediaPlaybackRequiresUserAction'
389
-      | 'dataDetectorTypes',
390
-  ): void {
400
+    propName: keyof WebViewSharedProps,
401
+  ) => {
391 402
     if (this.props[propName] !== prevProps[propName]) {
392
-      // eslint-disable-next-line no-console
393 403
       console.error(
394 404
         `Changes to property ${propName} do nothing after the initial render.`,
395 405
       );
396 406
     }
397
-  }
407
+  };
398 408
 
399
-  render(): React.ReactNode {
409
+  render() {
400 410
     let otherView = null;
401 411
 
402 412
     let scalesPageToFit;
@@ -421,7 +431,6 @@ export default class WebView extends React.Component<
421 431
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
422 432
       }
423 433
     } else if (this.state.viewState !== WebViewState.IDLE) {
424
-      // eslint-disable-next-line no-console
425 434
       console.error(
426 435
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
427 436
       );
@@ -438,42 +447,11 @@ export default class WebView extends React.Component<
438 447
 
439 448
     const nativeConfig = this.props.nativeConfig || {};
440 449
 
441
-    let { viewManager } = nativeConfig;
442
-
443
-    if (this.props.useWebKit) {
444
-      viewManager = viewManager || RNCWKWebViewManager;
445
-    } else {
446
-      viewManager = viewManager || RNCUIWebViewManager;
447
-    }
448
-
449
-    const compiledWhitelist = [
450
-      'about:blank',
451
-      ...(this.props.originWhitelist || []),
452
-    ].map(WebViewShared.originWhitelistToRegex);
453
-    const onShouldStartLoadWithRequest = (
454
-      event: NativeSyntheticEvent<WebViewIOSLoadRequestEvent>,
455
-    ): void => {
456
-      let shouldStart = true;
457
-      const { url } = event.nativeEvent;
458
-      const origin = WebViewShared.extractOrigin(url);
459
-      const passesWhitelist = compiledWhitelist.some(
460
-        (x): boolean => new RegExp(x).test(origin),
461
-      );
462
-      shouldStart = shouldStart && passesWhitelist;
463
-      if (!passesWhitelist) {
464
-        Linking.openURL(url);
465
-      }
466
-      if (this.props.onShouldStartLoadWithRequest) {
467
-        shouldStart
468
-          = shouldStart
469
-          && this.props.onShouldStartLoadWithRequest(event.nativeEvent);
470
-      }
471
-      invariant(viewManager != null, 'viewManager expected to be non-null');
472
-      viewManager.startLoadWithResult(
473
-        !!shouldStart,
474
-        event.nativeEvent.lockIdentifier,
475
-      );
476
-    };
450
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
451
+      this.onShouldStartLoadWithRequestCallback,
452
+      this.props.originWhitelist,
453
+      this.props.onShouldStartLoadWithRequest,
454
+    );
477 455
 
478 456
     const decelerationRate = processDecelerationRate(
479 457
       this.props.decelerationRate,
@@ -488,9 +466,13 @@ export default class WebView extends React.Component<
488 466
 
489 467
     const messagingEnabled = typeof this.props.onMessage === 'function';
490 468
 
491
-    const NativeWebView
492
-      = nativeConfig.component
493
-      || (this.props.useWebKit ? RNCWKWebView : RNCUIWebView);
469
+    let NativeWebView = nativeConfig.component;
470
+
471
+    if (this.props.useWebKit) {
472
+      NativeWebView = NativeWebView || RNCWKWebView;
473
+    } else {
474
+      NativeWebView = NativeWebView || RNCUIWebView;
475
+    }
494 476
 
495 477
     const webView = (
496 478
       <NativeWebView
@@ -538,3 +520,5 @@ export default class WebView extends React.Component<
538 520
     );
539 521
   }
540 522
 }
523
+
524
+export default WebView;

+ 60
- 9
src/WebViewShared.ts View File

@@ -1,13 +1,64 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @flow
9
+ */
10
+
1 11
 import escapeStringRegexp from 'escape-string-regexp';
12
+import { Linking } from 'react-native';
13
+import {
14
+  WebViewNavigationEvent,
15
+  WebViewNavigation,
16
+} from './types/WebViewTypes';
17
+
18
+const defaultOriginWhitelist = ['http://*', 'https://*'];
19
+
20
+const extractOrigin = (url: string): string => {
21
+  const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
22
+  return result === null ? '' : result[0];
23
+};
24
+
25
+const originWhitelistToRegex = (originWhitelist: string): string =>
26
+  escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*');
27
+
28
+const passesWhitelist = (compiledWhitelist: Array<string>, url: string) => {
29
+  const origin = extractOrigin(url);
30
+  return compiledWhitelist.some(x => new RegExp(x).test(origin));
31
+};
32
+
33
+const compileWhitelist = (originWhitelist?: string[]): string[] =>
34
+  ['about:blank', ...(originWhitelist || [])].map(originWhitelistToRegex);
35
+
36
+const createOnShouldStartLoadWithRequest = (
37
+  loadRequest: (
38
+    shouldStart: boolean,
39
+    url: string,
40
+    lockIdentifier?: number,
41
+  ) => void,
42
+  originWhitelist?: string[],
43
+  onShouldStartLoadWithRequest?: (event: WebViewNavigation) => boolean,
44
+) => ({ nativeEvent }: WebViewNavigationEvent) => {
45
+  let shouldStart = true;
46
+  const { url, lockIdentifier } = nativeEvent;
2 47
 
3
-const WebViewShared = {
4
-  defaultOriginWhitelist: ['http://*', 'https://*'],
5
-  extractOrigin: (url: string): string => {
6
-    const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url);
7
-    return result === null ? '' : result[0];
8
-  },
9
-  originWhitelistToRegex: (originWhitelist: string): string =>
10
-    escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*'),
48
+  if (!passesWhitelist(compileWhitelist(originWhitelist), url)) {
49
+    Linking.openURL(url);
50
+    shouldStart = false;
51
+  }
52
+
53
+  if (onShouldStartLoadWithRequest) {
54
+    shouldStart = onShouldStartLoadWithRequest(nativeEvent);
55
+  }
56
+
57
+  loadRequest(shouldStart, url, lockIdentifier);
11 58
 };
12 59
 
13
-export default WebViewShared;
60
+export {
61
+  defaultOriginWhitelist,
62
+  createOnShouldStartLoadWithRequest,
63
+  originWhitelistToRegex,
64
+};

+ 11
- 8
src/types/WebViewTypes.ts View File

@@ -48,6 +48,7 @@ export interface WebViewNavigation extends WebViewNativeEvent {
48 48
     | 'reload'
49 49
     | 'formresubmit'
50 50
     | 'other';
51
+  lockIdentifier?: number;
51 52
 }
52 53
 
53 54
 export interface WebViewMessage extends WebViewNativeEvent {
@@ -211,14 +212,6 @@ export interface IOSWebViewProps {
211 212
    */
212 213
   dataDetectorTypes?: DataDetectorTypes | DataDetectorTypes[];
213 214
 
214
-  /**
215
-   * Function that allows custom handling of any web view requests. Return
216
-   * `true` from the function to continue loading the request and `false`
217
-   * to stop loading.
218
-   * @platform ios
219
-   */
220
-  onShouldStartLoadWithRequest?: (event: WebViewIOSLoadRequestEvent) => any;
221
-
222 215
   /**
223 216
    * Boolean that determines whether HTML5 videos play inline or use the
224 217
    * native full-screen controller. The default value is `false`.
@@ -406,6 +399,16 @@ export interface WebViewSharedProps
406 399
    */
407 400
   onNavigationStateChange?: (event: WebViewNavigation) => any;
408 401
 
402
+  /**
403
+   * Function that allows custom handling of any web view requests. Return
404
+   * `true` from the function to continue loading the request and `false`
405
+   * to stop loading.
406
+   * @platform ios
407
+   */
408
+  onShouldStartLoadWithRequest?: (
409
+    event: NativeSyntheticEvent<WebViewNavigation>,
410
+  ) => any;
411
+
409 412
   /**
410 413
    * A function that is invoked when the webview calls `window.postMessage`.
411 414
    * Setting this property will inject a `postMessage` global into your

+ 10
- 9
yarn.lock View File

@@ -3791,7 +3791,7 @@ lodash.without@~4.4.0:
3791 3791
   version "4.4.0"
3792 3792
   resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
3793 3793
 
3794
-lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
3794
+lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
3795 3795
   version "4.17.11"
3796 3796
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
3797 3797
 
@@ -6510,18 +6510,19 @@ typedarray@^0.0.6:
6510 6510
   version "0.0.6"
6511 6511
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
6512 6512
 
6513
-typescript-eslint-parser@21.0.1:
6514
-  version "21.0.1"
6515
-  resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-21.0.1.tgz#710ea628a72144e1ec3a1d6b9f5b2b0a5a520e04"
6513
+typescript-eslint-parser@21.0.2:
6514
+  version "21.0.2"
6515
+  resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-21.0.2.tgz#270af10e4724528677fbcf34ea495284bec3a894"
6516
+  integrity sha512-u+pj4RVJBr4eTzj0n5npoXD/oRthvfUCjSKndhNI714MG0mQq2DJw5WP7qmonRNIFgmZuvdDOH3BHm9iOjIAfg==
6516 6517
   dependencies:
6517 6518
     eslint-scope "^4.0.0"
6518 6519
     eslint-visitor-keys "^1.0.0"
6519
-    lodash "^4.17.11"
6520
-    typescript-estree "5.0.0"
6520
+    typescript-estree "5.3.0"
6521 6521
 
6522
-typescript-estree@5.0.0:
6523
-  version "5.0.0"
6524
-  resolved "https://registry.yarnpkg.com/typescript-estree/-/typescript-estree-5.0.0.tgz#6aaa33b96abf5edce165099630e2ccd997a95609"
6522
+typescript-estree@5.3.0:
6523
+  version "5.3.0"
6524
+  resolved "https://registry.yarnpkg.com/typescript-estree/-/typescript-estree-5.3.0.tgz#fb6c977b5e21073eb16cbdc0338a7f752da99ff5"
6525
+  integrity sha512-Vu0KmYdSCkpae+J48wsFC1ti19Hq3Wi/lODUaE+uesc3gzqhWbZ5itWbsjylLVbjNW4K41RqDzSfnaYNbmEiMQ==
6525 6526
   dependencies:
6526 6527
     lodash.unescape "4.0.1"
6527 6528
     semver "5.5.0"