简体   繁体   English

我如何为反应原生的Android WebView设置AllowFileAccess?

[英]How can I setAllowFileAccess for a react-native android WebView?

I want to disable file access within an Android WebView I'm creating using react-native's built-in WebView component. 我想在我使用react-native的内置WebView组件创建的Android WebView中禁用文件访问。

The Android WebView docs say "File access is enabled by default.", and this is a security concern for my organization. Android WebView文档说“默认情况下启用文件访问。”,这是我组织的安全问题。

The react-native 0.31 docs mention a getWebViewHandle method that can be used to access the underlying WebView node; 机0.31文档提到了一个getWebViewHandle方法,该方法可用于访问基础WebView节点。 if this worked, then I could (presumably) write: 如果这行得通,那么我可以(大概)写:

import { WebView, Platform } from 'react-native';
//...
var reactWebview = <Webview [props here] />
if (Platform.OS === 'android') {
    var webview = reactWebview.getWebViewHandle();
    webview.setAllowFileAccess(false);
}

However, later versions of the react-native docs don't mention getWebViewHandle , and when I run code like this in react-native 0.44 on an Android device, I get the error webview.getWebViewHandle is not a function . 但是, react-native文档的更高版本没有提及getWebViewHandle ,并且当我在Android设备上的react-native 0.44中运行这样的代码时,出现错误webview.getWebViewHandle is not a function

My questions are: 我的问题是:

  1. Is file access enabled by default for the Android WebViews created by react-native? 默认情况下是否为react-native创建的Android WebView启用文件访问?

  2. If so, how can we disable this file access? 如果是这样,我们如何禁用此文件访问? Could we accomplish this by extending the WebView class, or would we need to fork and modify react-native? 我们可以通过扩展WebView类来完成此操作,还是需要派生和修改react-native?

Thanks for your time! 谢谢你的时间!

Questions 1: As see from the source code of ReactWebViewManager.java , RN does not call WebView.setAllowFileAccess , so file access is enabled by Android WebView, not by RN. 问题1:从ReactWebViewManager.java的源代码可以看出 ,RN不会调用WebView.setAllowFileAccess ,因此文件访问是由Android WebView而不是RN启用的。

Questions 2: You can create a custom WebView to do what you need, or just get the reference of your WebView from a Native Module , then you can access all the apis of Android WebView like setAllowFileAccess in that Native Module . 问题2:您可以创建自定义WebView来执行所需的操作,或者仅从本机模块获取WebView的引用,然后可以访问该本地模块中的Android WebView的所有setAllowFileAccess (例如setAllowFileAccess

Native Module 本机模块

public class WebViewSettingModule extends ReactContextBaseJavaModule {

    public WebViewSettingModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "WebViewSetting";
    }

    @ReactMethod
    public void setWebView() {

        Activity activity = getCurrentActivity();
        //the id for the ReactRootView is always be 1
        @IdRes int id = 1;
        View view = activity.findViewById(id);
        if (view instanceof ReactRootView) {
            ReactRootView reactRootView = (ReactRootView) view;
            //make sure the WebView is directly child of ReactRootView
            reactRootView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
                @Override
                public void onChildViewAdded(View parent, final View child) {
                    if (child instanceof WebView) {
                        Log.e("onChildViewAdded: ", ((WebView) child).getUrl());
                        //get the reference to the WebView and setAllowFileAccess
                        ((WebView) child).getSettings().setAllowFileAccess(false);
                    }
                }

                @Override
                public void onChildViewRemoved(View parent, View child) {

                }
            });
        }
    }
}

index.android.js index.android.js

import React, {Component} from "react";
import {AppRegistry, View, WebView} from "react-native";
//the native module
import MyWebView from "./src/MyWebView";

export default class WebViewSetting extends Component {

    componentDidMount() {
        //notify native code to modify WebView setting
        MyWebView.setWebView();
    }

    render() {
        return (
            <View style={{flex: 1}}>
                <WebView
                    source={{uri: 'https://github.com/'}}
                    style={{marginTop: 20}}/>
            </View>
        );
    }
}


AppRegistry.registerComponent('WebViewSetting', () => WebViewSetting);

the whole code can be found here 完整的代码可以在这里找到

I could not find this solution anywhere so I thought I would share, I hope it helps... 我在任何地方都找不到此解决方案,所以我想分享一下,希望对您有所帮助...

To enable the browse button to work and subsequently allow file access you can replace the above and this react-native files with these modified versions: 要启用浏览按钮并随后允许文件访问,您可以使用以下修改后的版本替换上述文件和该本机文件:

