Browse Source

Merge branch 'master' into typscript-migration

Thibault Malbranche 5 years ago
parent
commit
3943f3161f

+ 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,
@@ -55,7 +60,7 @@ const isWebViewUriSource = (
55 60
 ): source is WebViewSourceUri =>
56 61
   typeof source !== 'number' && !('html' in source);
57 62
 
58
-const defaultRenderLoading = (): React.ReactNode => (
63
+const defaultRenderLoading = () => (
59 64
   <View style={styles.loadingView}>
60 65
     <ActivityIndicator style={styles.loadingProgressBar} />
61 66
   </View>
@@ -66,15 +71,10 @@ type State = {
66 71
   lastErrorEvent: WebViewError | null;
67 72
 };
68 73
 
69
-const RNCWebView = requireNativeComponent('RNCWebView');
70
-
71 74
 /**
72 75
  * Renders a native WebView.
73 76
  */
74
-export default class WebView extends React.Component<
75
-  WebViewSharedProps,
76
-  State
77
-> {
77
+class WebView extends React.Component<WebViewSharedProps, State> {
78 78
   static defaultProps = {
79 79
     overScrollMode: 'always',
80 80
     javaScriptEnabled: true,
@@ -82,10 +82,10 @@ export default class WebView extends React.Component<
82 82
     scalesPageToFit: true,
83 83
     allowFileAccess: false,
84 84
     saveFormDataDisabled: false,
85
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
85
+    originWhitelist: defaultOriginWhitelist,
86 86
   };
87 87
 
88
-  static isFileUploadSupported = async (): Promise<boolean> =>
88
+  static isFileUploadSupported = async () =>
89 89
     // native implementation should return "true" only for Android 5+
90 90
     NativeModules.RNCWebView.isFileUploadSupported();
91 91
 
@@ -98,7 +98,7 @@ export default class WebView extends React.Component<
98 98
 
99 99
   webViewRef = React.createRef<React.ComponentClass>();
100 100
 
101
-  goForward = (): void => {
101
+  goForward = () => {
102 102
     UIManager.dispatchViewManagerCommand(
103 103
       this.getWebViewHandle(),
104 104
       UIManager.RNCWebView.Commands.goForward,
@@ -106,7 +106,7 @@ export default class WebView extends React.Component<
106 106
     );
107 107
   };
108 108
 
109
-  goBack = (): void => {
109
+  goBack = () => {
110 110
     UIManager.dispatchViewManagerCommand(
111 111
       this.getWebViewHandle(),
112 112
       UIManager.RNCWebView.Commands.goBack,
@@ -114,7 +114,7 @@ export default class WebView extends React.Component<
114 114
     );
115 115
   };
116 116
 
117
-  reload = (): void => {
117
+  reload = () => {
118 118
     this.setState({
119 119
       viewState: WebViewState.LOADING,
120 120
     });
@@ -125,7 +125,7 @@ export default class WebView extends React.Component<
125 125
     );
126 126
   };
127 127
 
128
-  stopLoading = (): void => {
128
+  stopLoading = () => {
129 129
     UIManager.dispatchViewManagerCommand(
130 130
       this.getWebViewHandle(),
131 131
       UIManager.RNCWebView.Commands.stopLoading,
@@ -133,7 +133,7 @@ export default class WebView extends React.Component<
133 133
     );
134 134
   };
135 135
 
136
-  postMessage = (data: string): void => {
136
+  postMessage = (data: string) => {
137 137
     UIManager.dispatchViewManagerCommand(
138 138
       this.getWebViewHandle(),
139 139
       UIManager.RNCWebView.Commands.postMessage,
@@ -147,7 +147,7 @@ export default class WebView extends React.Component<
147 147
    * on pages with a Content Security Policy that disallows eval(). If you need that
148 148
    * functionality, look into postMessage/onMessage.
149 149
    */
150
-  injectJavaScript = (data: string): void => {
150
+  injectJavaScript = (data: string) => {
151 151
     UIManager.dispatchViewManagerCommand(
152 152
       this.getWebViewHandle(),
153 153
       UIManager.RNCWebView.Commands.injectJavaScript,
@@ -159,7 +159,7 @@ export default class WebView extends React.Component<
159 159
    * We return an event with a bunch of fields including:
160 160
    *  url, title, loading, canGoBack, canGoForward
161 161
    */
162
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
162
+  updateNavigationState = (event: WebViewNavigationEvent) => {
163 163
     if (this.props.onNavigationStateChange) {
164 164
       this.props.onNavigationStateChange(event.nativeEvent);
165 165
     }
@@ -168,7 +168,7 @@ export default class WebView extends React.Component<
168 168
   getWebViewHandle = (): number | null =>
169 169
     findNodeHandle(this.webViewRef.current);
170 170
 
171
-  onLoadingStart = (event: WebViewNavigationEvent): void => {
171
+  onLoadingStart = (event: WebViewNavigationEvent) => {
172 172
     const { onLoadStart } = this.props;
173 173
     if (onLoadStart) {
174 174
       onLoadStart(event);
@@ -176,7 +176,7 @@ export default class WebView extends React.Component<
176 176
     this.updateNavigationState(event);
177 177
   };
178 178
 
179
-  onLoadingError = (event: WebViewErrorEvent): void => {
179
+  onLoadingError = (event: WebViewErrorEvent) => {
180 180
     event.persist(); // persist this event because we need to store it
181 181
     const { onError, onLoadEnd } = this.props;
182 182
     if (onError) {
@@ -187,14 +187,13 @@ export default class WebView extends React.Component<
187 187
     }
188 188
     // eslint-disable-next-line no-console
189 189
     console.warn('Encountered an error loading page', event.nativeEvent);
190
-
191 190
     this.setState({
192 191
       lastErrorEvent: event.nativeEvent,
193 192
       viewState: WebViewState.ERROR,
194 193
     });
195 194
   };
196 195
 
197
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
196
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
198 197
     const { onLoad, onLoadEnd } = this.props;
199 198
     if (onLoad) {
200 199
       onLoad(event);
@@ -208,23 +207,34 @@ export default class WebView extends React.Component<
208 207
     this.updateNavigationState(event);
209 208
   };
210 209
 
211
-  onMessage = (event: WebViewMessageEvent): void => {
210
+  onMessage = (event: WebViewMessageEvent) => {
212 211
     const { onMessage } = this.props;
213 212
     if (onMessage) {
214 213
       onMessage(event);
215 214
     }
216 215
   };
217 216
 
218
-  onLoadingProgress = (
219
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
220
-  ): void => {
217
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
221 218
     const { onLoadProgress } = this.props;
222 219
     if (onLoadProgress) {
223 220
       onLoadProgress(event);
224 221
     }
225 222
   };
226 223
 
227
-  render(): React.ReactNode {
224
+  onShouldStartLoadWithRequestCallback = (
225
+    shouldStart: boolean,
226
+    url: string,
227
+  ) => {
228
+    if (shouldStart) {
229
+      UIManager.dispatchViewManagerCommand(
230
+        this.getWebViewHandle(),
231
+        UIManager.RNCWebView.Commands.loadUrl,
232
+        [String(url)],
233
+      );
234
+    }
235
+  };
236
+
237
+  render() {
228 238
     let otherView = null;
229 239
 
230 240
     if (this.state.viewState === WebViewState.LOADING) {
@@ -243,7 +253,6 @@ export default class WebView extends React.Component<
243 253
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
244 254
       }
245 255
     } else if (this.state.viewState !== WebViewState.IDLE) {
246
-      // eslint-disable-next-line no-console
247 256
       console.error(
248 257
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
249 258
       );
@@ -267,24 +276,24 @@ export default class WebView extends React.Component<
267 276
 
268 277
     if (isWebViewUriSource(source)) {
269 278
       if (source.method === 'POST' && source.headers) {
270
-        // eslint-disable-next-line no-console
271 279
         console.warn(
272 280
           'WebView: `source.headers` is not supported when using POST.',
273 281
         );
274 282
       } else if (source.method === 'GET' && source.body) {
275
-        // eslint-disable-next-line no-console
276 283
         console.warn('WebView: `source.body` is not supported when using GET.');
277 284
       }
278 285
     }
279 286
 
280 287
     const nativeConfig = this.props.nativeConfig || {};
281 288
 
282
-    const originWhitelist = (this.props.originWhitelist || []).map(
283
-      WebViewShared.originWhitelistToRegex,
284
-    );
285
-
286 289
     const NativeWebView = nativeConfig.component || RNCWebView;
287 290
 
291
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
292
+      this.onShouldStartLoadWithRequestCallback,
293
+      this.props.originWhitelist,
294
+      this.props.onShouldStartLoadWithRequest,
295
+    );
296
+
288 297
     const webView = (
289 298
       <NativeWebView
290 299
         ref={this.webViewRef}
@@ -305,6 +314,7 @@ export default class WebView extends React.Component<
305 314
         automaticallyAdjustContentInsets={
306 315
           this.props.automaticallyAdjustContentInsets
307 316
         }
317
+        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
308 318
         onContentSizeChange={this.props.onContentSizeChange}
309 319
         onLoadingStart={this.onLoadingStart}
310 320
         onLoadingFinish={this.onLoadingFinish}
@@ -318,7 +328,6 @@ export default class WebView extends React.Component<
318 328
         allowUniversalAccessFromFileURLs={
319 329
           this.props.allowUniversalAccessFromFileURLs
320 330
         }
321
-        originWhitelist={originWhitelist}
322 331
         mixedContentMode={this.props.mixedContentMode}
323 332
         saveFormDataDisabled={this.props.saveFormDataDisabled}
324 333
         urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
@@ -334,3 +343,5 @@ export default class WebView extends React.Component<
334 343
     );
335 344
   }
336 345
 }
346
+
347
+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
 enum WebViewState {
87 91
   IDLE = 'IDLE',
88 92
   LOADING = 'LOADING',
@@ -105,7 +109,19 @@ type State = {
105 109
   lastErrorEvent: WebViewError | null;
106 110
 };
107 111
 
108
-const defaultRenderLoading = (): React.ReactNode => (
112
+const DataDetectorTypes = [
113
+  'phoneNumber',
114
+  'link',
115
+  'address',
116
+  'calendarEvent',
117
+  'trackingNumber',
118
+  'flightNumber',
119
+  'lookupSuggestion',
120
+  'none',
121
+  'all',
122
+];
123
+
124
+const defaultRenderLoading = () => (
109 125
   <View style={styles.loadingView}>
110 126
     <ActivityIndicator />
111 127
   </View>
@@ -114,7 +130,7 @@ const defaultRenderError = (
114 130
   errorDomain: string | undefined,
115 131
   errorCode: number,
116 132
   errorDesc: string,
117
-): React.ReactNode => (
133
+) => (
118 134
   <View style={styles.errorContainer}>
119 135
     <Text style={styles.errorTextTitle}>Error loading page</Text>
120 136
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
@@ -123,9 +139,6 @@ const defaultRenderError = (
123 139
   </View>
124 140
 );
125 141
 
126
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
127
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
128
-
129 142
 /**
130 143
  * `WebView` renders web content in a native view.
131 144
  *
@@ -148,20 +161,17 @@ const RNCWKWebView = requireNativeComponent('RNCWKWebView');
148 161
  * You can use this component to navigate back and forth in the web view's
149 162
  * history and configure various properties for the web content.
150 163
  */
151
-export default class WebView extends React.Component<
152
-  WebViewSharedProps,
153
-  State
154
-> {
164
+class WebView extends React.Component<WebViewSharedProps, State> {
155 165
   static JSNavigationScheme = JSNavigationScheme;
156 166
 
157 167
   static NavigationType = NavigationType;
158 168
 
159 169
   static defaultProps = {
160 170
     useWebKit: true,
161
-    originWhitelist: WebViewShared.defaultOriginWhitelist,
171
+    originWhitelist: defaultOriginWhitelist,
162 172
   };
163 173
 
164
-  static isFileUploadSupported = async (): Promise<boolean> =>
174
+  static isFileUploadSupported = async () =>
165 175
     // no native implementation for iOS, depends only on permissions
166 176
     true;
167 177
 
@@ -175,12 +185,11 @@ export default class WebView extends React.Component<
175 185
   webViewRef = React.createRef<React.ComponentClass>();
176 186
 
177 187
   // eslint-disable-next-line camelcase, react/sort-comp
178
-  UNSAFE_componentWillMount(): void {
188
+  UNSAFE_componentWillMount() {
179 189
     if (
180 190
       this.props.useWebKit === true
181 191
       && this.props.scalesPageToFit !== undefined
182 192
     ) {
183
-      // eslint-disable-next-line no-console
184 193
       console.warn(
185 194
         'The scalesPageToFit property is not supported when useWebKit = true',
186 195
       );
@@ -189,14 +198,13 @@ export default class WebView extends React.Component<
189 198
       !this.props.useWebKit
190 199
       && this.props.allowsBackForwardNavigationGestures
191 200
     ) {
192
-      // eslint-disable-next-line no-console
193 201
       console.warn(
194 202
         'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
195 203
       );
196 204
     }
197 205
   }
198 206
 
199
-  componentDidUpdate(prevProps: WebViewSharedProps): void {
207
+  componentDidUpdate(prevProps: WebViewSharedProps) {
200 208
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
201 209
       return;
202 210
     }
@@ -206,17 +214,24 @@ export default class WebView extends React.Component<
206 214
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
207 215
 
208 216
     if (this.props.scalesPageToFit !== undefined) {
209
-      // eslint-disable-next-line no-console
210 217
       console.warn(
211 218
         'The scalesPageToFit property is not supported when useWebKit = true',
212 219
       );
213 220
     }
214 221
   }
215 222
 
223
+  getCommands() {
224
+    if (!this.props.useWebKit) {
225
+      return UIManager.RNCUIWebView.Commands;
226
+    }
227
+
228
+    return UIManager.RNCWKWebView.Commands;
229
+  }
230
+
216 231
   /**
217 232
    * Go forward one page in the web view's history.
218 233
    */
219
-  goForward = (): void => {
234
+  goForward = () => {
220 235
     UIManager.dispatchViewManagerCommand(
221 236
       this.getWebViewHandle(),
222 237
       this.getCommands().goForward,
@@ -224,25 +239,10 @@ export default class WebView extends React.Component<
224 239
     );
225 240
   };
226 241
 
227
-  getCommands(): {
228
-    goForward: () => void;
229
-    goBack: () => void;
230
-    reload: () => void;
231
-    stopLoading: () => void;
232
-    postMessage: () => void;
233
-    injectJavaScript: () => void;
234
-  } {
235
-    if (!this.props.useWebKit) {
236
-      return UIManager.RNCUIWebView.Commands;
237
-    }
238
-
239
-    return UIManager.RNCWKWebView.Commands;
240
-  }
241
-
242 242
   /**
243 243
    * Go back one page in the web view's history.
244 244
    */
245
-  goBack = (): void => {
245
+  goBack = () => {
246 246
     UIManager.dispatchViewManagerCommand(
247 247
       this.getWebViewHandle(),
248 248
       this.getCommands().goBack,
@@ -253,7 +253,7 @@ export default class WebView extends React.Component<
253 253
   /**
254 254
    * Reloads the current page.
255 255
    */
256
-  reload = (): void => {
256
+  reload = () => {
257 257
     this.setState({ viewState: WebViewState.LOADING });
258 258
     UIManager.dispatchViewManagerCommand(
259 259
       this.getWebViewHandle(),
@@ -265,7 +265,7 @@ export default class WebView extends React.Component<
265 265
   /**
266 266
    * Stop loading the current page.
267 267
    */
268
-  stopLoading = (): void => {
268
+  stopLoading = () => {
269 269
     UIManager.dispatchViewManagerCommand(
270 270
       this.getWebViewHandle(),
271 271
       this.getCommands().stopLoading,
@@ -283,7 +283,7 @@ export default class WebView extends React.Component<
283 283
    * document.addEventListener('message', e => { document.title = e.data; });
284 284
    * ```
285 285
    */
286
-  postMessage = (data: string): void => {
286
+  postMessage = (data: string) => {
287 287
     UIManager.dispatchViewManagerCommand(
288 288
       this.getWebViewHandle(),
289 289
       this.getCommands().postMessage,
@@ -297,7 +297,7 @@ export default class WebView extends React.Component<
297 297
    * on pages with a Content Security Policy that disallows eval(). If you need that
298 298
    * functionality, look into postMessage/onMessage.
299 299
    */
300
-  injectJavaScript = (data: string): void => {
300
+  injectJavaScript = (data: string) => {
301 301
     UIManager.dispatchViewManagerCommand(
302 302
       this.getWebViewHandle(),
303 303
       this.getCommands().injectJavaScript,
@@ -309,7 +309,7 @@ export default class WebView extends React.Component<
309 309
    * We return an event with a bunch of fields including:
310 310
    *  url, title, loading, canGoBack, canGoForward
311 311
    */
312
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
312
+  updateNavigationState = (event: WebViewNavigationEvent) => {
313 313
     if (this.props.onNavigationStateChange) {
314 314
       this.props.onNavigationStateChange(event.nativeEvent);
315 315
     }
@@ -318,10 +318,9 @@ export default class WebView extends React.Component<
318 318
   /**
319 319
    * Returns the native `WebView` node.
320 320
    */
321
-  getWebViewHandle = (): number | null =>
322
-    findNodeHandle(this.webViewRef.current);
321
+  getWebViewHandle = () => findNodeHandle(this.webViewRef.current);
323 322
 
324
-  onLoadingStart = (event: WebViewNavigationEvent): void => {
323
+  onLoadingStart = (event: WebViewNavigationEvent) => {
325 324
     const { onLoadStart } = this.props;
326 325
     if (onLoadStart) {
327 326
       onLoadStart(event);
@@ -329,7 +328,7 @@ export default class WebView extends React.Component<
329 328
     this.updateNavigationState(event);
330 329
   };
331 330
 
332
-  onLoadingError = (event: WebViewErrorEvent): void => {
331
+  onLoadingError = (event: WebViewErrorEvent) => {
333 332
     event.persist(); // persist this event because we need to store it
334 333
     const { onError, onLoadEnd } = this.props;
335 334
     if (onError) {
@@ -340,14 +339,13 @@ export default class WebView extends React.Component<
340 339
     }
341 340
     // eslint-disable-next-line no-console
342 341
     console.warn('Encountered an error loading page', event.nativeEvent);
343
-
344 342
     this.setState({
345 343
       lastErrorEvent: event.nativeEvent,
346 344
       viewState: WebViewState.ERROR,
347 345
     });
348 346
   };
349 347
 
350
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
348
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
351 349
     const { onLoad, onLoadEnd } = this.props;
352 350
     if (onLoad) {
353 351
       onLoad(event);
@@ -361,38 +359,50 @@ export default class WebView extends React.Component<
361 359
     this.updateNavigationState(event);
362 360
   };
363 361
 
364
-  onMessage = (event: WebViewMessageEvent): void => {
362
+  onMessage = (event: WebViewMessageEvent) => {
365 363
     const { onMessage } = this.props;
366 364
     if (onMessage) {
367 365
       onMessage(event);
368 366
     }
369 367
   };
370 368
 
371
-  onLoadingProgress = (
372
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
373
-  ): void => {
369
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
374 370
     const { onLoadProgress } = this.props;
375 371
     if (onLoadProgress) {
376 372
       onLoadProgress(event);
377 373
     }
378 374
   };
379 375
 
380
-  showRedboxOnPropChanges(
376
+  onShouldStartLoadWithRequestCallback = (
377
+    shouldStart: boolean,
378
+    url: string,
379
+    lockIdentifier: number,
380
+  ) => {
381
+    const nativeConfig = this.props.nativeConfig || {};
382
+
383
+    let { viewManager } = nativeConfig;
384
+
385
+    if (this.props.useWebKit) {
386
+      viewManager = viewManager || RNCWKWebViewManager;
387
+    } else {
388
+      viewManager = viewManager || RNCUIWebViewManager;
389
+    }
390
+    invariant(viewManager != null, 'viewManager expected to be non-null');
391
+    viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
392
+  };
393
+
394
+  showRedboxOnPropChanges = (
381 395
     prevProps: WebViewSharedProps,
382
-    propName:
383
-      | 'allowsInlineMediaPlayback'
384
-      | 'mediaPlaybackRequiresUserAction'
385
-      | 'dataDetectorTypes',
386
-  ): void {
396
+    propName: keyof WebViewSharedProps,
397
+  ) => {
387 398
     if (this.props[propName] !== prevProps[propName]) {
388
-      // eslint-disable-next-line no-console
389 399
       console.error(
390 400
         `Changes to property ${propName} do nothing after the initial render.`,
391 401
       );
392 402
     }
393
-  }
403
+  };
394 404
 
395
-  render(): React.ReactNode {
405
+  render() {
396 406
     let otherView = null;
397 407
 
398 408
     let scalesPageToFit;
@@ -417,7 +427,6 @@ export default class WebView extends React.Component<
417 427
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
418 428
       }
419 429
     } else if (this.state.viewState !== WebViewState.IDLE) {
420
-      // eslint-disable-next-line no-console
421 430
       console.error(
422 431
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
423 432
       );
@@ -434,42 +443,11 @@ export default class WebView extends React.Component<
434 443
 
435 444
     const nativeConfig = this.props.nativeConfig || {};
436 445
 
437
-    let { viewManager } = nativeConfig;
438
-
439
-    if (this.props.useWebKit) {
440
-      viewManager = viewManager || RNCWKWebViewManager;
441
-    } else {
442
-      viewManager = viewManager || RNCUIWebViewManager;
443
-    }
444
-
445
-    const compiledWhitelist = [
446
-      'about:blank',
447
-      ...(this.props.originWhitelist || []),
448
-    ].map(WebViewShared.originWhitelistToRegex);
449
-    const onShouldStartLoadWithRequest = (
450
-      event: NativeSyntheticEvent<WebViewIOSLoadRequestEvent>,
451
-    ): void => {
452
-      let shouldStart = true;
453
-      const { url } = event.nativeEvent;
454
-      const origin = WebViewShared.extractOrigin(url);
455
-      const passesWhitelist = compiledWhitelist.some(
456
-        (x): boolean => new RegExp(x).test(origin),
457
-      );
458
-      shouldStart = shouldStart && passesWhitelist;
459
-      if (!passesWhitelist) {
460
-        Linking.openURL(url);
461
-      }
462
-      if (this.props.onShouldStartLoadWithRequest) {
463
-        shouldStart
464
-          = shouldStart
465
-          && this.props.onShouldStartLoadWithRequest(event.nativeEvent);
466
-      }
467
-      invariant(viewManager != null, 'viewManager expected to be non-null');
468
-      viewManager.startLoadWithResult(
469
-        !!shouldStart,
470
-        event.nativeEvent.lockIdentifier,
471
-      );
472
-    };
446
+    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
447
+      this.onShouldStartLoadWithRequestCallback,
448
+      this.props.originWhitelist,
449
+      this.props.onShouldStartLoadWithRequest,
450
+    );
473 451
 
474 452
     const decelerationRate = processDecelerationRate(
475 453
       this.props.decelerationRate,
@@ -484,9 +462,13 @@ export default class WebView extends React.Component<
484 462
 
485 463
     const messagingEnabled = typeof this.props.onMessage === 'function';
486 464
 
487
-    const NativeWebView
488
-      = nativeConfig.component
489
-      || (this.props.useWebKit ? RNCWKWebView : RNCUIWebView);
465
+    let NativeWebView = nativeConfig.component;
466
+
467
+    if (this.props.useWebKit) {
468
+      NativeWebView = NativeWebView || RNCWKWebView;
469
+    } else {
470
+      NativeWebView = NativeWebView || RNCUIWebView;
471
+    }
490 472
 
491 473
     const webView = (
492 474
       <NativeWebView
@@ -534,3 +516,5 @@ export default class WebView extends React.Component<
534 516
     );
535 517
   }
536 518
 }
519
+
520
+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"