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

+ 1
- 0
android/build.gradle View File

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

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

30
 import android.webkit.JavascriptInterface;
30
 import android.webkit.JavascriptInterface;
31
 import android.webkit.ValueCallback;
31
 import android.webkit.ValueCallback;
32
 import android.webkit.WebChromeClient;
32
 import android.webkit.WebChromeClient;
33
+import android.webkit.WebResourceRequest;
33
 import android.webkit.WebSettings;
34
 import android.webkit.WebSettings;
34
 import android.webkit.WebView;
35
 import android.webkit.WebView;
35
 import android.webkit.WebViewClient;
36
 import android.webkit.WebViewClient;
51
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
52
 import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
52
 import com.facebook.react.uimanager.events.Event;
53
 import com.facebook.react.uimanager.events.Event;
53
 import com.facebook.react.uimanager.events.EventDispatcher;
54
 import com.facebook.react.uimanager.events.EventDispatcher;
55
+import com.facebook.react.uimanager.events.RCTEventEmitter;
54
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
56
 import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
55
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
57
 import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
56
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
58
 import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
57
 import com.reactnativecommunity.webview.events.TopMessageEvent;
59
 import com.reactnativecommunity.webview.events.TopMessageEvent;
58
 import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
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
 import org.json.JSONException;
68
 import org.json.JSONException;
60
 import org.json.JSONObject;
69
 import org.json.JSONObject;
61
 
70
 
66
  *  - GO_BACK
75
  *  - GO_BACK
67
  *  - GO_FORWARD
76
  *  - GO_FORWARD
68
  *  - RELOAD
77
  *  - RELOAD
78
+ *  - LOAD_URL
69
  *
79
  *
70
  * {@link WebView} instances could emit following direct events:
80
  * {@link WebView} instances could emit following direct events:
71
  *  - topLoadingFinish
81
  *  - topLoadingFinish
72
  *  - topLoadingStart
82
  *  - topLoadingStart
73
  *  - topLoadingStart
83
  *  - topLoadingStart
74
  *  - topLoadingProgress
84
  *  - topLoadingProgress
85
+ *  - topShouldStartLoadWithRequest
75
  *
86
  *
76
  * Each event will carry the following properties:
87
  * Each event will carry the following properties:
77
  *  - target - view's react tag
88
  *  - target - view's react tag
99
   public static final int COMMAND_STOP_LOADING = 4;
110
   public static final int COMMAND_STOP_LOADING = 4;
100
   public static final int COMMAND_POST_MESSAGE = 5;
111
   public static final int COMMAND_POST_MESSAGE = 5;
101
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
112
   public static final int COMMAND_INJECT_JAVASCRIPT = 6;
113
+  public static final int COMMAND_LOAD_URL = 7;
102
 
114
 
103
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
115
   // Use `webView.loadUrl("about:blank")` to reliably reset the view
104
   // state and release page resources (including any running JavaScript).
116
   // state and release page resources (including any running JavaScript).
111
 
123
 
112
     protected boolean mLastLoadFailed = false;
124
     protected boolean mLastLoadFailed = false;
113
     protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
125
     protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
114
-    protected @Nullable List<Pattern> mOriginWhitelist;
115
 
126
 
116
     @Override
127
     @Override
117
     public void onPageFinished(WebView webView, String url) {
128
     public void onPageFinished(WebView webView, String url) {
139
 
150
 
140
     @Override
151
     @Override
141
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
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
       return true;
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
     @Override
165
     @Override
231
     public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
208
     public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
232
       mUrlPrefixesForDefaultIntent = specialUrls;
209
       mUrlPrefixesForDefaultIntent = specialUrls;
233
     }
210
     }
234
-
235
-    public void setOriginWhitelist(List<Pattern> originWhitelist) {
236
-      mOriginWhitelist = originWhitelist;
237
-    }
238
   }
211
   }
239
 
212
 