node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java node_modules /反应天然/ ReactAndroid / SRC /主/ JAVA / COM / Facebook的/反应/ UIManager的/ ThemedReactContext.java

/**
 * 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.facebook.react.uimanager;

import javax.annotation.Nullable;

import android.app.Activity;
import android.content.Context;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.LifecycleEventListener;

//

/**
 * Wraps {@link ReactContext} with the base {@link Context} passed into the constructor.
 * It provides also a way to start activities using the viewContext to which RN native views belong.
 * It delegates lifecycle listener registration to the original instance of {@link ReactContext}
 * which is supposed to receive the lifecycle events. At the same time we disallow receiving
 * lifecycle events for this wrapper instances.
 * TODO: T7538544 Rename ThemedReactContext to be in alignment with name of ReactApplicationContext
 */
public class ThemedReactContext extends ReactContext {

    private final ReactApplicationContext mReactApplicationContext;

    public ThemedReactContext(ReactApplicationContext reactApplicationContext, Context base) {
        super(base);
        initializeWithInstance(reactApplicationContext.getCatalystInstance());
        mReactApplicationContext = reactApplicationContext;
    }

    @Override
    public void addLifecycleEventListener(LifecycleEventListener listener) {
        mReactApplicationContext.addLifecycleEventListener(listener);
    }

    @Override
    public void removeLifecycleEventListener(LifecycleEventListener listener) {
        mReactApplicationContext.removeLifecycleEventListener(listener);
    }

    @Override
    public void addActivityEventListener(ActivityEventListener listener) {
        mReactApplicationContext.addActivityEventListener(listener);
    }


    @Override
    public void removeActivityEventListener(ActivityEventListener listener) {
        mReactApplicationContext.removeActivityEventListener(listener);
    }

    @Override
    public boolean hasCurrentActivity() {
        return mReactApplicationContext.hasCurrentActivity();
    }

    @Override
    public
    @Nullable
    Activity getCurrentActivity() {
        return mReactApplicationContext.getCurrentActivity();
    }
}

The second file is listed above 上面列出了第二个文件

node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/ReactWebViewManager.java node_modules /反应天然/ ReactAndroid / SRC /主/ JAVA / COM / Facebook的/反应/视图/ ReactWebViewManager.java

