react-native-webview.git

RNCWebViewManager.java 28KB

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