Geen omschrijving

RNCWebViewManager.java 27KB

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