/**
 * 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.facebook.react.views.webview;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
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 android.webkit.WebSettings;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ActivityEventListener;
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.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 org.json.JSONObject;
import org.json.JSONException;
@ReactModule(name = ReactWebViewManager.REACT_CLASS)
public class ReactWebViewManager extends SimpleViewManager<WebView> {

    protected static final String REACT_CLASS = "RCTWebView";

    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";

    public static final int COMMAND_GO_BACK = 1;
    public static final int COMMAND_GO_FORWARD = 2;
    public static final int COMMAND_RELOAD = 3;
    public static final int COMMAND_STOP_LOADING = 4;
    public static final int COMMAND_POST_MESSAGE = 5;
    public static final int COMMAND_INJECT_JAVASCRIPT = 6;

    private static final String BLANK_URL = "about:blank";
    public static final int INPUT_FILE_REQUEST_GALLERY_IMAGE = 1001;
    public static final int REQUEST_SELECT_FILE_LEGACY = 1012;

    private WebViewConfig mWebViewConfig;
    private
    @Nullable
    WebView.PictureListener mPictureListener;
    private ValueCallback<Uri[]> mFilePathCallbackArr;
    private ValueCallback<Uri> mFilePathCallback; // Legacy (Android 4.1+)
    private String mCameraPhotoPath;

    protected 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;

            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 {
                try {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    view.getContext().startActivity(intent);
                } catch (ActivityNotFoundException e) {
                    FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
                }
                return true;
            }
        }

        @Override
        public void onReceivedError(
                WebView webView,
                int errorCode,
                String description,
                String failingUrl) {
            super.onReceivedError(webView, errorCode, description, failingUrl);
            mLastLoadFailed = true;

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

    protected static class ReactWebView extends WebView implements LifecycleEventListener {
        private
        @Nullable
        String injectedJS;
        private boolean messagingEnabled = false;

        private class ReactWebViewBridge {
            ReactWebView mContext;

            ReactWebViewBridge(ReactWebView c) {
                mContext = c;
            }

            @JavascriptInterface
            public void postMessage(String message) {
                mContext.onMessage(message);
            }
        }
        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 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 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();
        }
    }

    public ReactWebViewManager() {
        mWebViewConfig = new WebViewConfig() {
            public void configWebView(WebView webView) {
            }
        };
    }

    public ReactWebViewManager(WebViewConfig webViewConfig) {
        mWebViewConfig = webViewConfig;
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    protected WebView createViewInstance(final ThemedReactContext reactContext) {
        ReactWebView webView = new ReactWebView(reactContext);
        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onConsoleMessage(ConsoleMessage message) {
                if (ReactBuildConfig.DEBUG) {
                    return super.onConsoleMessage(message);
                }
                return true;
            }

            @Override
            public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
                callback.invoke(origin, true, false);
            }

            private File createImageFile() throws IOException {
                String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
                String imageFileName = "JPEG_" + timeStamp + "_";
                File storageDir = Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_PICTURES);
                File imageFile = new File(
                        storageDir,
                        imageFileName + ".jpg"
                );

                return imageFile;
            }


            private Intent getVideoCaptureIntent() {
                Intent recordVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
                recordVideoIntent
                        .resolveActivity(
                                reactContext
                                    .getCurrentActivity()
                                    .getPackageManager()
                        );

                recordVideoIntent.putExtra("type", "foobar");
                return recordVideoIntent;
            }

            public boolean onShowFileChooser(
                    WebView webView,
                    ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams
            ) {
                if (mFilePathCallbackArr != null) {
                    mFilePathCallbackArr.onReceiveValue(null);
                }
                mFilePathCallbackArr = filePathCallback;

                Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                ComponentName comp = takePictureIntent
                                        .resolveActivity(
                                                reactContext
                                                        .getCurrentActivity()
                                                        .getPackageManager()
                                        );
                if (comp != null) {
                    File photoFile = null;
                    try {
                        photoFile = createImageFile();
                        takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                    } catch (IOException ex) {
                        FLog.e(ReactConstants.TAG, "Unable to create Image File", ex);
                    }

                    if (photoFile != null) {
                        mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                                Uri.fromFile(photoFile));
                    } else {
                        takePictureIntent = null;
                    }
                }

                Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                contentSelectionIntent.setType("*/*");

                Intent videoCaptureIntent = getVideoCaptureIntent();

                Intent[] intentArray;
                if (takePictureIntent != null) {
                    intentArray = new Intent[]{takePictureIntent};
                } else {
                    intentArray = new Intent[0];
                }

                intentArray = new Intent[]{
                        intentArray[0],
                        videoCaptureIntent
                };

                Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

                reactContext.getCurrentActivity().startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_GALLERY_IMAGE);

                return true;
            }
        });


        reactContext.addLifecycleEventListener(webView);

        reactContext.addActivityEventListener(new ActivityEventListener() {

            // Android 5+
            @Override
            public void onActivityResult (Activity activity, int requestCode, int resultCode, Intent data) {
                if(requestCode != INPUT_FILE_REQUEST_GALLERY_IMAGE || mFilePathCallbackArr == null) {
                    return;
                }
                Uri[] results = null;

                if(resultCode == Activity.RESULT_OK) {
                    if(data == null || data.getData() == null) {
                        if(mCameraPhotoPath != null) {
                            results = new Uri[]{Uri.parse(mCameraPhotoPath)};
                        }
                    } else {
                        String dataString = data.getDataString();
                        if (dataString != null) {
                            results = new Uri[]{Uri.parse(dataString)};
                        }
                    }
                }

                if(results == null) {
                    mFilePathCallbackArr.onReceiveValue(new Uri[]{});
                }
                else {
                    mFilePathCallbackArr.onReceiveValue(results);
                }
                mFilePathCallbackArr = null;
                return;
            }

            @Override
            public void onNewIntent(Intent intent) {}
        });

        mWebViewConfig.configWebView(webView);
        webView.getSettings().setBuiltInZoomControls(true);
        webView.getSettings().setDisplayZoomControls(false);
        webView.getSettings().setDomStorageEnabled(true);
        webView.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT));

        if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        return webView;
    }

    @ReactProp(name = "javaScriptEnabled")
    public void setJavaScriptEnabled(WebView view, boolean enabled) {
        view.getSettings().setJavaScriptEnabled(enabled);
    }

    @ReactProp(name = "scalesPageToFit")
    public void setScalesPageToFit(WebView view, boolean enabled) {
        view.getSettings().setUseWideViewPort(!enabled);
    }

    @ReactProp(name = "domStorageEnabled")
    public void setDomStorageEnabled(WebView view, boolean enabled) {
        view.getSettings().setDomStorageEnabled(enabled);
    }

    @ReactProp(name = "userAgent")
    public void setUserAgent(WebView view, @Nullable String userAgent) {
        if (userAgent != null) {
            // TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
            view.getSettings().setUserAgentString(userAgent);
        }
    }

    @ReactProp(name = "mediaPlaybackRequiresUserAction")
    public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
        view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
    }

    @ReactProp(name = "allowUniversalAccessFromFileURLs")
    public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
        view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
    }

    @ReactProp(name = "injectedJavaScript")
    public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
        ((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
    }

    @ReactProp(name = "messagingEnabled")
    public void setMessagingEnabled(WebView view, boolean enabled) {
        ((ReactWebView) view).setMessagingEnabled(enabled);
    }

    @ReactProp(name = "source")
    public void setSource(WebView view, @Nullable ReadableMap source) {
    if (source != null) {
        if (source.hasKey("html")) {
            String html = source.getString("html");
            if (source.hasKey("baseUrl")) {
                view.loadDataWithBaseURL(
                        source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
            } else {
                view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING);
            }
            return;
        }
        if (source.hasKey("uri")) {
            String url = source.getString("uri");
            String previousUrl = view.getUrl();
            if (previousUrl != null && previousUrl.equals(url)) {
                return;
            }
            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();
                    if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
                        if (view.getSettings() != null) {
                            view.getSettings().setUserAgentString(headers.getString(key));
                        }
                    } else {
                        headerMap.put(key, headers.getString(key));
                    }
                }
            }
            view.loadUrl(url, headerMap);
            return;
        }
    }
    view.loadUrl(BLANK_URL);
    }

    @ReactProp(name = "onContentSizeChange")
    public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
        if (sendContentSizeChangeEvents) {
            view.setPictureListener(getPictureListener());
        } else {
            view.setPictureListener(null);
        }
    }

    @ReactProp(name = "mixedContentMode")
    public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (mixedContentMode == null || "never".equals(mixedContentMode)) {
                view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
            } else if ("always".equals(mixedContentMode)) {
                view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
            } else if ("compatibility".equals(mixedContentMode)) {
                view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
            }
        }
    }

    @Override
    protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
        view.setWebViewClient(new ReactWebViewClient());
    }
    @Override
    public
    @Nullable
    Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(
                "goBack", COMMAND_GO_BACK,
                "goForward", COMMAND_GO_FORWARD,
                "reload", COMMAND_RELOAD,
                "stopLoading", COMMAND_STOP_LOADING,
                "postMessage", COMMAND_POST_MESSAGE,
                "injectJavaScript", COMMAND_INJECT_JAVASCRIPT
        );
    }

    @Override
    public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
        switch (commandId) {
            case COMMAND_GO_BACK:
                root.goBack();
                break;
            case COMMAND_GO_FORWARD:
                root.goForward();
                break;
            case COMMAND_RELOAD:
                root.reload();
                break;
            case COMMAND_STOP_LOADING:
                root.stopLoading();
                break;
            case COMMAND_POST_MESSAGE:
                try {
                    JSONObject eventInitDict = new JSONObject();
                    eventInitDict.put("data", args.getString(0));
                    root.loadUrl("javascript:(function () {" +
                            "var event;" +
                            "var data = " + eventInitDict.toString() + ";" +
                            "try {" +
                            "event = new MessageEvent('message', data);" +
                            "} catch (e) {" +
                            "event = document.createEvent('MessageEvent');" +
                            "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
                            "}" +
                            "document.dispatchEvent(event);" +
                            "})();");
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
                break;
            case COMMAND_INJECT_JAVASCRIPT:
                root.loadUrl("javascript:" + args.getString(0));
                break;
        }
    }

    @Override
    public void onDropViewInstance(WebView webView) {
        super.onDropViewInstance(webView);
        ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
        ((ReactWebView) webView).cleanupCallbacksAndDestroy();
    }

    private WebView.PictureListener getPictureListener() {
        if (mPictureListener == null) {
            mPictureListener = new WebView.PictureListener() {
                @Override
                public void onNewPicture(WebView webView, Picture picture) {
                    dispatchEvent(
                            webView,
                            new ContentSizeChangeEvent(
                                    webView.getId(),
                                    webView.getWidth(),
                                    webView.getContentHeight()));
                }
            };
        }
        return mPictureListener;
    }
    private static void dispatchEvent(WebView webView, Event event) {
        ReactContext reactContext = (ReactContext) webView.getContext();
        EventDispatcher eventDispatcher =
                reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
        eventDispatcher.dispatchEvent(event);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM