Browse Source

Merge branch 'master' into typscript-migration

Thibault Malbranche 6 years ago
parent
commit
3943f3161f

+ 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,
55
 ): source is WebViewSourceUri =>
60
 ): source is WebViewSourceUri =>
56
   typeof source !== 'number' && !('html' in source);
61
   typeof source !== 'number' && !('html' in source);
57
 
62
 
58
-const defaultRenderLoading = (): React.ReactNode => (
63
+const defaultRenderLoading = () => (
59
   <View style={styles.loadingView}>
64
   <View style={styles.loadingView}>
60
     <ActivityIndicator style={styles.loadingProgressBar} />
65
     <ActivityIndicator style={styles.loadingProgressBar} />
61
   </View>
66
   </View>
66
   lastErrorEvent: WebViewError | null;
71
   lastErrorEvent: WebViewError | null;
67
 };
72
 };
68
 
73
 
69
-const RNCWebView = requireNativeComponent('RNCWebView');
70
-
71
 /**
74
 /**
72
  * Renders a native WebView.
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
   static defaultProps = {
78
   static defaultProps = {
79
     overScrollMode: 'always',
79
     overScrollMode: 'always',
80
     javaScriptEnabled: true,
80
     javaScriptEnabled: true,
82
     scalesPageToFit: true,
82
     scalesPageToFit: true,
83
     allowFileAccess: false,
83
     allowFileAccess: false,
84
     saveFormDataDisabled: false,
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
     // native implementation should return "true" only for Android 5+
89
     // native implementation should return "true" only for Android 5+
90
     NativeModules.RNCWebView.isFileUploadSupported();
90
     NativeModules.RNCWebView.isFileUploadSupported();
91
 
91
 
98
 
98
 
99
   webViewRef = React.createRef<React.ComponentClass>();
99
   webViewRef = React.createRef<React.ComponentClass>();
100
 
100
 
101
-  goForward = (): void => {
101
+  goForward = () => {
102
     UIManager.dispatchViewManagerCommand(
102
     UIManager.dispatchViewManagerCommand(
103
       this.getWebViewHandle(),
103
       this.getWebViewHandle(),
104
       UIManager.RNCWebView.Commands.goForward,
104
       UIManager.RNCWebView.Commands.goForward,
106
     );
106
     );
107
   };
107
   };
108
 
108
 
109
-  goBack = (): void => {
109
+  goBack = () => {
110
     UIManager.dispatchViewManagerCommand(
110
     UIManager.dispatchViewManagerCommand(
111
       this.getWebViewHandle(),
111
       this.getWebViewHandle(),
112
       UIManager.RNCWebView.Commands.goBack,
112
       UIManager.RNCWebView.Commands.goBack,
114
     );
114
     );
115
   };
115
   };
116
 
116
 
117
-  reload = (): void => {
117
+  reload = () => {
118
     this.setState({
118
     this.setState({
119
       viewState: WebViewState.LOADING,
119
       viewState: WebViewState.LOADING,
120
     });
120
     });
125
     );
125
     );
126
   };
126
   };
127
 
127
 
128
-  stopLoading = (): void => {
128
+  stopLoading = () => {
129
     UIManager.dispatchViewManagerCommand(
129
     UIManager.dispatchViewManagerCommand(
130
       this.getWebViewHandle(),
130
       this.getWebViewHandle(),
131
       UIManager.RNCWebView.Commands.stopLoading,
131
       UIManager.RNCWebView.Commands.stopLoading,
133
     );
133
     );
134
   };
134
   };
135
 
135
 
136
-  postMessage = (data: string): void => {
136
+  postMessage = (data: string) => {
137
     UIManager.dispatchViewManagerCommand(
137
     UIManager.dispatchViewManagerCommand(
138
       this.getWebViewHandle(),
138
       this.getWebViewHandle(),
139
       UIManager.RNCWebView.Commands.postMessage,
139
       UIManager.RNCWebView.Commands.postMessage,
147
    * on pages with a Content Security Policy that disallows eval(). If you need that
147
    * on pages with a Content Security Policy that disallows eval(). If you need that
148
    * functionality, look into postMessage/onMessage.
148
    * functionality, look into postMessage/onMessage.
149
    */
