Sfoglia il codice sorgente

Merge branch 'master' into @salakar/bugfix/androidx-rn60

Thibault Malbranche 5 anni fa
parent
commit
eed7cc9e52
No account linked to committer's email address

+ 196
- 55
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java Vedi File

@@ -6,12 +6,17 @@ import android.app.DownloadManager;
6 6
 import android.content.Context;
7 7
 import android.content.Intent;
8 8
 import android.graphics.Bitmap;
9
+import android.graphics.Color;
9 10
 import android.net.Uri;
10 11
 import android.os.Build;
11 12
 import android.os.Environment;
13
+import android.support.annotation.RequiresApi;
12 14
 import android.text.TextUtils;
15
+import android.view.Gravity;
13 16
 import android.view.View;
17
+import android.view.ViewGroup;
14 18
 import android.view.ViewGroup.LayoutParams;
19
+import android.view.WindowManager;
15 20
 import android.webkit.ConsoleMessage;
16 21
 import android.webkit.CookieManager;
17 22
 import android.webkit.DownloadListener;
@@ -24,7 +29,11 @@ import android.webkit.WebResourceRequest;
24 29
 import android.webkit.WebSettings;
25 30
 import android.webkit.WebView;
26 31
 import android.webkit.WebViewClient;
32
+import android.widget.FrameLayout;
27 33
 
34
+import com.facebook.react.views.scroll.ScrollEvent;
35
+import com.facebook.react.views.scroll.ScrollEventType;
36
+import com.facebook.react.views.scroll.OnScrollDispatchHelper;
28 37
 import com.facebook.react.bridge.Arguments;
29 38
 import com.facebook.react.bridge.LifecycleEventListener;
30 39
 import com.facebook.react.bridge.ReactContext;
@@ -106,6 +115,9 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
106 115
   protected static final String BLANK_URL = "about:blank";
107 116
   protected WebViewConfig mWebViewConfig;
108 117
 
118
+  protected RNCWebChromeClient mWebChromeClient = null;
119
+  protected boolean mAllowsFullscreenVideo = false;
120
+
109 121
   public RNCWebViewManager() {
110 122
     mWebViewConfig = new WebViewConfig() {
111 123
       public void configWebView(WebView webView) {
@@ -137,59 +149,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
137 149
   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
138 150
   protected WebView createViewInstance(ThemedReactContext reactContext) {
139 151
     RNCWebView webView = createRNCWebViewInstance(reactContext);
140
-    webView.setWebChromeClient(new WebChromeClient() {
141
-      @Override
142
-      public boolean onConsoleMessage(ConsoleMessage message) {
143
-        if (ReactBuildConfig.DEBUG) {
144
-          return super.onConsoleMessage(message);
145
-        }
146
-        // Ignore console logs in non debug builds.
147
-        return true;
148
-      }
149
-
150
-
151
-      @Override
152
-      public void onProgressChanged(WebView webView, int newProgress) {
153
-        super.onProgressChanged(webView, newProgress);
154
-        WritableMap event = Arguments.createMap();
155
-        event.putDouble("target", webView.getId());
156
-        event.putString("title", webView.getTitle());
157
-        event.putBoolean("canGoBack", webView.canGoBack());
158
-        event.putBoolean("canGoForward", webView.canGoForward());
159
-        event.putDouble("progress", (float) newProgress / 100);
160
-        dispatchEvent(
161
-          webView,
162
-          new TopLoadingProgressEvent(
163
-            webView.getId(),
164
-            event));
165
-      }
166
-
167
-      @Override
168
-      public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
169
-        callback.invoke(origin, true, false);
170
-      }
171
-
172
-      protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
173
-        getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType);
174
-      }
175
-
176
-      protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
177
-        getModule(reactContext).startPhotoPickerIntent(filePathCallback, "");
178
-      }
179
-
180
-      protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
181
-        getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType);
182
-      }
183
-
184
-      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
185
-      @Override
186
-      public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
187
-        String[] acceptTypes = fileChooserParams.getAcceptTypes();
188
-        boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
189
-        Intent intent = fileChooserParams.createIntent();
190
-        return getModule(reactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
191
-      }
192
-    });
152
+    setupWebChromeClient(reactContext, webView);
193 153
     reactContext.addLifecycleEventListener(webView);
