Как я могу установитьAllowFileAccess для собственного Android WebView?

Я хочу отключить доступ к файлам в Android WebView, который я создаю, используя встроенный компонент WebView React-native.

В документах Android WebView говорится: "Доступ к файлам ограничен. включено по умолчанию.», и это проблема безопасности для моей организации.

В документах react-native 0.31 упоминается getWebViewHandle метод который можно использовать для доступа к базовому узлу WebView; если бы это сработало, то я мог бы (предположительно) написать:

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

Однако в более поздних версиях react-native docs не упоминается getWebViewHandle, и когда я запускаю такой код в React Native 0.44 на устройстве Android, я получаю сообщение об ошибке webview.getWebViewHandle is not a function.

Мои вопросы:

  1. Включен ли доступ к файлам по умолчанию для Android WebViews, созданных react-native?

  2. Если да, то как мы можем отключить доступ к этому файлу? Можем ли мы добиться этого, расширив класс WebView, или нам нужно будет разветвить и изменить react-native?

Спасибо за ваше время!


person Aaron    schedule 19.08.2017    source источник


Ответы (3)


Вопросы 1: Как видно из исходного кода ReactWebViewManager.java, RN не вызывает WebView.setAllowFileAccess, поэтому доступ к файлам разрешает Android WebView, а не RN.

Вопрос 2. Вы можете создать собственный WebView, чтобы делать то, что вам нужно, или просто получить ссылку на ваш WebView из Собственный модуль, то вы можете получить доступ ко всем API-интерфейсам Android WebView, например setAllowFileAccess, в этом Собственный модуль.

Собственный модуль

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

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

весь код можно найти здесь

person ufxmeng    schedule 24.08.2017

node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/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);
    }
}
person Wez    schedule 04.01.2018
comment
Это огромное количество кода, чтобы ответить на некоторые почти «да» / «нет» вопросы. Я даже не могу сказать, закопано ли там «как», потому что их так много! - person UKMonkey; 04.01.2018

Я нигде не мог найти это решение, поэтому решил поделиться, надеюсь, это поможет...

Чтобы кнопка обзора работала и впоследствии разрешила доступ к файлам, вы можете заменить вышеупомянутые и этот реактивные файлы этими модифицированными версиями:

node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/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();
    }
}

Второй файл указан выше

person Wez    schedule 04.01.2018