react-native-webview.git

RNCWebViewManager.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. package com.reactnativecommunity.webview;
  2. import android.annotation.TargetApi;
  3. import android.content.Context;
  4. import com.facebook.react.uimanager.UIManagerModule;
  5. import java.util.LinkedList;
  6. import java.util.List;
  7. import java.util.regex.Pattern;
  8. import javax.annotation.Nullable;
  9. import java.io.UnsupportedEncodingException;
  10. import java.net.URLEncoder;
  11. import java.util.ArrayList;
  12. import java.util.HashMap;
  13. import java.util.Locale;
  14. import java.util.Map;
  15. import android.content.ActivityNotFoundException;
  16. import android.content.Intent;
  17. import android.graphics.Bitmap;
  18. import android.graphics.Picture;
  19. import android.net.Uri;
  20. import android.os.Build;
  21. import android.text.TextUtils;
  22. import android.view.View;
  23. import android.view.ViewGroup.LayoutParams;
  24. import android.webkit.ConsoleMessage;
  25. import android.webkit.CookieManager;
  26. import android.webkit.GeolocationPermissions;
  27. import android.webkit.JavascriptInterface;
  28. import android.webkit.ValueCallback;
  29. import android.webkit.WebChromeClient;
  30. import android.webkit.WebResourceRequest;
  31. import android.webkit.WebSettings;
  32. import android.webkit.WebView;
  33. import android.webkit.WebViewClient;
  34. import com.facebook.common.logging.FLog;
  35. import com.facebook.react.bridge.Arguments;
  36. import com.facebook.react.bridge.LifecycleEventListener;
  37. import com.facebook.react.bridge.ReactContext;
  38. import com.facebook.react.bridge.ReadableArray;
  39. import com.facebook.react.bridge.ReadableMap;
  40. import com.facebook.react.bridge.ReadableMapKeySetIterator;
  41. import com.facebook.react.bridge.WritableMap;
  42. import com.facebook.react.common.MapBuilder;
  43. import com.facebook.react.common.ReactConstants;
  44. import com.facebook.react.common.build.ReactBuildConfig;
  45. import com.facebook.react.module.annotations.ReactModule;
  46. import com.facebook.react.uimanager.SimpleViewManager;
  47. import com.facebook.react.uimanager.ThemedReactContext;
  48. import com.facebook.react.uimanager.annotations.ReactProp;
  49. import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
  50. import com.facebook.react.uimanager.events.Event;
  51. import com.facebook.react.uimanager.events.EventDispatcher;
  52. import com.facebook.react.uimanager.events.RCTEventEmitter;
  53. import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
  54. import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
  55. import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
  56. import com.reactnativecommunity.webview.events.TopMessageEvent;
  57. import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
  58. import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
  59. import java.io.UnsupportedEncodingException;
  60. import java.util.ArrayList;
  61. import java.util.HashMap;
  62. import java.util.Locale;
  63. import java.util.Map;
  64. import javax.annotation.Nullable;
  65. import org.json.JSONException;
  66. import org.json.JSONObject;
  67. /**
  68. * Manages instances of {@link WebView}
  69. *
  70. * Can accept following commands:
  71. * - GO_BACK
  72. * - GO_FORWARD
  73. * - RELOAD
  74. * - LOAD_URL
  75. *
  76. * {@link WebView} instances could emit following direct events:
  77. * - topLoadingFinish
  78. * - topLoadingStart
  79. * - topLoadingStart
  80. * - topLoadingProgress
  81. * - topShouldStartLoadWithRequest
  82. *
  83. * Each event will carry the following properties:
  84. * - target - view's react tag
  85. * - url - url set for the webview
  86. * - loading - whether webview is in a loading state
  87. * - title - title of the current page
  88. * - canGoBack - boolean, whether there is anything on a history stack to go back
  89. * - canGoForward - boolean, whether it is possible to request GO_FORWARD command
  90. */
  91. @ReactModule(name = RNCWebViewManager.REACT_CLASS)
  92. public class RNCWebViewManager extends SimpleViewManager<WebView> {
  93. protected static final String REACT_CLASS = "RNCWebView";
  94. private RNCWebViewPackage aPackage;
  95. protected static final String HTML_ENCODING = "UTF-8";
  96. protected static final String HTML_MIME_TYPE = "text/html";
  97. protected static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE";
  98. protected static final String HTTP_METHOD_POST = "POST";
  99. public static final int COMMAND_GO_BACK = 1;
  100. public static final int COMMAND_GO_FORWARD = 2;
  101. public static final int COMMAND_RELOAD = 3;
  102. public static final int COMMAND_STOP_LOADING = 4;
  103. public static final int COMMAND_POST_MESSAGE = 5;
  104. public static final int COMMAND_INJECT_JAVASCRIPT = 6;
  105. public static final int COMMAND_LOAD_URL = 7;
  106. // Use `webView.loadUrl("about:blank")` to reliably reset the view
  107. // state and release page resources (including any running JavaScript).
  108. protected static final String BLANK_URL = "about:blank";
  109. protected WebViewConfig mWebViewConfig;
  110. protected @Nullable WebView.PictureListener mPictureListener;
  111. protected static class RNCWebViewClient extends WebViewClient {
  112. protected boolean mLastLoadFailed = false;
  113. protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent;
  114. @Override
  115. public void onPageFinished(WebView webView, String url) {
  116. super.onPageFinished(webView, url);
  117. if (!mLastLoadFailed) {
  118. RNCWebView reactWebView = (RNCWebView) webView;
  119. reactWebView.callInjectedJavaScript();
  120. reactWebView.linkBridge();
  121. emitFinishEvent(webView, url);
  122. }
  123. }
  124. @Override
  125. public void onPageStarted(WebView webView, String url, Bitmap favicon) {
  126. super.onPageStarted(webView, url, favicon);
  127. mLastLoadFailed = false;
  128. dispatchEvent(
  129. webView,
  130. new TopLoadingStartEvent(
  131. webView.getId(),
  132. createWebViewEvent(webView, url)));
  133. }
  134. @Override
  135. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  136. dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), url));
  137. return true;
  138. }
  139. @TargetApi(Build.VERSION_CODES.N)
  140. @Override
  141. public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
  142. dispatchEvent(view, new TopShouldStartLoadWithRequestEvent(view.getId(), request.getUrl().toString()));
  143. return true;
  144. }
  145. @Override
  146. public void onReceivedError(
  147. WebView webView,
  148. int errorCode,
  149. String description,
  150. String failingUrl) {
  151. super.onReceivedError(webView, errorCode, description, failingUrl);
  152. mLastLoadFailed = true;
  153. // In case of an error JS side expect to get a finish event first, and then get an error event
  154. // Android WebView does it in the opposite way, so we need to simulate that behavior
  155. emitFinishEvent(webView, failingUrl);
  156. WritableMap eventData = createWebViewEvent(webView, failingUrl);
  157. eventData.putDouble("code", errorCode);
  158. eventData.putString("description", description);
  159. dispatchEvent(
  160. webView,
  161. new TopLoadingErrorEvent(webView.getId(), eventData));
  162. }
  163. protected void emitFinishEvent(WebView webView, String url) {
  164. dispatchEvent(
  165. webView,
  166. new TopLoadingFinishEvent(
  167. webView.getId(),
  168. createWebViewEvent(webView, url)));
  169. }
  170. protected WritableMap createWebViewEvent(WebView webView, String url) {
  171. WritableMap event = Arguments.createMap();
  172. event.putDouble("target", webView.getId());
  173. // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
  174. // like onPageFinished
  175. event.putString("url", url);
  176. event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
  177. event.putString("title", webView.getTitle());
  178. event.putBoolean("canGoBack", webView.canGoBack());
  179. event.putBoolean("canGoForward", webView.canGoForward());
  180. return event;
  181. }
  182. public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
  183. mUrlPrefixesForDefaultIntent = specialUrls;
  184. }
  185. }
  186. /**
  187. * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
  188. * to call {@link WebView#destroy} on activity destroy event and also to clear the client
  189. */
  190. protected static class RNCWebView extends WebView implements LifecycleEventListener {
  191. protected @Nullable String injectedJS;
  192. protected boolean messagingEnabled = false;
  193. protected @Nullable RNCWebViewClient mRNCWebViewClient;
  194. protected class RNCWebViewBridge {
  195. RNCWebView mContext;
  196. RNCWebViewBridge(RNCWebView c) {
  197. mContext = c;
  198. }
  199. @JavascriptInterface
  200. public void postMessage(String message) {
  201. mContext.onMessage(message);
  202. }
  203. }
  204. /**
  205. * WebView must be created with an context of the current activity
  206. *
  207. * Activity Context is required for creation of dialogs internally by WebView
  208. * Reactive Native needed for access to ReactNative internal system functionality
  209. *
  210. */
  211. public RNCWebView(ThemedReactContext reactContext) {
  212. super(reactContext);
  213. }
  214. @Override
  215. public void onHostResume() {
  216. // do nothing
  217. }
  218. @Override
  219. public void onHostPause() {
  220. // do nothing
  221. }
  222. @Override
  223. public void onHostDestroy() {
  224. cleanupCallbacksAndDestroy();
  225. }
  226. @Override
  227. public void setWebViewClient(WebViewClient client) {
  228. super.setWebViewClient(client);
  229. mRNCWebViewClient = (RNCWebViewClient)client;
  230. }
  231. public @Nullable RNCWebViewClient getRNCWebViewClient() {
  232. return mRNCWebViewClient;
  233. }
  234. public void setInjectedJavaScript(@Nullable String js) {
  235. injectedJS = js;
  236. }
  237. protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
  238. return new RNCWebViewBridge(webView);
  239. }
  240. public void setMessagingEnabled(boolean enabled) {
  241. if (messagingEnabled == enabled) {
  242. return;
  243. }
  244. messagingEnabled = enabled;
  245. if (enabled) {
  246. addJavascriptInterface(createRNCWebViewBridge(this), BRIDGE_NAME);
  247. linkBridge();
  248. } else {
  249. removeJavascriptInterface(BRIDGE_NAME);
  250. }
  251. }
  252. protected void evaluateJavascriptWithFallback(String script) {
  253. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  254. evaluateJavascript(script, null);
  255. return;
  256. }
  257. try {
  258. loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
  259. } catch (UnsupportedEncodingException e) {
  260. // UTF-8 should always be supported
  261. throw new RuntimeException(e);
  262. }
  263. }
  264. public void callInjectedJavaScript() {
  265. if (getSettings().getJavaScriptEnabled() &&
  266. injectedJS != null &&
  267. !TextUtils.isEmpty(injectedJS)) {
  268. evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
  269. }
  270. }
  271. public void linkBridge() {
  272. if (messagingEnabled) {
  273. if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  274. // See isNative in lodash
  275. String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
  276. evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
  277. @Override
  278. public void onReceiveValue(String value) {
  279. if (value.equals("true")) {
  280. FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
  281. }
  282. }
  283. });
  284. }
  285. evaluateJavascriptWithFallback("(" +
  286. "window.originalPostMessage = window.postMessage," +
  287. "window.postMessage = function(data) {" +
  288. BRIDGE_NAME + ".postMessage(String(data));" +
  289. "}" +
  290. ")");
  291. }
  292. }
  293. public void onMessage(String message) {
  294. dispatchEvent(this, new TopMessageEvent(this.getId(), message));
  295. }
  296. protected void cleanupCallbacksAndDestroy() {
  297. setWebViewClient(null);
  298. destroy();
  299. }
  300. }
  301. public RNCWebViewManager() {
  302. mWebViewConfig = new WebViewConfig() {
  303. public void configWebView(WebView webView) {
  304. }
  305. };
  306. }
  307. public RNCWebViewManager(WebViewConfig webViewConfig) {
  308. mWebViewConfig = webViewConfig;
  309. }
  310. @Override
  311. public String getName() {
  312. return REACT_CLASS;
  313. }
  314. protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
  315. return new RNCWebView(reactContext);
  316. }
  317. @Override
  318. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  319. protected WebView createViewInstance(ThemedReactContext reactContext) {
  320. RNCWebView webView = createRNCWebViewInstance(reactContext);
  321. webView.setWebChromeClient(new WebChromeClient() {
  322. @Override
  323. public boolean onConsoleMessage(ConsoleMessage message) {
  324. if (ReactBuildConfig.DEBUG) {
  325. return super.onConsoleMessage(message);
  326. }
  327. // Ignore console logs in non debug builds.
  328. return true;
  329. }
  330. @Override
  331. public void onProgressChanged(WebView webView, int newProgress) {
  332. super.onProgressChanged(webView, newProgress);
  333. WritableMap event = Arguments.createMap();
  334. event.putDouble("target", webView.getId());
  335. event.putString("title", webView.getTitle());
  336. event.putBoolean("canGoBack", webView.canGoBack());
  337. event.putBoolean("canGoForward", webView.canGoForward());
  338. event.putDouble("progress", (float)newProgress/100);
  339. dispatchEvent(
  340. webView,
  341. new TopLoadingProgressEvent(
  342. webView.getId(),
  343. event));
  344. }
  345. @Override
  346. public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
  347. callback.invoke(origin, true, false);
  348. }
  349. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
  350. getModule().startPhotoPickerIntent(filePathCallback, acceptType);
  351. }
  352. protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
  353. getModule().startPhotoPickerIntent(filePathCallback, "");
  354. }
  355. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  356. getModule().startPhotoPickerIntent(filePathCallback, acceptType);
  357. }
  358. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  359. @Override
  360. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  361. String[] acceptTypes = fileChooserParams.getAcceptTypes();
  362. boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
  363. Intent intent = fileChooserParams.createIntent();
  364. return getModule().startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
  365. }
  366. });
  367. reactContext.addLifecycleEventListener(webView);
  368. mWebViewConfig.configWebView(webView);
  369. WebSettings settings = webView.getSettings();
  370. settings.setBuiltInZoomControls(true);
  371. settings.setDisplayZoomControls(false);
  372. settings.setDomStorageEnabled(true);
  373. settings.setAllowFileAccess(false);
  374. settings.setAllowContentAccess(false);
  375. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  376. settings.setAllowFileAccessFromFileURLs(false);
  377. setAllowUniversalAccessFromFileURLs(webView, false);
  378. }
  379. setMixedContentMode(webView, "never");
  380. // Fixes broken full-screen modals/galleries due to body height being 0.
  381. webView.setLayoutParams(
  382. new LayoutParams(LayoutParams.MATCH_PARENT,
  383. LayoutParams.MATCH_PARENT));
  384. setGeolocationEnabled(webView, false);
  385. if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  386. WebView.setWebContentsDebuggingEnabled(true);
  387. }
  388. return webView;
  389. }
  390. @ReactProp(name = "javaScriptEnabled")
  391. public void setJavaScriptEnabled(WebView view, boolean enabled) {
  392. view.getSettings().setJavaScriptEnabled(enabled);
  393. }
  394. @ReactProp(name = "overScrollMode")
  395. public void setOverScrollMode(WebView view, String overScrollModeString) {
  396. Integer overScrollMode;
  397. switch (overScrollModeString) {
  398. case "never":
  399. overScrollMode = View.OVER_SCROLL_NEVER;
  400. break;
  401. case "content":
  402. overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
  403. break;
  404. case "always":
  405. default:
  406. overScrollMode = View.OVER_SCROLL_ALWAYS;
  407. break;
  408. }
  409. view.setOverScrollMode(overScrollMode);
  410. }
  411. @ReactProp(name = "thirdPartyCookiesEnabled")
  412. public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
  413. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  414. CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
  415. }
  416. }
  417. @ReactProp(name = "scalesPageToFit")
  418. public void setScalesPageToFit(WebView view, boolean enabled) {
  419. view.getSettings().setUseWideViewPort(!enabled);
  420. }
  421. @ReactProp(name = "domStorageEnabled")
  422. public void setDomStorageEnabled(WebView view, boolean enabled) {
  423. view.getSettings().setDomStorageEnabled(enabled);
  424. }
  425. @ReactProp(name = "userAgent")
  426. public void setUserAgent(WebView view, @Nullable String userAgent) {
  427. if (userAgent != null) {
  428. // TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
  429. view.getSettings().setUserAgentString(userAgent);
  430. }
  431. }
  432. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  433. @ReactProp(name = "mediaPlaybackRequiresUserAction")
  434. public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
  435. view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
  436. }
  437. @ReactProp(name = "allowUniversalAccessFromFileURLs")
  438. public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
  439. view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
  440. }
  441. @ReactProp(name = "saveFormDataDisabled")
  442. public void setSaveFormDataDisabled(WebView view, boolean disable) {
  443. view.getSettings().setSaveFormData(!disable);
  444. }
  445. @ReactProp(name = "injectedJavaScript")
  446. public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
  447. ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
  448. }
  449. @ReactProp(name = "messagingEnabled")
  450. public void setMessagingEnabled(WebView view, boolean enabled) {
  451. ((RNCWebView) view).setMessagingEnabled(enabled);
  452. }
  453. @ReactProp(name = "source")
  454. public void setSource(WebView view, @Nullable ReadableMap source) {
  455. if (source != null) {
  456. if (source.hasKey("html")) {
  457. String html = source.getString("html");
  458. if (source.hasKey("baseUrl")) {
  459. view.loadDataWithBaseURL(
  460. source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
  461. } else {
  462. view.loadData(html, HTML_MIME_TYPE + "; charset=" + HTML_ENCODING, null);
  463. }
  464. return;
  465. }
  466. if (source.hasKey("uri")) {
  467. String url = source.getString("uri");
  468. String previousUrl = view.getUrl();
  469. if (previousUrl != null && previousUrl.equals(url)) {
  470. return;
  471. }
  472. if (source.hasKey("method")) {
  473. String method = source.getString("method");
  474. if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
  475. byte[] postData = null;
  476. if (source.hasKey("body")) {
  477. String body = source.getString("body");
  478. try {
  479. postData = body.getBytes("UTF-8");
  480. } catch (UnsupportedEncodingException e) {
  481. postData = body.getBytes();
  482. }
  483. }
  484. if (postData == null) {
  485. postData = new byte[0];
  486. }
  487. view.postUrl(url, postData);
  488. return;
  489. }
  490. }
  491. HashMap<String, String> headerMap = new HashMap<>();
  492. if (source.hasKey("headers")) {
  493. ReadableMap headers = source.getMap("headers");
  494. ReadableMapKeySetIterator iter = headers.keySetIterator();
  495. while (iter.hasNextKey()) {
  496. String key = iter.nextKey();
  497. if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
  498. if (view.getSettings() != null) {
  499. view.getSettings().setUserAgentString(headers.getString(key));
  500. }
  501. } else {
  502. headerMap.put(key, headers.getString(key));
  503. }
  504. }
  505. }
  506. view.loadUrl(url, headerMap);
  507. return;
  508. }
  509. }
  510. view.loadUrl(BLANK_URL);
  511. }
  512. @ReactProp(name = "onContentSizeChange")
  513. public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
  514. if (sendContentSizeChangeEvents) {
  515. view.setPictureListener(getPictureListener());
  516. } else {
  517. view.setPictureListener(null);
  518. }
  519. }
  520. @ReactProp(name = "mixedContentMode")
  521. public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
  522. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  523. if (mixedContentMode == null || "never".equals(mixedContentMode)) {
  524. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
  525. } else if ("always".equals(mixedContentMode)) {
  526. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  527. } else if ("compatibility".equals(mixedContentMode)) {
  528. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
  529. }
  530. }
  531. }
  532. @ReactProp(name = "urlPrefixesForDefaultIntent")
  533. public void setUrlPrefixesForDefaultIntent(
  534. WebView view,
  535. @Nullable ReadableArray urlPrefixesForDefaultIntent) {
  536. RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
  537. if (client != null && urlPrefixesForDefaultIntent != null) {
  538. client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
  539. }
  540. }
  541. @ReactProp(name = "allowFileAccess")
  542. public void setAllowFileAccess(
  543. WebView view,
  544. @Nullable Boolean allowFileAccess) {
  545. view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
  546. }
  547. @ReactProp(name = "geolocationEnabled")
  548. public void setGeolocationEnabled(
  549. WebView view,
  550. @Nullable Boolean isGeolocationEnabled) {
  551. view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
  552. }
  553. @Override
  554. protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
  555. // Do not register default touch emitter and let WebView implementation handle touches
  556. view.setWebViewClient(new RNCWebViewClient());
  557. }
  558. @Override
  559. public Map getExportedCustomDirectEventTypeConstants() {
  560. Map export = super.getExportedCustomDirectEventTypeConstants();
  561. if (export == null) {
  562. export = MapBuilder.newHashMap();
  563. }
  564. export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
  565. export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
  566. return export;
  567. }
  568. @Override
  569. public @Nullable Map<String, Integer> getCommandsMap() {
  570. return MapBuilder.of(
  571. "goBack", COMMAND_GO_BACK,
  572. "goForward", COMMAND_GO_FORWARD,
  573. "reload", COMMAND_RELOAD,
  574. "stopLoading", COMMAND_STOP_LOADING,
  575. "postMessage", COMMAND_POST_MESSAGE,
  576. "injectJavaScript", COMMAND_INJECT_JAVASCRIPT,
  577. "loadUrl", COMMAND_LOAD_URL
  578. );
  579. }
  580. @Override
  581. public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
  582. switch (commandId) {
  583. case COMMAND_GO_BACK:
  584. root.goBack();
  585. break;
  586. case COMMAND_GO_FORWARD:
  587. root.goForward();
  588. break;
  589. case COMMAND_RELOAD:
  590. root.reload();
  591. break;
  592. case COMMAND_STOP_LOADING:
  593. root.stopLoading();
  594. break;
  595. case COMMAND_POST_MESSAGE:
  596. try {
  597. RNCWebView reactWebView = (RNCWebView) root;
  598. JSONObject eventInitDict = new JSONObject();
  599. eventInitDict.put("data", args.getString(0));
  600. reactWebView.evaluateJavascriptWithFallback("(function () {" +
  601. "var event;" +
  602. "var data = " + eventInitDict.toString() + ";" +
  603. "try {" +
  604. "event = new MessageEvent('message', data);" +
  605. "} catch (e) {" +
  606. "event = document.createEvent('MessageEvent');" +
  607. "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
  608. "}" +
  609. "document.dispatchEvent(event);" +
  610. "})();");
  611. } catch (JSONException e) {
  612. throw new RuntimeException(e);
  613. }
  614. break;
  615. case COMMAND_INJECT_JAVASCRIPT:
  616. RNCWebView reactWebView = (RNCWebView) root;
  617. reactWebView.evaluateJavascriptWithFallback(args.getString(0));
  618. break;
  619. case COMMAND_LOAD_URL:
  620. if (args == null) {
  621. throw new RuntimeException("Arguments for loading an url are null!");
  622. }
  623. root.loadUrl(args.getString(0));
  624. break;
  625. }
  626. }
  627. @Override
  628. public void onDropViewInstance(WebView webView) {
  629. super.onDropViewInstance(webView);
  630. ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
  631. ((RNCWebView) webView).cleanupCallbacksAndDestroy();
  632. }
  633. protected WebView.PictureListener getPictureListener() {
  634. if (mPictureListener == null) {
  635. mPictureListener = new WebView.PictureListener() {
  636. @Override
  637. public void onNewPicture(WebView webView, Picture picture) {
  638. dispatchEvent(
  639. webView,
  640. new ContentSizeChangeEvent(
  641. webView.getId(),
  642. webView.getWidth(),
  643. webView.getContentHeight()));
  644. }
  645. };
  646. }
  647. return mPictureListener;
  648. }
  649. protected static void dispatchEvent(WebView webView, Event event) {
  650. ReactContext reactContext = (ReactContext) webView.getContext();
  651. EventDispatcher eventDispatcher =
  652. reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
  653. eventDispatcher.dispatchEvent(event);
  654. }
  655. public RNCWebViewPackage getPackage() {
  656. return this.aPackage;
  657. }
  658. public void setPackage(RNCWebViewPackage aPackage) {
  659. this.aPackage = aPackage;
  660. }
  661. public RNCWebViewModule getModule() {
  662. return this.aPackage.getModule();
  663. }
  664. }