暂无描述

RNCWebViewManager.java 25KB

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