react-native-webview.git

RNCWebViewManager.java 26KB

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