12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196 |
- package com.reactnativecommunity.webview;
-
- import android.annotation.SuppressLint;
- import android.annotation.TargetApi;
- import android.app.DownloadManager;
- import android.content.Context;
- import android.content.Intent;
- import android.content.pm.ActivityInfo;
- import android.content.pm.PackageManager;
- import android.graphics.Bitmap;
- import android.graphics.Color;
- import android.Manifest;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Environment;
- import androidx.annotation.RequiresApi;
- import androidx.core.content.ContextCompat;
- import android.text.TextUtils;
- import android.view.Gravity;
- import android.view.View;
- import android.view.ViewGroup;
- import android.view.ViewGroup.LayoutParams;
- import android.view.WindowManager;
- import android.webkit.ConsoleMessage;
- import android.webkit.CookieManager;
- import android.webkit.DownloadListener;
- import android.webkit.GeolocationPermissions;
- import android.webkit.JavascriptInterface;
- import android.webkit.PermissionRequest;
- import android.webkit.URLUtil;
- import android.webkit.ValueCallback;
- import android.webkit.WebChromeClient;
- import android.webkit.WebResourceRequest;
- import android.webkit.WebResourceResponse;
- import android.webkit.WebSettings;
- import android.webkit.WebView;
- import android.webkit.WebViewClient;
- import android.widget.FrameLayout;
-
- import com.facebook.react.views.scroll.ScrollEvent;
- import com.facebook.react.views.scroll.ScrollEventType;
- import com.facebook.react.views.scroll.OnScrollDispatchHelper;
- import com.facebook.react.bridge.Arguments;
- import com.facebook.react.bridge.CatalystInstance;
- 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.bridge.WritableNativeArray;
- import com.facebook.react.bridge.WritableNativeMap;
- 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.reactnativecommunity.webview.events.TopLoadingErrorEvent;
- import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
- import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
- import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
- import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
- import com.reactnativecommunity.webview.events.TopMessageEvent;
- import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
-
- import org.json.JSONException;
- import org.json.JSONObject;
-
- import java.io.UnsupportedEncodingException;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.net.URLEncoder;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Locale;
- import java.util.Map;
-
- import javax.annotation.Nullable;
-
- /**
- * Manages instances of {@link WebView}
- * <p>
- * Can accept following commands:
- * - GO_BACK
- * - GO_FORWARD
- * - RELOAD
- * - LOAD_URL
- * <p>
- * {@link WebView} instances could emit following direct events:
- * - topLoadingFinish
- * - topLoadingStart
- * - topLoadingStart
- * - topLoadingProgress
- * - topShouldStartLoadWithRequest
- * <p>
- * 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
- */
- @ReactModule(name = RNCWebViewManager.REACT_CLASS)
- public class RNCWebViewManager extends SimpleViewManager<WebView> {
-
- public static String activeUrl = null;
- 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;
- public static final int COMMAND_LOAD_URL = 7;
- public static final int COMMAND_FOCUS = 8;
-
- // android commands
- public static final int COMMAND_CLEAR_FORM_DATA = 1000;
- public static final int COMMAND_CLEAR_CACHE = 1001;
- public static final int COMMAND_CLEAR_HISTORY = 1002;
-
- protected static final String REACT_CLASS = "RNCWebView";
- protected static final String HTML_ENCODING = "UTF-8";
- protected static final String HTML_MIME_TYPE = "text/html";
- protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
- protected static final String HTTP_METHOD_POST = "POST";
- // Use `webView.loadUrl("about:blank")` to reliably reset the view
- // state and release page resources (including any running JavaScript).
- protected static final String BLANK_URL = "about:blank";
- protected WebViewConfig mWebViewConfig;
-
- protected RNCWebChromeClient mWebChromeClient = null;
- protected boolean mAllowsFullscreenVideo = false;
- protected @Nullable String mUserAgent = null;
- protected @Nullable String mUserAgentWithApplicationName = null;
-
- public RNCWebViewManager() {
- mWebViewConfig = new WebViewConfig() {
- public void configWebView(WebView webView) {
- }
- };
- }
-
- public RNCWebViewManager(WebViewConfig webViewConfig) {
- mWebViewConfig = webViewConfig;
- }
-
- protected static void dispatchEvent(WebView webView, Event event) {
- ReactContext reactContext = (ReactContext) webView.getContext();
- EventDispatcher eventDispatcher =
- reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
- eventDispatcher.dispatchEvent(event);
- }
-
- @Override
- public String getName() {
- return REACT_CLASS;
- }
-
- protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
- return new RNCWebView(reactContext);
- }
-
- @Override
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- protected WebView createViewInstance(ThemedReactContext reactContext) {
- RNCWebView webView = createRNCWebViewInstance(reactContext);
- setupWebChromeClient(reactContext, webView);
- reactContext.addLifecycleEventListener(webView);
- mWebViewConfig.configWebView(webView);
- WebSettings settings = webView.getSettings();
- settings.setBuiltInZoomControls(true);
- settings.setDisplayZoomControls(false);
- settings.setDomStorageEnabled(true);
-
- settings.setAllowFileAccess(false);
- settings.setAllowContentAccess(false);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- settings.setAllowFileAccessFromFileURLs(false);
- setAllowUniversalAccessFromFileURLs(webView, false);
- }
- setMixedContentMode(webView, "never");
-
- // Fixes broken full-screen modals/galleries due to body height being 0.
- webView.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
-
- if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- WebView.setWebContentsDebuggingEnabled(true);
- }
-
- webView.setDownloadListener(new DownloadListener() {
- public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
- RNCWebViewModule module = getModule(reactContext);
-
- DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
-
- String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
- String downloadMessage = "Downloading " + fileName;
-
- //Attempt to add cookie, if it exists
- URL urlObj = null;
- try {
- urlObj = new URL(url);
- String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
- String cookie = CookieManager.getInstance().getCookie(baseUrl);
- request.addRequestHeader("Cookie", cookie);
- } catch (MalformedURLException e) {
- System.out.println("Error getting cookie for DownloadManager: " + e.toString());
- e.printStackTrace();
- }
-
- //Finish setting up request
- request.addRequestHeader("User-Agent", userAgent);
- request.setTitle(fileName);
- request.setDescription(downloadMessage);
- request.allowScanningByMediaScanner();
- request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
-
- module.setDownloadRequest(request);
-
- if (module.grantFileDownloaderPermissions()) {
- module.downloadFile();
- }
- }
- });
-
- return webView;
- }
-
- @ReactProp(name = "javaScriptEnabled")
- public void setJavaScriptEnabled(WebView view, boolean enabled) {
- view.getSettings().setJavaScriptEnabled(enabled);
- }
-
- @ReactProp(name = "showsHorizontalScrollIndicator")
- public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
- view.setHorizontalScrollBarEnabled(enabled);
- }
-
- @ReactProp(name = "showsVerticalScrollIndicator")
- public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
- view.setVerticalScrollBarEnabled(enabled);
- }
-
- @ReactProp(name = "cacheEnabled")
- public void setCacheEnabled(WebView view, boolean enabled) {
- if (enabled) {
- Context ctx = view.getContext();
- if (ctx != null) {
- view.getSettings().setAppCachePath(ctx.getCacheDir().getAbsolutePath());
- view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
- view.getSettings().setAppCacheEnabled(true);
- }
- } else {
- view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
- view.getSettings().setAppCacheEnabled(false);
- }
- }
-
- @ReactProp(name = "cacheMode")
- public void setCacheMode(WebView view, String cacheModeString) {
- Integer cacheMode;
- switch (cacheModeString) {
- case "LOAD_CACHE_ONLY":
- cacheMode = WebSettings.LOAD_CACHE_ONLY;
- break;
- case "LOAD_CACHE_ELSE_NETWORK":
- cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK;
- break;
- case "LOAD_NO_CACHE":
- cacheMode = WebSettings.LOAD_NO_CACHE;
- break;
- case "LOAD_DEFAULT":
- default:
- cacheMode = WebSettings.LOAD_DEFAULT;
- break;
- }
- view.getSettings().setCacheMode(cacheMode);
- }
-
- @ReactProp(name = "androidHardwareAccelerationDisabled")
- public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
- if (disabled) {
- view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- }
- }
-
- @ReactProp(name = "overScrollMode")
- public void setOverScrollMode(WebView view, String overScrollModeString) {
- Integer overScrollMode;
- switch (overScrollModeString) {
- case "never":
- overScrollMode = View.OVER_SCROLL_NEVER;
- break;
- case "content":
- overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
- break;
- case "always":
- default:
- overScrollMode = View.OVER_SCROLL_ALWAYS;
- break;
- }
- view.setOverScrollMode(overScrollMode);
- }
-
- @ReactProp(name = "thirdPartyCookiesEnabled")
- public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
- }
- }
-
- @ReactProp(name = "textZoom")
- public void setTextZoom(WebView view, int value) {
- view.getSettings().setTextZoom(value);
- }
-
- @ReactProp(name = "scalesPageToFit")
- public void setScalesPageToFit(WebView view, boolean enabled) {
- view.getSettings().setLoadWithOverviewMode(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) {
- mUserAgent = userAgent;
- } else {
- mUserAgent = null;
- }
- this.setUserAgentString(view);
- }
-
- @ReactProp(name = "applicationNameForUserAgent")
- public void setApplicationNameForUserAgent(WebView view, @Nullable String applicationName) {
- if(applicationName != null) {
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
- mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
- }
- } else {
- mUserAgentWithApplicationName = null;
- }
- this.setUserAgentString(view);
- }
-
- protected void setUserAgentString(WebView view) {
- if(mUserAgent != null) {
- view.getSettings().setUserAgentString(mUserAgent);
- } else if(mUserAgentWithApplicationName != null) {
- view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
- } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- // handle unsets of `userAgent` prop as long as device is >= API 17
- view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- @ReactProp(name = "mediaPlaybackRequiresUserAction")
- public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
- view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
- }
-
- @ReactProp(name = "allowFileAccessFromFileURLs")
- public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
- view.getSettings().setAllowFileAccessFromFileURLs(allow);
- }
-
- @ReactProp(name = "allowUniversalAccessFromFileURLs")
- public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
- view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
- }
-
- @ReactProp(name = "saveFormDataDisabled")
- public void setSaveFormDataDisabled(WebView view, boolean disable) {
- view.getSettings().setSaveFormData(!disable);
- }
-
- @ReactProp(name = "injectedJavaScript")
- public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
- ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
- }
-
- @ReactProp(name = "messagingEnabled")
- public void setMessagingEnabled(WebView view, boolean enabled) {
- ((RNCWebView) view).setMessagingEnabled(enabled);
- }
-
- @ReactProp(name = "incognito")
- public void setIncognito(WebView view, boolean enabled) {
- // Remove all previous cookies
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- CookieManager.getInstance().removeAllCookies(null);
- } else {
- CookieManager.getInstance().removeAllCookie();
- }
-
- // Disable caching
- view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
- view.getSettings().setAppCacheEnabled(!enabled);
- view.clearHistory();
- view.clearCache(enabled);
-
- // No form data or autofill enabled
- view.clearFormData();
- view.getSettings().setSavePassword(!enabled);
- view.getSettings().setSaveFormData(!enabled);
- }
-
- @ReactProp(name = "source")
- public void setSource(WebView view, @Nullable ReadableMap source) {
- if (source != null) {
- if (source.hasKey("html")) {
- String html = source.getString("html");
- String baseUrl = source.hasKey("baseUrl") ? source.getString("baseUrl") : "";
- view.loadDataWithBaseURL(baseUrl, html, HTML_MIME_TYPE, HTML_ENCODING, null);
- 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.equalsIgnoreCase(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) {
- ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
- }
-
- @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);
- }
- }
- }
-
- @ReactProp(name = "urlPrefixesForDefaultIntent")
- public void setUrlPrefixesForDefaultIntent(
- WebView view,
- @Nullable ReadableArray urlPrefixesForDefaultIntent) {
- RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
- if (client != null && urlPrefixesForDefaultIntent != null) {
- client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
- }
- }
-
- @ReactProp(name = "allowsFullscreenVideo")
- public void setAllowsFullscreenVideo(
- WebView view,
- @Nullable Boolean allowsFullscreenVideo) {
- mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
- setupWebChromeClient((ReactContext)view.getContext(), view);
- }
-
- @ReactProp(name = "allowFileAccess")
- public void setAllowFileAccess(
- WebView view,
- @Nullable Boolean allowFileAccess) {
- view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
- }
-
- @ReactProp(name = "geolocationEnabled")
- public void setGeolocationEnabled(
- WebView view,
- @Nullable Boolean isGeolocationEnabled) {
- view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
- }
-
- @ReactProp(name = "onScroll")
- public void setOnScroll(WebView view, boolean hasScrollEvent) {
- ((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
- }
-
- @Override
- protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
- // Do not register default touch emitter and let WebView implementation handle touches
- view.setWebViewClient(new RNCWebViewClient());
- }
-
- @Override
- public Map getExportedCustomDirectEventTypeConstants() {
- Map export = super.getExportedCustomDirectEventTypeConstants();
- if (export == null) {
- export = MapBuilder.newHashMap();
- }
- export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
- export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
- export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
- export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
- return export;
- }
-
- @Override
- public @Nullable
- Map<String, Integer> getCommandsMap() {
- return MapBuilder.<String, Integer>builder()
- .put("goBack", COMMAND_GO_BACK)
- .put("goForward", COMMAND_GO_FORWARD)
- .put("reload", COMMAND_RELOAD)
- .put("stopLoading", COMMAND_STOP_LOADING)
- .put("postMessage", COMMAND_POST_MESSAGE)
- .put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
- .put("loadUrl", COMMAND_LOAD_URL)
- .put("requestFocus", COMMAND_FOCUS)
- .put("clearFormData", COMMAND_CLEAR_FORM_DATA)
- .put("clearCache", COMMAND_CLEAR_CACHE)
- .put("clearHistory", COMMAND_CLEAR_HISTORY)
- .build();
- }
-
- @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 {
- RNCWebView reactWebView = (RNCWebView) root;
- JSONObject eventInitDict = new JSONObject();
- eventInitDict.put("data", args.getString(0));
- reactWebView.evaluateJavascriptWithFallback("(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:
- RNCWebView reactWebView = (RNCWebView) root;
- reactWebView.evaluateJavascriptWithFallback(args.getString(0));
- break;
- case COMMAND_LOAD_URL:
- if (args == null) {
- throw new RuntimeException("Arguments for loading an url are null!");
- }
- root.loadUrl(args.getString(0));
- break;
- case COMMAND_FOCUS:
- root.requestFocus();
- break;
- case COMMAND_CLEAR_FORM_DATA:
- root.clearFormData();
- break;
- case COMMAND_CLEAR_CACHE:
- boolean includeDiskFiles = args != null && args.getBoolean(0);
- root.clearCache(includeDiskFiles);
- break;
- case COMMAND_CLEAR_HISTORY:
- root.clearHistory();
- break;
- }
- }
-
- @Override
- public void onDropViewInstance(WebView webView) {
- super.onDropViewInstance(webView);
- ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
- ((RNCWebView) webView).cleanupCallbacksAndDestroy();
- }
-
- public static RNCWebViewModule getModule(ReactContext reactContext) {
- return reactContext.getNativeModule(RNCWebViewModule.class);
- }
-
- protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
- if (mAllowsFullscreenVideo) {
- int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
- mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
- @Override
- public Bitmap getDefaultVideoPoster() {
- return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
- }
-
- @Override
- public void onShowCustomView(View view, CustomViewCallback callback) {
- if (mVideoView != null) {
- callback.onCustomViewHidden();
- return;
- }
-
- mVideoView = view;
- mCustomViewCallback = callback;
-
- mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
- mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
- }
-
- mVideoView.setBackgroundColor(Color.BLACK);
- getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
- mWebView.setVisibility(View.GONE);
-
- mReactContext.addLifecycleEventListener(this);
- }
-
- @Override
- public void onHideCustomView() {
- if (mVideoView == null) {
- return;
- }
-
- mVideoView.setVisibility(View.GONE);
- getRootView().removeView(mVideoView);
- mCustomViewCallback.onCustomViewHidden();
-
- mVideoView = null;
- mCustomViewCallback = null;
-
- mWebView.setVisibility(View.VISIBLE);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
- }
- mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation);
-
- mReactContext.removeLifecycleEventListener(this);
- }
- };
- webView.setWebChromeClient(mWebChromeClient);
- } else {
- if (mWebChromeClient != null) {
- mWebChromeClient.onHideCustomView();
- }
- mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
- @Override
- public Bitmap getDefaultVideoPoster() {
- return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
- }
- };
- webView.setWebChromeClient(mWebChromeClient);
- }
- }
-
- protected static class RNCWebViewClient extends WebViewClient {
-
- protected boolean mLastLoadFailed = false;
- protected @Nullable
- ReadableArray mUrlPrefixesForDefaultIntent;
-
- @Override
- public void onPageFinished(WebView webView, String url) {
- super.onPageFinished(webView, url);
-
- if (!mLastLoadFailed) {
- RNCWebView reactWebView = (RNCWebView) webView;
-
- reactWebView.callInjectedJavaScript();
-
- 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) {
- activeUrl = url;
- dispatchEvent(
- view,
- new TopShouldStartLoadWithRequestEvent(
- view.getId(),
- createWebViewEvent(view, url)));
- return true;
- }
-
-
- @TargetApi(Build.VERSION_CODES.N)
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
- final String url = request.getUrl().toString();
- return this.shouldOverrideUrlLoading(view, url);
- }
-
- @Override
- public void onReceivedError(
- WebView webView,
- int errorCode,
- String description,
- String failingUrl) {
- super.onReceivedError(webView, errorCode, description, failingUrl);
- mLastLoadFailed = true;
-
- // In case of an error JS side expect to get a finish event first, and then get an error event
- // Android WebView does it in the opposite way, so we need to simulate that behavior
- emitFinishEvent(webView, failingUrl);
-
- WritableMap eventData = createWebViewEvent(webView, failingUrl);
- eventData.putDouble("code", errorCode);
- eventData.putString("description", description);
-
- dispatchEvent(
- webView,
- new TopLoadingErrorEvent(webView.getId(), eventData));
- }
-
- @RequiresApi(api = Build.VERSION_CODES.M)
- @Override
- public void onReceivedHttpError(
- WebView webView,
- WebResourceRequest request,
- WebResourceResponse errorResponse) {
- super.onReceivedHttpError(webView, request, errorResponse);
-
- if (request.isForMainFrame()) {
- WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
- eventData.putInt("statusCode", errorResponse.getStatusCode());
- eventData.putString("description", errorResponse.getReasonPhrase());
-
- dispatchEvent(
- webView,
- new TopHttpErrorEvent(webView.getId(), eventData));
- }
- }
-
- protected void emitFinishEvent(WebView webView, String url) {
- dispatchEvent(
- webView,
- new TopLoadingFinishEvent(
- webView.getId(),
- createWebViewEvent(webView, url)));
- }
-
- protected WritableMap createWebViewEvent(WebView webView, String url) {
- WritableMap event = Arguments.createMap();
- event.putDouble("target", webView.getId());
- // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
- // like onPageFinished
- event.putString("url", url);
- event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
- event.putString("title", webView.getTitle());
- event.putBoolean("canGoBack", webView.canGoBack());
- event.putBoolean("canGoForward", webView.canGoForward());
- return event;
- }
-
- public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
- mUrlPrefixesForDefaultIntent = specialUrls;
- }
- }
-
- protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
- protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
-
- @RequiresApi(api = Build.VERSION_CODES.KITKAT)
- protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_FULLSCREEN |
- View.SYSTEM_UI_FLAG_IMMERSIVE |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-
- protected ReactContext mReactContext;
- protected View mWebView;
-
- protected View mVideoView;
- protected WebChromeClient.CustomViewCallback mCustomViewCallback;
-
- public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
- this.mReactContext = reactContext;
- this.mWebView = webView;
- }
-
- @Override
- public boolean onConsoleMessage(ConsoleMessage message) {
- if (ReactBuildConfig.DEBUG) {
- return super.onConsoleMessage(message);
- }
- // Ignore console logs in non debug builds.
- return true;
- }
-
- // Fix WebRTC permission request error.
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public void onPermissionRequest(final PermissionRequest request) {
- String[] requestedResources = request.getResources();
- ArrayList<String> permissions = new ArrayList<>();
- ArrayList<String> grantedPermissions = new ArrayList<String>();
- for (int i = 0; i < requestedResources.length; i++) {
- if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
- permissions.add(Manifest.permission.RECORD_AUDIO);
- } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
- permissions.add(Manifest.permission.CAMERA);
- }
- // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
- }
-
- for (int i = 0; i < permissions.size(); i++) {
- if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
- if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
- grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
- } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
- grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
- }
- }
-
- if (grantedPermissions.isEmpty()) {
- request.deny();
- } else {
- String[] grantedPermissionsArray = new String[grantedPermissions.size()];
- grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
- request.grant(grantedPermissionsArray);
- }
- }
-
- @Override
- public void onProgressChanged(WebView webView, int newProgress) {
- super.onProgressChanged(webView, newProgress);
- final String url = webView.getUrl();
- if (
- url != null
- && activeUrl != null
- && !url.equals(activeUrl)
- ) {
- return;
- }
- WritableMap event = Arguments.createMap();
- event.putDouble("target", webView.getId());
- event.putString("title", webView.getTitle());
- event.putString("url", url);
- event.putBoolean("canGoBack", webView.canGoBack());
- event.putBoolean("canGoForward", webView.canGoForward());
- event.putDouble("progress", (float) newProgress / 100);
- dispatchEvent(
- webView,
- new TopLoadingProgressEvent(
- webView.getId(),
- event));
- }
-
- @Override
- public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
- callback.invoke(origin, true, false);
- }
-
- protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
- getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
- }
-
- protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
- getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
- }
-
- protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
- getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
- String[] acceptTypes = fileChooserParams.getAcceptTypes();
- boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
- Intent intent = fileChooserParams.createIntent();
- return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
- }
-
- @Override
- public void onHostResume() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
- mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
- }
- }
-
- @Override
- public void onHostPause() { }
-
- @Override
- public void onHostDestroy() { }
-
- protected ViewGroup getRootView() {
- return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
- }
- }
-
- /**
- * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
- * to call {@link WebView#destroy} on activity destroy event and also to clear the client
- */
- protected static class RNCWebView extends WebView implements LifecycleEventListener {
- protected @Nullable
- String injectedJS;
- protected boolean messagingEnabled = false;
- protected @Nullable
- RNCWebViewClient mRNCWebViewClient;
- protected @Nullable
- CatalystInstance mCatalystInstance;
- protected boolean sendContentSizeChangeEvents = false;
- private OnScrollDispatchHelper mOnScrollDispatchHelper;
- protected boolean hasScrollEvent = false;
-
- /**
- * WebView must be created with an context of the current activity
- * <p>
- * Activity Context is required for creation of dialogs internally by WebView
- * Reactive Native needed for access to ReactNative internal system functionality
- */
- public RNCWebView(ThemedReactContext reactContext) {
- super(reactContext);
- }
-
- public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
- this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
- }
-
- public void setHasScrollEvent(boolean hasScrollEvent) {
- this.hasScrollEvent = hasScrollEvent;
- }
-
- @Override
- public void onHostResume() {
- // do nothing
- }
-
- @Override
- public void onHostPause() {
- // do nothing
- }
-
- @Override
- public void onHostDestroy() {
- cleanupCallbacksAndDestroy();
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int ow, int oh) {
- super.onSizeChanged(w, h, ow, oh);
-
- if (sendContentSizeChangeEvents) {
- dispatchEvent(
- this,
- new ContentSizeChangeEvent(
- this.getId(),
- w,
- h
- )
- );
- }
- }
-
- @Override
- public void setWebViewClient(WebViewClient client) {
- super.setWebViewClient(client);
- if (client instanceof RNCWebViewClient) {
- mRNCWebViewClient = (RNCWebViewClient) client;
- }
- }
-
- public @Nullable
- RNCWebViewClient getRNCWebViewClient() {
- return mRNCWebViewClient;
- }
-
- public void setInjectedJavaScript(@Nullable String js) {
- injectedJS = js;
- }
-
- protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
- return new RNCWebViewBridge(webView);
- }
-
- protected void createCatalystInstance() {
- ReactContext reactContext = (ReactContext) this.getContext();
-
- if (reactContext != null) {
- mCatalystInstance = reactContext.getCatalystInstance();
- }
- }
-
- @SuppressLint("AddJavascriptInterface")
- public void setMessagingEnabled(boolean enabled) {
- if (messagingEnabled == enabled) {
- return;
- }
-
- messagingEnabled = enabled;
-
- if (enabled) {
- addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
- this.createCatalystInstance();
- } else {
- removeJavascriptInterface(JAVASCRIPT_INTERFACE);
- }
- }
-
- protected void evaluateJavascriptWithFallback(String script) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- evaluateJavascript(script, null);
- return;
- }
-
- try {
- loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
- } catch (UnsupportedEncodingException e) {
- // UTF-8 should always be supported
- throw new RuntimeException(e);
- }
- }
-
- public void callInjectedJavaScript() {
- if (getSettings().getJavaScriptEnabled() &&
- injectedJS != null &&
- !TextUtils.isEmpty(injectedJS)) {
- evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
- }
- }
-
- public void onMessage(String message) {
- ReactContext reactContext = (ReactContext) this.getContext();
- RNCWebView mContext = this;
-
- if (mRNCWebViewClient != null) {
- WebView webView = this;
- webView.post(new Runnable() {
- @Override
- public void run() {
- if (mRNCWebViewClient == null) {
- return;
- }
- WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
- data.putString("data", message);
-
- if (mCatalystInstance != null) {
- mContext.sendDirectMessage(data);
- } else {
- dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
- }
- }
- });
- } else {
- WritableMap eventData = Arguments.createMap();
- eventData.putString("data", message);
-
- if (mCatalystInstance != null) {
- this.sendDirectMessage(eventData);
- } else {
- dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
- }
- }
- }
-
- protected void sendDirectMessage(WritableMap data) {
- WritableNativeMap event = new WritableNativeMap();
- event.putMap("nativeEvent", data);
-
- WritableNativeArray params = new WritableNativeArray();
- params.pushMap(event);
-
- mCatalystInstance.callFunction("WebViewMessageHandler", "onMessage", params);
- }
-
- protected void onScrollChanged(int x, int y, int oldX, int oldY) {
- super.onScrollChanged(x, y, oldX, oldY);
-
- if (!hasScrollEvent) {
- return;
- }
-
- if (mOnScrollDispatchHelper == null) {
- mOnScrollDispatchHelper = new OnScrollDispatchHelper();
- }
-
- if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
- ScrollEvent event = ScrollEvent.obtain(
- this.getId(),
- ScrollEventType.SCROLL,
- x,
- y,
- mOnScrollDispatchHelper.getXFlingVelocity(),
- mOnScrollDispatchHelper.getYFlingVelocity(),
- this.computeHorizontalScrollRange(),
- this.computeVerticalScrollRange(),
- this.getWidth(),
- this.getHeight());
-
- dispatchEvent(this, event);
- }
- }
-
- protected void cleanupCallbacksAndDestroy() {
- setWebViewClient(null);
- destroy();
- }
-
- protected class RNCWebViewBridge {
- RNCWebView mContext;
-
- RNCWebViewBridge(RNCWebView c) {
- mContext = c;
- }
-
- /**
- * This method is called whenever JavaScript running within the web view calls:
- * - window[JAVASCRIPT_INTERFACE].postMessage
- */
- @JavascriptInterface
- public void postMessage(String message) {
- mContext.onMessage(message);
- }
- }
- }
- }
|