Нема описа

RNCWebViewManager.java 41KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  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 String activeUrl = null;
  106. public static final int COMMAND_GO_BACK = 1;
  107. public static final int COMMAND_GO_FORWARD = 2;
  108. public static final int COMMAND_RELOAD = 3;
  109. public static final int COMMAND_STOP_LOADING = 4;
  110. public static final int COMMAND_POST_MESSAGE = 5;
  111. public static final int COMMAND_INJECT_JAVASCRIPT = 6;
  112. public static final int COMMAND_LOAD_URL = 7;
  113. public static final int COMMAND_FOCUS = 8;
  114. // android commands
  115. public static final int COMMAND_CLEAR_FORM_DATA = 1000;
  116. public static final int COMMAND_CLEAR_CACHE = 1001;
  117. public static final int COMMAND_CLEAR_HISTORY = 1002;
  118. protected static final String REACT_CLASS = "RNCWebView";
  119. protected static final String HTML_ENCODING = "UTF-8";
  120. protected static final String HTML_MIME_TYPE = "text/html";
  121. protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
  122. protected static final String HTTP_METHOD_POST = "POST";
  123. // Use `webView.loadUrl("about:blank")` to reliably reset the view
  124. // state and release page resources (including any running JavaScript).
  125. protected static final String BLANK_URL = "about:blank";
  126. protected WebViewConfig mWebViewConfig;
  127. protected RNCWebChromeClient mWebChromeClient = null;
  128. protected boolean mAllowsFullscreenVideo = false;
  129. protected @Nullable String mUserAgent = null;
  130. protected @Nullable String mUserAgentWithApplicationName = null;
  131. public RNCWebViewManager() {
  132. mWebViewConfig = new WebViewConfig() {
  133. public void configWebView(WebView webView) {
  134. }
  135. };
  136. }
  137. public RNCWebViewManager(WebViewConfig webViewConfig) {
  138. mWebViewConfig = webViewConfig;
  139. }
  140. protected static void dispatchEvent(WebView webView, Event event) {
  141. ReactContext reactContext = (ReactContext) webView.getContext();
  142. EventDispatcher eventDispatcher =
  143. reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
  144. eventDispatcher.dispatchEvent(event);
  145. }
  146. @Override
  147. public String getName() {
  148. return REACT_CLASS;
  149. }
  150. protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
  151. return new RNCWebView(reactContext);
  152. }
  153. @Override
  154. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  155. protected WebView createViewInstance(ThemedReactContext reactContext) {
  156. RNCWebView webView = createRNCWebViewInstance(reactContext);
  157. setupWebChromeClient(reactContext, webView);
  158. reactContext.addLifecycleEventListener(webView);
  159. mWebViewConfig.configWebView(webView);
  160. WebSettings settings = webView.getSettings();
  161. settings.setBuiltInZoomControls(true);
  162. settings.setDisplayZoomControls(false);
  163. settings.setDomStorageEnabled(true);
  164. settings.setAllowFileAccess(false);
  165. settings.setAllowContentAccess(false);
  166. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  167. settings.setAllowFileAccessFromFileURLs(false);
  168. setAllowUniversalAccessFromFileURLs(webView, false);
  169. }
  170. setMixedContentMode(webView, "never");
  171. // Fixes broken full-screen modals/galleries due to body height being 0.
  172. webView.setLayoutParams(
  173. new LayoutParams(LayoutParams.MATCH_PARENT,
  174. LayoutParams.MATCH_PARENT));
  175. if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  176. WebView.setWebContentsDebuggingEnabled(true);
  177. }
  178. webView.setDownloadListener(new DownloadListener() {
  179. public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
  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 = "allowFileAccessFromFileURLs")
  335. public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
  336. view.getSettings().setAllowFileAccessFromFileURLs(allow);
  337. }
  338. @ReactProp(name = "allowUniversalAccessFromFileURLs")
  339. public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
  340. view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
  341. }
  342. @ReactProp(name = "saveFormDataDisabled")
  343. public void setSaveFormDataDisabled(WebView view, boolean disable) {
  344. view.getSettings().setSaveFormData(!disable);
  345. }
  346. @ReactProp(name = "injectedJavaScript")
  347. public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
  348. ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
  349. }
  350. @ReactProp(name = "messagingEnabled")
  351. public void setMessagingEnabled(WebView view, boolean enabled) {
  352. ((RNCWebView) view).setMessagingEnabled(enabled);
  353. }
  354. @ReactProp(name = "incognito")
  355. public void setIncognito(WebView view, boolean enabled) {
  356. // Remove all previous cookies
  357. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  358. CookieManager.getInstance().removeAllCookies(null);
  359. } else {
  360. CookieManager.getInstance().removeAllCookie();
  361. }
  362. // Disable caching
  363. view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  364. view.getSettings().setAppCacheEnabled(!enabled);
  365. view.clearHistory();
  366. view.clearCache(enabled);
  367. // No form data or autofill enabled
  368. view.clearFormData();
  369. view.getSettings().setSavePassword(!enabled);
  370. view.getSettings().setSaveFormData(!enabled);
  371. }
  372. @ReactProp(name = "source")
  373. public void setSource(WebView view, @Nullable ReadableMap source) {
  374. if (source != null) {
  375. if (source.hasKey("html")) {
  376. String html = source.getString("html");
  377. String baseUrl = source.hasKey("baseUrl") ? source.getString("baseUrl") : "";
  378. view.loadDataWithBaseURL(baseUrl, html, HTML_MIME_TYPE, HTML_ENCODING, null);
  379. return;
  380. }
  381. if (source.hasKey("uri")) {
  382. String url = source.getString("uri");
  383. String previousUrl = view.getUrl();
  384. if (previousUrl != null && previousUrl.equals(url)) {
  385. return;
  386. }
  387. if (source.hasKey("method")) {
  388. String method = source.getString("method");
  389. if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
  390. byte[] postData = null;
  391. if (source.hasKey("body")) {
  392. String body = source.getString("body");
  393. try {
  394. postData = body.getBytes("UTF-8");
  395. } catch (UnsupportedEncodingException e) {
  396. postData = body.getBytes();
  397. }
  398. }
  399. if (postData == null) {
  400. postData = new byte[0];
  401. }
  402. view.postUrl(url, postData);
  403. return;
  404. }
  405. }
  406. HashMap<String, String> headerMap = new HashMap<>();
  407. if (source.hasKey("headers")) {
  408. ReadableMap headers = source.getMap("headers");
  409. ReadableMapKeySetIterator iter = headers.keySetIterator();
  410. while (iter.hasNextKey()) {
  411. String key = iter.nextKey();
  412. if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
  413. if (view.getSettings() != null) {
  414. view.getSettings().setUserAgentString(headers.getString(key));
  415. }
  416. } else {
  417. headerMap.put(key, headers.getString(key));
  418. }
  419. }
  420. }
  421. view.loadUrl(url, headerMap);
  422. return;
  423. }
  424. }
  425. view.loadUrl(BLANK_URL);
  426. }
  427. @ReactProp(name = "onContentSizeChange")
  428. public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
  429. ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
  430. }
  431. @ReactProp(name = "mixedContentMode")
  432. public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
  433. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  434. if (mixedContentMode == null || "never".equals(mixedContentMode)) {
  435. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
  436. } else if ("always".equals(mixedContentMode)) {
  437. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  438. } else if ("compatibility".equals(mixedContentMode)) {
  439. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
  440. }
  441. }
  442. }
  443. @ReactProp(name = "urlPrefixesForDefaultIntent")
  444. public void setUrlPrefixesForDefaultIntent(
  445. WebView view,
  446. @Nullable ReadableArray urlPrefixesForDefaultIntent) {
  447. RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
  448. if (client != null && urlPrefixesForDefaultIntent != null) {
  449. client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
  450. }
  451. }
  452. @ReactProp(name = "allowsFullscreenVideo")
  453. public void setAllowsFullscreenVideo(
  454. WebView view,
  455. @Nullable Boolean allowsFullscreenVideo) {
  456. mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
  457. setupWebChromeClient((ReactContext)view.getContext(), view);
  458. }
  459. @ReactProp(name = "allowFileAccess")
  460. public void setAllowFileAccess(
  461. WebView view,
  462. @Nullable Boolean allowFileAccess) {
  463. view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
  464. }
  465. @ReactProp(name = "geolocationEnabled")
  466. public void setGeolocationEnabled(
  467. WebView view,
  468. @Nullable Boolean isGeolocationEnabled) {
  469. view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
  470. }
  471. @ReactProp(name = "onScroll")
  472. public void setOnScroll(WebView view, boolean hasScrollEvent) {
  473. ((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
  474. }
  475. @Override
  476. protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
  477. // Do not register default touch emitter and let WebView implementation handle touches
  478. view.setWebViewClient(new RNCWebViewClient());
  479. }
  480. @Override
  481. public Map getExportedCustomDirectEventTypeConstants() {
  482. Map export = super.getExportedCustomDirectEventTypeConstants();
  483. if (export == null) {
  484. export = MapBuilder.newHashMap();
  485. }
  486. export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
  487. export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
  488. export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
  489. export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
  490. return export;
  491. }
  492. @Override
  493. public @Nullable
  494. Map<String, Integer> getCommandsMap() {
  495. return MapBuilder.<String, Integer>builder()
  496. .put("goBack", COMMAND_GO_BACK)
  497. .put("goForward", COMMAND_GO_FORWARD)
  498. .put("reload", COMMAND_RELOAD)
  499. .put("stopLoading", COMMAND_STOP_LOADING)
  500. .put("postMessage", COMMAND_POST_MESSAGE)
  501. .put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
  502. .put("loadUrl", COMMAND_LOAD_URL)
  503. .put("requestFocus", COMMAND_FOCUS)
  504. .put("clearFormData", COMMAND_CLEAR_FORM_DATA)
  505. .put("clearCache", COMMAND_CLEAR_CACHE)
  506. .put("clearHistory", COMMAND_CLEAR_HISTORY)
  507. .build();
  508. }
  509. @Override
  510. public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
  511. switch (commandId) {
  512. case COMMAND_GO_BACK:
  513. root.goBack();
  514. break;
  515. case COMMAND_GO_FORWARD:
  516. root.goForward();
  517. break;
  518. case COMMAND_RELOAD:
  519. root.reload();
  520. break;
  521. case COMMAND_STOP_LOADING:
  522. root.stopLoading();
  523. break;
  524. case COMMAND_POST_MESSAGE:
  525. try {
  526. RNCWebView reactWebView = (RNCWebView) root;
  527. JSONObject eventInitDict = new JSONObject();
  528. eventInitDict.put("data", args.getString(0));
  529. reactWebView.evaluateJavascriptWithFallback("(function () {" +
  530. "var event;" +
  531. "var data = " + eventInitDict.toString() + ";" +
  532. "try {" +
  533. "event = new MessageEvent('message', data);" +
  534. "} catch (e) {" +
  535. "event = document.createEvent('MessageEvent');" +
  536. "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
  537. "}" +
  538. "document.dispatchEvent(event);" +
  539. "})();");
  540. } catch (JSONException e) {
  541. throw new RuntimeException(e);
  542. }
  543. break;
  544. case COMMAND_INJECT_JAVASCRIPT:
  545. RNCWebView reactWebView = (RNCWebView) root;
  546. reactWebView.evaluateJavascriptWithFallback(args.getString(0));
  547. break;
  548. case COMMAND_LOAD_URL:
  549. if (args == null) {
  550. throw new RuntimeException("Arguments for loading an url are null!");
  551. }
  552. root.loadUrl(args.getString(0));
  553. break;
  554. case COMMAND_FOCUS:
  555. root.requestFocus();
  556. break;
  557. case COMMAND_CLEAR_FORM_DATA:
  558. root.clearFormData();
  559. break;
  560. case COMMAND_CLEAR_CACHE:
  561. boolean includeDiskFiles = args != null && args.getBoolean(0);
  562. root.clearCache(includeDiskFiles);
  563. break;
  564. case COMMAND_CLEAR_HISTORY:
  565. root.clearHistory();
  566. break;
  567. }
  568. }
  569. @Override
  570. public void onDropViewInstance(WebView webView) {
  571. super.onDropViewInstance(webView);
  572. ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
  573. ((RNCWebView) webView).cleanupCallbacksAndDestroy();
  574. }
  575. public static RNCWebViewModule getModule(ReactContext reactContext) {
  576. return reactContext.getNativeModule(RNCWebViewModule.class);
  577. }
  578. protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
  579. if (mAllowsFullscreenVideo) {
  580. int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
  581. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  582. @Override
  583. public Bitmap getDefaultVideoPoster() {
  584. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  585. }
  586. @Override
  587. public void onShowCustomView(View view, CustomViewCallback callback) {
  588. if (mVideoView != null) {
  589. callback.onCustomViewHidden();
  590. return;
  591. }
  592. mVideoView = view;
  593. mCustomViewCallback = callback;
  594. mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
  595. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  596. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  597. mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  598. }
  599. mVideoView.setBackgroundColor(Color.BLACK);
  600. getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
  601. mWebView.setVisibility(View.GONE);
  602. mReactContext.addLifecycleEventListener(this);
  603. }
  604. @Override
  605. public void onHideCustomView() {
  606. if (mVideoView == null) {
  607. return;
  608. }
  609. mVideoView.setVisibility(View.GONE);
  610. getRootView().removeView(mVideoView);
  611. mCustomViewCallback.onCustomViewHidden();
  612. mVideoView = null;
  613. mCustomViewCallback = null;
  614. mWebView.setVisibility(View.VISIBLE);
  615. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  616. mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  617. }
  618. mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation);
  619. mReactContext.removeLifecycleEventListener(this);
  620. }
  621. };
  622. webView.setWebChromeClient(mWebChromeClient);
  623. } else {
  624. if (mWebChromeClient != null) {
  625. mWebChromeClient.onHideCustomView();
  626. }
  627. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  628. @Override
  629. public Bitmap getDefaultVideoPoster() {
  630. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  631. }
  632. };
  633. webView.setWebChromeClient(mWebChromeClient);
  634. }
  635. }
  636. protected static class RNCWebViewClient extends WebViewClient {
  637. protected boolean mLastLoadFailed = false;
  638. protected @Nullable
  639. ReadableArray mUrlPrefixesForDefaultIntent;
  640. @Override
  641. public void onPageFinished(WebView webView, String url) {
  642. super.onPageFinished(webView, url);
  643. if (!mLastLoadFailed) {
  644. RNCWebView reactWebView = (RNCWebView) webView;
  645. reactWebView.callInjectedJavaScript();
  646. emitFinishEvent(webView, url);
  647. }
  648. }
  649. @Override
  650. public void onPageStarted(WebView webView, String url, Bitmap favicon) {
  651. super.onPageStarted(webView, url, favicon);
  652. mLastLoadFailed = false;
  653. dispatchEvent(
  654. webView,
  655. new TopLoadingStartEvent(
  656. webView.getId(),
  657. createWebViewEvent(webView, url)));
  658. }
  659. @Override
  660. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  661. activeUrl = url;
  662. dispatchEvent(
  663. view,
  664. new TopShouldStartLoadWithRequestEvent(
  665. view.getId(),
  666. createWebViewEvent(view, url)));
  667. return true;
  668. }
  669. @TargetApi(Build.VERSION_CODES.N)
  670. @Override
  671. public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
  672. final String url = request.getUrl().toString();
  673. return this.shouldOverrideUrlLoading(view, url);
  674. }
  675. @Override
  676. public void onReceivedError(
  677. WebView webView,
  678. int errorCode,
  679. String description,
  680. String failingUrl) {
  681. super.onReceivedError(webView, errorCode, description, failingUrl);
  682. mLastLoadFailed = true;
  683. // In case of an error JS side expect to get a finish event first, and then get an error event
  684. // Android WebView does it in the opposite way, so we need to simulate that behavior
  685. emitFinishEvent(webView, failingUrl);
  686. WritableMap eventData = createWebViewEvent(webView, failingUrl);
  687. eventData.putDouble("code", errorCode);
  688. eventData.putString("description", description);
  689. dispatchEvent(
  690. webView,
  691. new TopLoadingErrorEvent(webView.getId(), eventData));
  692. }
  693. @RequiresApi(api = Build.VERSION_CODES.M)
  694. @Override
  695. public void onReceivedHttpError(
  696. WebView webView,
  697. WebResourceRequest request,
  698. WebResourceResponse errorResponse) {
  699. super.onReceivedHttpError(webView, request, errorResponse);
  700. if (request.isForMainFrame()) {
  701. WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
  702. eventData.putInt("statusCode", errorResponse.getStatusCode());
  703. eventData.putString("description", errorResponse.getReasonPhrase());
  704. dispatchEvent(
  705. webView,
  706. new TopHttpErrorEvent(webView.getId(), eventData));
  707. }
  708. }
  709. protected void emitFinishEvent(WebView webView, String url) {
  710. dispatchEvent(
  711. webView,
  712. new TopLoadingFinishEvent(
  713. webView.getId(),
  714. createWebViewEvent(webView, url)));
  715. }
  716. protected WritableMap createWebViewEvent(WebView webView, String url) {
  717. WritableMap event = Arguments.createMap();
  718. event.putDouble("target", webView.getId());
  719. // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
  720. // like onPageFinished
  721. event.putString("url", url);
  722. event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
  723. event.putString("title", webView.getTitle());
  724. event.putBoolean("canGoBack", webView.canGoBack());
  725. event.putBoolean("canGoForward", webView.canGoForward());
  726. return event;
  727. }
  728. public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
  729. mUrlPrefixesForDefaultIntent = specialUrls;
  730. }
  731. }
  732. protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
  733. protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
  734. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
  735. @RequiresApi(api = Build.VERSION_CODES.KITKAT)
  736. protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  737. View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  738. View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  739. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
  740. View.SYSTEM_UI_FLAG_FULLSCREEN |
  741. View.SYSTEM_UI_FLAG_IMMERSIVE |
  742. View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  743. protected ReactContext mReactContext;
  744. protected View mWebView;
  745. protected View mVideoView;
  746. protected WebChromeClient.CustomViewCallback mCustomViewCallback;
  747. public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
  748. this.mReactContext = reactContext;
  749. this.mWebView = webView;
  750. }
  751. @Override
  752. public boolean onConsoleMessage(ConsoleMessage message) {
  753. if (ReactBuildConfig.DEBUG) {
  754. return super.onConsoleMessage(message);
  755. }
  756. // Ignore console logs in non debug builds.
  757. return true;
  758. }
  759. // Fix WebRTC permission request error.
  760. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  761. @Override
  762. public void onPermissionRequest(final PermissionRequest request) {
  763. String[] requestedResources = request.getResources();
  764. ArrayList<String> permissions = new ArrayList<>();
  765. ArrayList<String> grantedPermissions = new ArrayList<String>();
  766. for (int i = 0; i < requestedResources.length; i++) {
  767. if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
  768. permissions.add(Manifest.permission.RECORD_AUDIO);
  769. } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
  770. permissions.add(Manifest.permission.CAMERA);
  771. }
  772. // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
  773. }
  774. for (int i = 0; i < permissions.size(); i++) {
  775. if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
  776. continue;
  777. }
  778. if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
  779. grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
  780. } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
  781. grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
  782. }
  783. }
  784. if (grantedPermissions.isEmpty()) {
  785. request.deny();
  786. } else {
  787. String[] grantedPermissionsArray = new String[grantedPermissions.size()];
  788. grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
  789. request.grant(grantedPermissionsArray);
  790. }
  791. }
  792. @Override
  793. public void onProgressChanged(WebView webView, int newProgress) {
  794. super.onProgressChanged(webView, newProgress);
  795. final String url = webView.getUrl();
  796. if (
  797. url != null
  798. && activeUrl != null
  799. && !url.equals(activeUrl)
  800. ) {
  801. return;
  802. }
  803. WritableMap event = Arguments.createMap();
  804. event.putDouble("target", webView.getId());
  805. event.putString("title", webView.getTitle());
  806. event.putString("url", url);
  807. event.putBoolean("canGoBack", webView.canGoBack());
  808. event.putBoolean("canGoForward", webView.canGoForward());
  809. event.putDouble("progress", (float) newProgress / 100);
  810. dispatchEvent(
  811. webView,
  812. new TopLoadingProgressEvent(
  813. webView.getId(),
  814. event));
  815. }
  816. @Override
  817. public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
  818. callback.invoke(origin, true, false);
  819. }
  820. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
  821. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  822. }
  823. protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
  824. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
  825. }
  826. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  827. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  828. }
  829. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  830. @Override
  831. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  832. String[] acceptTypes = fileChooserParams.getAcceptTypes();
  833. boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
  834. Intent intent = fileChooserParams.createIntent();
  835. return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
  836. }
  837. @Override
  838. public void onHostResume() {
  839. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
  840. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  841. }
  842. }
  843. @Override
  844. public void onHostPause() { }
  845. @Override
  846. public void onHostDestroy() { }
  847. protected ViewGroup getRootView() {
  848. return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
  849. }
  850. }
  851. /**
  852. * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
  853. * to call {@link WebView#destroy} on activity destroy event and also to clear the client
  854. */
  855. protected static class RNCWebView extends WebView implements LifecycleEventListener {
  856. protected @Nullable
  857. String injectedJS;
  858. protected boolean messagingEnabled = false;
  859. protected @Nullable
  860. RNCWebViewClient mRNCWebViewClient;
  861. protected @Nullable
  862. CatalystInstance mCatalystInstance;
  863. protected boolean sendContentSizeChangeEvents = false;
  864. private OnScrollDispatchHelper mOnScrollDispatchHelper;
  865. protected boolean hasScrollEvent = false;
  866. /**
  867. * WebView must be created with an context of the current activity
  868. * <p>
  869. * Activity Context is required for creation of dialogs internally by WebView
  870. * Reactive Native needed for access to ReactNative internal system functionality
  871. */
  872. public RNCWebView(ThemedReactContext reactContext) {
  873. super(reactContext);
  874. }
  875. public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
  876. this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
  877. }
  878. public void setHasScrollEvent(boolean hasScrollEvent) {
  879. this.hasScrollEvent = hasScrollEvent;
  880. }
  881. @Override
  882. public void onHostResume() {
  883. // do nothing
  884. }
  885. @Override
  886. public void onHostPause() {
  887. // do nothing
  888. }
  889. @Override
  890. public void onHostDestroy() {
  891. cleanupCallbacksAndDestroy();
  892. }
  893. @Override
  894. protected void onSizeChanged(int w, int h, int ow, int oh) {
  895. super.onSizeChanged(w, h, ow, oh);
  896. if (sendContentSizeChangeEvents) {
  897. dispatchEvent(
  898. this,
  899. new ContentSizeChangeEvent(
  900. this.getId(),
  901. w,
  902. h
  903. )
  904. );
  905. }
  906. }
  907. @Override
  908. public void setWebViewClient(WebViewClient client) {
  909. super.setWebViewClient(client);
  910. if (client instanceof RNCWebViewClient) {
  911. mRNCWebViewClient = (RNCWebViewClient) client;
  912. }
  913. }
  914. public @Nullable
  915. RNCWebViewClient getRNCWebViewClient() {
  916. return mRNCWebViewClient;
  917. }
  918. public void setInjectedJavaScript(@Nullable String js) {
  919. injectedJS = js;
  920. }
  921. protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
  922. return new RNCWebViewBridge(webView);
  923. }
  924. protected void createCatalystInstance() {
  925. ReactContext reactContext = (ReactContext) this.getContext();
  926. if (reactContext != null) {
  927. mCatalystInstance = reactContext.getCatalystInstance();
  928. }
  929. }
  930. @SuppressLint("AddJavascriptInterface")
  931. public void setMessagingEnabled(boolean enabled) {
  932. if (messagingEnabled == enabled) {
  933. return;
  934. }
  935. messagingEnabled = enabled;
  936. if (enabled) {
  937. addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
  938. this.createCatalystInstance();
  939. } else {
  940. removeJavascriptInterface(JAVASCRIPT_INTERFACE);
  941. }
  942. }
  943. protected void evaluateJavascriptWithFallback(String script) {
  944. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  945. evaluateJavascript(script, null);
  946. return;
  947. }
  948. try {
  949. loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
  950. } catch (UnsupportedEncodingException e) {
  951. // UTF-8 should always be supported
  952. throw new RuntimeException(e);
  953. }
  954. }
  955. public void callInjectedJavaScript() {
  956. if (getSettings().getJavaScriptEnabled() &&
  957. injectedJS != null &&
  958. !TextUtils.isEmpty(injectedJS)) {
  959. evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
  960. }
  961. }
  962. public void onMessage(String message) {
  963. ReactContext reactContext = (ReactContext) this.getContext();
  964. RNCWebView mContext = this;
  965. if (mRNCWebViewClient != null) {
  966. WebView webView = this;
  967. webView.post(new Runnable() {
  968. @Override
  969. public void run() {
  970. if (mRNCWebViewClient == null) {
  971. return;
  972. }
  973. WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
  974. data.putString("data", message);
  975. if (mCatalystInstance != null) {
  976. mContext.sendDirectMessage(data);
  977. } else {
  978. dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
  979. }
  980. }
  981. });
  982. } else {
  983. WritableMap eventData = Arguments.createMap();
  984. eventData.putString("data", message);
  985. if (mCatalystInstance != null) {
  986. this.sendDirectMessage(eventData);
  987. } else {
  988. dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
  989. }
  990. }
  991. }
  992. protected void sendDirectMessage(WritableMap data) {
  993. WritableNativeMap event = new WritableNativeMap();
  994. event.putMap("nativeEvent", data);
  995. WritableNativeArray params = new WritableNativeArray();
  996. params.pushMap(event);
  997. mCatalystInstance.callFunction("WebViewMessageHandler", "onMessage", params);
  998. }
  999. protected void onScrollChanged(int x, int y, int oldX, int oldY) {
  1000. super.onScrollChanged(x, y, oldX, oldY);
  1001. if (!hasScrollEvent) {
  1002. return;
  1003. }
  1004. if (mOnScrollDispatchHelper == null) {
  1005. mOnScrollDispatchHelper = new OnScrollDispatchHelper();
  1006. }
  1007. if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
  1008. ScrollEvent event = ScrollEvent.obtain(
  1009. this.getId(),
  1010. ScrollEventType.SCROLL,
  1011. x,
  1012. y,
  1013. mOnScrollDispatchHelper.getXFlingVelocity(),
  1014. mOnScrollDispatchHelper.getYFlingVelocity(),
  1015. this.computeHorizontalScrollRange(),
  1016. this.computeVerticalScrollRange(),
  1017. this.getWidth(),
  1018. this.getHeight());
  1019. dispatchEvent(this, event);
  1020. }
  1021. }
  1022. protected void cleanupCallbacksAndDestroy() {
  1023. setWebViewClient(null);
  1024. destroy();
  1025. }
  1026. protected class RNCWebViewBridge {
  1027. RNCWebView mContext;
  1028. RNCWebViewBridge(RNCWebView c) {
  1029. mContext = c;
  1030. }
  1031. /**
  1032. * This method is called whenever JavaScript running within the web view calls:
  1033. * - window[JAVASCRIPT_INTERFACE].postMessage
  1034. */
  1035. @JavascriptInterface
  1036. public void postMessage(String message) {
  1037. mContext.onMessage(message);
  1038. }
  1039. }
  1040. }
  1041. }