No Description

RNCWebViewManager.java 50KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430
  1. package com.reactnativecommunity.webview;
  2. import android.annotation.SuppressLint;
  3. import android.annotation.TargetApi;
  4. import android.app.DownloadManager;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.pm.ActivityInfo;
  8. import android.content.pm.PackageManager;
  9. import android.graphics.Bitmap;
  10. import android.graphics.Color;
  11. import android.Manifest;
  12. import android.net.http.SslError;
  13. import android.net.Uri;
  14. import android.os.Build;
  15. import android.os.Environment;
  16. import androidx.annotation.RequiresApi;
  17. import androidx.core.content.ContextCompat;
  18. import android.text.TextUtils;
  19. import android.util.Log;
  20. import android.view.Gravity;
  21. import android.view.View;
  22. import android.view.ViewGroup;
  23. import android.view.ViewGroup.LayoutParams;
  24. import android.view.WindowManager;
  25. import android.webkit.ConsoleMessage;
  26. import android.webkit.CookieManager;
  27. import android.webkit.DownloadListener;
  28. import android.webkit.GeolocationPermissions;
  29. import android.webkit.JavascriptInterface;
  30. import android.webkit.RenderProcessGoneDetail;
  31. import android.webkit.SslErrorHandler;
  32. import android.webkit.PermissionRequest;
  33. import android.webkit.URLUtil;
  34. import android.webkit.ValueCallback;
  35. import android.webkit.WebChromeClient;
  36. import android.webkit.WebResourceRequest;
  37. import android.webkit.WebResourceResponse;
  38. import android.webkit.WebSettings;
  39. import android.webkit.WebView;
  40. import android.webkit.WebViewClient;
  41. import android.widget.FrameLayout;
  42. import com.facebook.react.views.scroll.ScrollEvent;
  43. import com.facebook.react.views.scroll.ScrollEventType;
  44. import com.facebook.react.views.scroll.OnScrollDispatchHelper;
  45. import com.facebook.react.bridge.Arguments;
  46. import com.facebook.react.bridge.CatalystInstance;
  47. import com.facebook.react.bridge.LifecycleEventListener;
  48. import com.facebook.react.bridge.ReactContext;
  49. import com.facebook.react.bridge.ReadableArray;
  50. import com.facebook.react.bridge.ReadableMap;
  51. import com.facebook.react.bridge.ReadableMapKeySetIterator;
  52. import com.facebook.react.bridge.WritableMap;
  53. import com.facebook.react.bridge.WritableNativeArray;
  54. import com.facebook.react.bridge.WritableNativeMap;
  55. import com.facebook.react.common.MapBuilder;
  56. import com.facebook.react.common.build.ReactBuildConfig;
  57. import com.facebook.react.module.annotations.ReactModule;
  58. import com.facebook.react.uimanager.SimpleViewManager;
  59. import com.facebook.react.uimanager.ThemedReactContext;
  60. import com.facebook.react.uimanager.UIManagerModule;
  61. import com.facebook.react.uimanager.annotations.ReactProp;
  62. import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
  63. import com.facebook.react.uimanager.events.Event;
  64. import com.facebook.react.uimanager.events.EventDispatcher;
  65. import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
  66. import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
  67. import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
  68. import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
  69. import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
  70. import com.reactnativecommunity.webview.events.TopMessageEvent;
  71. import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
  72. import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
  73. import org.json.JSONException;
  74. import org.json.JSONObject;
  75. import java.io.UnsupportedEncodingException;
  76. import java.net.MalformedURLException;
  77. import java.net.URL;
  78. import java.net.URLEncoder;
  79. import java.util.ArrayList;
  80. import java.util.HashMap;
  81. import java.util.Locale;
  82. import java.util.Map;
  83. import javax.annotation.Nullable;
  84. /**
  85. * Manages instances of {@link WebView}
  86. * <p>
  87. * Can accept following commands:
  88. * - GO_BACK
  89. * - GO_FORWARD
  90. * - RELOAD
  91. * - LOAD_URL
  92. * <p>
  93. * {@link WebView} instances could emit following direct events:
  94. * - topLoadingFinish
  95. * - topLoadingStart
  96. * - topLoadingStart
  97. * - topLoadingProgress
  98. * - topShouldStartLoadWithRequest
  99. * <p>
  100. * Each event will carry the following properties:
  101. * - target - view's react tag
  102. * - url - url set for the webview
  103. * - loading - whether webview is in a loading state
  104. * - title - title of the current page
  105. * - canGoBack - boolean, whether there is anything on a history stack to go back
  106. * - canGoForward - boolean, whether it is possible to request GO_FORWARD command
  107. */
  108. @ReactModule(name = RNCWebViewManager.REACT_CLASS)
  109. public class RNCWebViewManager extends SimpleViewManager<WebView> {
  110. public static final int COMMAND_GO_BACK = 1;
  111. public static final int COMMAND_GO_FORWARD = 2;
  112. public static final int COMMAND_RELOAD = 3;
  113. public static final int COMMAND_STOP_LOADING = 4;
  114. public static final int COMMAND_POST_MESSAGE = 5;
  115. public static final int COMMAND_INJECT_JAVASCRIPT = 6;
  116. public static final int COMMAND_LOAD_URL = 7;
  117. public static final int COMMAND_FOCUS = 8;
  118. // android commands
  119. public static final int COMMAND_CLEAR_FORM_DATA = 1000;
  120. public static final int COMMAND_CLEAR_CACHE = 1001;
  121. public static final int COMMAND_CLEAR_HISTORY = 1002;
  122. protected static final String REACT_CLASS = "RNCWebView";
  123. protected static final String HTML_ENCODING = "UTF-8";
  124. protected static final String HTML_MIME_TYPE = "text/html";
  125. protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
  126. protected static final String HTTP_METHOD_POST = "POST";
  127. // Use `webView.loadUrl("about:blank")` to reliably reset the view
  128. // state and release page resources (including any running JavaScript).
  129. protected static final String BLANK_URL = "about:blank";
  130. protected WebViewConfig mWebViewConfig;
  131. protected RNCWebChromeClient mWebChromeClient = null;
  132. protected boolean mAllowsFullscreenVideo = false;
  133. protected @Nullable String mUserAgent = null;
  134. protected @Nullable String mUserAgentWithApplicationName = null;
  135. public RNCWebViewManager() {
  136. mWebViewConfig = new WebViewConfig() {
  137. public void configWebView(WebView webView) {
  138. }
  139. };
  140. }
  141. public RNCWebViewManager(WebViewConfig webViewConfig) {
  142. mWebViewConfig = webViewConfig;
  143. }
  144. protected static void dispatchEvent(WebView webView, Event event) {
  145. ReactContext reactContext = (ReactContext) webView.getContext();
  146. EventDispatcher eventDispatcher =
  147. reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
  148. eventDispatcher.dispatchEvent(event);
  149. }
  150. @Override
  151. public String getName() {
  152. return REACT_CLASS;
  153. }
  154. protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
  155. return new RNCWebView(reactContext);
  156. }
  157. @Override
  158. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  159. protected WebView createViewInstance(ThemedReactContext reactContext) {
  160. RNCWebView webView = createRNCWebViewInstance(reactContext);
  161. setupWebChromeClient(reactContext, webView);
  162. reactContext.addLifecycleEventListener(webView);
  163. mWebViewConfig.configWebView(webView);
  164. WebSettings settings = webView.getSettings();
  165. settings.setBuiltInZoomControls(true);
  166. settings.setDisplayZoomControls(false);
  167. settings.setDomStorageEnabled(true);
  168. settings.setAllowFileAccess(false);
  169. settings.setAllowContentAccess(false);
  170. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  171. settings.setAllowFileAccessFromFileURLs(false);
  172. setAllowUniversalAccessFromFileURLs(webView, false);
  173. }
  174. setMixedContentMode(webView, "never");
  175. // Fixes broken full-screen modals/galleries due to body height being 0.
  176. webView.setLayoutParams(
  177. new LayoutParams(LayoutParams.MATCH_PARENT,
  178. LayoutParams.MATCH_PARENT));
  179. if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  180. WebView.setWebContentsDebuggingEnabled(true);
  181. }
  182. webView.setDownloadListener(new DownloadListener() {
  183. public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
  184. webView.setIgnoreErrFailedForThisURL(url);
  185. RNCWebViewModule module = getModule(reactContext);
  186. DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
  187. String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
  188. String downloadMessage = "Downloading " + fileName;
  189. //Attempt to add cookie, if it exists
  190. URL urlObj = null;
  191. try {
  192. urlObj = new URL(url);
  193. String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
  194. String cookie = CookieManager.getInstance().getCookie(baseUrl);
  195. request.addRequestHeader("Cookie", cookie);
  196. } catch (MalformedURLException e) {
  197. System.out.println("Error getting cookie for DownloadManager: " + e.toString());
  198. e.printStackTrace();
  199. }
  200. //Finish setting up request
  201. request.addRequestHeader("User-Agent", userAgent);
  202. request.setTitle(fileName);
  203. request.setDescription(downloadMessage);
  204. request.allowScanningByMediaScanner();
  205. request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  206. request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
  207. module.setDownloadRequest(request);
  208. if (module.grantFileDownloaderPermissions()) {
  209. module.downloadFile();
  210. }
  211. }
  212. });
  213. return webView;
  214. }
  215. @ReactProp(name = "javaScriptEnabled")
  216. public void setJavaScriptEnabled(WebView view, boolean enabled) {
  217. view.getSettings().setJavaScriptEnabled(enabled);
  218. }
  219. @ReactProp(name = "showsHorizontalScrollIndicator")
  220. public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
  221. view.setHorizontalScrollBarEnabled(enabled);
  222. }
  223. @ReactProp(name = "showsVerticalScrollIndicator")
  224. public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
  225. view.setVerticalScrollBarEnabled(enabled);
  226. }
  227. @ReactProp(name = "cacheEnabled")
  228. public void setCacheEnabled(WebView view, boolean enabled) {
  229. if (enabled) {
  230. Context ctx = view.getContext();
  231. if (ctx != null) {
  232. view.getSettings().setAppCachePath(ctx.getCacheDir().getAbsolutePath());
  233. view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
  234. view.getSettings().setAppCacheEnabled(true);
  235. }
  236. } else {
  237. view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  238. view.getSettings().setAppCacheEnabled(false);
  239. }
  240. }
  241. @ReactProp(name = "cacheMode")
  242. public void setCacheMode(WebView view, String cacheModeString) {
  243. Integer cacheMode;
  244. switch (cacheModeString) {
  245. case "LOAD_CACHE_ONLY":
  246. cacheMode = WebSettings.LOAD_CACHE_ONLY;
  247. break;
  248. case "LOAD_CACHE_ELSE_NETWORK":
  249. cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK;
  250. break;
  251. case "LOAD_NO_CACHE":
  252. cacheMode = WebSettings.LOAD_NO_CACHE;
  253. break;
  254. case "LOAD_DEFAULT":
  255. default:
  256. cacheMode = WebSettings.LOAD_DEFAULT;
  257. break;
  258. }
  259. view.getSettings().setCacheMode(cacheMode);
  260. }
  261. @ReactProp(name = "androidHardwareAccelerationDisabled")
  262. public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
  263. if (disabled) {
  264. view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  265. }
  266. }
  267. @ReactProp(name = "androidLayerType")
  268. public void setLayerType(WebView view, String layerTypeString) {
  269. int layerType = View.LAYER_TYPE_NONE;
  270. switch (layerTypeString) {
  271. case "hardware":
  272. layerType = View.LAYER_TYPE_HARDWARE;
  273. break;
  274. case "software":
  275. layerType = View.LAYER_TYPE_SOFTWARE;
  276. break;
  277. }
  278. view.setLayerType(layerType, null);
  279. }
  280. @ReactProp(name = "overScrollMode")
  281. public void setOverScrollMode(WebView view, String overScrollModeString) {
  282. Integer overScrollMode;
  283. switch (overScrollModeString) {
  284. case "never":
  285. overScrollMode = View.OVER_SCROLL_NEVER;
  286. break;
  287. case "content":
  288. overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
  289. break;
  290. case "always":
  291. default:
  292. overScrollMode = View.OVER_SCROLL_ALWAYS;
  293. break;
  294. }
  295. view.setOverScrollMode(overScrollMode);
  296. }
  297. @ReactProp(name = "thirdPartyCookiesEnabled")
  298. public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
  299. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  300. CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
  301. }
  302. }
  303. @ReactProp(name = "textZoom")
  304. public void setTextZoom(WebView view, int value) {
  305. view.getSettings().setTextZoom(value);
  306. }
  307. @ReactProp(name = "scalesPageToFit")
  308. public void setScalesPageToFit(WebView view, boolean enabled) {
  309. view.getSettings().setLoadWithOverviewMode(enabled);
  310. view.getSettings().setUseWideViewPort(enabled);
  311. }
  312. @ReactProp(name = "domStorageEnabled")
  313. public void setDomStorageEnabled(WebView view, boolean enabled) {
  314. view.getSettings().setDomStorageEnabled(enabled);
  315. }
  316. @ReactProp(name = "userAgent")
  317. public void setUserAgent(WebView view, @Nullable String userAgent) {
  318. if (userAgent != null) {
  319. mUserAgent = userAgent;
  320. } else {
  321. mUserAgent = null;
  322. }
  323. this.setUserAgentString(view);
  324. }
  325. @ReactProp(name = "applicationNameForUserAgent")
  326. public void setApplicationNameForUserAgent(WebView view, @Nullable String applicationName) {
  327. if(applicationName != null) {
  328. if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  329. String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
  330. mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
  331. }
  332. } else {
  333. mUserAgentWithApplicationName = null;
  334. }
  335. this.setUserAgentString(view);
  336. }
  337. protected void setUserAgentString(WebView view) {
  338. if(mUserAgent != null) {
  339. view.getSettings().setUserAgentString(mUserAgent);
  340. } else if(mUserAgentWithApplicationName != null) {
  341. view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
  342. } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  343. // handle unsets of `userAgent` prop as long as device is >= API 17
  344. view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
  345. }
  346. }
  347. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  348. @ReactProp(name = "mediaPlaybackRequiresUserAction")
  349. public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
  350. view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
  351. }
  352. @ReactProp(name = "javaScriptCanOpenWindowsAutomatically")
  353. public void setJavaScriptCanOpenWindowsAutomatically(WebView view, boolean enabled) {
  354. view.getSettings().setJavaScriptCanOpenWindowsAutomatically(enabled);
  355. }
  356. @ReactProp(name = "allowFileAccessFromFileURLs")
  357. public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
  358. view.getSettings().setAllowFileAccessFromFileURLs(allow);
  359. }
  360. @ReactProp(name = "allowUniversalAccessFromFileURLs")
  361. public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
  362. view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
  363. }
  364. @ReactProp(name = "saveFormDataDisabled")
  365. public void setSaveFormDataDisabled(WebView view, boolean disable) {
  366. view.getSettings().setSaveFormData(!disable);
  367. }
  368. @ReactProp(name = "injectedJavaScript")
  369. public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
  370. ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
  371. }
  372. @ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
  373. public void setInjectedJavaScriptBeforeContentLoaded(WebView view, @Nullable String injectedJavaScriptBeforeContentLoaded) {
  374. ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoaded(injectedJavaScriptBeforeContentLoaded);
  375. }
  376. @ReactProp(name = "injectedJavaScriptForMainFrameOnly")
  377. public void setInjectedJavaScriptForMainFrameOnly(WebView view, boolean enabled) {
  378. ((RNCWebView) view).setInjectedJavaScriptForMainFrameOnly(enabled);
  379. }
  380. @ReactProp(name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly")
  381. public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(WebView view, boolean enabled) {
  382. ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(enabled);
  383. }
  384. @ReactProp(name = "messagingEnabled")
  385. public void setMessagingEnabled(WebView view, boolean enabled) {
  386. ((RNCWebView) view).setMessagingEnabled(enabled);
  387. }
  388. @ReactProp(name = "messagingModuleName")
  389. public void setMessagingModuleName(WebView view, String moduleName) {
  390. ((RNCWebView) view).setMessagingModuleName(moduleName);
  391. }
  392. @ReactProp(name = "incognito")
  393. public void setIncognito(WebView view, boolean enabled) {
  394. // Don't do anything when incognito is disabled
  395. if (!enabled) {
  396. return;
  397. }
  398. // Remove all previous cookies
  399. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  400. CookieManager.getInstance().removeAllCookies(null);
  401. } else {
  402. CookieManager.getInstance().removeAllCookie();
  403. }
  404. // Disable caching
  405. view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  406. view.getSettings().setAppCacheEnabled(false);
  407. view.clearHistory();
  408. view.clearCache(true);
  409. // No form data or autofill enabled
  410. view.clearFormData();
  411. view.getSettings().setSavePassword(false);
  412. view.getSettings().setSaveFormData(false);
  413. }
  414. @ReactProp(name = "source")
  415. public void setSource(WebView view, @Nullable ReadableMap source) {
  416. if (source != null) {
  417. if (source.hasKey("html")) {
  418. String html = source.getString("html");
  419. String baseUrl = source.hasKey("baseUrl") ? source.getString("baseUrl") : "";
  420. view.loadDataWithBaseURL(baseUrl, html, HTML_MIME_TYPE, HTML_ENCODING, null);
  421. return;
  422. }
  423. if (source.hasKey("uri")) {
  424. String url = source.getString("uri");
  425. String previousUrl = view.getUrl();
  426. if (previousUrl != null && previousUrl.equals(url)) {
  427. return;
  428. }
  429. if (source.hasKey("method")) {
  430. String method = source.getString("method");
  431. if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
  432. byte[] postData = null;
  433. if (source.hasKey("body")) {
  434. String body = source.getString("body");
  435. try {
  436. postData = body.getBytes("UTF-8");
  437. } catch (UnsupportedEncodingException e) {
  438. postData = body.getBytes();
  439. }
  440. }
  441. if (postData == null) {
  442. postData = new byte[0];
  443. }
  444. view.postUrl(url, postData);
  445. return;
  446. }
  447. }
  448. HashMap<String, String> headerMap = new HashMap<>();
  449. if (source.hasKey("headers")) {
  450. ReadableMap headers = source.getMap("headers");
  451. ReadableMapKeySetIterator iter = headers.keySetIterator();
  452. while (iter.hasNextKey()) {
  453. String key = iter.nextKey();
  454. if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
  455. if (view.getSettings() != null) {
  456. view.getSettings().setUserAgentString(headers.getString(key));
  457. }
  458. } else {
  459. headerMap.put(key, headers.getString(key));
  460. }
  461. }
  462. }
  463. view.loadUrl(url, headerMap);
  464. return;
  465. }
  466. }
  467. view.loadUrl(BLANK_URL);
  468. }
  469. @ReactProp(name = "onContentSizeChange")
  470. public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
  471. ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
  472. }
  473. @ReactProp(name = "mixedContentMode")
  474. public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
  475. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  476. if (mixedContentMode == null || "never".equals(mixedContentMode)) {
  477. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
  478. } else if ("always".equals(mixedContentMode)) {
  479. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  480. } else if ("compatibility".equals(mixedContentMode)) {
  481. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
  482. }
  483. }
  484. }
  485. @ReactProp(name = "urlPrefixesForDefaultIntent")
  486. public void setUrlPrefixesForDefaultIntent(
  487. WebView view,
  488. @Nullable ReadableArray urlPrefixesForDefaultIntent) {
  489. RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
  490. if (client != null && urlPrefixesForDefaultIntent != null) {
  491. client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
  492. }
  493. }
  494. @ReactProp(name = "allowsFullscreenVideo")
  495. public void setAllowsFullscreenVideo(
  496. WebView view,
  497. @Nullable Boolean allowsFullscreenVideo) {
  498. mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
  499. setupWebChromeClient((ReactContext)view.getContext(), view);
  500. }
  501. @ReactProp(name = "allowFileAccess")
  502. public void setAllowFileAccess(
  503. WebView view,
  504. @Nullable Boolean allowFileAccess) {
  505. view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
  506. }
  507. @ReactProp(name = "geolocationEnabled")
  508. public void setGeolocationEnabled(
  509. WebView view,
  510. @Nullable Boolean isGeolocationEnabled) {
  511. view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
  512. }
  513. @ReactProp(name = "onScroll")
  514. public void setOnScroll(WebView view, boolean hasScrollEvent) {
  515. ((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
  516. }
  517. @Override
  518. protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
  519. // Do not register default touch emitter and let WebView implementation handle touches
  520. view.setWebViewClient(new RNCWebViewClient());
  521. }
  522. @Override
  523. public Map getExportedCustomDirectEventTypeConstants() {
  524. Map export = super.getExportedCustomDirectEventTypeConstants();
  525. if (export == null) {
  526. export = MapBuilder.newHashMap();
  527. }
  528. export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
  529. export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
  530. export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
  531. export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
  532. export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
  533. return export;
  534. }
  535. @Override
  536. public @Nullable
  537. Map<String, Integer> getCommandsMap() {
  538. return MapBuilder.<String, Integer>builder()
  539. .put("goBack", COMMAND_GO_BACK)
  540. .put("goForward", COMMAND_GO_FORWARD)
  541. .put("reload", COMMAND_RELOAD)
  542. .put("stopLoading", COMMAND_STOP_LOADING)
  543. .put("postMessage", COMMAND_POST_MESSAGE)
  544. .put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
  545. .put("loadUrl", COMMAND_LOAD_URL)
  546. .put("requestFocus", COMMAND_FOCUS)
  547. .put("clearFormData", COMMAND_CLEAR_FORM_DATA)
  548. .put("clearCache", COMMAND_CLEAR_CACHE)
  549. .put("clearHistory", COMMAND_CLEAR_HISTORY)
  550. .build();
  551. }
  552. @Override
  553. public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
  554. switch (commandId) {
  555. case COMMAND_GO_BACK:
  556. root.goBack();
  557. break;
  558. case COMMAND_GO_FORWARD:
  559. root.goForward();
  560. break;
  561. case COMMAND_RELOAD:
  562. root.reload();
  563. break;
  564. case COMMAND_STOP_LOADING:
  565. root.stopLoading();
  566. break;
  567. case COMMAND_POST_MESSAGE:
  568. try {
  569. RNCWebView reactWebView = (RNCWebView) root;
  570. JSONObject eventInitDict = new JSONObject();
  571. eventInitDict.put("data", args.getString(0));
  572. reactWebView.evaluateJavascriptWithFallback("(function () {" +
  573. "var event;" +
  574. "var data = " + eventInitDict.toString() + ";" +
  575. "try {" +
  576. "event = new MessageEvent('message', data);" +
  577. "} catch (e) {" +
  578. "event = document.createEvent('MessageEvent');" +
  579. "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
  580. "}" +
  581. "document.dispatchEvent(event);" +
  582. "})();");
  583. } catch (JSONException e) {
  584. throw new RuntimeException(e);
  585. }
  586. break;
  587. case COMMAND_INJECT_JAVASCRIPT:
  588. RNCWebView reactWebView = (RNCWebView) root;
  589. reactWebView.evaluateJavascriptWithFallback(args.getString(0));
  590. break;
  591. case COMMAND_LOAD_URL:
  592. if (args == null) {
  593. throw new RuntimeException("Arguments for loading an url are null!");
  594. }
  595. ((RNCWebView) root).progressChangedFilter.setWaitingForCommandLoadUrl(false);
  596. root.loadUrl(args.getString(0));
  597. break;
  598. case COMMAND_FOCUS:
  599. root.requestFocus();
  600. break;
  601. case COMMAND_CLEAR_FORM_DATA:
  602. root.clearFormData();
  603. break;
  604. case COMMAND_CLEAR_CACHE:
  605. boolean includeDiskFiles = args != null && args.getBoolean(0);
  606. root.clearCache(includeDiskFiles);
  607. break;
  608. case COMMAND_CLEAR_HISTORY:
  609. root.clearHistory();
  610. break;
  611. }
  612. }
  613. @Override
  614. public void onDropViewInstance(WebView webView) {
  615. super.onDropViewInstance(webView);
  616. ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
  617. ((RNCWebView) webView).cleanupCallbacksAndDestroy();
  618. }
  619. public static RNCWebViewModule getModule(ReactContext reactContext) {
  620. return reactContext.getNativeModule(RNCWebViewModule.class);
  621. }
  622. protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
  623. if (mAllowsFullscreenVideo) {
  624. int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
  625. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  626. @Override
  627. public Bitmap getDefaultVideoPoster() {
  628. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  629. }
  630. @Override
  631. public void onShowCustomView(View view, CustomViewCallback callback) {
  632. if (mVideoView != null) {
  633. callback.onCustomViewHidden();
  634. return;
  635. }
  636. mVideoView = view;
  637. mCustomViewCallback = callback;
  638. mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
  639. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  640. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  641. mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  642. }
  643. mVideoView.setBackgroundColor(Color.BLACK);
  644. getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
  645. mWebView.setVisibility(View.GONE);
  646. mReactContext.addLifecycleEventListener(this);
  647. }
  648. @Override
  649. public void onHideCustomView() {
  650. if (mVideoView == null) {
  651. return;
  652. }
  653. mVideoView.setVisibility(View.GONE);
  654. getRootView().removeView(mVideoView);
  655. mCustomViewCallback.onCustomViewHidden();
  656. mVideoView = null;
  657. mCustomViewCallback = null;
  658. mWebView.setVisibility(View.VISIBLE);
  659. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  660. mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  661. }
  662. mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation);
  663. mReactContext.removeLifecycleEventListener(this);
  664. }
  665. };
  666. webView.setWebChromeClient(mWebChromeClient);
  667. } else {
  668. if (mWebChromeClient != null) {
  669. mWebChromeClient.onHideCustomView();
  670. }
  671. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  672. @Override
  673. public Bitmap getDefaultVideoPoster() {
  674. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  675. }
  676. };
  677. webView.setWebChromeClient(mWebChromeClient);
  678. }
  679. }
  680. protected static class RNCWebViewClient extends WebViewClient {
  681. protected boolean mLastLoadFailed = false;
  682. protected @Nullable
  683. ReadableArray mUrlPrefixesForDefaultIntent;
  684. protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
  685. protected @Nullable String ignoreErrFailedForThisURL = null;
  686. public void setIgnoreErrFailedForThisURL(@Nullable String url) {
  687. ignoreErrFailedForThisURL = url;
  688. }
  689. @Override
  690. public void onPageFinished(WebView webView, String url) {
  691. super.onPageFinished(webView, url);
  692. if (!mLastLoadFailed) {
  693. RNCWebView reactWebView = (RNCWebView) webView;
  694. reactWebView.callInjectedJavaScript();
  695. emitFinishEvent(webView, url);
  696. }
  697. }
  698. @Override
  699. public void onPageStarted(WebView webView, String url, Bitmap favicon) {
  700. super.onPageStarted(webView, url, favicon);
  701. mLastLoadFailed = false;
  702. RNCWebView reactWebView = (RNCWebView) webView;
  703. reactWebView.callInjectedJavaScriptBeforeContentLoaded();
  704. dispatchEvent(
  705. webView,
  706. new TopLoadingStartEvent(
  707. webView.getId(),
  708. createWebViewEvent(webView, url)));
  709. }
  710. @Override
  711. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  712. progressChangedFilter.setWaitingForCommandLoadUrl(true);
  713. dispatchEvent(
  714. view,
  715. new TopShouldStartLoadWithRequestEvent(
  716. view.getId(),
  717. createWebViewEvent(view, url)));
  718. return true;
  719. }
  720. @TargetApi(Build.VERSION_CODES.N)
  721. @Override
  722. public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
  723. final String url = request.getUrl().toString();
  724. return this.shouldOverrideUrlLoading(view, url);
  725. }
  726. @Override
  727. public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
  728. handler.cancel();
  729. int code = error.getPrimaryError();
  730. String failingUrl = error.getUrl();
  731. String description = "";
  732. String descriptionPrefix = "SSL error: ";
  733. // https://developer.android.com/reference/android/net/http/SslError.html
  734. switch (code) {
  735. case SslError.SSL_DATE_INVALID:
  736. description = "The date of the certificate is invalid";
  737. break;
  738. case SslError.SSL_EXPIRED:
  739. description = "The certificate has expired";
  740. break;
  741. case SslError.SSL_IDMISMATCH:
  742. description = "Hostname mismatch";
  743. break;
  744. case SslError.SSL_INVALID:
  745. description = "A generic error occurred";
  746. break;
  747. case SslError.SSL_NOTYETVALID:
  748. description = "The certificate is not yet valid";
  749. break;
  750. case SslError.SSL_UNTRUSTED:
  751. description = "The certificate authority is not trusted";
  752. break;
  753. default:
  754. description = "Unknown SSL Error";
  755. break;
  756. }
  757. description = descriptionPrefix + description;
  758. this.onReceivedError(
  759. webView,
  760. code,
  761. description,
  762. failingUrl
  763. );
  764. }
  765. @Override
  766. public void onReceivedError(
  767. WebView webView,
  768. int errorCode,
  769. String description,
  770. String failingUrl) {
  771. if (ignoreErrFailedForThisURL != null
  772. && failingUrl.equals(ignoreErrFailedForThisURL)
  773. && errorCode == -1
  774. && description.equals("net::ERR_FAILED")) {
  775. // This is a workaround for a bug in the WebView.
  776. // See these chromium issues for more context:
  777. // https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
  778. // https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
  779. // This entire commit should be reverted once this bug is resolved in chromium.
  780. setIgnoreErrFailedForThisURL(null);
  781. return;
  782. }
  783. super.onReceivedError(webView, errorCode, description, failingUrl);
  784. mLastLoadFailed = true;
  785. // In case of an error JS side expect to get a finish event first, and then get an error event
  786. // Android WebView does it in the opposite way, so we need to simulate that behavior
  787. emitFinishEvent(webView, failingUrl);
  788. WritableMap eventData = createWebViewEvent(webView, failingUrl);
  789. eventData.putDouble("code", errorCode);
  790. eventData.putString("description", description);
  791. dispatchEvent(
  792. webView,
  793. new TopLoadingErrorEvent(webView.getId(), eventData));
  794. }
  795. @RequiresApi(api = Build.VERSION_CODES.M)
  796. @Override
  797. public void onReceivedHttpError(
  798. WebView webView,
  799. WebResourceRequest request,
  800. WebResourceResponse errorResponse) {
  801. super.onReceivedHttpError(webView, request, errorResponse);
  802. if (request.isForMainFrame()) {
  803. WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
  804. eventData.putInt("statusCode", errorResponse.getStatusCode());
  805. eventData.putString("description", errorResponse.getReasonPhrase());
  806. dispatchEvent(
  807. webView,
  808. new TopHttpErrorEvent(webView.getId(), eventData));
  809. }
  810. }
  811. @TargetApi(Build.VERSION_CODES.O)
  812. @Override
  813. public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
  814. // WebViewClient.onRenderProcessGone was added in O.
  815. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
  816. return false;
  817. }
  818. super.onRenderProcessGone(webView, detail);
  819. if(detail.didCrash()){
  820. Log.e("RNCWebViewManager", "The WebView rendering process crashed.");
  821. }
  822. else{
  823. Log.w("RNCWebViewManager", "The WebView rendering process was killed by the system.");
  824. }
  825. // if webView is null, we cannot return any event
  826. // since the view is already dead/disposed
  827. // still prevent the app crash by returning true.
  828. if(webView == null){
  829. return true;
  830. }
  831. WritableMap event = createWebViewEvent(webView, webView.getUrl());
  832. event.putBoolean("didCrash", detail.didCrash());
  833. dispatchEvent(
  834. webView,
  835. new TopRenderProcessGoneEvent(webView.getId(), event)
  836. );
  837. // returning false would crash the app.
  838. return true;
  839. }
  840. protected void emitFinishEvent(WebView webView, String url) {
  841. dispatchEvent(
  842. webView,
  843. new TopLoadingFinishEvent(
  844. webView.getId(),
  845. createWebViewEvent(webView, url)));
  846. }
  847. protected WritableMap createWebViewEvent(WebView webView, String url) {
  848. WritableMap event = Arguments.createMap();
  849. event.putDouble("target", webView.getId());
  850. // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
  851. // like onPageFinished
  852. event.putString("url", url);
  853. event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
  854. event.putString("title", webView.getTitle());
  855. event.putBoolean("canGoBack", webView.canGoBack());
  856. event.putBoolean("canGoForward", webView.canGoForward());
  857. return event;
  858. }
  859. public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
  860. mUrlPrefixesForDefaultIntent = specialUrls;
  861. }
  862. public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
  863. progressChangedFilter = filter;
  864. }
  865. }
  866. protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
  867. protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
  868. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
  869. @RequiresApi(api = Build.VERSION_CODES.KITKAT)
  870. protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  871. View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  872. View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  873. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
  874. View.SYSTEM_UI_FLAG_FULLSCREEN |
  875. View.SYSTEM_UI_FLAG_IMMERSIVE |
  876. View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  877. protected ReactContext mReactContext;
  878. protected View mWebView;
  879. protected View mVideoView;
  880. protected WebChromeClient.CustomViewCallback mCustomViewCallback;
  881. protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
  882. public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
  883. this.mReactContext = reactContext;
  884. this.mWebView = webView;
  885. }
  886. @Override
  887. public boolean onConsoleMessage(ConsoleMessage message) {
  888. if (ReactBuildConfig.DEBUG) {
  889. return super.onConsoleMessage(message);
  890. }
  891. // Ignore console logs in non debug builds.
  892. return true;
  893. }
  894. // Fix WebRTC permission request error.
  895. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  896. @Override
  897. public void onPermissionRequest(final PermissionRequest request) {
  898. String[] requestedResources = request.getResources();
  899. ArrayList<String> permissions = new ArrayList<>();
  900. ArrayList<String> grantedPermissions = new ArrayList<String>();
  901. for (int i = 0; i < requestedResources.length; i++) {
  902. if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
  903. permissions.add(Manifest.permission.RECORD_AUDIO);
  904. } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
  905. permissions.add(Manifest.permission.CAMERA);
  906. }
  907. // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
  908. }
  909. for (int i = 0; i < permissions.size(); i++) {
  910. if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
  911. continue;
  912. }
  913. if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
  914. grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
  915. } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
  916. grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
  917. }
  918. }
  919. if (grantedPermissions.isEmpty()) {
  920. request.deny();
  921. } else {
  922. String[] grantedPermissionsArray = new String[grantedPermissions.size()];
  923. grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
  924. request.grant(grantedPermissionsArray);
  925. }
  926. }
  927. @Override
  928. public void onProgressChanged(WebView webView, int newProgress) {
  929. super.onProgressChanged(webView, newProgress);
  930. final String url = webView.getUrl();
  931. if (progressChangedFilter.isWaitingForCommandLoadUrl()) {
  932. return;
  933. }
  934. WritableMap event = Arguments.createMap();
  935. event.putDouble("target", webView.getId());
  936. event.putString("title", webView.getTitle());
  937. event.putString("url", url);
  938. event.putBoolean("canGoBack", webView.canGoBack());
  939. event.putBoolean("canGoForward", webView.canGoForward());
  940. event.putDouble("progress", (float) newProgress / 100);
  941. dispatchEvent(
  942. webView,
  943. new TopLoadingProgressEvent(
  944. webView.getId(),
  945. event));
  946. }
  947. @Override
  948. public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
  949. callback.invoke(origin, true, false);
  950. }
  951. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
  952. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  953. }
  954. protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
  955. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
  956. }
  957. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  958. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  959. }
  960. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  961. @Override
  962. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  963. String[] acceptTypes = fileChooserParams.getAcceptTypes();
  964. boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
  965. return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptTypes, allowMultiple);
  966. }
  967. @Override
  968. public void onHostResume() {
  969. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
  970. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  971. }
  972. }
  973. @Override
  974. public void onHostPause() { }
  975. @Override
  976. public void onHostDestroy() { }
  977. protected ViewGroup getRootView() {
  978. return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
  979. }
  980. public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
  981. progressChangedFilter = filter;
  982. }
  983. }
  984. /**
  985. * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
  986. * to call {@link WebView#destroy} on activity destroy event and also to clear the client
  987. */
  988. protected static class RNCWebView extends WebView implements LifecycleEventListener {
  989. protected @Nullable
  990. String injectedJS;
  991. protected @Nullable
  992. String injectedJSBeforeContentLoaded;
  993. /**
  994. * android.webkit.WebChromeClient fundamentally does not support JS injection into frames other
  995. * than the main frame, so these two properties are mostly here just for parity with iOS & macOS.
  996. */
  997. protected boolean injectedJavaScriptForMainFrameOnly = true;
  998. protected boolean injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true;
  999. protected boolean messagingEnabled = false;
  1000. protected @Nullable
  1001. String messagingModuleName;
  1002. protected @Nullable
  1003. RNCWebViewClient mRNCWebViewClient;
  1004. protected @Nullable
  1005. CatalystInstance mCatalystInstance;
  1006. protected boolean sendContentSizeChangeEvents = false;
  1007. private OnScrollDispatchHelper mOnScrollDispatchHelper;
  1008. protected boolean hasScrollEvent = false;
  1009. protected ProgressChangedFilter progressChangedFilter;
  1010. /**
  1011. * WebView must be created with an context of the current activity
  1012. * <p>
  1013. * Activity Context is required for creation of dialogs internally by WebView
  1014. * Reactive Native needed for access to ReactNative internal system functionality
  1015. */
  1016. public RNCWebView(ThemedReactContext reactContext) {
  1017. super(reactContext);
  1018. progressChangedFilter = new ProgressChangedFilter();
  1019. }
  1020. public void setIgnoreErrFailedForThisURL(String url) {
  1021. mRNCWebViewClient.setIgnoreErrFailedForThisURL(url);
  1022. }
  1023. public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
  1024. this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
  1025. }
  1026. public void setHasScrollEvent(boolean hasScrollEvent) {
  1027. this.hasScrollEvent = hasScrollEvent;
  1028. }
  1029. @Override
  1030. public void onHostResume() {
  1031. // do nothing
  1032. }
  1033. @Override
  1034. public void onHostPause() {
  1035. // do nothing
  1036. }
  1037. @Override
  1038. public void onHostDestroy() {
  1039. cleanupCallbacksAndDestroy();
  1040. }
  1041. @Override
  1042. protected void onSizeChanged(int w, int h, int ow, int oh) {
  1043. super.onSizeChanged(w, h, ow, oh);
  1044. if (sendContentSizeChangeEvents) {
  1045. dispatchEvent(
  1046. this,
  1047. new ContentSizeChangeEvent(
  1048. this.getId(),
  1049. w,
  1050. h
  1051. )
  1052. );
  1053. }
  1054. }
  1055. @Override
  1056. public void setWebViewClient(WebViewClient client) {
  1057. super.setWebViewClient(client);
  1058. if (client instanceof RNCWebViewClient) {
  1059. mRNCWebViewClient = (RNCWebViewClient) client;
  1060. mRNCWebViewClient.setProgressChangedFilter(progressChangedFilter);
  1061. }
  1062. }
  1063. WebChromeClient mWebChromeClient;
  1064. @Override
  1065. public void setWebChromeClient(WebChromeClient client) {
  1066. this.mWebChromeClient = client;
  1067. super.setWebChromeClient(client);
  1068. if (client instanceof RNCWebChromeClient) {
  1069. ((RNCWebChromeClient) client).setProgressChangedFilter(progressChangedFilter);
  1070. }
  1071. }
  1072. public @Nullable
  1073. RNCWebViewClient getRNCWebViewClient() {
  1074. return mRNCWebViewClient;
  1075. }
  1076. public void setInjectedJavaScript(@Nullable String js) {
  1077. injectedJS = js;
  1078. }
  1079. public void setInjectedJavaScriptBeforeContentLoaded(@Nullable String js) {
  1080. injectedJSBeforeContentLoaded = js;
  1081. }
  1082. public void setInjectedJavaScriptForMainFrameOnly(boolean enabled) {
  1083. injectedJavaScriptForMainFrameOnly = enabled;
  1084. }
  1085. public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(boolean enabled) {
  1086. injectedJavaScriptBeforeContentLoadedForMainFrameOnly = enabled;
  1087. }
  1088. protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
  1089. return new RNCWebViewBridge(webView);
  1090. }
  1091. protected void createCatalystInstance() {
  1092. ReactContext reactContext = (ReactContext) this.getContext();
  1093. if (reactContext != null) {
  1094. mCatalystInstance = reactContext.getCatalystInstance();
  1095. }
  1096. }
  1097. @SuppressLint("AddJavascriptInterface")
  1098. public void setMessagingEnabled(boolean enabled) {
  1099. if (messagingEnabled == enabled) {
  1100. return;
  1101. }
  1102. messagingEnabled = enabled;
  1103. if (enabled) {
  1104. addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
  1105. this.createCatalystInstance();
  1106. } else {
  1107. removeJavascriptInterface(JAVASCRIPT_INTERFACE);
  1108. }
  1109. }
  1110. public void setMessagingModuleName(String moduleName) {
  1111. messagingModuleName = moduleName;
  1112. }
  1113. protected void evaluateJavascriptWithFallback(String script) {
  1114. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  1115. evaluateJavascript(script, null);
  1116. return;
  1117. }
  1118. try {
  1119. loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
  1120. } catch (UnsupportedEncodingException e) {
  1121. // UTF-8 should always be supported
  1122. throw new RuntimeException(e);
  1123. }
  1124. }
  1125. public void callInjectedJavaScript() {
  1126. if (getSettings().getJavaScriptEnabled() &&
  1127. injectedJS != null &&
  1128. !TextUtils.isEmpty(injectedJS)) {
  1129. evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
  1130. }
  1131. }
  1132. public void callInjectedJavaScriptBeforeContentLoaded() {
  1133. if (getSettings().getJavaScriptEnabled() &&
  1134. injectedJSBeforeContentLoaded != null &&
  1135. !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
  1136. evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
  1137. }
  1138. }
  1139. public void onMessage(String message) {
  1140. ReactContext reactContext = (ReactContext) this.getContext();
  1141. RNCWebView mContext = this;
  1142. if (mRNCWebViewClient != null) {
  1143. WebView webView = this;
  1144. webView.post(new Runnable() {
  1145. @Override
  1146. public void run() {
  1147. if (mRNCWebViewClient == null) {
  1148. return;
  1149. }
  1150. WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
  1151. data.putString("data", message);
  1152. if (mCatalystInstance != null) {
  1153. mContext.sendDirectMessage(data);
  1154. } else {
  1155. dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
  1156. }
  1157. }
  1158. });
  1159. } else {
  1160. WritableMap eventData = Arguments.createMap();
  1161. eventData.putString("data", message);
  1162. if (mCatalystInstance != null) {
  1163. this.sendDirectMessage(eventData);
  1164. } else {
  1165. dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
  1166. }
  1167. }
  1168. }
  1169. protected void sendDirectMessage(WritableMap data) {
  1170. WritableNativeMap event = new WritableNativeMap();
  1171. event.putMap("nativeEvent", data);
  1172. WritableNativeArray params = new WritableNativeArray();
  1173. params.pushMap(event);
  1174. mCatalystInstance.callFunction(messagingModuleName, "onMessage", params);
  1175. }
  1176. protected void onScrollChanged(int x, int y, int oldX, int oldY) {
  1177. super.onScrollChanged(x, y, oldX, oldY);
  1178. if (!hasScrollEvent) {
  1179. return;
  1180. }
  1181. if (mOnScrollDispatchHelper == null) {
  1182. mOnScrollDispatchHelper = new OnScrollDispatchHelper();
  1183. }
  1184. if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
  1185. ScrollEvent event = ScrollEvent.obtain(
  1186. this.getId(),
  1187. ScrollEventType.SCROLL,
  1188. x,
  1189. y,
  1190. mOnScrollDispatchHelper.getXFlingVelocity(),
  1191. mOnScrollDispatchHelper.getYFlingVelocity(),
  1192. this.computeHorizontalScrollRange(),
  1193. this.computeVerticalScrollRange(),
  1194. this.getWidth(),
  1195. this.getHeight());
  1196. dispatchEvent(this, event);
  1197. }
  1198. }
  1199. protected void cleanupCallbacksAndDestroy() {
  1200. setWebViewClient(null);
  1201. destroy();
  1202. }
  1203. @Override
  1204. public void destroy() {
  1205. if (mWebChromeClient != null) {
  1206. mWebChromeClient.onHideCustomView();
  1207. }
  1208. super.destroy();
  1209. }
  1210. protected class RNCWebViewBridge {
  1211. RNCWebView mContext;
  1212. RNCWebViewBridge(RNCWebView c) {
  1213. mContext = c;
  1214. }
  1215. /**
  1216. * This method is called whenever JavaScript running within the web view calls:
  1217. * - window[JAVASCRIPT_INTERFACE].postMessage
  1218. */
  1219. @JavascriptInterface
  1220. public void postMessage(String message) {
  1221. mContext.onMessage(message);
  1222. }
  1223. }
  1224. protected static class ProgressChangedFilter {
  1225. private boolean waitingForCommandLoadUrl = false;
  1226. public void setWaitingForCommandLoadUrl(boolean isWaiting) {
  1227. waitingForCommandLoadUrl = isWaiting;
  1228. }
  1229. public boolean isWaitingForCommandLoadUrl() {
  1230. return waitingForCommandLoadUrl;
  1231. }
  1232. }
  1233. }
  1234. }