react-native-webview.git

RNCWebViewManager.java 44KB

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