Sin descripción

RNCWebViewManager.java 46KB

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