Browse Source

Merge branch 'master' into sr_injectedjs_startup_android

Salvatore Randazzo 4 years ago
parent
commit
8a00786dd0
No account linked to committer's email address

+ 2
- 0
README.md View File

@@ -34,6 +34,8 @@ This project follows [semantic versioning](https://semver.org/). We do not hesit
34 34
 
35 35
 Current Version: ![version](https://img.shields.io/npm/v/react-native-webview.svg)
36 36
 
37
+- [8.0.0](https://github.com/react-native-community/react-native-webview/releases/tag/v8.0.0) - onNavigationStateChange now triggers with hash url changes
38
+
37 39
 - [7.0.1](https://github.com/react-native-community/react-native-webview/releases/tag/v7.0.1) - Removed UIWebView
38 40
 
39 41
 - [6.0.**2**](https://github.com/react-native-community/react-native-webview/releases/tag/v6.0.2) - Update to AndroidX. Make sure to enable it in your project's `android/gradle.properties`. See [Getting Started Guide](docs/Getting-Started.md).

+ 1
- 1
android/gradle.properties View File

@@ -1,4 +1,4 @@
1
-ReactNativeWebView_kotlinVersion=1.3.11
1
+ReactNativeWebView_kotlinVersion=1.3.50
2 2
 ReactNativeWebView_compileSdkVersion=28
3 3
 ReactNativeWebView_buildToolsVersion=28.0.3
4 4
 ReactNativeWebView_targetSdkVersion=28

+ 11
- 1
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

@@ -647,6 +647,11 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
647 647
     if (mAllowsFullscreenVideo) {
648 648
       int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
649 649
       mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
650
+        @Override
651
+        public Bitmap getDefaultVideoPoster() {
652
+          return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
653
+        }
654
+        
650 655
         @Override
651 656
         public void onShowCustomView(View view, CustomViewCallback callback) {
652 657
           if (mVideoView != null) {
@@ -699,7 +704,12 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
699 704
       if (mWebChromeClient != null) {
700 705
         mWebChromeClient.onHideCustomView();
701 706
       }
702
-      mWebChromeClient = new RNCWebChromeClient(reactContext, webView);
707
+      mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
708
+        @Override
709
+        public Bitmap getDefaultVideoPoster() {
710
+          return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
711
+        }
712
+      };
703 713
       webView.setWebChromeClient(mWebChromeClient);
704 714
     }
705 715
   }

+ 33
- 38
docs/Guide.md View File

@@ -149,44 +149,6 @@ class MyWeb extends Component {
149 149
 }
150 150
 ```
151 151
 
152
-#### Intercepting hash URL changes
153
-
154
-While `onNavigationStateChange` will trigger on URL changes, it does not trigger when only the hash URL ("anchor") changes, e.g. from `https://example.com/users#list` to `https://example.com/users#help`.
155
-
156
-You can inject some JavaScript to wrap the history functions in order to intercept these hash URL changes.
157
-
158
-```jsx
159
-<WebView
160
-  source={{ uri: someURI }}
161
-  injectedJavaScript={`
162
-    (function() {
163
-      function wrap(fn) {
164
-        return function wrapper() {
165
-          var res = fn.apply(this, arguments);
166
-          window.ReactNativeWebView.postMessage('navigationStateChange');
167
-          return res;
168
-        }
169
-      }
170
-
171
-      history.pushState = wrap(history.pushState);
172
-      history.replaceState = wrap(history.replaceState);
173
-      window.addEventListener('popstate', function() {
174
-        window.ReactNativeWebView.postMessage('navigationStateChange');
175
-      });
176
-    })();
177
-
178
-    true;
179
-  `}
180
-  onMessage={({ nativeEvent: state }) => {
181
-    if (state.data === 'navigationStateChange') {
182
-      // Navigation state updated, can check state.canGoBack, etc.
183
-    }
184
-  }}
185
-/>
186
-```
187
-
188
-Thanks to [Janic Duplessis](https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-483956651) for this workaround.
189
-
190 152
 ### Add support for File Upload
191 153
 
192 154
 ##### iOS
@@ -336,6 +298,39 @@ _Under the hood_
336 298
 > On iOS, `injectedJavaScript` runs a method on WebView called `evaluateJavaScript:completionHandler:`
337 299
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
338 300
 
301
+
302
+#### The `injectedJavaScriptBeforeContentLoaded` prop
303
+
304
+This is a script that runs **before** the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away. This is useful if you want to inject anything into the window, localstorage, or document prior to the web code executing. 
305
+
306
+```jsx
307
+import React, { Component } from 'react';
308
+import { View } from 'react-native';
309
+import { WebView } from 'react-native-webview';
310
+
311
+export default class App extends Component {
312
+  render() {
313
+    const runFirst = `
314
+      window.isNativeApp = true;
315
+      true; // note: this is required, or you'll sometimes get silent failures
316
+    `;
317
+    return (
318
+      <View style={{ flex: 1 }}>
319
+        <WebView
320
+          source={{
321
+            uri:
322
+              'https://github.com/react-native-community/react-native-webview',
323
+          }}
324
+          injectedJavaScriptBeforeContentLoaded={runFirst}
325
+        />
326
+      </View>
327
+    );
328
+  }
329
+}
330
+```
331
+
332
+This runs the JavaScript in the `runFirst` string before the page is loaded. In this case, the value of `window.isNativeApp` will be set to true before the web code executes. 
333
+
339 334
 #### The `injectJavaScript` method
340 335
 
341 336
 While convenient, the downside to the previously mentioned `injectedJavaScript` prop is that it only runs once. That's why we also expose a method on the webview ref called `injectJavaScript` (note the slightly different name!).

+ 37
- 7
docs/Reference.md View File

@@ -7,6 +7,7 @@ This document lays out the current public properties and methods for the React N
7 7
 - [`source`](Reference.md#source)
8 8
 - [`automaticallyAdjustContentInsets`](Reference.md#automaticallyadjustcontentinsets)
9 9
 - [`injectedJavaScript`](Reference.md#injectedjavascript)
10
+- [`injectedJavaScriptBeforeContentLoaded`](Reference.md#injectedJavaScriptBeforeContentLoaded)
10 11
 - [`mediaPlaybackRequiresUserAction`](Reference.md#mediaplaybackrequiresuseraction)
11 12
 - [`nativeConfig`](Reference.md#nativeconfig)
12 13
 - [`onError`](Reference.md#onerror)
@@ -144,6 +145,35 @@ const INJECTED_JAVASCRIPT = `(function() {
144 145
 
145 146
 ---
146 147
 
148
+### `injectedJavaScriptBeforeContentLoaded`
149
+
150
+Set this to provide JavaScript that will be injected into the web page after the document element is created, but before any other content is loaded. Make sure the string evaluates to a valid type (`true` works) and doesn't otherwise throw an exception.
151
+On iOS, see [WKUserScriptInjectionTimeAtDocumentStart](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentstart?language=objc)
152
+
153
+| Type   | Required |
154
+| ------ | -------- |
155
+| string | No       |
156
+
157
+To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
158
+
159
+Example:
160
+
161
+Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage)
162
+
163
+```jsx
164
+const INJECTED_JAVASCRIPT = `(function() {
165
+    window.ReactNativeWebView.postMessage(JSON.stringify(window.location));
166
+})();`;
167
+
168
+<WebView
169
+  source={{ uri: 'https://facebook.github.io/react-native' }}
170
+  injectedJavaScriptBeforeContentLoaded={INJECTED_JAVASCRIPT}
171
+  onMessage={this.onMessage}
172
+/>;
173
+```
174
+
175
+---
176
+
147 177
 ### `mediaPlaybackRequiresUserAction`
148 178
 
149 179
 Boolean that determines whether HTML5 audio and video requires the user to tap them before they start playing. The default value is `true`. (Android API minimum version 17).
@@ -656,11 +686,11 @@ Boolean value to control whether DOM Storage is enabled. Used only in Android.
656 686
 
657 687
 ### `javaScriptEnabled`
658 688
 
659
-Boolean value to enable JavaScript in the `WebView`. Used on Android only as JavaScript is enabled by default on iOS. The default value is `true`.
689
+Boolean value to enable JavaScript in the `WebView`. The default value is `true`.
660 690
 
661
-| Type | Required | Platform |
662
-| ---- | -------- | -------- |
663
-| bool | No       | Android  |
691
+| Type | Required |
692
+| ---- | -------- |
693
+| bool | No       |
664 694
 
665 695
 ---
666 696
 
@@ -884,9 +914,9 @@ Set whether Geolocation is enabled in the `WebView`. The default value is `false
884 914
 
885 915
 Boolean that sets whether JavaScript running in the context of a file scheme URL should be allowed to access content from other file scheme URLs. The default value is `false`.
886 916
 
887
-| Type | Required | Platform |
888
-| ---- | -------- | -------- |
889
-| bool | No       | Android  |
917
+| Type | Required |
918
+| ---- | -------- |
919
+| bool | No       |
890 920
 
891 921
 ---
892 922
 

+ 7
- 0
ios/RNCWebView.h View File

@@ -19,12 +19,18 @@
19 19
 
20 20
 @end
21 21
 
22
+@interface RNCWeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
23
+@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
24
+- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
25
+@end
26
+
22 27
 @interface RNCWebView : RCTView
23 28
 
24 29
 @property (nonatomic, weak) id<RNCWebViewDelegate> _Nullable delegate;
25 30
 @property (nonatomic, copy) NSDictionary * _Nullable source;
26 31
 @property (nonatomic, assign) BOOL messagingEnabled;
27 32
 @property (nonatomic, copy) NSString * _Nullable injectedJavaScript;
33
+@property (nonatomic, copy) NSString * _Nullable injectedJavaScriptBeforeContentLoaded;
28 34
 @property (nonatomic, assign) BOOL scrollEnabled;
29 35
 @property (nonatomic, assign) BOOL sharedCookiesEnabled;
30 36
 @property (nonatomic, assign) BOOL pagingEnabled;
@@ -46,6 +52,7 @@
46 52
 @property (nonatomic, copy) NSString * _Nullable applicationNameForUserAgent;
47 53
 @property (nonatomic, assign) BOOL cacheEnabled;
48 54
 @property (nonatomic, assign) BOOL javaScriptEnabled;
55
+@property (nonatomic, assign) BOOL allowFileAccessFromFileURLs;
49 56
 @property (nonatomic, assign) BOOL allowsLinkPreview;
50 57
 @property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
51 58
 @property (nonatomic, assign) BOOL showsVerticalScrollIndicator;

+ 84
- 17
ios/RNCWebView.m View File

@@ -14,6 +14,7 @@
14 14
 #import "objc/runtime.h"
15 15
 
16 16
 static NSTimer *keyboardTimer;
17
+static NSString *const HistoryShimName = @"ReactNativeHistoryShim";
17 18
 static NSString *const MessageHandlerName = @"ReactNativeWebView";
18 19
 static NSURLCredential* clientAuthenticationCredential;
19 20
 static NSDictionary* customCertificatesForHost;
@@ -138,8 +139,16 @@ static NSDictionary* customCertificatesForHost;
138 139
   if (self.window != nil && _webView == nil) {
139 140
     WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
140 141
     WKPreferences *prefs = [[WKPreferences alloc]init];
142
+    BOOL _prefsUsed = NO;
141 143
     if (!_javaScriptEnabled) {
142 144
       prefs.javaScriptEnabled = NO;
145
+      _prefsUsed = YES;
146
+    }
147
+    if (_allowFileAccessFromFileURLs) {
148
+      [prefs setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
149
+      _prefsUsed = YES;
150
+    }
151
+    if (_prefsUsed) {
143 152
       wkWebViewConfig.preferences = prefs;
144 153
     }
145 154
     if (_incognito) {
@@ -152,8 +161,35 @@ static NSDictionary* customCertificatesForHost;
152 161
     }
153 162
     wkWebViewConfig.userContentController = [WKUserContentController new];
154 163
 
164
+    // Shim the HTML5 history API:
165
+    [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self]
166
+                                                              name:HistoryShimName];
167
+    NSString *source = [NSString stringWithFormat:
168
+      @"(function(history) {\n"
169
+      "  function notify(type) {\n"
170
+      "    setTimeout(function() {\n"
171
+      "      window.webkit.messageHandlers.%@.postMessage(type)\n"
172
+      "    }, 0)\n"
173
+      "  }\n"
174
+      "  function shim(f) {\n"
175
+      "    return function pushState() {\n"
176
+      "      notify('other')\n"
177
+      "      return f.apply(history, arguments)\n"
178
+      "    }\n"
179
+      "  }\n"
180
+      "  history.pushState = shim(history.pushState)\n"
181
+      "  history.replaceState = shim(history.replaceState)\n"
182
+      "  window.addEventListener('popstate', function() {\n"
183
+      "    notify('backforward')\n"
184
+      "  })\n"
185
+      "})(window.history)\n", HistoryShimName
186
+    ];
187
+    WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
188
+    [wkWebViewConfig.userContentController addUserScript:script];
189
+
155 190
     if (_messagingEnabled) {
156
-      [wkWebViewConfig.userContentController addScriptMessageHandler:self name:MessageHandlerName];
191
+      [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self]
192
+                                                                name:MessageHandlerName];
157 193
 
158 194
       NSString *source = [NSString stringWithFormat:
159 195
         @"window.%@ = {"
@@ -165,6 +201,12 @@ static NSDictionary* customCertificatesForHost;
165 201
 
166 202
       WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
167 203
       [wkWebViewConfig.userContentController addUserScript:script];
204
+        
205
+      if (_injectedJavaScriptBeforeContentLoaded) {
206
+        // If user has provided an injectedJavascript prop, execute it at the start of the document
207
+        WKUserScript *injectedScript = [[WKUserScript alloc] initWithSource:_injectedJavaScriptBeforeContentLoaded injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
208
+        [wkWebViewConfig.userContentController addUserScript:injectedScript];
209
+      }
168 210
     }
169 211
 
170 212
     wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
@@ -390,10 +432,18 @@ static NSDictionary* customCertificatesForHost;
390 432
 - (void)userContentController:(WKUserContentController *)userContentController
391 433
        didReceiveScriptMessage:(WKScriptMessage *)message
392 434
 {
393
-  if (_onMessage != nil) {
394
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
395
-    [event addEntriesFromDictionary: @{@"data": message.body}];
396
-    _onMessage(event);
435
+  if ([message.name isEqualToString:HistoryShimName]) {
436
+    if (_onLoadingFinish) {
437
+      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
438
+      [event addEntriesFromDictionary: @{@"navigationType": message.body}];
439
+      _onLoadingFinish(event);
440
+    }
441
+  } else if ([message.name isEqualToString:MessageHandlerName]) {
442
+    if (_onMessage) {
443
+      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
444
+      [event addEntriesFromDictionary: @{@"data": message.body}];
445
+      _onMessage(event);
446
+    }
397 447
   }
398 448
 }
399 449
 
@@ -914,7 +964,7 @@ static NSDictionary* customCertificatesForHost;
914 964
       return;
915 965
     }
916 966
 
917
-    if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) {
967
+    if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102 || [error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 101) {
918 968
       // Error code 102 "Frame load interrupted" is raised by the WKWebView
919 969
       // when the URL is from an http redirect. This is a common pattern when
920 970
       // implementing OAuth with a WebView.
@@ -949,19 +999,19 @@ static NSDictionary* customCertificatesForHost;
949 999
  * Called when the navigation is complete.
950 1000
  * @see https://fburl.com/rtys6jlb
951 1001
  */
952
-- (void)      webView:(WKWebView *)webView
1002
+- (void)webView:(WKWebView *)webView
953 1003
   didFinishNavigation:(WKNavigation *)navigation
954 1004
 {
955
-  if (_injectedJavaScript) {
956
-    [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
957
-      NSMutableDictionary *event = [self baseEvent];
958
-      event[@"jsEvaluationValue"] = jsEvaluationValue;
959
-
960
-      if (self.onLoadingFinish) {
961
-        self.onLoadingFinish(event);
962
-      }
963
-    }];
964
-  } else if (_onLoadingFinish) {
1005
+   if (_injectedJavaScript) {
1006
+     [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
1007
+       NSMutableDictionary *event = [self baseEvent];
1008
+       event[@"jsEvaluationValue"] = jsEvaluationValue;
1009
+
1010
+       if (self.onLoadingFinish) {
1011
+         self.onLoadingFinish(event);
1012
+       }
1013
+     }];
1014
+   } else if (_onLoadingFinish) {
965 1015
     _onLoadingFinish([self baseEvent]);
966 1016
   }
967 1017
 }
@@ -1029,3 +1079,20 @@ static NSDictionary* customCertificatesForHost;
1029 1079
 }
1030 1080
 
1031 1081
 @end
1082
+
1083
+@implementation RNCWeakScriptMessageDelegate
1084
+
1085
+- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
1086
+    self = [super init];
1087
+    if (self) {
1088
+        _scriptDelegate = scriptDelegate;
1089
+    }
1090
+    return self;
1091
+}
1092
+
1093
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
1094
+    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
1095
+}
1096
+
1097
+@end
1098
+

+ 2
- 0
ios/RNCWebViewManager.m View File

@@ -51,7 +51,9 @@ RCT_EXPORT_VIEW_PROPERTY(onHttpError, RCTDirectEventBlock)
51 51
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
52 52
 RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock)
53 53
 RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
54
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString)
54 55
 RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL)
56
+RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL)
55 57
 RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL)
56 58
 RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL)
57 59
 #if WEBKIT_IOS_10_APIS_AVAILABLE

+ 1
- 1
package.json View File

@@ -8,7 +8,7 @@
8 8
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
9 9
   ],
10 10
   "license": "MIT",
11
-  "version": "7.5.1",
11
+  "version": "8.0.2",
12 12
   "homepage": "https://github.com/react-native-community/react-native-webview#readme",
13 13
   "scripts": {
14 14
     "ci": "CI=true && yarn lint && yarn test",

+ 9
- 2
src/WebViewTypes.ts View File

@@ -20,7 +20,7 @@ type AndroidWebViewCommands = 'clearHistory' | 'clearCache' | 'clearFormData';
20 20
 
21 21
 interface RNCWebViewUIManager<Commands extends string> extends UIManagerStatic {
22 22
   getViewManagerConfig: (
23
-      name: string,
23
+    name: string,
24 24
   ) => {
25 25
     Commands: {[key in Commands]: number};
26 26
   };
@@ -216,6 +216,7 @@ export interface CommonNativeWebViewProps extends ViewProps {
216 216
   cacheEnabled?: boolean;
217 217
   incognito?: boolean;
218 218
   injectedJavaScript?: string;
219
+  injectedJavaScriptBeforeContentLoaded?: string;
219 220
   mediaPlaybackRequiresUserAction?: boolean;
220 221
   messagingEnabled: boolean;
221 222
   onScroll?: (event: NativeScrollEvent) => void;
@@ -501,7 +502,7 @@ export interface AndroidWebViewProps extends WebViewSharedProps {
501 502
    */
502 503
   geolocationEnabled?: boolean;
503 504
 
504
-
505
+  
505 506
   /**
506 507
    * Boolean that sets whether JavaScript running in the context of a file
507 508
    * scheme URL should be allowed to access content from other file scheme URLs.
@@ -685,6 +686,12 @@ export interface WebViewSharedProps extends ViewProps {
685 686
    */
686 687
   injectedJavaScript?: string;
687 688
 
689
+  /**
690
+   * Set this to provide JavaScript that will be injected into the web page
691
+   * once the webview is initialized but before the view loads any content.
692
+   */
693
+  injectedJavaScriptBeforeContentLoaded?: string;
694
+
688 695
   /**
689 696
    * Boolean value that determines whether a horizontal scroll indicator is
690 697
    * shown in the `WebView`. The default value is `true`.

+ 321
- 233
yarn.lock
File diff suppressed because it is too large
View File