Merge branch 'master' into @salakar/bugfix/androidx-rn60
This commit is contained in:
commit
eed7cc9e52
|
@ -6,12 +6,17 @@ import android.app.DownloadManager;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.DownloadListener;
|
||||
|
@ -24,7 +29,11 @@ import android.webkit.WebResourceRequest;
|
|||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.views.scroll.ScrollEvent;
|
||||
import com.facebook.react.views.scroll.ScrollEventType;
|
||||
import com.facebook.react.views.scroll.OnScrollDispatchHelper;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
@ -106,6 +115,9 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
protected static final String BLANK_URL = "about:blank";
|
||||
protected WebViewConfig mWebViewConfig;
|
||||
|
||||
protected RNCWebChromeClient mWebChromeClient = null;
|
||||
protected boolean mAllowsFullscreenVideo = false;
|
||||
|
||||
public RNCWebViewManager() {
|
||||
mWebViewConfig = new WebViewConfig() {
|
||||
public void configWebView(WebView webView) {
|
||||
|
@ -137,59 +149,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
protected WebView createViewInstance(ThemedReactContext reactContext) {
|
||||
RNCWebView webView = createRNCWebViewInstance(reactContext);
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage message) {
|
||||
if (ReactBuildConfig.DEBUG) {
|
||||
return super.onConsoleMessage(message);
|
||||
}
|
||||
// Ignore console logs in non debug builds.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView webView, int newProgress) {
|
||||
super.onProgressChanged(webView, newProgress);
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble("target", webView.getId());
|
||||
event.putString("title", webView.getTitle());
|
||||
event.putBoolean("canGoBack", webView.canGoBack());
|
||||
event.putBoolean("canGoForward", webView.canGoForward());
|
||||
event.putDouble("progress", (float) newProgress / 100);
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingProgressEvent(
|
||||
webView.getId(),
|
||||
event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||
callback.invoke(origin, true, false);
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
|
||||
getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
|
||||
getModule(reactContext).startPhotoPickerIntent(filePathCallback, "");
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
|
||||
getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
|
||||
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||
boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
return getModule(reactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
|
||||
}
|
||||
});
|
||||
setupWebChromeClient(reactContext, webView);
|
||||
reactContext.addLifecycleEventListener(webView);
|
||||
mWebViewConfig.configWebView(webView);
|
||||
WebSettings settings = webView.getSettings();
|
||||
|
@ -210,7 +170,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
new LayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT));
|
||||
|
||||
setGeolocationEnabled(webView, false);
|
||||
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
|
@ -290,6 +249,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
|
||||
if (disabled) {
|
||||
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
} else {
|
||||
view.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,6 +413,14 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "allowsFullscreenVideo")
|
||||
public void setAllowsFullscreenVideo(
|
||||
WebView view,
|
||||
@Nullable Boolean allowsFullscreenVideo) {
|
||||
mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
|
||||
setupWebChromeClient((ReactContext)view.getContext(), view);
|
||||
}
|
||||
|
||||
@ReactProp(name = "allowFileAccess")
|
||||
public void setAllowFileAccess(
|
||||
WebView view,
|
||||
|
@ -480,6 +449,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
}
|
||||
export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
|
||||
export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
|
||||
export.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"));
|
||||
return export;
|
||||
}
|
||||
|
||||
|
@ -552,10 +522,67 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
((RNCWebView) webView).cleanupCallbacksAndDestroy();
|
||||
}
|
||||
|
||||
public RNCWebViewModule getModule(ReactContext reactContext) {
|
||||
public static RNCWebViewModule getModule(ReactContext reactContext) {
|
||||
return reactContext.getNativeModule(RNCWebViewModule.class);
|
||||
}
|
||||
|
||||
protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
|
||||
if (mAllowsFullscreenVideo) {
|
||||
mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
if (mVideoView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
mVideoView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
|
||||
mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
||||
}
|
||||
|
||||
mVideoView.setBackgroundColor(Color.BLACK);
|
||||
getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
|
||||
mWebView.setVisibility(View.GONE);
|
||||
|
||||
mReactContext.addLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
if (mVideoView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mVideoView.setVisibility(View.GONE);
|
||||
getRootView().removeView(mVideoView);
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
mVideoView = null;
|
||||
mCustomViewCallback = null;
|
||||
|
||||
mWebView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
|
||||
}
|
||||
|
||||
mReactContext.removeLifecycleEventListener(this);
|
||||
}
|
||||
};
|
||||
webView.setWebChromeClient(mWebChromeClient);
|
||||
} else {
|
||||
if (mWebChromeClient != null) {
|
||||
mWebChromeClient.onHideCustomView();
|
||||
}
|
||||
mWebChromeClient = new RNCWebChromeClient(reactContext, webView);
|
||||
webView.setWebChromeClient(mWebChromeClient);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class RNCWebViewClient extends WebViewClient {
|
||||
|
||||
protected boolean mLastLoadFailed = false;
|
||||
|
@ -653,6 +680,99 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
}
|
||||
}
|
||||
|
||||
protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
|
||||
protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
protected ReactContext mReactContext;
|
||||
protected View mWebView;
|
||||
|
||||
protected View mVideoView;
|
||||
protected WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
|
||||
this.mReactContext = reactContext;
|
||||
this.mWebView = webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage message) {
|
||||
if (ReactBuildConfig.DEBUG) {
|
||||
return super.onConsoleMessage(message);
|
||||
}
|
||||
// Ignore console logs in non debug builds.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView webView, int newProgress) {
|
||||
super.onProgressChanged(webView, newProgress);
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble("target", webView.getId());
|
||||
event.putString("title", webView.getTitle());
|
||||
event.putBoolean("canGoBack", webView.canGoBack());
|
||||
event.putBoolean("canGoForward", webView.canGoForward());
|
||||
event.putDouble("progress", (float) newProgress / 100);
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingProgressEvent(
|
||||
webView.getId(),
|
||||
event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||
callback.invoke(origin, true, false);
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
|
||||
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
|
||||
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
|
||||
}
|
||||
|
||||
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
|
||||
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
|
||||
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||
boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
|
||||
mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() { }
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() { }
|
||||
|
||||
protected ViewGroup getRootView() {
|
||||
return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
|
||||
* 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> {
|
|||
protected @Nullable
|
||||
RNCWebViewClient mRNCWebViewClient;
|
||||
protected boolean sendContentSizeChangeEvents = false;
|
||||
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
|
||||
|
||||
/**
|
||||
* WebView must be created with an context of the current activity
|
||||
|
@ -770,6 +891,26 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
|||
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
|
||||
}
|
||||
|
||||
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
|
||||
super.onScrollChanged(x, y, oldX, oldY);
|
||||
|
||||
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
|
||||
ScrollEvent event = ScrollEvent.obtain(
|
||||
this.getId(),
|
||||
ScrollEventType.SCROLL,
|
||||
x,
|
||||
y,
|
||||
mOnScrollDispatchHelper.getXFlingVelocity(),
|
||||
mOnScrollDispatchHelper.getYFlingVelocity(),
|
||||
this.computeHorizontalScrollRange(),
|
||||
this.computeVerticalScrollRange(),
|
||||
this.getWidth(),
|
||||
this.getHeight());
|
||||
|
||||
dispatchEvent(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
protected void cleanupCallbacksAndDestroy() {
|
||||
setWebViewClient(null);
|
||||
destroy();
|
||||
|
|
|
@ -14,7 +14,6 @@ _This guide is currently a work in progress._
|
|||
- [Add support for File Download](Guide.md#add-support-for-file-download)
|
||||
- [Communicating between JS and Native](Guide.md#communicating-between-js-and-native)
|
||||
|
||||
|
||||
### Basic inline HTML
|
||||
|
||||
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';
|
|||
class MyWeb extends Component {
|
||||
render() {
|
||||
return (
|
||||
<WebView
|
||||
source={{uri: 'https://facebook.github.io/react-native/'}}
|
||||
/>
|
||||
<WebView source={{ uri: 'https://facebook.github.io/react-native/' }} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +68,7 @@ class MyWeb extends Component {
|
|||
return (
|
||||
<WebView
|
||||
ref={ref => (this.webview = ref)}
|
||||
source={{uri: 'https://facebook.github.io/react-native/'}}
|
||||
source={{ uri: 'https://facebook.github.io/react-native/' }}
|
||||
onNavigationStateChange={this.handleWebViewNavigationStateChange}
|
||||
/>
|
||||
);
|
||||
|
@ -87,7 +84,7 @@ class MyWeb extends Component {
|
|||
// canGoForward?: boolean;
|
||||
// }
|
||||
const { url } = newNavState;
|
||||
if (!url) return
|
||||
if (!url) return;
|
||||
|
||||
// handle certain doctypes
|
||||
if (url.includes('.pdf')) {
|
||||
|
@ -112,10 +109,48 @@ class MyWeb extends Component {
|
|||
const redirectTo = 'window.location = "' + newURL + '"';
|
||||
this.webview.injectJavaScript(redirectTo);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Intercepting hash URL changes
|
||||
|
||||
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`.
|
||||
|
||||
You can inject some JavaScript to wrap the history functions in order to intercept these hash URL changes.
|
||||
|
||||
```jsx
|
||||
<WebView
|
||||
source={{ uri: someURI }}
|
||||
injectedJavaScript={`
|
||||
(function() {
|
||||
function wrap(fn) {
|
||||
return function wrapper() {
|
||||
var res = fn.apply(this, arguments);
|
||||
window.ReactNativeWebView.postMessage('navigationStateChange');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
history.pushState = wrap(history.pushState);
|
||||
history.replaceState = wrap(history.replaceState);
|
||||
window.addEventListener('popstate', function() {
|
||||
window.ReactNativeWebView.postMessage('navigationStateChange');
|
||||
});
|
||||
})();
|
||||
|
||||
true;
|
||||
`}
|
||||
onMessage={({ nativeEvent: state }) => {
|
||||
if (state.data === 'navigationStateChange') {
|
||||
// Navigation state updated, can check state.canGoBack, etc.
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Thanks to [Janic Duplessis](https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-483956651) for this workaround.
|
||||
|
||||
### Add support for File Upload
|
||||
|
||||
##### iOS
|
||||
|
@ -123,18 +158,21 @@ class MyWeb extends Component {
|
|||
For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
|
||||
|
||||
Photo capture:
|
||||
|
||||
```
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Take pictures for certain activities</string>
|
||||
```
|
||||
|
||||
Gallery selection:
|
||||
|
||||
```
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Select pictures for certain activities</string>
|
||||
```
|
||||
|
||||
Video recording:
|
||||
|
||||
```
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Need microphone access for recording videos</string>
|
||||
|
@ -143,6 +181,7 @@ Video recording:
|
|||
##### Android
|
||||
|
||||
Add permission in AndroidManifest.xml:
|
||||
|
||||
```xml
|
||||
<manifest ...>
|
||||
......
|
||||
|
@ -173,7 +212,7 @@ WebView.isFileUploadSupported().then(res => {
|
|||
|
||||
### Multiple Files Upload
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
// multiple file selection
|
||||
|
@ -190,6 +229,7 @@ You can control __single__ or __multiple__ file selection by specifing the [`mul
|
|||
For iOS, all you need to do is specify the permissions in your `ios/[project]/Info.plist` file:
|
||||
|
||||
Save to gallery:
|
||||
|
||||
```
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save pictures for certain activities.</string>
|
||||
|
@ -225,9 +265,9 @@ To accomplish this, React Native WebView exposes three different options:
|
|||
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.
|
||||
|
||||
```jsx
|
||||
import React, { Component } from "react";
|
||||
import { View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
|
@ -241,7 +281,7 @@ export default class App extends Component {
|
|||
<WebView
|
||||
source={{
|
||||
uri:
|
||||
"https://github.com/react-native-community/react-native-webview"
|
||||
'https://github.com/react-native-community/react-native-webview',
|
||||
}}
|
||||
injectedJavaScript={runFirst}
|
||||
/>
|
||||
|
@ -255,7 +295,7 @@ This runs the JavaScript in the `runFirst` string once the page is loaded. In th
|
|||
|
||||
<img alt="screenshot of Github repo" width="200" src="https://user-images.githubusercontent.com/1479215/53609254-e5dc9c00-3b7a-11e9-9118-bc4e520ce6ca.png" />
|
||||
|
||||
*Under the hood*
|
||||
_Under the hood_
|
||||
|
||||
> On iOS, `injectedJavaScript` runs a method on WKWebView called `evaluateJavaScript:completionHandler:`
|
||||
> 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
|
|||
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!).
|
||||
|
||||
```jsx
|
||||
import React, { Component } from "react";
|
||||
import { View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
|
@ -286,7 +326,7 @@ export default class App extends Component {
|
|||
ref={r => (this.webref = r)}
|
||||
source={{
|
||||
uri:
|
||||
"https://github.com/react-native-community/react-native-webview"
|
||||
'https://github.com/react-native-community/react-native-webview',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
@ -299,7 +339,7 @@ After 3 seconds, this code turns the background blue:
|
|||
|
||||
<img alt="Screenshot of app showing injected javascript" width="200" src="https://user-images.githubusercontent.com/1479215/53670433-93a98280-3c2f-11e9-85a5-0e4650993817.png" />
|
||||
|
||||
*Under the hood*
|
||||
_Under the hood_
|
||||
|
||||
> On iOS, `injectJavaScript` calls WKWebView's `evaluateJS:andThen:`
|
||||
> On Android, `injectJavaScript` calls Android WebView's `evaluateJavascriptWithFallback` method
|
||||
|
@ -313,9 +353,9 @@ You _must_ set `onMessage` or the `window.ReactNativeWebView.postMessage` method
|
|||
`window.ReactNativeWebView.postMessage` only accepts one argument which must be a string.
|
||||
|
||||
```jsx
|
||||
import React, { Component } from "react";
|
||||
import { View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
|
@ -349,4 +389,3 @@ export default class App extends Component {
|
|||
This code will result in this alert:
|
||||
|
||||
<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" />
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ This document lays out the current public properties and methods for the React N
|
|||
- [`mixedContentMode`](Reference.md#mixedcontentmode)
|
||||
- [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled)
|
||||
- [`userAgent`](Reference.md#useragent)
|
||||
- [`allowsFullscreenVideo`](Reference.md#allowsfullscreenvideo)
|
||||
- [`allowsInlineMediaPlayback`](Reference.md#allowsinlinemediaplayback)
|
||||
- [`bounces`](Reference.md#bounces)
|
||||
- [`overScrollMode`](Reference.md#overscrollmode)
|
||||
|
@ -114,6 +115,22 @@ Set this to provide JavaScript that will be injected into the web page when the
|
|||
|
||||
To learn more, read the [Communicating between JS and Native](Guide.md#communicating-between-js-and-native) guide.
|
||||
|
||||
Example:
|
||||
|
||||
Post message a JSON object of `window.location` to be handled by [`onMessage`](Reference.md#onmessage)
|
||||
|
||||
```jsx
|
||||
const INJECTED_JAVASCRIPT = `(function() {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify(window.location));
|
||||
})();`;
|
||||
|
||||
<WebView
|
||||
source={{ uri: 'https://facebook.github.io/react-native' }}
|
||||
injectedJavaScript={INJECTED_JAVASCRIPT}
|
||||
onMessage={this.onMessage}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mediaPlaybackRequiresUserAction`
|
||||
|
@ -369,6 +386,8 @@ title
|
|||
url
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
### `originWhitelist`
|
||||
|
@ -589,6 +608,16 @@ Sets the user-agent for the `WebView`. This will only work for iOS if you are us
|
|||
|
||||
---
|
||||
|
||||
### `allowsFullscreenVideo`
|
||||
|
||||
Boolean that determines whether videos are allowed to be played in fullscreen. The default value is `false`.
|
||||
|
||||
| Type | Required | Platform |
|
||||
| ---- | -------- | -------- |
|
||||
| bool | No | Android |
|
||||
|
||||
---
|
||||
|
||||
### `allowsInlineMediaPlayback`
|
||||
|
||||
Boolean that determines whether HTML5 videos play inline or use the native full-screen controller. The default value is `false`.
|
||||
|
|
|
@ -34,6 +34,7 @@ static NSURLCredential* clientAuthenticationCredential;
|
|||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onScroll;
|
||||
@property (nonatomic, copy) WKWebView *webView;
|
||||
@end
|
||||
|
||||
|
@ -509,6 +510,30 @@ static NSURLCredential* clientAuthenticationCredential;
|
|||
if (!_scrollEnabled) {
|
||||
scrollView.bounds = _webView.bounds;
|
||||
}
|
||||
else if (_onScroll != nil) {
|
||||
NSDictionary *event = @{
|
||||
@"contentOffset": @{
|
||||
@"x": @(scrollView.contentOffset.x),
|
||||
@"y": @(scrollView.contentOffset.y)
|
||||
},
|
||||
@"contentInset": @{
|
||||
@"top": @(scrollView.contentInset.top),
|
||||
@"left": @(scrollView.contentInset.left),
|
||||
@"bottom": @(scrollView.contentInset.bottom),
|
||||
@"right": @(scrollView.contentInset.right)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(scrollView.contentSize.width),
|
||||
@"height": @(scrollView.contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(scrollView.frame.size.width),
|
||||
@"height": @(scrollView.frame.size.height)
|
||||
},
|
||||
@"zoomScale": @(scrollView.zoomScale ?: 1),
|
||||
};
|
||||
_onScroll(event);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled
|
||||
|
|
|
@ -56,6 +56,7 @@ RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
|
|||
*/
|
||||
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"Thibault Malbranche <malbranche.thibault@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "5.8.1",
|
||||
"version": "5.8.2",
|
||||
"homepage": "https://github.com/react-native-community/react-native-webview#readme",
|
||||
"scripts": {
|
||||
"ci": "CI=true && yarn lint && yarn test",
|
||||
|
|
|
@ -48,6 +48,7 @@ class WebView extends React.Component<AndroidWebViewProps, State> {
|
|||
javaScriptEnabled: true,
|
||||
thirdPartyCookiesEnabled: true,
|
||||
scalesPageToFit: true,
|
||||
allowsFullscreenVideo: false,
|
||||
allowFileAccess: false,
|
||||
saveFormDataDisabled: false,
|
||||
cacheEnabled: true,
|
||||
|
|
|
@ -382,6 +382,7 @@ class WebView extends React.Component<IOSWebViewProps, State> {
|
|||
onLoadingProgress={this.onLoadingProgress}
|
||||
onLoadingStart={this.onLoadingStart}
|
||||
onMessage={this.onMessage}
|
||||
onScroll={this.props.onScroll}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
ref={this.webViewRef}
|
||||
scalesPageToFit={scalesPageToFit}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
NativeMethodsMixin,
|
||||
Constructor,
|
||||
UIManagerStatic,
|
||||
NativeScrollEvent,
|
||||
} from 'react-native';
|
||||
|
||||
export interface WebViewCommands {
|
||||
|
@ -213,6 +214,7 @@ export interface CommonNativeWebViewProps extends ViewProps {
|
|||
injectedJavaScript?: string;
|
||||
mediaPlaybackRequiresUserAction?: boolean;
|
||||
messagingEnabled: boolean;
|
||||
onScroll?: (event: NativeScrollEvent) => void;
|
||||
onLoadingError: (event: WebViewErrorEvent) => void;
|
||||
onLoadingFinish: (event: WebViewNavigationEvent) => void;
|
||||
onLoadingProgress: (event: WebViewProgressEvent) => void;
|
||||
|
@ -542,6 +544,11 @@ export interface WebViewSharedProps extends ViewProps {
|
|||
*/
|
||||
renderLoading?: () => ReactElement;
|
||||
|
||||
/**
|
||||
* Function that is invoked when the `WebView` scrolls.
|
||||
*/
|
||||
onScroll?: (event: NativeScrollEvent) => void;
|
||||
|
||||
/**
|
||||
* Function that is invoked when the `WebView` has finished loading.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue