react-native-webview.git

RNCWebViewManager.java 41KB

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