react-native-webview.git

RNCWebViewManager.java 29KB

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