194 154
     mWebViewConfig.configWebView(webView);
195 155
     WebSettings settings = webView.getSettings();
@@ -210,7 +170,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
210 170
       new LayoutParams(LayoutParams.MATCH_PARENT,
211 171
         LayoutParams.MATCH_PARENT));
212 172
 
213
-    setGeolocationEnabled(webView, false);
214 173
     if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
215 174
       WebView.setWebContentsDebuggingEnabled(true);
216 175
     }
@@ -290,6 +249,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
290 249
   public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
291 250
     if (disabled) {
292 251
       view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
252
+    } else {
253
+      view.setLayerType(View.LAYER_TYPE_NONE, null);
293 254
     }
294 255
   }
295 256
 
@@ -452,6 +413,14 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
452 413
     }
453 414
   }
454 415
 
416
+  @ReactProp(name = "allowsFullscreenVideo")
417
+  public void setAllowsFullscreenVideo(
418
+    WebView view,
419
+    @Nullable Boolean allowsFullscreenVideo) {
420
+    mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
421
+    setupWebChromeClient((ReactContext)view.getContext(), view);
422
+  }
423
+
455 424
   @ReactProp(name = "allowFileAccess")
456 425
   public void setAllowFileAccess(
457 426
     WebView view,
@@ -480,6 +449,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
480 449
     }
481 450
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
482 451
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
452
+    export.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"));
483 453
     return export;
484 454
   }
485 455
 
@@ -552,10 +522,67 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
552 522
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
553 523
   }
554 524
 
555
-  public RNCWebViewModule getModule(ReactContext reactContext) {
525
+  public static RNCWebViewModule getModule(ReactContext reactContext) {
556 526
     return reactContext.getNativeModule(RNCWebViewModule.class);
557 527
   }
558 528
 
529
+  protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
530
+    if (mAllowsFullscreenVideo) {
531
+      mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
532
+        @Override
533
+        public void onShowCustomView(View view, CustomViewCallback callback) {
534
+          if (mVideoView != null) {
535
+            callback.onCustomViewHidden();
536
+            return;
537
+          }
538
+
539
+          mVideoView = view;
540
+          mCustomViewCallback = callback;
541
+
542
+          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
543
+            mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
544
+            mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
545
+          }
546
+
547
+          mVideoView.setBackgroundColor(Color.BLACK);
548
+          getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
549
+          mWebView.setVisibility(View.GONE);
550
+
551
+          mReactContext.addLifecycleEventListener(this);
552
+        }
553
+
554
+        @Override
555
+        public void onHideCustomView() {
556
+          if (mVideoView == null) {
557
+            return;
558
+          }
559
+
560
+          mVideoView.setVisibility(View.GONE);
561
+          getRootView().removeView(mVideoView);
562
+          mCustomViewCallback.onCustomViewHidden();
563
+
564
+          mVideoView = null;
565
+          mCustomViewCallback = null;
566
+
567
+          mWebView.setVisibility(View.VISIBLE);
568
+
569
+          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
570
+            mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
571
+          }
572
+
573
+          mReactContext.removeLifecycleEventListener(this);
574
+        }
575
+      };
576
+      webView.setWebChromeClient(mWebChromeClient);
577
+    } else {
578
+      if (mWebChromeClient != null) {
579
+        mWebChromeClient.onHideCustomView();
580
+      }
581
+      mWebChromeClient = new RNCWebChromeClient(reactContext, webView);
582
+      webView.setWebChromeClient(mWebChromeClient);
583
+    }
584
+  }
585
+
559 586
   protected static class RNCWebViewClient extends WebViewClient {
560 587
 
561 588
     protected boolean mLastLoadFailed = false;
@@ -653,6 +680,99 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
653 680
     }