240
   /**
213
   /**
656
     view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
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
   @Override
632
   @Override
674
   protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
633
   protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
675
     // Do not register default touch emitter and let WebView implementation handle touches
634
     // Do not register default touch emitter and let WebView implementation handle touches
678
 
637
 
679
   @Override
638
   @Override
680
   public Map getExportedCustomDirectEventTypeConstants() {
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
   @Override
649
   @Override
691
         "reload", COMMAND_RELOAD,
654
         "reload", COMMAND_RELOAD,
692
         "stopLoading", COMMAND_STOP_LOADING,
655
         "stopLoading", COMMAND_STOP_LOADING,
693
         "postMessage", COMMAND_POST_MESSAGE,
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
         RNCWebView reactWebView = (RNCWebView) root;
698
         RNCWebView reactWebView = (RNCWebView) root;
735
         reactWebView.evaluateJavascriptWithFallback(args.getString(0));
699
         reactWebView.evaluateJavascriptWithFallback(args.getString(0));
736
         break;
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

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
 
102
 
103
 ### `injectedJavaScript`
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
 | Type   | Required |
107
 | Type   | Required |
108
 | ------ | -------- |
108
 | ------ | -------- |

+ 89
- 37
ios/RNCWKWebView.m View File

8
 #import "RNCWKWebView.h"
8
 #import "RNCWKWebView.h"
9
 #import <React/RCTConvert.h>
9
 #import <React/RCTConvert.h>
10
 #import <React/RCTAutoInsetsProtocol.h>
10
 #import <React/RCTAutoInsetsProtocol.h>
11
+#import <UIKit/UIKit.h>
11
 
12
 
12
 #import "objc/runtime.h"
13
 #import "objc/runtime.h"
13
 
14
 
14
-static NSTimer *keyboardTimer;
15
 static NSString *const MessageHanderName = @"ReactNative";
15
 static NSString *const MessageHanderName = @"ReactNative";
16
 
16
 
17
 // runtime trick to remove WKWebView keyboard default toolbar
17
 // runtime trick to remove WKWebView keyboard default toolbar
70
     _automaticallyAdjustContentInsets = YES;
70
     _automaticallyAdjustContentInsets = YES;
71
     _contentInset = UIEdgeInsetsZero;
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
   return self;
73
   return self;
87
 }
74
 }
88
 
75
 
144
     [super removeFromSuperview];
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
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
134
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
169
     if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
135
     if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
170
         if(_onLoadingProgress){
136
         if(_onLoadingProgress){
339
 
305
 
340
 #pragma mark - WKNavigationDelegate methods
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
  * Decides whether to allow or cancel a navigation.
391
  * Decides whether to allow or cancel a navigation.
344
  * @see https://fburl.com/42r9fxob
392
  * @see https://fburl.com/42r9fxob
429
           thenCall: (void (^)(NSString*)) callback
477
           thenCall: (void (^)(NSString*)) callback
430
 {
478
 {
431
   [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
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
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
7
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
8
   ],
8
   ],
9
   "license": "MIT",
9
   "license": "MIT",
10
-  "version": "2.13.0",
10
+  "version": "2.14.3",
11
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
11
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
12
   "scripts": {
12
   "scripts": {
13
     "ci:publish": "yarn semantic-release",
13
     "ci:publish": "yarn semantic-release",
43
     "react-native": "0.57.5",
43
     "react-native": "0.57.5",
44
     "semantic-release": "15.10.3",
44
     "semantic-release": "15.10.3",
45
     "typescript": "3.1.6",
45
     "typescript": "3.1.6",
46
-    "typescript-eslint-parser": "21.0.1"
46
+    "typescript-eslint-parser": "21.0.2"
47
   },
47
   },
48
   "repository": {
48
   "repository": {
49
     "type": "git",
49
     "type": "git",

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

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

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

1
 import React from 'react';
1
 import React from 'react';
2
 import {
2
 import {
3
   ActivityIndicator,
3
   ActivityIndicator,
4
-  Linking,
5
   StyleSheet,
4
   StyleSheet,
6
   Text,
5
   Text,
7
   UIManager,
6
   UIManager,
15
 
14
 
16
 import invariant from 'invariant';
15
 import invariant from 'invariant';
17
 
16
 
18
-import WebViewShared from './WebViewShared';
17
+import {
18
+  defaultOriginWhitelist,
19
+  createOnShouldStartLoadWithRequest,
20
+} from './WebViewShared';
21
+
19
 import {
22
 import {
20
   WebViewSourceUri,
23
   WebViewSourceUri,
21
   WebViewError,
24
   WebViewError,
22
-  WebViewIOSLoadRequestEvent,
23
   WebViewErrorEvent,
25
   WebViewErrorEvent,
24
   WebViewMessageEvent,
26
   WebViewMessageEvent,
25
   WebViewNavigationEvent,
27
   WebViewNavigationEvent,
28
   WebViewProgressEvent,
30
   WebViewProgressEvent,
29
 } from './types/WebViewTypes';
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
 const BGWASH = 'rgba(255,255,255,0.8)';
33
 const BGWASH = 'rgba(255,255,255,0.8)';
49
 
34
 
50
 const styles = StyleSheet.create({
35
 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
 const WebViewState: {
90
 const WebViewState: {
87
   IDLE: 'IDLE';
91
   IDLE: 'IDLE';
88
   LOADING: 'LOADING';
92
   LOADING: 'LOADING';
109
   lastErrorEvent: WebViewError | null;
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
   <View style={styles.loadingView}>
129
   <View style={styles.loadingView}>
114
     <ActivityIndicator />
130
     <ActivityIndicator />
115
   </View>
131
   </View>
118
   errorDomain: string | undefined,
134
   errorDomain: string | undefined,
119
   errorCode: number,
135
   errorCode: number,
120
   errorDesc: string,
136
   errorDesc: string,
121
-): React.ReactNode => (
137
+) => (
122
   <View style={styles.errorContainer}>
138
   <View style={styles.errorContainer}>
123
     <Text style={styles.errorTextTitle}>Error loading page</Text>
139
     <Text style={styles.errorTextTitle}>Error loading page</Text>
124
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
140
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
127
   </View>
143
   </View>
128
 );
144
 );
129
 
145
 
130
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
131
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
132
-
133
 /**
146
 /**
134
  * `WebView` renders web content in a native view.
147
  * `WebView` renders web content in a native view.
135
  *
148
  *
152
  * You can use this component to navigate back and forth in the web view's
165
  * You can use this component to navigate back and forth in the web view's
153
  * history and configure various properties for the web content.
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
   static JSNavigationScheme = JSNavigationScheme;
169
   static JSNavigationScheme = JSNavigationScheme;
160
 
170
 
161
   static NavigationType = NavigationType;
171
   static NavigationType = NavigationType;
162
 
172
 
163
   static defaultProps = {
173
   static defaultProps = {
164
     useWebKit: true,
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
     // no native implementation for iOS, depends only on permissions
179
     // no native implementation for iOS, depends only on permissions
170
     true;
180
     true;
171
 
181
 
179
   webViewRef = React.createRef<React.ComponentClass>();
189
   webViewRef = React.createRef<React.ComponentClass>();
180
 
190
 
181
   // eslint-disable-next-line camelcase, react/sort-comp
191
   // eslint-disable-next-line camelcase, react/sort-comp
182
-  UNSAFE_componentWillMount(): void {
192
+  UNSAFE_componentWillMount() {
183
     if (
193
     if (
184
       this.props.useWebKit === true
194
       this.props.useWebKit === true
185
       && this.props.scalesPageToFit !== undefined
195
       && this.props.scalesPageToFit !== undefined
186
     ) {
196
     ) {
187
-      // eslint-disable-next-line no-console
188
       console.warn(
197
       console.warn(
189
         'The scalesPageToFit property is not supported when useWebKit = true',
198
         'The scalesPageToFit property is not supported when useWebKit = true',
190
       );
199
       );
193
       !this.props.useWebKit
202
       !this.props.useWebKit
194
       && this.props.allowsBackForwardNavigationGestures
203
       && this.props.allowsBackForwardNavigationGestures
195
     ) {
204
     ) {
196
-      // eslint-disable-next-line no-console
197
       console.warn(
205
       console.warn(
198
         'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
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
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
212
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
205
       return;
213
       return;
206
     }
214
     }
210
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
218
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
211
 
219
 
212
     if (this.props.scalesPageToFit !== undefined) {
220
     if (this.props.scalesPageToFit !== undefined) {
213
-      // eslint-disable-next-line no-console
214
       console.warn(
221
       console.warn(
215
         'The scalesPageToFit property is not supported when useWebKit = true',
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
    * Go forward one page in the web view's history.
236
    * Go forward one page in the web view's history.
222
    */
