暫無描述

RNCWebViewManager.java 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  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 android.content.pm.ActivityInfo;
  7. import android.content.pm.PackageManager;
  8. import android.graphics.Bitmap;
  9. import android.graphics.Color;
  10. import android.Manifest;
  11. import android.net.http.SslError;
  12. import android.net.Uri;
  13. import android.os.Build;
  14. import android.os.Environment;
  15. import android.os.SystemClock;
  16. import android.text.TextUtils;
  17. import android.util.Log;
  18. import android.view.Gravity;
  19. import android.view.View;
  20. import android.view.ViewGroup;
  21. import android.view.ViewGroup.LayoutParams;
  22. import android.view.WindowManager;
  23. import android.webkit.ConsoleMessage;
  24. import android.webkit.CookieManager;
  25. import android.webkit.DownloadListener;
  26. import android.webkit.GeolocationPermissions;
  27. import android.webkit.JavascriptInterface;
  28. import android.webkit.RenderProcessGoneDetail;
  29. import android.webkit.SslErrorHandler;
  30. import android.webkit.PermissionRequest;
  31. import android.webkit.URLUtil;
  32. import android.webkit.ValueCallback;
  33. import android.webkit.WebChromeClient;
  34. import android.webkit.WebResourceRequest;
  35. import android.webkit.WebResourceResponse;
  36. import android.webkit.WebSettings;
  37. import android.webkit.WebView;
  38. import android.webkit.WebViewClient;
  39. import android.widget.FrameLayout;
  40. import androidx.annotation.Nullable;
  41. import androidx.annotation.RequiresApi;
  42. import androidx.core.content.ContextCompat;
  43. import androidx.core.util.Pair;
  44. import com.facebook.common.logging.FLog;
  45. import com.facebook.react.views.scroll.ScrollEvent;
  46. import com.facebook.react.views.scroll.ScrollEventType;
  47. import com.facebook.react.views.scroll.OnScrollDispatchHelper;
  48. import com.facebook.react.bridge.Arguments;
  49. import com.facebook.react.bridge.CatalystInstance;
  50. import com.facebook.react.bridge.LifecycleEventListener;
  51. import com.facebook.react.bridge.ReactContext;
  52. import com.facebook.react.bridge.ReadableArray;
  53. import com.facebook.react.bridge.ReadableMap;
  54. import com.facebook.react.bridge.ReadableMapKeySetIterator;
  55. import com.facebook.react.bridge.WritableMap;
  56. import com.facebook.react.bridge.WritableNativeArray;
  57. import com.facebook.react.bridge.WritableNativeMap;
  58. import com.facebook.react.common.MapBuilder;
  59. import com.facebook.react.common.build.ReactBuildConfig;
  60. import com.facebook.react.module.annotations.ReactModule;
  61. import com.facebook.react.uimanager.SimpleViewManager;
  62. import com.facebook.react.uimanager.ThemedReactContext;
  63. import com.facebook.react.uimanager.UIManagerModule;
  64. import com.facebook.react.uimanager.annotations.ReactProp;
  65. import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
  66. import com.facebook.react.uimanager.events.Event;
  67. import com.facebook.react.uimanager.events.EventDispatcher;
  68. import com.reactnativecommunity.webview.RNCWebViewModule.ShouldOverrideUrlLoadingLock.ShouldOverrideCallbackState;
  69. import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
  70. import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
  71. import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
  72. import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
  73. import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
  74. import com.reactnativecommunity.webview.events.TopMessageEvent;
  75. import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
  76. import com.reactnativecommunity.webview.events.TopRenderProcessGoneEvent;
  77. import org.json.JSONException;
  78. import org.json.JSONObject;
  79. import java.io.UnsupportedEncodingException;
  80. import java.net.MalformedURLException;
  81. import java.net.URL;
  82. import java.net.URLEncoder;
  83. import java.util.ArrayList;
  84. import java.util.HashMap;
  85. import java.util.Locale;
  86. import java.util.Map;
  87. import java.util.concurrent.atomic.AtomicReference;
  88. /**
  89. * Manages instances of {@link WebView}
  90. * <p>
  91. * Can accept following commands:
  92. * - GO_BACK
  93. * - GO_FORWARD
  94. * - RELOAD
  95. * - LOAD_URL
  96. * <p>
  97. * {@link WebView} instances could emit following direct events:
  98. * - topLoadingFinish
  99. * - topLoadingStart
  100. * - topLoadingStart
  101. * - topLoadingProgress
  102. * - topShouldStartLoadWithRequest
  103. * <p>
  104. * Each event will carry the following properties:
  105. * - target - view's react tag
  106. * - url - url set for the webview
  107. * - loading - whether webview is in a loading state
  108. * - title - title of the current page
  109. * - canGoBack - boolean, whether there is anything on a history stack to go back
  110. * - canGoForward - boolean, whether it is possible to request GO_FORWARD command
  111. */
  112. @ReactModule(name = RNCWebViewManager.REACT_CLASS)
  113. public class RNCWebViewManager extends SimpleViewManager<WebView> {
  114. private static final String TAG = "RNCWebViewManager";
  115. public static final int COMMAND_GO_BACK = 1;
  116. public static final int COMMAND_GO_FORWARD = 2;
  117. public static final int COMMAND_RELOAD = 3;
  118. public static final int COMMAND_STOP_LOADING = 4;
  119. public static final int COMMAND_POST_MESSAGE = 5;
  120. public static final int COMMAND_INJECT_JAVASCRIPT = 6;
  121. public static final int COMMAND_LOAD_URL = 7;
  122. public static final int COMMAND_FOCUS = 8;
  123. // android commands
  124. public static final int COMMAND_CLEAR_FORM_DATA = 1000;
  125. public static final int COMMAND_CLEAR_CACHE = 1001;
  126. public static final int COMMAND_CLEAR_HISTORY = 1002;
  127. protected static final String REACT_CLASS = "RNCWebView";
  128. protected static final String HTML_ENCODING = "UTF-8";
  129. protected static final String HTML_MIME_TYPE = "text/html";
  130. protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
  131. protected static final String HTTP_METHOD_POST = "POST";
  132. // Use `webView.loadUrl("about:blank")` to reliably reset the view
  133. // state and release page resources (including any running JavaScript).
  134. protected static final String BLANK_URL = "about:blank";
  135. protected static final int SHOULD_OVERRIDE_URL_LOADING_TIMEOUT = 250;
  136. protected WebViewConfig mWebViewConfig;
  137. protected RNCWebChromeClient mWebChromeClient = null;
  138. protected boolean mAllowsFullscreenVideo = false;
  139. protected @Nullable String mUserAgent = null;
  140. protected @Nullable String mUserAgentWithApplicationName = null;
  141. public RNCWebViewManager() {
  142. mWebViewConfig = new WebViewConfig() {
  143. public void configWebView(WebView webView) {
  144. }
  145. };
  146. }
  147. public RNCWebViewManager(WebViewConfig webViewConfig) {
  148. mWebViewConfig = webViewConfig;
  149. }
  150. protected static void dispatchEvent(WebView webView, Event event) {
  151. ReactContext reactContext = (ReactContext) webView.getContext();
  152. EventDispatcher eventDispatcher =
  153. reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
  154. eventDispatcher.dispatchEvent(event);
  155. }
  156. @Override
  157. public String getName() {
  158. return REACT_CLASS;
  159. }
  160. protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
  161. return new RNCWebView(reactContext);
  162. }
  163. @Override
  164. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  165. protected WebView createViewInstance(ThemedReactContext reactContext) {
  166. RNCWebView webView = createRNCWebViewInstance(reactContext);
  167. setupWebChromeClient(reactContext, webView);
  168. reactContext.addLifecycleEventListener(webView);
  169. mWebViewConfig.configWebView(webView);
  170. WebSettings settings = webView.getSettings();
  171. settings.setBuiltInZoomControls(true);
  172. settings.setDisplayZoomControls(false);
  173. settings.setDomStorageEnabled(true);
  174. settings.setAllowFileAccess(false);
  175. settings.setAllowContentAccess(false);
  176. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  177. settings.setAllowFileAccessFromFileURLs(false);
  178. setAllowUniversalAccessFromFileURLs(webView, false);
  179. }
  180. setMixedContentMode(webView, "never");
  181. // Fixes broken full-screen modals/galleries due to body height being 0.
  182. webView.setLayoutParams(
  183. new LayoutParams(LayoutParams.MATCH_PARENT,
  184. LayoutParams.MATCH_PARENT));
  185. if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  186. WebView.setWebContentsDebuggingEnabled(true);
  187. }
  188. webView.setDownloadListener(new DownloadListener() {
  189. public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
  190. webView.setIgnoreErrFailedForThisURL(url);
  191. RNCWebViewModule module = getModule(reactContext);
  192. DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
  193. String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
  194. String downloadMessage = "Downloading " + fileName;
  195. //Attempt to add cookie, if it exists
  196. URL urlObj = null;
  197. try {
  198. urlObj = new URL(url);
  199. String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
  200. String cookie = CookieManager.getInstance().getCookie(baseUrl);
  201. request.addRequestHeader("Cookie", cookie);
  202. } catch (MalformedURLException e) {
  203. System.out.println("Error getting cookie for DownloadManager: " + e.toString());
  204. e.printStackTrace();
  205. }
  206. //Finish setting up request
  207. request.addRequestHeader("User-Agent", userAgent);
  208. request.setTitle(fileName);
  209. request.setDescription(downloadMessage);
  210. request.allowScanningByMediaScanner();
  211. request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  212. request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
  213. module.setDownloadRequest(request);
  214. if (module.grantFileDownloaderPermissions()) {
  215. module.downloadFile();
  216. }
  217. }
  218. });
  219. return webView;
  220. }
  221. @ReactProp(name = "javaScriptEnabled")
  222. public void setJavaScriptEnabled(WebView view, boolean enabled) {
  223. view.getSettings().setJavaScriptEnabled(enabled);
  224. }
  225. @ReactProp(name = "showsHorizontalScrollIndicator")
  226. public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
  227. view.setHorizontalScrollBarEnabled(enabled);
  228. }
  229. @ReactProp(name = "showsVerticalScrollIndicator")
  230. public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
  231. view.setVerticalScrollBarEnabled(enabled);
  232. }
  233. @ReactProp(name = "cacheEnabled")
  234. public void setCacheEnabled(WebView view, boolean enabled) {
  235. if (enabled) {
  236. Context ctx = view.getContext();
  237. if (ctx != null) {
  238. view.getSettings().setAppCachePath(ctx.getCacheDir().getAbsolutePath());
  239. view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
  240. view.getSettings().setAppCacheEnabled(true);
  241. }
  242. } else {
  243. view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  244. view.getSettings().setAppCacheEnabled(false);
  245. }
  246. }
  247. @ReactProp(name = "cacheMode")
  248. public void setCacheMode(WebView view, String cacheModeString) {
  249. Integer cacheMode;
  250. switch (cacheModeString) {
  251. case "LOAD_CACHE_ONLY":
  252. cacheMode = WebSettings.LOAD_CACHE_ONLY;
  253. break;
  254. case "LOAD_CACHE_ELSE_NETWORK":
  255. cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK;
  256. break;
  257. case "LOAD_NO_CACHE":
  258. cacheMode = WebSettings.LOAD_NO_CACHE;
  259. break;
  260. case "LOAD_DEFAULT":
  261. default:
  262. cacheMode = WebSettings.LOAD_DEFAULT;
  263. break;
  264. }
  265. view.getSettings().setCacheMode(cacheMode);
  266. }
  267. @ReactProp(name = "androidHardwareAccelerationDisabled")
  268. public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
  269. if (disabled) {
  270. view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  271. }
  272. }
  273. @ReactProp(name = "androidLayerType")
  274. public void setLayerType(WebView view, String layerTypeString) {
  275. int layerType = View.LAYER_TYPE_NONE;
  276. switch (layerTypeString) {
  277. case "hardware":
  278. layerType = View.LAYER_TYPE_HARDWARE;
  279. break;
  280. case "software":
  281. layerType = View.LAYER_TYPE_SOFTWARE;
  282. break;
  283. }
  284. view.setLayerType(layerType, null);
  285. }
  286. @ReactProp(name = "overScrollMode")
  287. public void setOverScrollMode(WebView view, String overScrollModeString) {
  288. Integer overScrollMode;
  289. switch (overScrollModeString) {
  290. case "never":
  291. overScrollMode = View.OVER_SCROLL_NEVER;
  292. break;
  293. case "content":
  294. overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
  295. break;
  296. case "always":
  297. default:
  298. overScrollMode = View.OVER_SCROLL_ALWAYS;
  299. break;
  300. }
  301. view.setOverScrollMode(overScrollMode);
  302. }
  303. @ReactProp(name = "thirdPartyCookiesEnabled")
  304. public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
  305. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  306. CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
  307. }
  308. }
  309. @ReactProp(name = "textZoom")
  310. public void setTextZoom(WebView view, int value) {
  311. view.getSettings().setTextZoom(value);
  312. }
  313. @ReactProp(name = "scalesPageToFit")
  314. public void setScalesPageToFit(WebView view, boolean enabled) {
  315. view.getSettings().setLoadWithOverviewMode(enabled);
  316. view.getSettings().setUseWideViewPort(enabled);
  317. }
  318. @ReactProp(name = "domStorageEnabled")
  319. public void setDomStorageEnabled(WebView view, boolean enabled) {
  320. view.getSettings().setDomStorageEnabled(enabled);
  321. }
  322. @ReactProp(name = "userAgent")
  323. public void setUserAgent(WebView view, @Nullable String userAgent) {
  324. if (userAgent != null) {
  325. mUserAgent = userAgent;
  326. } else {
  327. mUserAgent = null;
  328. }
  329. this.setUserAgentString(view);
  330. }
  331. @ReactProp(name = "applicationNameForUserAgent")
  332. public void setApplicationNameForUserAgent(WebView view, @Nullable String applicationName) {
  333. if(applicationName != null) {
  334. if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  335. String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
  336. mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
  337. }
  338. } else {
  339. mUserAgentWithApplicationName = null;
  340. }
  341. this.setUserAgentString(view);
  342. }
  343. protected void setUserAgentString(WebView view) {
  344. if(mUserAgent != null) {
  345. view.getSettings().setUserAgentString(mUserAgent);
  346. } else if(mUserAgentWithApplicationName != null) {
  347. view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
  348. } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  349. // handle unsets of `userAgent` prop as long as device is >= API 17
  350. view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
  351. }
  352. }
  353. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  354. @ReactProp(name = "mediaPlaybackRequiresUserAction")
  355. public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
  356. view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
  357. }
  358. @ReactProp(name = "javaScriptCanOpenWindowsAutomatically")
  359. public void setJavaScriptCanOpenWindowsAutomatically(WebView view, boolean enabled) {
  360. view.getSettings().setJavaScriptCanOpenWindowsAutomatically(enabled);
  361. }
  362. @ReactProp(name = "allowFileAccessFromFileURLs")
  363. public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
  364. view.getSettings().setAllowFileAccessFromFileURLs(allow);
  365. }
  366. @ReactProp(name = "allowUniversalAccessFromFileURLs")
  367. public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
  368. view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
  369. }
  370. @ReactProp(name = "saveFormDataDisabled")
  371. public void setSaveFormDataDisabled(WebView view, boolean disable) {
  372. view.getSettings().setSaveFormData(!disable);
  373. }
  374. @ReactProp(name = "injectedJavaScript")
  375. public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
  376. ((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
  377. }
  378. @ReactProp(name = "injectedJavaScriptBeforeContentLoaded")
  379. public void setInjectedJavaScriptBeforeContentLoaded(WebView view, @Nullable String injectedJavaScriptBeforeContentLoaded) {
  380. ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoaded(injectedJavaScriptBeforeContentLoaded);
  381. }
  382. @ReactProp(name = "injectedJavaScriptForMainFrameOnly")
  383. public void setInjectedJavaScriptForMainFrameOnly(WebView view, boolean enabled) {
  384. ((RNCWebView) view).setInjectedJavaScriptForMainFrameOnly(enabled);
  385. }
  386. @ReactProp(name = "injectedJavaScriptBeforeContentLoadedForMainFrameOnly")
  387. public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(WebView view, boolean enabled) {
  388. ((RNCWebView) view).setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(enabled);
  389. }
  390. @ReactProp(name = "messagingEnabled")
  391. public void setMessagingEnabled(WebView view, boolean enabled) {
  392. ((RNCWebView) view).setMessagingEnabled(enabled);
  393. }
  394. @ReactProp(name = "messagingModuleName")
  395. public void setMessagingModuleName(WebView view, String moduleName) {
  396. ((RNCWebView) view).setMessagingModuleName(moduleName);
  397. }
  398. @ReactProp(name = "incognito")
  399. public void setIncognito(WebView view, boolean enabled) {
  400. // Don't do anything when incognito is disabled
  401. if (!enabled) {
  402. return;
  403. }
  404. // Remove all previous cookies
  405. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  406. CookieManager.getInstance().removeAllCookies(null);
  407. } else {
  408. CookieManager.getInstance().removeAllCookie();
  409. }
  410. // Disable caching
  411. view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  412. view.getSettings().setAppCacheEnabled(false);
  413. view.clearHistory();
  414. view.clearCache(true);
  415. // No form data or autofill enabled
  416. view.clearFormData();
  417. view.getSettings().setSavePassword(false);
  418. view.getSettings().setSaveFormData(false);
  419. }
  420. @ReactProp(name = "source")
  421. public void setSource(WebView view, @Nullable ReadableMap source) {
  422. if (source != null) {
  423. if (source.hasKey("html")) {
  424. String html = source.getString("html");
  425. String baseUrl = source.hasKey("baseUrl") ? source.getString("baseUrl") : "";
  426. view.loadDataWithBaseURL(baseUrl, html, HTML_MIME_TYPE, HTML_ENCODING, null);
  427. return;
  428. }
  429. if (source.hasKey("uri")) {
  430. String url = source.getString("uri");
  431. String previousUrl = view.getUrl();
  432. if (previousUrl != null && previousUrl.equals(url)) {
  433. return;
  434. }
  435. if (source.hasKey("method")) {
  436. String method = source.getString("method");
  437. if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
  438. byte[] postData = null;
  439. if (source.hasKey("body")) {
  440. String body = source.getString("body");
  441. try {
  442. postData = body.getBytes("UTF-8");
  443. } catch (UnsupportedEncodingException e) {
  444. postData = body.getBytes();
  445. }
  446. }
  447. if (postData == null) {
  448. postData = new byte[0];
  449. }
  450. view.postUrl(url, postData);
  451. return;
  452. }
  453. }
  454. HashMap<String, String> headerMap = new HashMap<>();
  455. if (source.hasKey("headers")) {
  456. ReadableMap headers = source.getMap("headers");
  457. ReadableMapKeySetIterator iter = headers.keySetIterator();
  458. while (iter.hasNextKey()) {
  459. String key = iter.nextKey();
  460. if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
  461. if (view.getSettings() != null) {
  462. view.getSettings().setUserAgentString(headers.getString(key));
  463. }
  464. } else {
  465. headerMap.put(key, headers.getString(key));
  466. }
  467. }
  468. }
  469. view.loadUrl(url, headerMap);
  470. return;
  471. }
  472. }
  473. view.loadUrl(BLANK_URL);
  474. }
  475. @ReactProp(name = "onContentSizeChange")
  476. public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
  477. ((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
  478. }
  479. @ReactProp(name = "mixedContentMode")
  480. public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
  481. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  482. if (mixedContentMode == null || "never".equals(mixedContentMode)) {
  483. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
  484. } else if ("always".equals(mixedContentMode)) {
  485. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  486. } else if ("compatibility".equals(mixedContentMode)) {
  487. view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
  488. }
  489. }
  490. }
  491. @ReactProp(name = "urlPrefixesForDefaultIntent")
  492. public void setUrlPrefixesForDefaultIntent(
  493. WebView view,
  494. @Nullable ReadableArray urlPrefixesForDefaultIntent) {
  495. RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
  496. if (client != null && urlPrefixesForDefaultIntent != null) {
  497. client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
  498. }
  499. }
  500. @ReactProp(name = "allowsFullscreenVideo")
  501. public void setAllowsFullscreenVideo(
  502. WebView view,
  503. @Nullable Boolean allowsFullscreenVideo) {
  504. mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
  505. setupWebChromeClient((ReactContext)view.getContext(), view);
  506. }
  507. @ReactProp(name = "allowFileAccess")
  508. public void setAllowFileAccess(
  509. WebView view,
  510. @Nullable Boolean allowFileAccess) {
  511. view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
  512. }
  513. @ReactProp(name = "geolocationEnabled")
  514. public void setGeolocationEnabled(
  515. WebView view,
  516. @Nullable Boolean isGeolocationEnabled) {
  517. view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
  518. }
  519. @ReactProp(name = "onScroll")
  520. public void setOnScroll(WebView view, boolean hasScrollEvent) {
  521. ((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
  522. }
  523. @Override
  524. protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
  525. // Do not register default touch emitter and let WebView implementation handle touches
  526. view.setWebViewClient(new RNCWebViewClient());
  527. }
  528. @Override
  529. public Map getExportedCustomDirectEventTypeConstants() {
  530. Map export = super.getExportedCustomDirectEventTypeConstants();
  531. if (export == null) {
  532. export = MapBuilder.newHashMap();
  533. }
  534. export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
  535. export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
  536. export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
  537. export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
  538. export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
  539. return export;
  540. }
  541. @Override
  542. public @Nullable
  543. Map<String, Integer> getCommandsMap() {
  544. return MapBuilder.<String, Integer>builder()
  545. .put("goBack", COMMAND_GO_BACK)
  546. .put("goForward", COMMAND_GO_FORWARD)
  547. .put("reload", COMMAND_RELOAD)
  548. .put("stopLoading", COMMAND_STOP_LOADING)
  549. .put("postMessage", COMMAND_POST_MESSAGE)
  550. .put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
  551. .put("loadUrl", COMMAND_LOAD_URL)
  552. .put("requestFocus", COMMAND_FOCUS)
  553. .put("clearFormData", COMMAND_CLEAR_FORM_DATA)
  554. .put("clearCache", COMMAND_CLEAR_CACHE)
  555. .put("clearHistory", COMMAND_CLEAR_HISTORY)
  556. .build();
  557. }
  558. @Override
  559. public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
  560. switch (commandId) {
  561. case COMMAND_GO_BACK:
  562. root.goBack();
  563. break;
  564. case COMMAND_GO_FORWARD:
  565. root.goForward();
  566. break;
  567. case COMMAND_RELOAD:
  568. root.reload();
  569. break;
  570. case COMMAND_STOP_LOADING:
  571. root.stopLoading();
  572. break;
  573. case COMMAND_POST_MESSAGE:
  574. try {
  575. RNCWebView reactWebView = (RNCWebView) root;
  576. JSONObject eventInitDict = new JSONObject();
  577. eventInitDict.put("data", args.getString(0));
  578. reactWebView.evaluateJavascriptWithFallback("(function () {" +
  579. "var event;" +
  580. "var data = " + eventInitDict.toString() + ";" +
  581. "try {" +
  582. "event = new MessageEvent('message', data);" +
  583. "} catch (e) {" +
  584. "event = document.createEvent('MessageEvent');" +
  585. "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
  586. "}" +
  587. "document.dispatchEvent(event);" +
  588. "})();");
  589. } catch (JSONException e) {
  590. throw new RuntimeException(e);
  591. }
  592. break;
  593. case COMMAND_INJECT_JAVASCRIPT:
  594. RNCWebView reactWebView = (RNCWebView) root;
  595. reactWebView.evaluateJavascriptWithFallback(args.getString(0));
  596. break;
  597. case COMMAND_LOAD_URL:
  598. if (args == null) {
  599. throw new RuntimeException("Arguments for loading an url are null!");
  600. }
  601. ((RNCWebView) root).progressChangedFilter.setWaitingForCommandLoadUrl(false);
  602. root.loadUrl(args.getString(0));
  603. break;
  604. case COMMAND_FOCUS:
  605. root.requestFocus();
  606. break;
  607. case COMMAND_CLEAR_FORM_DATA:
  608. root.clearFormData();
  609. break;
  610. case COMMAND_CLEAR_CACHE:
  611. boolean includeDiskFiles = args != null && args.getBoolean(0);
  612. root.clearCache(includeDiskFiles);
  613. break;
  614. case COMMAND_CLEAR_HISTORY:
  615. root.clearHistory();
  616. break;
  617. }
  618. }
  619. @Override
  620. public void onDropViewInstance(WebView webView) {
  621. super.onDropViewInstance(webView);
  622. ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
  623. ((RNCWebView) webView).cleanupCallbacksAndDestroy();
  624. }
  625. public static RNCWebViewModule getModule(ReactContext reactContext) {
  626. return reactContext.getNativeModule(RNCWebViewModule.class);
  627. }
  628. protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
  629. if (mAllowsFullscreenVideo) {
  630. int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
  631. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  632. @Override
  633. public Bitmap getDefaultVideoPoster() {
  634. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  635. }
  636. @Override
  637. public void onShowCustomView(View view, CustomViewCallback callback) {
  638. if (mVideoView != null) {
  639. callback.onCustomViewHidden();
  640. return;
  641. }
  642. mVideoView = view;
  643. mCustomViewCallback = callback;
  644. mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
  645. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  646. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  647. mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  648. }
  649. mVideoView.setBackgroundColor(Color.BLACK);
  650. getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
  651. mWebView.setVisibility(View.GONE);
  652. mReactContext.addLifecycleEventListener(this);
  653. }
  654. @Override
  655. public void onHideCustomView() {
  656. if (mVideoView == null) {
  657. return;
  658. }
  659. mVideoView.setVisibility(View.GONE);
  660. getRootView().removeView(mVideoView);
  661. mCustomViewCallback.onCustomViewHidden();
  662. mVideoView = null;
  663. mCustomViewCallback = null;
  664. mWebView.setVisibility(View.VISIBLE);
  665. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  666. mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  667. }
  668. mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation);
  669. mReactContext.removeLifecycleEventListener(this);
  670. }
  671. };
  672. webView.setWebChromeClient(mWebChromeClient);
  673. } else {
  674. if (mWebChromeClient != null) {
  675. mWebChromeClient.onHideCustomView();
  676. }
  677. mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
  678. @Override
  679. public Bitmap getDefaultVideoPoster() {
  680. return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
  681. }
  682. };
  683. webView.setWebChromeClient(mWebChromeClient);
  684. }
  685. }
  686. protected static class RNCWebViewClient extends WebViewClient {
  687. protected boolean mLastLoadFailed = false;
  688. protected @Nullable
  689. ReadableArray mUrlPrefixesForDefaultIntent;
  690. protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
  691. protected @Nullable String ignoreErrFailedForThisURL = null;
  692. public void setIgnoreErrFailedForThisURL(@Nullable String url) {
  693. ignoreErrFailedForThisURL = url;
  694. }
  695. @Override
  696. public void onPageFinished(WebView webView, String url) {
  697. super.onPageFinished(webView, url);
  698. if (!mLastLoadFailed) {
  699. RNCWebView reactWebView = (RNCWebView) webView;
  700. reactWebView.callInjectedJavaScript();
  701. emitFinishEvent(webView, url);
  702. }
  703. }
  704. @Override
  705. public void onPageStarted(WebView webView, String url, Bitmap favicon) {
  706. super.onPageStarted(webView, url, favicon);
  707. mLastLoadFailed = false;
  708. RNCWebView reactWebView = (RNCWebView) webView;
  709. reactWebView.callInjectedJavaScriptBeforeContentLoaded();
  710. dispatchEvent(
  711. webView,
  712. new TopLoadingStartEvent(
  713. webView.getId(),
  714. createWebViewEvent(webView, url)));
  715. }
  716. @Override
  717. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  718. final RNCWebView rncWebView = (RNCWebView) view;
  719. final boolean isJsDebugging = ((ReactContext) view.getContext()).getJavaScriptContextHolder().get() == 0;
  720. if (!isJsDebugging && rncWebView.mCatalystInstance != null) {
  721. final Pair<Integer, AtomicReference<ShouldOverrideCallbackState>> lock = RNCWebViewModule.shouldOverrideUrlLoadingLock.getNewLock();
  722. final int lockIdentifier = lock.first;
  723. final AtomicReference<ShouldOverrideCallbackState> lockObject = lock.second;
  724. final WritableMap event = createWebViewEvent(view, url);
  725. event.putInt("lockIdentifier", lockIdentifier);
  726. rncWebView.sendDirectMessage("onShouldStartLoadWithRequest", event);
  727. try {
  728. assert lockObject != null;
  729. synchronized (lockObject) {
  730. final long startTime = SystemClock.elapsedRealtime();
  731. while (lockObject.get() == ShouldOverrideCallbackState.UNDECIDED) {
  732. if (SystemClock.elapsedRealtime() - startTime > SHOULD_OVERRIDE_URL_LOADING_TIMEOUT) {
  733. FLog.w(TAG, "Did not receive response to shouldOverrideUrlLoading in time, defaulting to allow loading.");
  734. RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
  735. return false;
  736. }
  737. lockObject.wait(SHOULD_OVERRIDE_URL_LOADING_TIMEOUT);
  738. }
  739. }
  740. } catch (InterruptedException e) {
  741. FLog.e(TAG, "shouldOverrideUrlLoading was interrupted while waiting for result.", e);
  742. RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
  743. return false;
  744. }
  745. final boolean shouldOverride = lockObject.get() == ShouldOverrideCallbackState.SHOULD_OVERRIDE;
  746. RNCWebViewModule.shouldOverrideUrlLoadingLock.removeLock(lockIdentifier);
  747. return shouldOverride;
  748. } else {
  749. FLog.w(TAG, "Couldn't use blocking synchronous call for onShouldStartLoadWithRequest due to debugging or missing Catalyst instance, falling back to old event-and-load.");
  750. progressChangedFilter.setWaitingForCommandLoadUrl(true);
  751. dispatchEvent(
  752. view,
  753. new TopShouldStartLoadWithRequestEvent(
  754. view.getId(),
  755. createWebViewEvent(view, url)));
  756. return true;
  757. }
  758. }
  759. @TargetApi(Build.VERSION_CODES.N)
  760. @Override
  761. public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
  762. final String url = request.getUrl().toString();
  763. return this.shouldOverrideUrlLoading(view, url);
  764. }
  765. @Override
  766. public void onReceivedSslError(final WebView webView, final SslErrorHandler handler, final SslError error) {
  767. handler.cancel();
  768. int code = error.getPrimaryError();
  769. String failingUrl = error.getUrl();
  770. String description = "";
  771. String descriptionPrefix = "SSL error: ";
  772. // https://developer.android.com/reference/android/net/http/SslError.html
  773. switch (code) {
  774. case SslError.SSL_DATE_INVALID:
  775. description = "The date of the certificate is invalid";
  776. break;
  777. case SslError.SSL_EXPIRED:
  778. description = "The certificate has expired";
  779. break;
  780. case SslError.SSL_IDMISMATCH:
  781. description = "Hostname mismatch";
  782. break;
  783. case SslError.SSL_INVALID:
  784. description = "A generic error occurred";
  785. break;
  786. case SslError.SSL_NOTYETVALID:
  787. description = "The certificate is not yet valid";
  788. break;
  789. case SslError.SSL_UNTRUSTED:
  790. description = "The certificate authority is not trusted";
  791. break;
  792. default:
  793. description = "Unknown SSL Error";
  794. break;
  795. }
  796. description = descriptionPrefix + description;
  797. this.onReceivedError(
  798. webView,
  799. code,
  800. description,
  801. failingUrl
  802. );
  803. }
  804. @Override
  805. public void onReceivedError(
  806. WebView webView,
  807. int errorCode,
  808. String description,
  809. String failingUrl) {
  810. if (ignoreErrFailedForThisURL != null
  811. && failingUrl.equals(ignoreErrFailedForThisURL)
  812. && errorCode == -1
  813. && description.equals("net::ERR_FAILED")) {
  814. // This is a workaround for a bug in the WebView.
  815. // See these chromium issues for more context:
  816. // https://bugs.chromium.org/p/chromium/issues/detail?id=1023678
  817. // https://bugs.chromium.org/p/chromium/issues/detail?id=1050635
  818. // This entire commit should be reverted once this bug is resolved in chromium.
  819. setIgnoreErrFailedForThisURL(null);
  820. return;
  821. }
  822. super.onReceivedError(webView, errorCode, description, failingUrl);
  823. mLastLoadFailed = true;
  824. // In case of an error JS side expect to get a finish event first, and then get an error event
  825. // Android WebView does it in the opposite way, so we need to simulate that behavior
  826. emitFinishEvent(webView, failingUrl);
  827. WritableMap eventData = createWebViewEvent(webView, failingUrl);
  828. eventData.putDouble("code", errorCode);
  829. eventData.putString("description", description);
  830. dispatchEvent(
  831. webView,
  832. new TopLoadingErrorEvent(webView.getId(), eventData));
  833. }
  834. @RequiresApi(api = Build.VERSION_CODES.M)
  835. @Override
  836. public void onReceivedHttpError(
  837. WebView webView,
  838. WebResourceRequest request,
  839. WebResourceResponse errorResponse) {
  840. super.onReceivedHttpError(webView, request, errorResponse);
  841. if (request.isForMainFrame()) {
  842. WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
  843. eventData.putInt("statusCode", errorResponse.getStatusCode());
  844. eventData.putString("description", errorResponse.getReasonPhrase());
  845. dispatchEvent(
  846. webView,
  847. new TopHttpErrorEvent(webView.getId(), eventData));
  848. }
  849. }
  850. @TargetApi(Build.VERSION_CODES.O)
  851. @Override
  852. public boolean onRenderProcessGone(WebView webView, RenderProcessGoneDetail detail) {
  853. // WebViewClient.onRenderProcessGone was added in O.
  854. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
  855. return false;
  856. }
  857. super.onRenderProcessGone(webView, detail);
  858. if(detail.didCrash()){
  859. Log.e("RNCWebViewManager", "The WebView rendering process crashed.");
  860. }
  861. else{
  862. Log.w("RNCWebViewManager", "The WebView rendering process was killed by the system.");
  863. }
  864. // if webView is null, we cannot return any event
  865. // since the view is already dead/disposed
  866. // still prevent the app crash by returning true.
  867. if(webView == null){
  868. return true;
  869. }
  870. WritableMap event = createWebViewEvent(webView, webView.getUrl());
  871. event.putBoolean("didCrash", detail.didCrash());
  872. dispatchEvent(
  873. webView,
  874. new TopRenderProcessGoneEvent(webView.getId(), event)
  875. );
  876. // returning false would crash the app.
  877. return true;
  878. }
  879. protected void emitFinishEvent(WebView webView, String url) {
  880. dispatchEvent(
  881. webView,
  882. new TopLoadingFinishEvent(
  883. webView.getId(),
  884. createWebViewEvent(webView, url)));
  885. }
  886. protected WritableMap createWebViewEvent(WebView webView, String url) {
  887. WritableMap event = Arguments.createMap();
  888. event.putDouble("target", webView.getId());
  889. // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
  890. // like onPageFinished
  891. event.putString("url", url);
  892. event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
  893. event.putString("title", webView.getTitle());
  894. event.putBoolean("canGoBack", webView.canGoBack());
  895. event.putBoolean("canGoForward", webView.canGoForward());
  896. return event;
  897. }
  898. public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
  899. mUrlPrefixesForDefaultIntent = specialUrls;
  900. }
  901. public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
  902. progressChangedFilter = filter;
  903. }
  904. }
  905. protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
  906. protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
  907. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
  908. @RequiresApi(api = Build.VERSION_CODES.KITKAT)
  909. protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  910. View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  911. View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
  912. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
  913. View.SYSTEM_UI_FLAG_FULLSCREEN |
  914. View.SYSTEM_UI_FLAG_IMMERSIVE |
  915. View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  916. protected ReactContext mReactContext;
  917. protected View mWebView;
  918. protected View mVideoView;
  919. protected WebChromeClient.CustomViewCallback mCustomViewCallback;
  920. protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
  921. public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
  922. this.mReactContext = reactContext;
  923. this.mWebView = webView;
  924. }
  925. @Override
  926. public boolean onConsoleMessage(ConsoleMessage message) {
  927. if (ReactBuildConfig.DEBUG) {
  928. return super.onConsoleMessage(message);
  929. }
  930. // Ignore console logs in non debug builds.
  931. return true;
  932. }
  933. // Fix WebRTC permission request error.
  934. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  935. @Override
  936. public void onPermissionRequest(final PermissionRequest request) {
  937. String[] requestedResources = request.getResources();
  938. ArrayList<String> permissions = new ArrayList<>();
  939. ArrayList<String> grantedPermissions = new ArrayList<String>();
  940. for (int i = 0; i < requestedResources.length; i++) {
  941. if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
  942. permissions.add(Manifest.permission.RECORD_AUDIO);
  943. } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
  944. permissions.add(Manifest.permission.CAMERA);
  945. }
  946. // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
  947. }
  948. for (int i = 0; i < permissions.size(); i++) {
  949. if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
  950. continue;
  951. }
  952. if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
  953. grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
  954. } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
  955. grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
  956. }
  957. }
  958. if (grantedPermissions.isEmpty()) {
  959. request.deny();
  960. } else {
  961. String[] grantedPermissionsArray = new String[grantedPermissions.size()];
  962. grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
  963. request.grant(grantedPermissionsArray);
  964. }
  965. }
  966. @Override
  967. public void onProgressChanged(WebView webView, int newProgress) {
  968. super.onProgressChanged(webView, newProgress);
  969. final String url = webView.getUrl();
  970. if (progressChangedFilter.isWaitingForCommandLoadUrl()) {
  971. return;
  972. }
  973. WritableMap event = Arguments.createMap();
  974. event.putDouble("target", webView.getId());
  975. event.putString("title", webView.getTitle());
  976. event.putString("url", url);
  977. event.putBoolean("canGoBack", webView.canGoBack());
  978. event.putBoolean("canGoForward", webView.canGoForward());
  979. event.putDouble("progress", (float) newProgress / 100);
  980. dispatchEvent(
  981. webView,
  982. new TopLoadingProgressEvent(
  983. webView.getId(),
  984. event));
  985. }
  986. @Override
  987. public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
  988. callback.invoke(origin, true, false);
  989. }
  990. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
  991. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  992. }
  993. protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
  994. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
  995. }
  996. protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  997. getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
  998. }
  999. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  1000. @Override
  1001. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  1002. String[] acceptTypes = fileChooserParams.getAcceptTypes();
  1003. boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
  1004. return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptTypes, allowMultiple);
  1005. }
  1006. @Override
  1007. public void onHostResume() {
  1008. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
  1009. mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
  1010. }
  1011. }
  1012. @Override
  1013. public void onHostPause() { }
  1014. @Override
  1015. public void onHostDestroy() { }
  1016. protected ViewGroup getRootView() {
  1017. return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
  1018. }
  1019. public void setProgressChangedFilter(RNCWebView.ProgressChangedFilter filter) {
  1020. progressChangedFilter = filter;
  1021. }
  1022. }
  1023. /**
  1024. * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
  1025. * to call {@link WebView#destroy} on activity destroy event and also to clear the client
  1026. */
  1027. protected static class RNCWebView extends WebView implements LifecycleEventListener {
  1028. protected @Nullable
  1029. String injectedJS;
  1030. protected @Nullable
  1031. String injectedJSBeforeContentLoaded;
  1032. /**
  1033. * android.webkit.WebChromeClient fundamentally does not support JS injection into frames other
  1034. * than the main frame, so these two properties are mostly here just for parity with iOS & macOS.
  1035. */
  1036. protected boolean injectedJavaScriptForMainFrameOnly = true;
  1037. protected boolean injectedJavaScriptBeforeContentLoadedForMainFrameOnly = true;
  1038. protected boolean messagingEnabled = false;
  1039. protected @Nullable
  1040. String messagingModuleName;
  1041. protected @Nullable
  1042. RNCWebViewClient mRNCWebViewClient;
  1043. protected @Nullable
  1044. CatalystInstance mCatalystInstance;
  1045. protected boolean sendContentSizeChangeEvents = false;
  1046. private OnScrollDispatchHelper mOnScrollDispatchHelper;
  1047. protected boolean hasScrollEvent = false;
  1048. protected ProgressChangedFilter progressChangedFilter;
  1049. /**
  1050. * WebView must be created with an context of the current activity
  1051. * <p>
  1052. * Activity Context is required for creation of dialogs internally by WebView
  1053. * Reactive Native needed for access to ReactNative internal system functionality
  1054. */
  1055. public RNCWebView(ThemedReactContext reactContext) {
  1056. super(reactContext);
  1057. this.createCatalystInstance();
  1058. progressChangedFilter = new ProgressChangedFilter();
  1059. }
  1060. public void setIgnoreErrFailedForThisURL(String url) {
  1061. mRNCWebViewClient.setIgnoreErrFailedForThisURL(url);
  1062. }
  1063. public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
  1064. this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
  1065. }
  1066. public void setHasScrollEvent(boolean hasScrollEvent) {
  1067. this.hasScrollEvent = hasScrollEvent;
  1068. }
  1069. @Override
  1070. public void onHostResume() {
  1071. // do nothing
  1072. }
  1073. @Override
  1074. public void onHostPause() {
  1075. // do nothing
  1076. }
  1077. @Override
  1078. public void onHostDestroy() {
  1079. cleanupCallbacksAndDestroy();
  1080. }
  1081. @Override
  1082. protected void onSizeChanged(int w, int h, int ow, int oh) {
  1083. super.onSizeChanged(w, h, ow, oh);
  1084. if (sendContentSizeChangeEvents) {
  1085. dispatchEvent(
  1086. this,
  1087. new ContentSizeChangeEvent(
  1088. this.getId(),
  1089. w,
  1090. h
  1091. )
  1092. );
  1093. }
  1094. }
  1095. @Override
  1096. public void setWebViewClient(WebViewClient client) {
  1097. super.setWebViewClient(client);
  1098. if (client instanceof RNCWebViewClient) {
  1099. mRNCWebViewClient = (RNCWebViewClient) client;
  1100. mRNCWebViewClient.setProgressChangedFilter(progressChangedFilter);
  1101. }
  1102. }
  1103. WebChromeClient mWebChromeClient;
  1104. @Override
  1105. public void setWebChromeClient(WebChromeClient client) {
  1106. this.mWebChromeClient = client;
  1107. super.setWebChromeClient(client);
  1108. if (client instanceof RNCWebChromeClient) {
  1109. ((RNCWebChromeClient) client).setProgressChangedFilter(progressChangedFilter);
  1110. }
  1111. }
  1112. public @Nullable
  1113. RNCWebViewClient getRNCWebViewClient() {
  1114. return mRNCWebViewClient;
  1115. }
  1116. public void setInjectedJavaScript(@Nullable String js) {
  1117. injectedJS = js;
  1118. }
  1119. public void setInjectedJavaScriptBeforeContentLoaded(@Nullable String js) {
  1120. injectedJSBeforeContentLoaded = js;
  1121. }
  1122. public void setInjectedJavaScriptForMainFrameOnly(boolean enabled) {
  1123. injectedJavaScriptForMainFrameOnly = enabled;
  1124. }
  1125. public void setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly(boolean enabled) {
  1126. injectedJavaScriptBeforeContentLoadedForMainFrameOnly = enabled;
  1127. }
  1128. protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
  1129. return new RNCWebViewBridge(webView);
  1130. }
  1131. protected void createCatalystInstance() {
  1132. ReactContext reactContext = (ReactContext) this.getContext();
  1133. if (reactContext != null) {
  1134. mCatalystInstance = reactContext.getCatalystInstance();
  1135. }
  1136. }
  1137. @SuppressLint("AddJavascriptInterface")
  1138. public void setMessagingEnabled(boolean enabled) {
  1139. if (messagingEnabled == enabled) {
  1140. return;
  1141. }
  1142. messagingEnabled = enabled;
  1143. if (enabled) {
  1144. addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
  1145. } else {
  1146. removeJavascriptInterface(JAVASCRIPT_INTERFACE);
  1147. }
  1148. }
  1149. public void setMessagingModuleName(String moduleName) {
  1150. messagingModuleName = moduleName;
  1151. }
  1152. protected void evaluateJavascriptWithFallback(String script) {
  1153. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  1154. evaluateJavascript(script, null);
  1155. return;
  1156. }
  1157. try {
  1158. loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
  1159. } catch (UnsupportedEncodingException e) {
  1160. // UTF-8 should always be supported
  1161. throw new RuntimeException(e);
  1162. }
  1163. }
  1164. public void callInjectedJavaScript() {
  1165. if (getSettings().getJavaScriptEnabled() &&
  1166. injectedJS != null &&
  1167. !TextUtils.isEmpty(injectedJS)) {
  1168. evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
  1169. }
  1170. }
  1171. public void callInjectedJavaScriptBeforeContentLoaded() {
  1172. if (getSettings().getJavaScriptEnabled() &&
  1173. injectedJSBeforeContentLoaded != null &&
  1174. !TextUtils.isEmpty(injectedJSBeforeContentLoaded)) {
  1175. evaluateJavascriptWithFallback("(function() {\n" + injectedJSBeforeContentLoaded + ";\n})();");
  1176. }
  1177. }
  1178. public void onMessage(String message) {
  1179. ReactContext reactContext = (ReactContext) this.getContext();
  1180. RNCWebView mContext = this;
  1181. if (mRNCWebViewClient != null) {
  1182. WebView webView = this;
  1183. webView.post(new Runnable() {
  1184. @Override
  1185. public void run() {
  1186. if (mRNCWebViewClient == null) {
  1187. return;
  1188. }
  1189. WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
  1190. data.putString("data", message);
  1191. if (mCatalystInstance != null) {
  1192. mContext.sendDirectMessage("onMessage", data);
  1193. } else {
  1194. dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
  1195. }
  1196. }
  1197. });
  1198. } else {
  1199. WritableMap eventData = Arguments.createMap();
  1200. eventData.putString("data", message);
  1201. if (mCatalystInstance != null) {
  1202. this.sendDirectMessage("onMessage", eventData);
  1203. } else {
  1204. dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
  1205. }
  1206. }
  1207. }
  1208. protected void sendDirectMessage(final String method, WritableMap data) {
  1209. WritableNativeMap event = new WritableNativeMap();
  1210. event.putMap("nativeEvent", data);
  1211. WritableNativeArray params = new WritableNativeArray();
  1212. params.pushMap(event);
  1213. mCatalystInstance.callFunction(messagingModuleName, method, params);
  1214. }
  1215. protected void onScrollChanged(int x, int y, int oldX, int oldY) {
  1216. super.onScrollChanged(x, y, oldX, oldY);
  1217. if (!hasScrollEvent) {
  1218. return;
  1219. }
  1220. if (mOnScrollDispatchHelper == null) {
  1221. mOnScrollDispatchHelper = new OnScrollDispatchHelper();
  1222. }
  1223. if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
  1224. ScrollEvent event = ScrollEvent.obtain(
  1225. this.getId(),
  1226. ScrollEventType.SCROLL,
  1227. x,
  1228. y,
  1229. mOnScrollDispatchHelper.getXFlingVelocity(),
  1230. mOnScrollDispatchHelper.getYFlingVelocity(),
  1231. this.computeHorizontalScrollRange(),
  1232. this.computeVerticalScrollRange(),
  1233. this.getWidth(),
  1234. this.getHeight());
  1235. dispatchEvent(this, event);
  1236. }
  1237. }
  1238. protected void cleanupCallbacksAndDestroy() {
  1239. setWebViewClient(null);
  1240. destroy();
  1241. }
  1242. @Override
  1243. public void destroy() {
  1244. if (mWebChromeClient != null) {
  1245. mWebChromeClient.onHideCustomView();
  1246. }
  1247. super.destroy();
  1248. }
  1249. protected class RNCWebViewBridge {
  1250. RNCWebView mContext;
  1251. RNCWebViewBridge(RNCWebView c) {
  1252. mContext = c;
  1253. }
  1254. /**
  1255. * This method is called whenever JavaScript running within the web view calls:
  1256. * - window[JAVASCRIPT_INTERFACE].postMessage
  1257. */
  1258. @JavascriptInterface
  1259. public void postMessage(String message) {
  1260. mContext.onMessage(message);
  1261. }
  1262. }
  1263. protected static class ProgressChangedFilter {
  1264. private boolean waitingForCommandLoadUrl = false;
  1265. public void setWaitingForCommandLoadUrl(boolean isWaiting) {
  1266. waitingForCommandLoadUrl = isWaiting;
  1267. }
  1268. public boolean isWaitingForCommandLoadUrl() {
  1269. return waitingForCommandLoadUrl;
  1270. }
  1271. }
  1272. }
  1273. }