Java tutorial
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.testproject.webview; import javax.annotation.Nullable; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import android.app.Activity; import android.content.Context; import android.os.Build; import android.text.TextUtils; import android.webkit.WebView; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; 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.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.WritableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.SystemClock; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; import org.xwalk.core.XWalkNavigationHistory; import org.xwalk.core.XWalkResourceClient; import org.xwalk.core.XWalkView; import org.xwalk.core.XWalkWebResourceRequest; import org.xwalk.core.XWalkWebResourceResponse; import org.xwalk.core.internal.XWalkViewBridge; /** * Manages instances of {@link WebView} * * Can accept following commands: * - GO_BACK * - GO_FORWARD * - RELOAD * * {@link WebView} instances could emit following direct events: * - topLoadingFinish * - topLoadingStart * - topLoadingError * * Each event will carry the following properties: * - target - view's react tag * - url - url set for the webview * - loading - whether webview is in a loading state * - title - title of the current page * - canGoBack - boolean, whether there is anything on a history stack to go back * - canGoForward - boolean, whether it is possible to request GO_FORWARD command */ public class XWalkViewManager extends SimpleViewManager<XWalkView> { private static final String REACT_CLASS = "XWalkView"; private static final String HTML_ENCODING = "UTF-8"; private static final String HTML_MIME_TYPE = "text/html; charset=utf-8"; private static final String HTTP_METHOD_POST = "POST"; public static final int COMMAND_GO_BACK = 1; public static final int COMMAND_GO_FORWARD = 2; public static final int COMMAND_RELOAD = 3; // Use `webView.loadUrl("about:blank")` to reliably reset the view // state and release page resources (including any running JavaScript). private static final String BLANK_URL = "about:blank"; private XWalkViewConfig mWebViewConfig; private Activity mActivity; private static class ResourceClient extends XWalkResourceClient { private boolean mLastLoadFailed = false; private int progressInPercent = 0; public ResourceClient(XWalkView view) { super(view); } @Override public void onReceivedResponseHeaders(XWalkView view, XWalkWebResourceRequest request, XWalkWebResourceResponse response) { super.onReceivedResponseHeaders(view, request, response); } @Override public void onLoadFinished(XWalkView webView, String url) { super.onLoadFinished(webView, url); if (!mLastLoadFailed) { RXWalkView reactWebView = (RXWalkView) webView; reactWebView.callInjectedJavaScript(); emitFinishEvent(webView, url); } } @Override public void onLoadStarted(XWalkView webView, String url) { super.onLoadStarted(webView, url); mLastLoadFailed = false; dispatchEvent(webView, new TopLoadingStartEvent(webView.getId(), SystemClock.nanoTime(), createWebViewEvent(webView, url))); } @Override public void onReceivedLoadError(XWalkView webView, int errorCode, String description, String failingUrl) { super.onReceivedLoadError(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(), SystemClock.nanoTime(), eventData)); } @Override public void doUpdateVisitedHistory(XWalkView webView, String url, boolean isReload) { super.doUpdateVisitedHistory(webView, url, isReload); dispatchEvent(webView, new TopLoadingStartEvent(webView.getId(), SystemClock.nanoTime(), createWebViewEvent(webView, url))); } private void emitFinishEvent(XWalkView webView, String url) { dispatchEvent(webView, new TopLoadingFinishEvent(webView.getId(), SystemClock.nanoTime(), createWebViewEvent(webView, url))); } private static void dispatchEvent(XWalkView webView, Event event) { ReactContext reactContext = (ReactContext) ((RXWalkView) webView).getReactContext(); EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class) .getEventDispatcher(); eventDispatcher.dispatchEvent(event); } @Override public void onProgressChanged(XWalkView view, int progressInPercent) { super.onProgressChanged(view, progressInPercent); this.progressInPercent = progressInPercent; } private WritableMap createWebViewEvent(XWalkView 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 && this.progressInPercent != 100); event.putString("title", webView.getTitle()); event.putBoolean("canGoBack", webView.getNavigationHistory().canGoBack()); event.putBoolean("canGoForward", webView.getNavigationHistory().canGoForward()); return event; } } /** * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order * to call {@link WebView#destroy} on activty destroy event and also to clear the client */ private static class RXWalkView extends XWalkView implements LifecycleEventListener { private @Nullable String injectedJS; private ReactContext reactContext; /** * 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 RXWalkView(Context context) { super(context); } public ReactContext getReactContext() { return reactContext; } public void setReactContext(ReactContext reactContext) { this.reactContext = 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 callInjectedJavaScript() { if (injectedJS != null && !TextUtils.isEmpty(injectedJS)) { super.load(null, "javascript:(function() {\n" + injectedJS + ";\n})();"); } } private void cleanupCallbacksAndDestroy() { super.setResourceClient(null); super.onDestroy(); } } public XWalkViewManager(Activity activity) { mActivity = activity; mWebViewConfig = new XWalkViewConfig() { public void configWebView(XWalkView webView) { } }; } public XWalkViewManager(XWalkViewConfig webViewConfig) { mWebViewConfig = webViewConfig; } @Override public String getName() { return REACT_CLASS; } @Override protected XWalkView createViewInstance(ThemedReactContext reactContext) { RXWalkView webView = new RXWalkView(mActivity); webView.setReactContext(reactContext); webView.setResourceClient(new ResourceClient(webView)); reactContext.addLifecycleEventListener(webView); mWebViewConfig.configWebView(webView); if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); } return webView; } @ReactProp(name = "url") public void setUrl(XWalkView view, @android.support.annotation.Nullable String url) { view.load(url, null); } @ReactProp(name = "javaScriptEnabled") public void setJavaScriptEnabled(XWalkView view, @android.support.annotation.Nullable boolean enabled) { try { Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge"); ___getBridge.setAccessible(true); XWalkViewBridge xWalkViewBridge = null; xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view); xWalkViewBridge.getSettings().setJavaScriptEnabled(enabled); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @ReactProp(name = "scalesPageToFit") public void setScalesPageToFit(XWalkView view, boolean enabled) { view.getSettings().setUseWideViewPort(!enabled); } @ReactProp(name = "domStorageEnabled") public void setDomStorageEnabled(XWalkView view, boolean enabled) { try { Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge"); ___getBridge.setAccessible(true); XWalkViewBridge xWalkViewBridge = null; xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view); xWalkViewBridge.getSettings().setDomStorageEnabled(enabled); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @ReactProp(name = "userAgent") public void setUserAgent(XWalkView view, @Nullable String userAgent) { view.getSettings().setUserAgentString(userAgent); } @ReactProp(name = "mediaPlaybackRequiresUserAction") public void setMediaPlaybackRequiresUserAction(XWalkView view, boolean requires) { try { Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge"); ___getBridge.setAccessible(true); XWalkViewBridge xWalkViewBridge = null; xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view); xWalkViewBridge.getSettings().setMediaPlaybackRequiresUserGesture(requires); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @ReactProp(name = "injectedJavaScript") public void setInjectedJavaScript(XWalkView view, @Nullable String injectedJavaScript) { ((RXWalkView) view).setInjectedJavaScript(injectedJavaScript); } @ReactProp(name = "source") public void setSource(XWalkView view, @Nullable ReadableMap source) { if (source != null) { if (source.hasKey("html")) { String html = source.getString("html"); if (source.hasKey("baseUrl")) { view.load(source.getString("baseUrl"), html); } else { view.load(null, html); } return; } if (source.hasKey("uri")) { String url = source.getString("uri"); if (source.hasKey("method")) { String method = source.getString("method"); if (method.equals(HTTP_METHOD_POST)) { byte[] postData = null; if (source.hasKey("body")) { String body = source.getString("body"); try { postData = body.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { postData = body.getBytes(); } } if (postData == null) { postData = new byte[0]; } // view.postUrl(url, postData); return; } } HashMap<String, String> headerMap = new HashMap<>(); if (source.hasKey("headers")) { ReadableMap headers = source.getMap("headers"); ReadableMapKeySetIterator iter = headers.keySetIterator(); while (iter.hasNextKey()) { String key = iter.nextKey(); headerMap.put(key, headers.getString(key)); } } // view.loadUrl(url, headerMap); view.load(url, null); return; } } view.load(null, BLANK_URL); } @Override protected void addEventEmitters(ThemedReactContext reactContext, XWalkView view) { // Do not register default touch emitter and let WebView implementation handle touches view.setResourceClient(new ResourceClient(view)); } @Override public @Nullable Map<String, Integer> getCommandsMap() { return MapBuilder.of("goBack", COMMAND_GO_BACK, "goForward", COMMAND_GO_FORWARD, "reload", COMMAND_RELOAD); } @Override public void receiveCommand(XWalkView view, int commandId, @Nullable ReadableArray args) { switch (commandId) { case COMMAND_GO_BACK: view.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1); break; case COMMAND_GO_FORWARD: view.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.FORWARD, 1); break; case COMMAND_RELOAD: view.reload(XWalkView.RELOAD_NORMAL); break; } } @Override public void onDropViewInstance(XWalkView webView) { super.onDropViewInstance(webView); ((ThemedReactContext) ((RXWalkView) webView).getReactContext()) .removeLifecycleEventListener((RXWalkView) webView); ((RXWalkView) webView).cleanupCallbacksAndDestroy(); } }