237
    */
223
-  goForward = (): void => {
238
+  goForward = () => {
224
     UIManager.dispatchViewManagerCommand(
239
     UIManager.dispatchViewManagerCommand(
225
       this.getWebViewHandle(),
240
       this.getWebViewHandle(),
226
       this.getCommands().goForward,
241
       this.getCommands().goForward,
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
    * Go back one page in the web view's history.
247
    * Go back one page in the web view's history.
248
    */
248
    */
249
-  goBack = (): void => {
249
+  goBack = () => {
250
     UIManager.dispatchViewManagerCommand(
250
     UIManager.dispatchViewManagerCommand(
251
       this.getWebViewHandle(),
251
       this.getWebViewHandle(),
252
       this.getCommands().goBack,
252
       this.getCommands().goBack,
257
   /**
257
   /**
258
    * Reloads the current page.
258
    * Reloads the current page.
259
    */
259
    */
260
-  reload = (): void => {
260
+  reload = () => {
261
     this.setState({ viewState: WebViewState.LOADING });
261
     this.setState({ viewState: WebViewState.LOADING });
262
     UIManager.dispatchViewManagerCommand(
262
     UIManager.dispatchViewManagerCommand(
263
       this.getWebViewHandle(),
263
       this.getWebViewHandle(),
269
   /**
269
   /**
270
    * Stop loading the current page.
270
    * Stop loading the current page.
271
    */
271
    */
272
-  stopLoading = (): void => {
272
+  stopLoading = () => {
273
     UIManager.dispatchViewManagerCommand(
273
     UIManager.dispatchViewManagerCommand(
274
       this.getWebViewHandle(),
274
       this.getWebViewHandle(),
275
       this.getCommands().stopLoading,
275
       this.getCommands().stopLoading,
287
    * document.addEventListener('message', e => { document.title = e.data; });
287
    * document.addEventListener('message', e => { document.title = e.data; });
288
    * ```
288
    * ```
289
    */
289
    */
290
-  postMessage = (data: string): void => {
290
+  postMessage = (data: string) => {
291
     UIManager.dispatchViewManagerCommand(
291
     UIManager.dispatchViewManagerCommand(
292
       this.getWebViewHandle(),
292
       this.getWebViewHandle(),
293
       this.getCommands().postMessage,
293
       this.getCommands().postMessage,
301
    * on pages with a Content Security Policy that disallows eval(). If you need that
301
    * on pages with a Content Security Policy that disallows eval(). If you need that
302
    * functionality, look into postMessage/onMessage.
302
    * functionality, look into postMessage/onMessage.
303
    */
303
    */
304
-  injectJavaScript = (data: string): void => {
304
+  injectJavaScript = (data: string) => {
305
     UIManager.dispatchViewManagerCommand(
305
     UIManager.dispatchViewManagerCommand(
306
       this.getWebViewHandle(),
306
       this.getWebViewHandle(),
307
       this.getCommands().injectJavaScript,
307
       this.getCommands().injectJavaScript,
313
    * We return an event with a bunch of fields including:
313
    * We return an event with a bunch of fields including:
314
    *  url, title, loading, canGoBack, canGoForward
314
    *  url, title, loading, canGoBack, canGoForward
315
    */
315
    */
316
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
316
+  updateNavigationState = (event: WebViewNavigationEvent) => {
317
     if (this.props.onNavigationStateChange) {
317
     if (this.props.onNavigationStateChange) {
318
       this.props.onNavigationStateChange(event.nativeEvent);
318
       this.props.onNavigationStateChange(event.nativeEvent);
319
     }
319
     }
322
   /**
322
   /**
323
    * Returns the native `WebView` node.
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
     const { onLoadStart } = this.props;
328
     const { onLoadStart } = this.props;
330
     if (onLoadStart) {
329
     if (onLoadStart) {
331
       onLoadStart(event);
330
       onLoadStart(event);
333
     this.updateNavigationState(event);
332
     this.updateNavigationState(event);
334
   };
333
   };
335
 
334
 
336
-  onLoadingError = (event: WebViewErrorEvent): void => {
335
+  onLoadingError = (event: WebViewErrorEvent) => {
337
     event.persist(); // persist this event because we need to store it
336
     event.persist(); // persist this event because we need to store it
338
     const { onError, onLoadEnd } = this.props;
337
     const { onError, onLoadEnd } = this.props;
339
     if (onError) {
338
     if (onError) {
344
     }
343
     }
345
     // eslint-disable-next-line no-console
344
     // eslint-disable-next-line no-console
346
     console.warn('Encountered an error loading page', event.nativeEvent);
345
     console.warn('Encountered an error loading page', event.nativeEvent);
347
-
348
     this.setState({
346
     this.setState({
349
       lastErrorEvent: event.nativeEvent,
347
       lastErrorEvent: event.nativeEvent,
350
       viewState: WebViewState.ERROR,
348
       viewState: WebViewState.ERROR,
351
     });
349
     });
352
   };
350
   };
353
 
351
 
354
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
352
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
355
     const { onLoad, onLoadEnd } = this.props;
353
     const { onLoad, onLoadEnd } = this.props;
356
     if (onLoad) {
354
     if (onLoad) {
357
       onLoad(event);
355
       onLoad(event);
365
     this.updateNavigationState(event);
363
     this.updateNavigationState(event);
366
   };
364
   };
367
 
365
 
368
-  onMessage = (event: WebViewMessageEvent): void => {
366
+  onMessage = (event: WebViewMessageEvent) => {
369
     const { onMessage } = this.props;
367
     const { onMessage } = this.props;
370
     if (onMessage) {
368
     if (onMessage) {
371
       onMessage(event);
369
       onMessage(event);
372
     }
370
     }
373
   };
371
   };
374
 
372
 
375
-  onLoadingProgress = (
376
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
377
-  ): void => {
373
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
378
     const { onLoadProgress } = this.props;
374
     const { onLoadProgress } = this.props;
379
     if (onLoadProgress) {
375
     if (onLoadProgress) {
380
       onLoadProgress(event);
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
     prevProps: WebViewSharedProps,
399
     prevProps: WebViewSharedProps,
386
-    propName:
387
-      | 'allowsInlineMediaPlayback'
388
-      | 'mediaPlaybackRequiresUserAction'
389
-      | 'dataDetectorTypes',
390
-  ): void {
400
+    propName: keyof WebViewSharedProps,
401
+  ) => {
391
     if (this.props[propName] !== prevProps[propName]) {
402
     if (this.props[propName] !== prevProps[propName]) {
392
-      // eslint-disable-next-line no-console
393
       console.error(
403
       console.error(
394
         `Changes to property ${propName} do nothing after the initial render.`,
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
     let otherView = null;
410
     let otherView = null;
401
 
411
 
402
     let scalesPageToFit;
412
     let scalesPageToFit;
421
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
431
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
422
       }
432
       }
423
     } else if (this.state.viewState !== WebViewState.IDLE) {
433
     } else if (this.state.viewState !== WebViewState.IDLE) {
424
-      // eslint-disable-next-line no-console
425
       console.error(
434
       console.error(
426
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
435
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
427
       );
436
       );
438
 
447
 
439
     const nativeConfig = this.props.nativeConfig || {};
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
     const decelerationRate = processDecelerationRate(
456
     const decelerationRate = processDecelerationRate(
479
       this.props.decelerationRate,
457
       this.props.decelerationRate,
488
 
466
 
489
     const messagingEnabled = typeof this.props.onMessage === 'function';
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
     const webView = (
477
     const webView = (
496
       <NativeWebView
478
       <NativeWebView
538
     );
520
     );
539
   }
521
   }
540
 }
522
 }
523
+
524
+export default WebView;

+ 60
- 9
src/WebViewShared.ts View File

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
 import escapeStringRegexp from 'escape-string-regexp';
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
     | 'reload'
48
     | 'reload'
49
     | 'formresubmit'
49
     | 'formresubmit'
50
     | 'other';
50
     | 'other';
51
+  lockIdentifier?: number;
51
 }
52
 }
52
 
53
 
53
 export interface WebViewMessage extends WebViewNativeEvent {
54
 export interface WebViewMessage extends WebViewNativeEvent {
211
    */
212
    */
212
   dataDetectorTypes?: DataDetectorTypes | DataDetectorTypes[];
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
    * Boolean that determines whether HTML5 videos play inline or use the
216
    * Boolean that determines whether HTML5 videos play inline or use the
224
    * native full-screen controller. The default value is `false`.
217
    * native full-screen controller. The default value is `false`.
406
    */
399
    */
407
   onNavigationStateChange?: (event: WebViewNavigation) => any;
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
    * A function that is invoked when the webview calls `window.postMessage`.
413
    * A function that is invoked when the webview calls `window.postMessage`.
411
    * Setting this property will inject a `postMessage` global into your
414
    * Setting this property will inject a `postMessage` global into your

+ 10
- 9
yarn.lock View File

3791
   version "4.4.0"
3791
   version "4.4.0"
3792
   resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
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
   version "4.17.11"
3795
   version "4.17.11"
3796
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
3796
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
3797
 
3797
 
6510
   version "0.0.6"
6510
   version "0.0.6"
6511
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
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
   dependencies:
6517
   dependencies:
6517
     eslint-scope "^4.0.0"
6518
     eslint-scope "^4.0.0"
6518
     eslint-visitor-keys "^1.0.0"
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
   dependencies:
6526
   dependencies:
6526
     lodash.unescape "4.0.1"
6527
     lodash.unescape "4.0.1"
6527
     semver "5.5.0"
6528
     semver "5.5.0"