654 681
   }
655 682
 
683
+  protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
684
+    protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
685
+      LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
686
+
687
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
688
+    protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
689
+      View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
690
+      View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
691
+      View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
692
+      View.SYSTEM_UI_FLAG_FULLSCREEN |
693
+      View.SYSTEM_UI_FLAG_IMMERSIVE |
694
+      View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
695
+
696
+    protected ReactContext mReactContext;
697
+    protected View mWebView;
698
+
699
+    protected View mVideoView;
700
+    protected WebChromeClient.CustomViewCallback mCustomViewCallback;
701
+
702
+    public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
703
+      this.mReactContext = reactContext;
704
+      this.mWebView = webView;
705
+    }
706
+
707
+    @Override
708
+    public boolean onConsoleMessage(ConsoleMessage message) {
709
+      if (ReactBuildConfig.DEBUG) {
710
+        return super.onConsoleMessage(message);
711
+      }
712
+      // Ignore console logs in non debug builds.
713
+      return true;
714
+    }
715
+
716
+    @Override
717
+    public void onProgressChanged(WebView webView, int newProgress) {
718
+      super.onProgressChanged(webView, newProgress);
719
+      WritableMap event = Arguments.createMap();
720
+      event.putDouble("target", webView.getId());
721
+      event.putString("title", webView.getTitle());
722
+      event.putBoolean("canGoBack", webView.canGoBack());
723
+      event.putBoolean("canGoForward", webView.canGoForward());
724
+      event.putDouble("progress", (float) newProgress / 100);
725
+      dispatchEvent(
726
+        webView,
727
+        new TopLoadingProgressEvent(
728
+          webView.getId(),
729
+          event));
730
+    }
731
+
732
+    @Override
733
+    public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
734
+      callback.invoke(origin, true, false);
735
+    }
736
+
737
+    protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
738
+      getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
739
+    }
740
+
741
+    protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
742
+      getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
743
+    }
744
+
745
+    protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
746
+      getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
747
+    }
748
+
749
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
750
+    @Override
751
+    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
752
+      String[] acceptTypes = fileChooserParams.getAcceptTypes();
753
+      boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
754
+      Intent intent = fileChooserParams.createIntent();
755
+      return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
756
+    }
757
+
758
+    @Override
759
+    public void onHostResume() {
760
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
761
+        mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
762
+      }
763
+    }
764
+
765
+    @Override
766
+    public void onHostPause() { }
767
+
768
+    @Override
769
+    public void onHostDestroy() { }
770
+
771
+    protected ViewGroup getRootView() {
772
+      return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
773
+    }
774
+  }
775
+
656 776
   /**
657 777
    * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
658 778
    * to call {@link WebView#destroy} on activity destroy event and also to clear the client
@@ -664,6 +784,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
664 784
     protected @Nullable
665 785
     RNCWebViewClient mRNCWebViewClient;
666 786
     protected boolean sendContentSizeChangeEvents = false;
787
+    private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
667 788
 
668 789
     /**
669 790
      * WebView must be created with an context of the current activity
@@ -770,6 +891,26 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
770 891
       dispatchEvent(this, new TopMessageEvent(this.getId(), message));
771 892
     }
772 893
 
894
+    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
895
+      super.onScrollChanged(x, y, oldX, oldY);
896
+
897
+      if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
898
+        ScrollEvent event = ScrollEvent.obtain(
899
+                this.getId(),
900
+                ScrollEventType.SCROLL,
901
+                x,
902
+                y,
903
+                mOnScrollDispatchHelper.getXFlingVelocity(),
904
+                mOnScrollDispatchHelper.getYFlingVelocity(),
905
+                this.computeHorizontalScrollRange(),
906
+                this.computeVerticalScrollRange(),
907
+                this.getWidth(),
908
+                this.getHeight());
909
+
910
+        dispatchEvent(this, event);
911
+      }
912
+    }
913
+
773 914
     protected void cleanupCallbacksAndDestroy() {
774 915
       setWebViewClient(null);
775 916
       destroy();

+ 61
- 22
docs/Guide.md Vedi File

@@ -14,7 +14,6 @@ _This guide is currently a work in progress._
14 14
 - [Add support for File Download](Guide.md#add-support-for-file-download)
15 15
 - [Communicating between JS and Native](Guide.md#communicating-between-js-and-native)
16 16
 
17
-
18 17
 ### Basic inline HTML
19 18
 
20 19
 The simplest way to use the WebView is to simply pipe in the HTML you want to display. Note that setting an `html` source requires the [originWhiteList](Reference.md#originWhiteList) property to be set to `['*']`.
@@ -48,9 +47,7 @@ import { WebView } from 'react-native-webview';
48 47
 class MyWeb extends Component {
49 48
   render() {
50 49
     return (
51
-      <WebView
52
-        source={{uri: 'https://facebook.github.io/react-native/'}}
53
-      />
50
+      <WebView source={{ uri: 'https://facebook.github.io/react-native/' }} />
54 51
     );
55 52
   }
56 53
 }
@@ -71,7 +68,7 @@ class MyWeb extends Component {
71 68
     return (
72 69
       <WebView
73 70
         ref={ref => (this.webview = ref)}
74
-        source={{uri: 'https://facebook.github.io/react-native/'}}
71
+        source={{ uri: 'https://facebook.github.io/react-native/' }}
75 72
         onNavigationStateChange={this.handleWebViewNavigationStateChange}
76 73
       />
77 74
     );
@@ -87,7 +84,7 @@ class MyWeb extends Component {
87 84
     //   canGoForward?: boolean;
88 85
     // }
89 86
     const { url } = newNavState;
90
-    if (!url) return
87
+    if (!url) return;
91 88
 
92 89
     // handle certain doctypes
93 90
     if (url.includes('.pdf')) {
@@ -112,10 +109,48 @@ class MyWeb extends Component {
112 109
       const redirectTo = 'window.location = "' + newURL + '"';
113 110
       this.webview.injectJavaScript(redirectTo);
114 111
     }
115
-  }
112
+  };
116 113
 }
117 114
 ```
118 115
 
116
+#### Intercepting hash URL changes
117
+
118
+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`.
119
+
120
+You can inject some JavaScript to wrap the history functions in order to intercept these hash URL changes.
121
+
122
+```jsx
123
+<WebView
124
+  source={{ uri: someURI }}
125
+  injectedJavaScript={`
126
+    (function() {
127
+      function wrap(fn) {
128
+        return function wrapper() {
129
+          var res = fn.apply(this, arguments);
130
+          window.ReactNativeWebView.postMessage('navigationStateChange');
131
+          return res;
132
+        }
133
+      }
134
+
135
+      history.pushState = wrap(history.pushState);
136
+      history.replaceState = wrap(history.replaceState);
137
+      window.addEventListener('popstate', function() {
138
+        window.ReactNativeWebView.postMessage('navigationStateChange');
139
+      });
140
+    })();
141
+
142
+    true;
143
+  `}
144
+  onMessage={({ nativeEvent: state }) => {
145
+    if (state.data === 'navigationStateChange') {
146
+      // Navigation state updated, can check state.canGoBack, etc.
147
+    }
148
+  }}
149
+/>
150
+```
151
+
152
+Thanks to [Janic Duplessis](https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-483956651) for this workaround.
153
+
119 154
 ### Add support for File Upload
120 155
 
121 156
 ##### iOS
@@ -123,18 +158,21 @@ class MyWeb extends Component {
123 158
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
124 159
 
125 160
 Photo capture:
161
+
126 162
 ```
127 163
 <key>NSCameraUsageDescription</key>
128 164
 <string>Take pictures for certain activities</string>
129 165
 ```
130 166
 
131 167
 Gallery selection:
168
+
132 169
 ```
133 170
 <key>NSPhotoLibraryUsageDescription</key>
134 171
 <string>Select pictures for certain activities</string>
135 172
 ```
136 173
 
137 174
 Video recording:
175
+
138 176
 ```
139 177
 <key>NSMicrophoneUsageDescription</key>
140 178
 <string>Need microphone access for recording videos</string>
@@ -143,6 +181,7 @@ Video recording:
143 181
 ##### Android
144 182
 
145 183
 Add permission in AndroidManifest.xml:
184
+
146 185
 ```xml
147 186
 <manifest ...>
148 187
   ......
@@ -173,7 +212,7 @@ WebView.isFileUploadSupported().then(res => {
173 212
 
174 213
 ### Multiple Files Upload
175 214
 
176
-You can control __single__ or __multiple__ file selection by specifing the [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple) attribute on your `input` element:
215
+You can control **single** or **multiple** file selection by specifing the [`multiple`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple) attribute on your `input` element:
177 216
 
178 217
 ```
179 218
 // multiple file selection
@@ -190,6 +229,7 @@ You can control __single__ or __multiple__ file selection by specifing the [`mul
190 229
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
191 230
 
192 231
 Save to gallery:
232
+
193 233
 ```
194 234
 <key>NSPhotoLibraryAddUsageDescription</key>
195 235
 <string>Save pictures for certain activities.</string>
@@ -225,9 +265,9 @@ To accomplish this, React Native WebView exposes three different options:
225 265
 This is a script that runs immediately after the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away.
226 266
 
227 267
 ```jsx
228
-import React, { Component } from "react";
229
-import { View } from "react-native";
230
-import { WebView } from "react-native-webview";
268
+import React, { Component } from 'react';
269
+import { View } from 'react-native';
270
+import { WebView } from 'react-native-webview';
231 271
 
232 272
 export default class App extends Component {
233 273
   render() {
@@ -241,7 +281,7 @@ export default class App extends Component {
241 281
         <WebView
242 282
           source={{
243 283
             uri:
244
-              "https://github.com/react-native-community/react-native-webview"
284
+              'https://github.com/react-native-community/react-native-webview',
245 285
           }}
246 286
           injectedJavaScript={runFirst}
247 287
         />
@@ -255,7 +295,7 @@ This runs the JavaScript in the `runFirst` string once the page is loaded. In th
255 295
 
256 296
 <img alt="screenshot of Github repo" width="200" src="https://user-images.githubusercontent.com/1479215/53609254-e5dc9c00-3b7a-11e9-9118-bc4e520ce6ca.png" />
257 297
 
258
-*Under the hood*
298
+_Under the hood_
259 299
 
260 300
 > On iOS, `injectedJavaScript` runs a method on WKWebView called `evaluateJavaScript:completionHandler:`
261 301
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
@@ -265,9 +305,9 @@ This runs the JavaScript in the `runFirst` string once the page is loaded. In th
265 305
 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!).
266 306
 
267 307
 ```jsx
268
-import React, { Component } from "react";
269
-import { View } from "react-native";
270
-import { WebView } from "react-native-webview";
308
+import React, { Component } from 'react';
309
+import { View } from 'react-native';
310
+import { WebView } from 'react-native-webview';
271 311
 
272 312
 export default class App extends Component {
273 313
   render() {
@@ -286,7 +326,7 @@ export default class App extends Component {
286 326
           ref={r => (this.webref = r)}
287 327
           source={{
288 328
             uri:
289
-              "https://github.com/react-native-community/react-native-webview"
329
+              'https://github.com/react-native-community/react-native-webview',
290 330
           }}
291 331
         />
292 332
       </View>
@@ -299,7 +339,7 @@ After 3 seconds, this code turns the background blue:
299 339
 
300 340
 <img alt="Screenshot of app showing injected javascript" width="200" src="https://user-images.githubusercontent.com/1479215/53670433-93a98280-3c2f-11e9-85a5-0e4650993817.png" />
301 341
 
302
-*Under the hood*
342
+_Under the hood_
303 343
 
304 344
 > On iOS, `injectJavaScript` calls WKWebView's `evaluateJS:andThen:`
305 345
 > On Android, `injectJavaScript` calls Android WebView's `evaluateJavascriptWithFallback` method
@@ -313,9 +353,9 @@ You _must_ set `onMessage` or the `window.ReactNativeWebView.postMessage` method
313 353
 `window.ReactNativeWebView.postMessage` only accepts one argument which must be a string.
314 354
 
315 355
 ```jsx
316
-import React, { Component } from "react";
317
-import { View } from "react-native";
318
-import { WebView } from "react-native-webview";
356
+import React, { Component } from 'react';
357
+import { View } from 'react-native';
358
+import { WebView } from 'react-native-webview';
319 359
 
320 360
 export default class App extends Component {
321 361
   render() {
@@ -349,4 +389,3 @@ export default class App extends Component {
349 389
 This code will result in this alert:
350 390
 
351 391
 <img alt="Alert showing communication from web page to React Native" width="200" src="https://user-images.githubusercontent.com/1479215/53671269-7e822300-3c32-11e9-9937-7ddc34ba8af3.png" />
352
-

+ 29
- 0
docs/Reference.md Vedi File

@@ -30,6 +30,7 @@ This document lays out the current public properties and methods for the React N
30 30
 - [`mixedContentMode`](Reference.md#mixedcontentmode)
31 31
 - [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled)
32 32
 - [`userAgent`](Reference.md#useragent)
33
+- [`allowsFullscreenVideo`](Reference.md#allowsfullscreenvideo)
33 34
 - [`allowsInlineMediaPlayback`](Reference.md#allowsinlinemediaplayback)
34 35
 - [`bounces`](Reference.md#bounces)
35 36
 - [`overScrollMode`](Reference.md#overscrollmode)
@@ -114,6 +115,22 @@ Set this to provide JavaScript that will be injected into the web page when the
114 115
 
115 116
 To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
116 117
 
118
+Example:
119
+
120
+Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage)
121
+
122
+```jsx
123
+const INJECTED_JAVASCRIPT = `(function() {
124
+    window.ReactNativeWebView.postMessage(JSON.stringify(window.location));
125
+})();`;
126
+
127
+<WebView
128
+  source={{ uri: 'https://facebook.github.io/react-native' }}
129
+  injectedJavaScript={INJECTED_JAVASCRIPT}
130
+  onMessage={this.onMessage}
131
+/>
132
+```
133
+
117 134
 ---
118 135
 
119 136
 ### `mediaPlaybackRequiresUserAction`
@@ -369,6 +386,8 @@ title
369 386
 url
370 387
 ```
371 388
 
389
+Note that this method will not be invoked on hash URL changes (e.g. from `https://example.com/users#list` to `https://example.com/users#help`). There is a workaround for this that is described [in the Guide](Guide.md#intercepting-hash-url-changes).
390
+
372 391
 ---
373 392
 
374 393
 ### `originWhitelist`
@@ -589,6 +608,16 @@ Sets the user-agent for the `WebView`. This will only work for iOS if you are us
589 608
 
590 609
 ---
591 610
 
611
+### `allowsFullscreenVideo`
612
+
613
+Boolean that determines whether videos are allowed to be played in fullscreen. The default value is `false`.
614
+
615
+| Type | Required | Platform |
616
+| ---- | -------- | -------- |
617
+| bool | No       | Android  |
618
+
619
+---
620
+
592 621
 ### `allowsInlineMediaPlayback`
593 622
 
594 623
 Boolean that determines whether HTML5 videos play inline or use the native full-screen controller. The default value is `false`.

+ 25
- 0
ios/RNCWKWebView.m Vedi File

@@ -34,6 +34,7 @@ static NSURLCredential* clientAuthenticationCredential;
34 34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
35 35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
36 36
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
37
+@property (nonatomic, copy) RCTDirectEventBlock onScroll;
37 38
 @property (nonatomic, copy) WKWebView *webView;
38 39
 @end
39 40
 
@@ -509,6 +510,30 @@ static NSURLCredential* clientAuthenticationCredential;
509 510
   if (!_scrollEnabled) {
510 511
     scrollView.bounds = _webView.bounds;
511 512
   }
513
+  else if (_onScroll != nil) {
514
+    NSDictionary *event = @{
515
+      @"contentOffset": @{
516
+          @"x": @(scrollView.contentOffset.x),
517
+          @"y": @(scrollView.contentOffset.y)
518
+          },
519
+      @"contentInset": @{
520
+          @"top": @(scrollView.contentInset.top),
521
+          @"left": @(scrollView.contentInset.left),
522
+          @"bottom": @(scrollView.contentInset.bottom),
523
+          @"right": @(scrollView.contentInset.right)
524
+          },
525
+      @"contentSize": @{
526
+          @"width": @(scrollView.contentSize.width),
527
+          @"height": @(scrollView.contentSize.height)
528
+          },
529
+      @"layoutMeasurement": @{
530
+          @"width": @(scrollView.frame.size.width),
531
+          @"height": @(scrollView.frame.size.height)
532
+          },
533
+      @"zoomScale": @(scrollView.zoomScale ?: 1),
534
+      };
535
+    _onScroll(event);
536
+  }
512 537
 }
513 538
 
514 539
 - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled

+ 1
- 0
ios/RNCWKWebViewManager.m Vedi File

@@ -56,6 +56,7 @@ RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
56 56
  */