149
    */
150
-  injectJavaScript = (data: string): void => {
150
+  injectJavaScript = (data: string) => {
151
     UIManager.dispatchViewManagerCommand(
151
     UIManager.dispatchViewManagerCommand(
152
       this.getWebViewHandle(),
152
       this.getWebViewHandle(),
153
       UIManager.RNCWebView.Commands.injectJavaScript,
153
       UIManager.RNCWebView.Commands.injectJavaScript,
159
    * We return an event with a bunch of fields including:
159
    * We return an event with a bunch of fields including:
160
    *  url, title, loading, canGoBack, canGoForward
160
    *  url, title, loading, canGoBack, canGoForward
161
    */
161
    */
162
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
162
+  updateNavigationState = (event: WebViewNavigationEvent) => {
163
     if (this.props.onNavigationStateChange) {
163
     if (this.props.onNavigationStateChange) {
164
       this.props.onNavigationStateChange(event.nativeEvent);
164
       this.props.onNavigationStateChange(event.nativeEvent);
165
     }
165
     }
168
   getWebViewHandle = (): number | null =>
168
   getWebViewHandle = (): number | null =>
169
     findNodeHandle(this.webViewRef.current);
169
     findNodeHandle(this.webViewRef.current);
170
 
170
 
171
-  onLoadingStart = (event: WebViewNavigationEvent): void => {
171
+  onLoadingStart = (event: WebViewNavigationEvent) => {
172
     const { onLoadStart } = this.props;
172
     const { onLoadStart } = this.props;
173
     if (onLoadStart) {
173
     if (onLoadStart) {
174
       onLoadStart(event);
174
       onLoadStart(event);
176
     this.updateNavigationState(event);
176
     this.updateNavigationState(event);
177
   };
177
   };
178
 
178
 
179
-  onLoadingError = (event: WebViewErrorEvent): void => {
179
+  onLoadingError = (event: WebViewErrorEvent) => {
180
     event.persist(); // persist this event because we need to store it
180
     event.persist(); // persist this event because we need to store it
181
     const { onError, onLoadEnd } = this.props;
181
     const { onError, onLoadEnd } = this.props;
182
     if (onError) {
182
     if (onError) {
187
     }
187
     }
188
     // eslint-disable-next-line no-console
188
     // eslint-disable-next-line no-console
189
     console.warn('Encountered an error loading page', event.nativeEvent);
189
     console.warn('Encountered an error loading page', event.nativeEvent);
190
-
191
     this.setState({
190
     this.setState({
192
       lastErrorEvent: event.nativeEvent,
191
       lastErrorEvent: event.nativeEvent,
193
       viewState: WebViewState.ERROR,
192
       viewState: WebViewState.ERROR,
194
     });
193
     });
195
   };
194
   };
196
 
195
 
197
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
196
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
198
     const { onLoad, onLoadEnd } = this.props;
197
     const { onLoad, onLoadEnd } = this.props;
199
     if (onLoad) {
198
     if (onLoad) {
200
       onLoad(event);
199
       onLoad(event);
208
     this.updateNavigationState(event);
207
     this.updateNavigationState(event);
209
   };
208
   };
210
 
209
 
211
-  onMessage = (event: WebViewMessageEvent): void => {
210
+  onMessage = (event: WebViewMessageEvent) => {
212
     const { onMessage } = this.props;
211
     const { onMessage } = this.props;
213
     if (onMessage) {
212
     if (onMessage) {
214
       onMessage(event);
213
       onMessage(event);
215
     }
214
     }
216
   };
215
   };
217
 
216
 
218
-  onLoadingProgress = (
219
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
220
-  ): void => {
217
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
221
     const { onLoadProgress } = this.props;
218
     const { onLoadProgress } = this.props;
222
     if (onLoadProgress) {
219
     if (onLoadProgress) {
223
       onLoadProgress(event);
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
     let otherView = null;
238
     let otherView = null;
229
 
239
 
230
     if (this.state.viewState === WebViewState.LOADING) {
240
     if (this.state.viewState === WebViewState.LOADING) {
243
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
253
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
244
       }
254
       }
245
     } else if (this.state.viewState !== WebViewState.IDLE) {
255
     } else if (this.state.viewState !== WebViewState.IDLE) {
246
-      // eslint-disable-next-line no-console
247
       console.error(
256
       console.error(
248
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
257
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
249
       );
258
       );
267
 
276
 
268
     if (isWebViewUriSource(source)) {
277
     if (isWebViewUriSource(source)) {
269
       if (source.method === 'POST' && source.headers) {
278
       if (source.method === 'POST' && source.headers) {
270
-        // eslint-disable-next-line no-console
271
         console.warn(
279
         console.warn(
272
           'WebView: `source.headers` is not supported when using POST.',
280
           'WebView: `source.headers` is not supported when using POST.',
273
         );
281
         );
274
       } else if (source.method === 'GET' && source.body) {
282
       } else if (source.method === 'GET' && source.body) {
275
-        // eslint-disable-next-line no-console
276
         console.warn('WebView: `source.body` is not supported when using GET.');
283
         console.warn('WebView: `source.body` is not supported when using GET.');
277
       }
284
       }
278
     }
285
     }
279
 
286
 
280
     const nativeConfig = this.props.nativeConfig || {};
287
     const nativeConfig = this.props.nativeConfig || {};
281
 
288
 
282
-    const originWhitelist = (this.props.originWhitelist || []).map(
283
-      WebViewShared.originWhitelistToRegex,
284
-    );
285
-
286
     const NativeWebView = nativeConfig.component || RNCWebView;
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
     const webView = (
297
     const webView = (
289
       <NativeWebView
298
       <NativeWebView
290
         ref={this.webViewRef}
299
         ref={this.webViewRef}
305
         automaticallyAdjustContentInsets={
314
         automaticallyAdjustContentInsets={
306
           this.props.automaticallyAdjustContentInsets
315
           this.props.automaticallyAdjustContentInsets
307
         }
316
         }
317
+        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
308
         onContentSizeChange={this.props.onContentSizeChange}
318
         onContentSizeChange={this.props.onContentSizeChange}
309
         onLoadingStart={this.onLoadingStart}
319
         onLoadingStart={this.onLoadingStart}
310
         onLoadingFinish={this.onLoadingFinish}
320
         onLoadingFinish={this.onLoadingFinish}
318
         allowUniversalAccessFromFileURLs={
328
         allowUniversalAccessFromFileURLs={
319
           this.props.allowUniversalAccessFromFileURLs
329
           this.props.allowUniversalAccessFromFileURLs
320
         }
330
         }
321
-        originWhitelist={originWhitelist}
322
         mixedContentMode={this.props.mixedContentMode}
331
         mixedContentMode={this.props.mixedContentMode}
323
         saveFormDataDisabled={this.props.saveFormDataDisabled}
332
         saveFormDataDisabled={this.props.saveFormDataDisabled}
324
         urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
333
         urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
334
     );
343
     );
335
   }
344
   }
336
 }
345
 }
346
+
347
+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
 enum WebViewState {
90
 enum WebViewState {
87
   IDLE = 'IDLE',
91
   IDLE = 'IDLE',
88
   LOADING = 'LOADING',
92
   LOADING = 'LOADING',
105
   lastErrorEvent: WebViewError | null;
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
   <View style={styles.loadingView}>
125
   <View style={styles.loadingView}>
110
     <ActivityIndicator />
126
     <ActivityIndicator />
111
   </View>
127
   </View>
114
   errorDomain: string | undefined,
130
   errorDomain: string | undefined,
115
   errorCode: number,
131
   errorCode: number,
116
   errorDesc: string,
132
   errorDesc: string,
117
-): React.ReactNode => (
133
+) => (
118
   <View style={styles.errorContainer}>
134
   <View style={styles.errorContainer}>
119
     <Text style={styles.errorTextTitle}>Error loading page</Text>
135
     <Text style={styles.errorTextTitle}>Error loading page</Text>
120
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
136
     <Text style={styles.errorText}>{`Domain: ${errorDomain}`}</Text>
123
   </View>
139
   </View>
124
 );
140
 );
125
 
141
 
126
-const RNCUIWebView = requireNativeComponent('RNCUIWebView');
127
-const RNCWKWebView = requireNativeComponent('RNCWKWebView');
128
-
129
 /**
142
 /**
130
  * `WebView` renders web content in a native view.
143
  * `WebView` renders web content in a native view.
131
  *
144
  *
148
  * You can use this component to navigate back and forth in the web view's
161
  * You can use this component to navigate back and forth in the web view's
149
  * history and configure various properties for the web content.
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
   static JSNavigationScheme = JSNavigationScheme;
165
   static JSNavigationScheme = JSNavigationScheme;
156
 
166
 
157
   static NavigationType = NavigationType;
167
   static NavigationType = NavigationType;
158
 
168
 
159
   static defaultProps = {
169
   static defaultProps = {
160
     useWebKit: true,
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
     // no native implementation for iOS, depends only on permissions
175
     // no native implementation for iOS, depends only on permissions
166
     true;
176
     true;
167
 
177
 
175
   webViewRef = React.createRef<React.ComponentClass>();
185
   webViewRef = React.createRef<React.ComponentClass>();
176
 
186
 
177
   // eslint-disable-next-line camelcase, react/sort-comp
187
   // eslint-disable-next-line camelcase, react/sort-comp
178
-  UNSAFE_componentWillMount(): void {
188
+  UNSAFE_componentWillMount() {
179
     if (
189
     if (
180
       this.props.useWebKit === true
190
       this.props.useWebKit === true
181
       && this.props.scalesPageToFit !== undefined
191
       && this.props.scalesPageToFit !== undefined
182
     ) {
192
     ) {
183
-      // eslint-disable-next-line no-console
184
       console.warn(
193
       console.warn(
185
         'The scalesPageToFit property is not supported when useWebKit = true',
194
         'The scalesPageToFit property is not supported when useWebKit = true',
186
       );
195
       );
189
       !this.props.useWebKit
198
       !this.props.useWebKit
190
       && this.props.allowsBackForwardNavigationGestures
199
       && this.props.allowsBackForwardNavigationGestures
191
     ) {
200
     ) {
192
-      // eslint-disable-next-line no-console
193
       console.warn(
201
       console.warn(
194
         'The allowsBackForwardNavigationGestures property is not supported when useWebKit = false',
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
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
208
     if (!(prevProps.useWebKit && this.props.useWebKit)) {
201
       return;
209
       return;
202
     }
210
     }
206
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
214
     this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
207
 
215
 
208
     if (this.props.scalesPageToFit !== undefined) {
216
     if (this.props.scalesPageToFit !== undefined) {
209
-      // eslint-disable-next-line no-console
210
       console.warn(
217
       console.warn(
211
         'The scalesPageToFit property is not supported when useWebKit = true',
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
    * Go forward one page in the web view's history.
232
    * Go forward one page in the web view's history.
218
    */
233
    */
219
-  goForward = (): void => {
234
+  goForward = () => {
220
     UIManager.dispatchViewManagerCommand(
235
     UIManager.dispatchViewManagerCommand(
221
       this.getWebViewHandle(),
236
       this.getWebViewHandle(),
222
       this.getCommands().goForward,
237
       this.getCommands().goForward,
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
    * Go back one page in the web view's history.
243
    * Go back one page in the web view's history.
244
    */
244
    */
245
-  goBack = (): void => {
245
+  goBack = () => {
246
     UIManager.dispatchViewManagerCommand(
246
     UIManager.dispatchViewManagerCommand(
247
       this.getWebViewHandle(),
247
       this.getWebViewHandle(),
248
       this.getCommands().goBack,
248
       this.getCommands().goBack,
253
   /**
253
   /**
254
    * Reloads the current page.
254
    * Reloads the current page.
255
    */
255
    */
256
-  reload = (): void => {
256
+  reload = () => {
257
     this.setState({ viewState: WebViewState.LOADING });
257
     this.setState({ viewState: WebViewState.LOADING });
258
     UIManager.dispatchViewManagerCommand(
258
     UIManager.dispatchViewManagerCommand(
259
       this.getWebViewHandle(),
259
       this.getWebViewHandle(),
265
   /**
265
   /**
266
    * Stop loading the current page.
266
    * Stop loading the current page.
267
    */
267
    */
268
-  stopLoading = (): void => {
268
+  stopLoading = () => {
269
     UIManager.dispatchViewManagerCommand(
269
     UIManager.dispatchViewManagerCommand(
270
       this.getWebViewHandle(),
270
       this.getWebViewHandle(),
271
       this.getCommands().stopLoading,
271
       this.getCommands().stopLoading,
283
    * document.addEventListener('message', e => { document.title = e.data; });
283
    * document.addEventListener('message', e => { document.title = e.data; });
284
    * ```
284
    * ```
285
    */
285
    */
286
-  postMessage = (data: string): void => {
286
+  postMessage = (data: string) => {
287
     UIManager.dispatchViewManagerCommand(
287
     UIManager.dispatchViewManagerCommand(
288
       this.getWebViewHandle(),
288
       this.getWebViewHandle(),
289
       this.getCommands().postMessage,
289
       this.getCommands().postMessage,
297
    * on pages with a Content Security Policy that disallows eval(). If you need that
297
    * on pages with a Content Security Policy that disallows eval(). If you need that
298
    * functionality, look into postMessage/onMessage.
298
    * functionality, look into postMessage/onMessage.
299
    */
299
    */
300
-  injectJavaScript = (data: string): void => {
300
+  injectJavaScript = (data: string) => {
301
     UIManager.dispatchViewManagerCommand(
301
     UIManager.dispatchViewManagerCommand(
302
       this.getWebViewHandle(),
302
       this.getWebViewHandle(),
303
       this.getCommands().injectJavaScript,
303
       this.getCommands().injectJavaScript,
309
    * We return an event with a bunch of fields including:
309
    * We return an event with a bunch of fields including:
310
    *  url, title, loading, canGoBack, canGoForward
310
    *  url, title, loading, canGoBack, canGoForward
311
    */
311
    */
312
-  updateNavigationState = (event: WebViewNavigationEvent): void => {
312
+  updateNavigationState = (event: WebViewNavigationEvent) => {
313
     if (this.props.onNavigationStateChange) {
313
     if (this.props.onNavigationStateChange) {
314
       this.props.onNavigationStateChange(event.nativeEvent);
314
       this.props.onNavigationStateChange(event.nativeEvent);
315
     }
315
     }
318
   /**
318
   /**
319
    * Returns the native `WebView` node.
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
     const { onLoadStart } = this.props;
324
     const { onLoadStart } = this.props;
326
     if (onLoadStart) {
325
     if (onLoadStart) {
327
       onLoadStart(event);
326
       onLoadStart(event);
329
     this.updateNavigationState(event);
328
     this.updateNavigationState(event);
330
   };
329
   };
331
 
330
 
332
-  onLoadingError = (event: WebViewErrorEvent): void => {
331
+  onLoadingError = (event: WebViewErrorEvent) => {
333
     event.persist(); // persist this event because we need to store it
332
     event.persist(); // persist this event because we need to store it
334
     const { onError, onLoadEnd } = this.props;
333
     const { onError, onLoadEnd } = this.props;
335
     if (onError) {
334
     if (onError) {
340
     }
339
     }
341
     // eslint-disable-next-line no-console
340
     // eslint-disable-next-line no-console
342
     console.warn('Encountered an error loading page', event.nativeEvent);
341
     console.warn('Encountered an error loading page', event.nativeEvent);
343
-
344
     this.setState({
342
     this.setState({
345
       lastErrorEvent: event.nativeEvent,
343
       lastErrorEvent: event.nativeEvent,
346
       viewState: WebViewState.ERROR,
344
       viewState: WebViewState.ERROR,
347
     });
345
     });
348
   };
346
   };
349
 
347
 
350
-  onLoadingFinish = (event: WebViewNavigationEvent): void => {
348
+  onLoadingFinish = (event: WebViewNavigationEvent) => {
351
     const { onLoad, onLoadEnd } = this.props;
349
     const { onLoad, onLoadEnd } = this.props;
352
     if (onLoad) {
350
     if (onLoad) {
353
       onLoad(event);
351
       onLoad(event);
361
     this.updateNavigationState(event);
359
     this.updateNavigationState(event);
362
   };
360
   };
363
 
361
 
364
-  onMessage = (event: WebViewMessageEvent): void => {
362
+  onMessage = (event: WebViewMessageEvent) => {
365
     const { onMessage } = this.props;
363
     const { onMessage } = this.props;
366
     if (onMessage) {
364
     if (onMessage) {
367
       onMessage(event);
365
       onMessage(event);
368
     }
366
     }
369
   };
367
   };
370
 
368
 
371
-  onLoadingProgress = (
372
-    event: NativeSyntheticEvent<WebViewProgressEvent>,
373
-  ): void => {
369
+  onLoadingProgress = (event: NativeSyntheticEvent<WebViewProgressEvent>) => {
374
     const { onLoadProgress } = this.props;
370
     const { onLoadProgress } = this.props;
375
     if (onLoadProgress) {
371
     if (onLoadProgress) {
376
       onLoadProgress(event);
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
     prevProps: WebViewSharedProps,
395
     prevProps: WebViewSharedProps,
382
-    propName:
383
-      | 'allowsInlineMediaPlayback'
384
-      | 'mediaPlaybackRequiresUserAction'
385
-      | 'dataDetectorTypes',
386
-  ): void {
396
+    propName: keyof WebViewSharedProps,
397
+  ) => {
387
     if (this.props[propName] !== prevProps[propName]) {
398
     if (this.props[propName] !== prevProps[propName]) {
388
-      // eslint-disable-next-line no-console
389
       console.error(
399
       console.error(
390
         `Changes to property ${propName} do nothing after the initial render.`,
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
     let otherView = null;
406
     let otherView = null;
397
 
407
 
398
     let scalesPageToFit;
408
     let scalesPageToFit;
417
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
427
         invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
418
       }
428
       }
419
     } else if (this.state.viewState !== WebViewState.IDLE) {
429
     } else if (this.state.viewState !== WebViewState.IDLE) {
420
-      // eslint-disable-next-line no-console
421
       console.error(
430
       console.error(
422
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
431
         `RNCWebView invalid state encountered: ${this.state.viewState}`,
423
       );
432
       );
434
 
443
 
435
     const nativeConfig = this.props.nativeConfig || {};
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
     const decelerationRate = processDecelerationRate(
452
     const decelerationRate = processDecelerationRate(
475
       this.props.decelerationRate,
453
       this.props.decelerationRate,
484
 
462
 
485
     const messagingEnabled = typeof this.props.onMessage === 'function';
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
     const webView = (
473
     const webView = (
492
       <NativeWebView
474
       <NativeWebView
534
     );
516
     );
535
   }
517
   }
536
 }
518
 }
519
+
520
+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"