mirror of
https://github.com/status-im/react-native-webview-bridge.git
synced 2026-08-31 13:21:15 +00:00
re-define ReactWebView and ReactWebViewClient for RN 0.38.*
This commit is contained in:
+258
-19
@@ -1,22 +1,50 @@
|
||||
package com.github.alinz.reactnativewebviewbridge;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Picture;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.views.webview.events.TopLoadingErrorEvent;
|
||||
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
|
||||
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
|
||||
import com.facebook.react.views.webview.events.TopMessageEvent;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
import android.text.TextUtils;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import android.os.Build;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
import com.facebook.react.views.webview.WebViewConfig;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
@@ -24,6 +52,7 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
private static final String REACT_CLASS = "RCTWebViewBridge";
|
||||
private static final String HTML_ENCODING = "UTF-8";
|
||||
private static final String HTML_MIME_TYPE = "text/html; charset=utf-8";
|
||||
private static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE";
|
||||
|
||||
private static final String HTTP_METHOD_POST = "POST";
|
||||
|
||||
@@ -50,9 +79,7 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
@Nullable
|
||||
Map<String, Integer> getCommandsMap() {
|
||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
||||
Map<String, Integer> commandsMap = super.getCommandsMap();
|
||||
|
||||
commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE);
|
||||
@@ -66,7 +93,7 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
private int last = 100;
|
||||
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress < 70) {
|
||||
if (newProgress < 90) {
|
||||
if(was == false || last < newProgress){
|
||||
was = true;
|
||||
last = newProgress;
|
||||
@@ -144,6 +171,18 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
root.getSettings().setAllowUniversalAccessFromFileURLs(allows);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "injectedJavaScript")
|
||||
public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
|
||||
((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "messagingEnabled")
|
||||
public void setMessagingEnabled(WebView view, boolean enabled) {
|
||||
((ReactWebView) view).setMessagingEnabled(enabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "injectedOnStartLoadingJavaScript")
|
||||
public void setInjectedOnStartLoadingJavaScript(WebView view, @Nullable String injectedJavaScript) {
|
||||
((ReactWebView) view).setInjectedOnStartLoadingJavaScript(injectedJavaScript);
|
||||
@@ -155,15 +194,78 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
view.setWebViewClient(new ReactWebViewClient());
|
||||
}
|
||||
|
||||
protected static class ReactWebView extends ReactWebViewManager.ReactWebView {
|
||||
private static class ReactWebView extends WebView implements LifecycleEventListener {
|
||||
private @Nullable String injectedJS;
|
||||
private @Nullable String injectedOnStartLoadingJS;
|
||||
private boolean messagingEnabled = false;
|
||||
|
||||
private class ReactWebViewBridge {
|
||||
ReactWebView mContext;
|
||||
|
||||
ReactWebViewBridge(ReactWebView c) {
|
||||
mContext = c;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void postMessage(String message) {
|
||||
mContext.onMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebView must be created with an context of the current activity
|
||||
*
|
||||
* Activity Context is required for creation of dialogs internally by WebView
|
||||
* Reactive Native needed for access to ReactNative internal system functionality
|
||||
*
|
||||
*/
|
||||
public ReactWebView(ThemedReactContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
cleanupCallbacksAndDestroy();
|
||||
}
|
||||
|
||||
public void setInjectedJavaScript(@Nullable String js) {
|
||||
injectedJS = js;
|
||||
}
|
||||
|
||||
public void setInjectedOnStartLoadingJavaScript(@Nullable String js) {
|
||||
injectedOnStartLoadingJS = js;
|
||||
}
|
||||
|
||||
public ReactWebView(ThemedReactContext reactContext) {
|
||||
super(reactContext);
|
||||
public void setMessagingEnabled(boolean enabled) {
|
||||
if (messagingEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
messagingEnabled = enabled;
|
||||
if (enabled) {
|
||||
addJavascriptInterface(new ReactWebViewBridge(this), BRIDGE_NAME);
|
||||
linkBridge();
|
||||
} else {
|
||||
removeJavascriptInterface(BRIDGE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void callInjectedJavaScript() {
|
||||
if (getSettings().getJavaScriptEnabled() &&
|
||||
injectedJS != null &&
|
||||
!TextUtils.isEmpty(injectedJS)) {
|
||||
loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();");
|
||||
}
|
||||
}
|
||||
|
||||
public void callInjectedOnStartLoadingJavaScript() {
|
||||
@@ -178,15 +280,152 @@ public class WebViewBridgeManager extends ReactWebViewManager {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ReactWebViewClient extends ReactWebViewManager.ReactWebViewClient {
|
||||
@Override
|
||||
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
|
||||
ReactWebView reactWebView = (ReactWebView) webView;
|
||||
reactWebView.callInjectedOnStartLoadingJavaScript();
|
||||
super.onPageStarted(webView, url, favicon);
|
||||
public void linkBridge() {
|
||||
if (messagingEnabled) {
|
||||
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// See isNative in lodash
|
||||
String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
|
||||
evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
if (value.equals("true")) {
|
||||
FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadUrl("javascript:(" +
|
||||
"window.originalPostMessage = window.postMessage," +
|
||||
"window.postMessage = function(data) {" +
|
||||
BRIDGE_NAME + ".postMessage(String(data));" +
|
||||
"}" +
|
||||
")");
|
||||
}
|
||||
}
|
||||
|
||||
public void onMessage(String message) {
|
||||
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
|
||||
}
|
||||
|
||||
private void cleanupCallbacksAndDestroy() {
|
||||
setWebViewClient(null);
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReactWebViewClient extends WebViewClient {
|
||||
|
||||
private boolean mLastLoadFailed = false;
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView webView, String url) {
|
||||
super.onPageFinished(webView, url);
|
||||
|
||||
if (!mLastLoadFailed) {
|
||||
ReactWebView reactWebView = (ReactWebView) webView;
|
||||
reactWebView.callInjectedJavaScript();
|
||||
reactWebView.linkBridge();
|
||||
emitFinishEvent(webView, url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
|
||||
super.onPageStarted(webView, url, favicon);
|
||||
mLastLoadFailed = false;
|
||||
|
||||
ReactWebView reactWebView = (ReactWebView) webView;
|
||||
reactWebView.callInjectedOnStartLoadingJavaScript();
|
||||
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingStartEvent(
|
||||
webView.getId(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("http://") || url.startsWith("https://") ||
|
||||
url.startsWith("file://")) {
|
||||
return false;
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
view.getContext().startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(
|
||||
WebView webView,
|
||||
int errorCode,
|
||||
String description,
|
||||
String failingUrl) {
|
||||
super.onReceivedError(webView, errorCode, description, failingUrl);
|
||||
mLastLoadFailed = true;
|
||||
|
||||
// In case of an error JS side expect to get a finish event first, and then get an error event
|
||||
// Android WebView does it in the opposite way, so we need to simulate that behavior
|
||||
emitFinishEvent(webView, failingUrl);
|
||||
|
||||
WritableMap eventData = createWebViewEvent(webView, failingUrl);
|
||||
eventData.putDouble("code", errorCode);
|
||||
eventData.putString("description", description);
|
||||
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingErrorEvent(webView.getId(), eventData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
|
||||
super.doUpdateVisitedHistory(webView, url, isReload);
|
||||
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingStartEvent(
|
||||
webView.getId(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
private void emitFinishEvent(WebView webView, String url) {
|
||||
dispatchEvent(
|
||||
webView,
|
||||
new TopLoadingFinishEvent(
|
||||
webView.getId(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
private WritableMap createWebViewEvent(WebView webView, String url) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble("target", webView.getId());
|
||||
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
|
||||
// like onPageFinished
|
||||
event.putString("url", url);
|
||||
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
|
||||
event.putString("title", webView.getTitle());
|
||||
event.putBoolean("canGoBack", webView.canGoBack());
|
||||
event.putBoolean("canGoForward", webView.canGoForward());
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(WebView webView) {
|
||||
//super.onDropViewInstance(webView);
|
||||
((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
|
||||
((ReactWebView) webView).cleanupCallbacksAndDestroy();
|
||||
}
|
||||
|
||||
private static void dispatchEvent(WebView webView, Event event) {
|
||||
ReactContext reactContext = (ReactContext) webView.getContext();
|
||||
EventDispatcher eventDispatcher =
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
eventDispatcher.dispatchEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user