com.testproject.webview.XWalkViewManager.java Source code

Java tutorial

Introduction

Here is the source code for com.testproject.webview.XWalkViewManager.java

Source

/**
 * 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();
    }
}