57 57
 RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
58 58
 RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
59
+RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
59 60
 
60 61
 RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
61 62
 {

+ 1
- 1
package.json Vedi File

@@ -8,7 +8,7 @@
8 8
     "Thibault Malbranche <malbranche.thibault@gmail.com>"
9 9
   ],
10 10
   "license": "MIT",
11
-  "version": "5.8.1",
11
+  "version": "5.8.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",

+ 1
- 0
src/WebView.android.tsx Vedi File

@@ -48,6 +48,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
48 48
     javaScriptEnabled: true,
49 49
     thirdPartyCookiesEnabled: true,
50 50
     scalesPageToFit: true,
51
+    allowsFullscreenVideo: false,
51 52
     allowFileAccess: false,
52 53
     saveFormDataDisabled: false,
53 54
     cacheEnabled: true,

+ 1
- 0
src/WebView.ios.tsx Vedi File

@@ -382,6 +382,7 @@ class WebView extends React.Component<IOSWebViewProps, State> {
382 382
         onLoadingProgress={this.onLoadingProgress}
383 383
         onLoadingStart={this.onLoadingStart}
384 384
         onMessage={this.onMessage}
385
+        onScroll={this.props.onScroll}
385 386
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
386 387
         ref={this.webViewRef}
387 388
         scalesPageToFit={scalesPageToFit}

+ 7
- 0
src/WebViewTypes.ts Vedi File

@@ -7,6 +7,7 @@ import {
7 7
   NativeMethodsMixin,
8 8
   Constructor,
9 9
   UIManagerStatic,
10
+  NativeScrollEvent,
10 11
 } from 'react-native';
11 12
 
12 13
 export interface WebViewCommands {
@@ -213,6 +214,7 @@ export interface CommonNativeWebViewProps extends ViewProps {
213 214
   injectedJavaScript?: string;
214 215
   mediaPlaybackRequiresUserAction?: boolean;
215 216
   messagingEnabled: boolean;
217
+  onScroll?: (event: NativeScrollEvent) => void;
216 218
   onLoadingError: (event: WebViewErrorEvent) => void;
217 219
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
218 220
   onLoadingProgress: (event: WebViewProgressEvent) => void;
@@ -542,6 +544,11 @@ export interface WebViewSharedProps extends ViewProps {
542 544
    */
543 545
   renderLoading?: () => ReactElement;
544 546
 
547
+  /**
548
+   * Function that is invoked when the `WebView` scrolls.
549
+   */
550
+  onScroll?: (event: NativeScrollEvent) => void;
551
+
545 552
   /**
546 553
    * Function that is invoked when the `WebView` has finished loading.
547 554
    */