Pārlūkot izejas kodu

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

Thibault Malbranche 6 gadus atpakaļ
vecāks
revīzija
eed7cc9e52
No account linked to committer's email address

+ 196
- 55
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java Parādīt failu

6
 import android.content.Context;
6
 import android.content.Context;
7
 import android.content.Intent;
7
 import android.content.Intent;
8
 import android.graphics.Bitmap;
8
 import android.graphics.Bitmap;
9
+import android.graphics.Color;
9
 import android.net.Uri;
10
 import android.net.Uri;
10
 import android.os.Build;
11
 import android.os.Build;
11
 import android.os.Environment;
12
 import android.os.Environment;
13
+import android.support.annotation.RequiresApi;
12
 import android.text.TextUtils;
14
 import android.text.TextUtils;
15
+import android.view.Gravity;
13
 import android.view.View;
16
 import android.view.View;
17
+import android.view.ViewGroup;
14
 import android.view.ViewGroup.LayoutParams;
18
 import android.view.ViewGroup.LayoutParams;
19
+import android.view.WindowManager;
15
 import android.webkit.ConsoleMessage;
20
 import android.webkit.ConsoleMessage;
16
 import android.webkit.CookieManager;
21
 import android.webkit.CookieManager;
17
 import android.webkit.DownloadListener;
22
 import android.webkit.DownloadListener;
24
 import android.webkit.WebSettings;
29
 import android.webkit.WebSettings;
25
 import android.webkit.WebView;
30
 import android.webkit.WebView;
26
 import android.webkit.WebViewClient;
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
 import com.facebook.react.bridge.Arguments;
37
 import com.facebook.react.bridge.Arguments;
29
 import com.facebook.react.bridge.LifecycleEventListener;
38
 import com.facebook.react.bridge.LifecycleEventListener;
30
 import com.facebook.react.bridge.ReactContext;
39
 import com.facebook.react.bridge.ReactContext;
106
   protected static final String BLANK_URL = "about:blank";
115
   protected static final String BLANK_URL = "about:blank";
107
   protected WebViewConfig mWebViewConfig;
116
   protected WebViewConfig mWebViewConfig;
108
 
117
 
118
+  protected RNCWebChromeClient mWebChromeClient = null;
119
+  protected boolean mAllowsFullscreenVideo = false;
120
+
109
   public RNCWebViewManager() {
121
   public RNCWebViewManager() {
110
     mWebViewConfig = new WebViewConfig() {
122
     mWebViewConfig = new WebViewConfig() {
111
       public void configWebView(WebView webView) {
123
       public void configWebView(WebView webView) {
137
   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
149
   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
138
   protected WebView createViewInstance(ThemedReactContext reactContext) {
150
   protected WebView createViewInstance(ThemedReactContext reactContext) {
139
     RNCWebView webView = createRNCWebViewInstance(reactContext);
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
     reactContext.addLifecycleEventListener(webView);
153
     reactContext.addLifecycleEventListener(webView);
194
     mWebViewConfig.configWebView(webView);
154
     mWebViewConfig.configWebView(webView);
195
     WebSettings settings = webView.getSettings();
155
     WebSettings settings = webView.getSettings();
210
       new LayoutParams(LayoutParams.MATCH_PARENT,
170
       new LayoutParams(LayoutParams.MATCH_PARENT,
211
         LayoutParams.MATCH_PARENT));
171
         LayoutParams.MATCH_PARENT));
212
 
172
 
213
-    setGeolocationEnabled(webView, false);
214
     if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
173
     if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
215
       WebView.setWebContentsDebuggingEnabled(true);
174
       WebView.setWebContentsDebuggingEnabled(true);
216
     }
175
     }
290
   public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
249
   public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
291
     if (disabled) {
250
     if (disabled) {
292
       view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
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
     }
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
   @ReactProp(name = "allowFileAccess")
424
   @ReactProp(name = "allowFileAccess")
456
   public void setAllowFileAccess(
425
   public void setAllowFileAccess(
457
     WebView view,
426
     WebView view,
480
     }
449
     }
481
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
450
     export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
482
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
451
     export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
452
+    export.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"));
483
     return export;
453
     return export;
484
   }
454
   }
485
 
455
 
552
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
522
     ((RNCWebView) webView).cleanupCallbacksAndDestroy();
553
   }
523
   }
554
 
524
 
555
-  public RNCWebViewModule getModule(ReactContext reactContext) {
525
+  public static RNCWebViewModule getModule(ReactContext reactContext) {
556
     return reactContext.getNativeModule(RNCWebViewModule.class);
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
   protected static class RNCWebViewClient extends WebViewClient {
586
   protected static class RNCWebViewClient extends WebViewClient {
560
 
587
 
561
     protected boolean mLastLoadFailed = false;
588
     protected boolean mLastLoadFailed = false;
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
    * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
777
    * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
658
    * to call {@link WebView#destroy} on activity destroy event and also to clear the client
778
    * to call {@link WebView#destroy} on activity destroy event and also to clear the client
664
     protected @Nullable
784
     protected @Nullable
665
     RNCWebViewClient mRNCWebViewClient;
785
     RNCWebViewClient mRNCWebViewClient;
666
     protected boolean sendContentSizeChangeEvents = false;
786
     protected boolean sendContentSizeChangeEvents = false;
787
+    private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
667
 
788
 
668
     /**
789
     /**
669
      * WebView must be created with an context of the current activity
790
      * WebView must be created with an context of the current activity
770
       dispatchEvent(this, new TopMessageEvent(this.getId(), message));
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
     protected void cleanupCallbacksAndDestroy() {
914
     protected void cleanupCallbacksAndDestroy() {
774
       setWebViewClient(null);
915
       setWebViewClient(null);
775
       destroy();
916
       destroy();

+ 61
- 22
docs/Guide.md Parādīt failu

14
 - [Add support for File Download](Guide.md#add-support-for-file-download)
14
 - [Add support for File Download](Guide.md#add-support-for-file-download)
15
 - [Communicating between JS and Native](Guide.md#communicating-between-js-and-native)
15
 - [Communicating between JS and Native](Guide.md#communicating-between-js-and-native)
16
 
16
 
17
-
18
 ### Basic inline HTML
17
 ### Basic inline HTML
19
 
18
 
20
 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 `['*']`.
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
 class MyWeb extends Component {
47
 class MyWeb extends Component {
49
   render() {
48
   render() {
50
     return (
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
     return (
68
     return (
72
       <WebView
69
       <WebView
73
         ref={ref => (this.webview = ref)}
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
         onNavigationStateChange={this.handleWebViewNavigationStateChange}
72
         onNavigationStateChange={this.handleWebViewNavigationStateChange}
76
       />
73
       />
77
     );
74
     );
87
     //   canGoForward?: boolean;
84
     //   canGoForward?: boolean;
88
     // }
85
     // }
89
     const { url } = newNavState;
86
     const { url } = newNavState;
90
-    if (!url) return
87
+    if (!url) return;
91
 
88
 
92
     // handle certain doctypes
89
     // handle certain doctypes
93
     if (url.includes('.pdf')) {
90
     if (url.includes('.pdf')) {
112
       const redirectTo = 'window.location = "' + newURL + '"';
109
       const redirectTo = 'window.location = "' + newURL + '"';
113
       this.webview.injectJavaScript(redirectTo);
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
 ### Add support for File Upload
154
 ### Add support for File Upload
120
 
155
 
121
 ##### iOS
156
 ##### iOS
123
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
158
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
124
 
159
 
125
 Photo capture:
160
 Photo capture:
161
+
126
 ```
162
 ```
127
 <key>NSCameraUsageDescription</key>
163
 <key>NSCameraUsageDescription</key>
128
 <string>Take pictures for certain activities</string>
164
 <string>Take pictures for certain activities</string>
129
 ```
165
 ```
130
 
166
 
131
 Gallery selection:
167
 Gallery selection:
168
+
132
 ```
169
 ```
133
 <key>NSPhotoLibraryUsageDescription</key>
170
 <key>NSPhotoLibraryUsageDescription</key>
134
 <string>Select pictures for certain activities</string>
171
 <string>Select pictures for certain activities</string>
135
 ```
172
 ```
136
 
173
 
137
 Video recording:
174
 Video recording:
175
+
138
 ```
176
 ```
139
 <key>NSMicrophoneUsageDescription</key>
177
 <key>NSMicrophoneUsageDescription</key>
140
 <string>Need microphone access for recording videos</string>
178
 <string>Need microphone access for recording videos</string>
143
 ##### Android
181
 ##### Android
144
 
182
 
145
 Add permission in AndroidManifest.xml:
183
 Add permission in AndroidManifest.xml:
184
+
146
 ```xml
185
 ```xml
147
 <manifest ...>
186
 <manifest ...>
148
   ......
187
   ......
173
 
212
 
174
 ### Multiple Files Upload
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
 // multiple file selection
218
 // multiple file selection
190
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
229
 For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
191
 
230
 
192
 Save to gallery:
231
 Save to gallery:
232
+
193
 ```
233
 ```
194
 <key>NSPhotoLibraryAddUsageDescription</key>
234
 <key>NSPhotoLibraryAddUsageDescription</key>
195
 <string>Save pictures for certain activities.</string>
235
 <string>Save pictures for certain activities.</string>
225
 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.
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
 ```jsx
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
 export default class App extends Component {
272
 export default class App extends Component {
233
   render() {
273
   render() {
241
         <WebView
281
         <WebView
242
           source={{
282
           source={{
243
             uri:
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
           injectedJavaScript={runFirst}
286
           injectedJavaScript={runFirst}
247
         />
287
         />
255
 
295
 
256
 <img alt="screenshot of Github repo" width="200" src="https://user-images.githubusercontent.com/1479215/53609254-e5dc9c00-3b7a-11e9-9118-bc4e520ce6ca.png" />
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
 > On iOS, `injectedJavaScript` runs a method on WKWebView called `evaluateJavaScript:completionHandler:`
300
 > On iOS, `injectedJavaScript` runs a method on WKWebView called `evaluateJavaScript:completionHandler:`
261
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
301
 > On Android, `injectedJavaScript` runs a method on the Android WebView called `evaluateJavascriptWithFallback`
265
 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!).
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
 ```jsx
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
 export default class App extends Component {
312
 export default class App extends Component {
273
   render() {
313
   render() {
286
           ref={r => (this.webref = r)}
326
           ref={r => (this.webref = r)}
287
           source={{
327
           source={{
288
             uri:
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
       </View>
332
       </View>
299
 
339
 
300
 <img alt="Screenshot of app showing injected javascript" width="200" src="https://user-images.githubusercontent.com/1479215/53670433-93a98280-3c2f-11e9-85a5-0e4650993817.png" />
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
 > On iOS, `injectJavaScript` calls WKWebView's `evaluateJS:andThen:`
344
 > On iOS, `injectJavaScript` calls WKWebView's `evaluateJS:andThen:`
305
 > On Android, `injectJavaScript` calls Android WebView's `evaluateJavascriptWithFallback` method
345
 > On Android, `injectJavaScript` calls Android WebView's `evaluateJavascriptWithFallback` method
313
 `window.ReactNativeWebView.postMessage` only accepts one argument which must be a string.
353
 `window.ReactNativeWebView.postMessage` only accepts one argument which must be a string.
314
 
354
 
315
 ```jsx
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
 export default class App extends Component {
360
 export default class App extends Component {
321
   render() {
361
   render() {
349
 This code will result in this alert:
389
 This code will result in this alert:
350
 
390
 
351
 <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" />
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 Parādīt failu

30
 - [`mixedContentMode`](Reference.md#mixedcontentmode)
30
 - [`mixedContentMode`](Reference.md#mixedcontentmode)
31
 - [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled)
31
 - [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled)
32
 - [`userAgent`](Reference.md#useragent)
32
 - [`userAgent`](Reference.md#useragent)
33
+- [`allowsFullscreenVideo`](Reference.md#allowsfullscreenvideo)
33
 - [`allowsInlineMediaPlayback`](Reference.md#allowsinlinemediaplayback)
34
 - [`allowsInlineMediaPlayback`](Reference.md#allowsinlinemediaplayback)
34
 - [`bounces`](Reference.md#bounces)
35
 - [`bounces`](Reference.md#bounces)
35
 - [`overScrollMode`](Reference.md#overscrollmode)
36
 - [`overScrollMode`](Reference.md#overscrollmode)
114
 
115
 
115
 To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
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
 ### `mediaPlaybackRequiresUserAction`
136
 ### `mediaPlaybackRequiresUserAction`
369
 url
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
 ### `originWhitelist`
393
 ### `originWhitelist`
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
 ### `allowsInlineMediaPlayback`
621
 ### `allowsInlineMediaPlayback`
593
 
622
 
594
 Boolean that determines whether HTML5 videos play inline or use the native full-screen controller. The default value is `false`.
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 Parādīt failu

34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
34
 @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
35
 @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
36
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
36
 @property (nonatomic, copy) RCTDirectEventBlock onMessage;
37
+@property (nonatomic, copy) RCTDirectEventBlock onScroll;
37
 @property (nonatomic, copy) WKWebView *webView;
38
 @property (nonatomic, copy) WKWebView *webView;
38
 @end
39
 @end
39
 
40
 
509
   if (!_scrollEnabled) {
510
   if (!_scrollEnabled) {
510
     scrollView.bounds = _webView.bounds;
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
 - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled
539
 - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled

+ 1
- 0
ios/RNCWKWebViewManager.m Parādīt failu

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

+ 1
- 1
package.json Parādīt failu

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

+ 1
- 0
src/WebView.android.tsx Parādīt failu

48
     javaScriptEnabled: true,
48
     javaScriptEnabled: true,
49
     thirdPartyCookiesEnabled: true,
49
     thirdPartyCookiesEnabled: true,
50
     scalesPageToFit: true,
50
     scalesPageToFit: true,
51
+    allowsFullscreenVideo: false,
51
     allowFileAccess: false,
52
     allowFileAccess: false,
52
     saveFormDataDisabled: false,
53
     saveFormDataDisabled: false,
53
     cacheEnabled: true,
54
     cacheEnabled: true,

+ 1
- 0
src/WebView.ios.tsx Parādīt failu

382
         onLoadingProgress={this.onLoadingProgress}
382
         onLoadingProgress={this.onLoadingProgress}
383
         onLoadingStart={this.onLoadingStart}
383
         onLoadingStart={this.onLoadingStart}
384
         onMessage={this.onMessage}
384
         onMessage={this.onMessage}
385
+        onScroll={this.props.onScroll}
385
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
386
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
386
         ref={this.webViewRef}
387
         ref={this.webViewRef}
387
         scalesPageToFit={scalesPageToFit}
388
         scalesPageToFit={scalesPageToFit}

+ 7
- 0
src/WebViewTypes.ts Parādīt failu

7
   NativeMethodsMixin,
7
   NativeMethodsMixin,
8
   Constructor,
8
   Constructor,
9
   UIManagerStatic,
9
   UIManagerStatic,
10
+  NativeScrollEvent,
10
 } from 'react-native';
11
 } from 'react-native';
11
 
12
 
12
 export interface WebViewCommands {
13
 export interface WebViewCommands {
213
   injectedJavaScript?: string;
214
   injectedJavaScript?: string;
214
   mediaPlaybackRequiresUserAction?: boolean;
215
   mediaPlaybackRequiresUserAction?: boolean;
215
   messagingEnabled: boolean;
216
   messagingEnabled: boolean;
217
+  onScroll?: (event: NativeScrollEvent) => void;
216
   onLoadingError: (event: WebViewErrorEvent) => void;
218
   onLoadingError: (event: WebViewErrorEvent) => void;
217
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
219
   onLoadingFinish: (event: WebViewNavigationEvent) => void;
218
   onLoadingProgress: (event: WebViewProgressEvent) => void;
220
   onLoadingProgress: (event: WebViewProgressEvent) => void;
542
    */
544
    */
543
   renderLoading?: () => ReactElement;
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
    * Function that is invoked when the `WebView` has finished loading.
553
    * Function that is invoked when the `WebView` has finished loading.
547
    */
554
    */