webview_flutter.dart 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import 'dart:async';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/gestures.dart';
  7. import 'package:flutter/widgets.dart';
  8. import 'platform_interface.dart';
  9. import 'src/webview_android.dart';
  10. import 'src/webview_cupertino.dart';
  11. /// Optional callback invoked when a web view is first created. [controller] is
  12. /// the [WebViewController] for the created web view.
  13. typedef void WebViewCreatedCallback(WebViewController controller);
  14. /// Describes the state of JavaScript support in a given web view.
  15. enum JavascriptMode {
  16. /// JavaScript execution is disabled.
  17. disabled,
  18. /// JavaScript execution is not restricted.
  19. unrestricted,
  20. }
  21. /// A message that was sent by JavaScript code running in a [WebView].
  22. class JavascriptMessage {
  23. /// Constructs a JavaScript message object.
  24. ///
  25. /// The `message` parameter must not be null.
  26. const JavascriptMessage(this.message) : assert(message != null);
  27. /// The contents of the message that was sent by the JavaScript code.
  28. final String message;
  29. }
  30. /// Callback type for handling messages sent from Javascript running in a web view.
  31. typedef void JavascriptMessageHandler(JavascriptMessage message);
  32. /// Information about a navigation action that is about to be executed.
  33. class NavigationRequest {
  34. NavigationRequest._({this.url, this.isForMainFrame});
  35. /// The URL that will be loaded if the navigation is executed.
  36. final String url;
  37. /// Whether the navigation request is to be loaded as the main frame.
  38. final bool isForMainFrame;
  39. @override
  40. String toString() {
  41. return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)';
  42. }
  43. }
  44. /// A decision on how to handle a navigation request.
  45. enum NavigationDecision {
  46. /// Prevent the navigation from taking place.
  47. prevent,
  48. /// Allow the navigation to take place.
  49. navigate,
  50. }
  51. /// Decides how to handle a specific navigation request.
  52. ///
  53. /// The returned [NavigationDecision] determines how the navigation described by
  54. /// `navigation` should be handled.
  55. ///
  56. /// See also: [WebView.navigationDelegate].
  57. typedef FutureOr<NavigationDecision> NavigationDelegate(
  58. NavigationRequest navigation);
  59. /// Signature for when a [WebView] has started loading a page.
  60. typedef void PageStartedCallback(String url);
  61. /// Signature for when a [WebView] has finished loading a page.
  62. typedef void PageFinishedCallback(String url);
  63. /// Specifies possible restrictions on automatic media playback.
  64. ///
  65. /// This is typically used in [WebView.initialMediaPlaybackPolicy].
  66. // The method channel implementation is marshalling this enum to the value's index, so the order
  67. // is important.
  68. enum AutoMediaPlaybackPolicy {
  69. /// Starting any kind of media playback requires a user action.
  70. ///
  71. /// For example: JavaScript code cannot start playing media unless the code was executed
  72. /// as a result of a user action (like a touch event).
  73. require_user_action_for_all_media_types,
  74. /// Starting any kind of media playback is always allowed.
  75. ///
  76. /// For example: JavaScript code that's triggered when the page is loaded can start playing
  77. /// video or audio.
  78. always_allow,
  79. }
  80. final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\$');
  81. /// A named channel for receiving messaged from JavaScript code running inside a web view.
  82. class JavascriptChannel {
  83. /// Constructs a Javascript channel.
  84. ///
  85. /// The parameters `name` and `onMessageReceived` must not be null.
  86. JavascriptChannel({
  87. @required this.name,
  88. @required this.onMessageReceived,
  89. }) : assert(name != null),
  90. assert(onMessageReceived != null),
  91. assert(_validChannelNames.hasMatch(name));
  92. /// The channel's name.
  93. ///
  94. /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to
  95. /// the Javascript window object's property named `name`.
  96. ///
  97. /// The name must start with a letter or underscore(_), followed by any combination of those
  98. /// characters plus digits.
  99. ///
  100. /// Note that any JavaScript existing `window` property with this name will be overriden.
  101. ///
  102. /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism.
  103. final String name;
  104. /// A callback that's invoked when a message is received through the channel.
  105. final JavascriptMessageHandler onMessageReceived;
  106. }
  107. /// A web view widget for showing html content.
  108. class WebView extends StatefulWidget {
  109. /// Creates a new web view.
  110. ///
  111. /// The web view can be controlled using a `WebViewController` that is passed to the
  112. /// `onWebViewCreated` callback once the web view is created.
  113. ///
  114. /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null.
  115. const WebView({
  116. Key key,
  117. this.onWebViewCreated,
  118. this.initialUrl,
  119. this.javascriptMode = JavascriptMode.disabled,
  120. this.javascriptChannels,
  121. this.navigationDelegate,
  122. this.gestureRecognizers,
  123. this.onPageStarted,
  124. this.onPageFinished,
  125. this.debuggingEnabled = false,
  126. this.gestureNavigationEnabled = false,
  127. this.userAgent,
  128. this.initialMediaPlaybackPolicy =
  129. AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
  130. }) : assert(javascriptMode != null),
  131. assert(initialMediaPlaybackPolicy != null),
  132. super(key: key);
  133. static WebViewPlatform _platform;
  134. /// Sets a custom [WebViewPlatform].
  135. ///
  136. /// This property can be set to use a custom platform implementation for WebViews.
  137. ///
  138. /// Setting `platform` doesn't affect [WebView]s that were already created.
  139. ///
  140. /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
  141. static set platform(WebViewPlatform platform) {
  142. _platform = platform;
  143. }
  144. /// The WebView platform that's used by this WebView.
  145. ///
  146. /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS.
  147. static WebViewPlatform get platform {
  148. if (_platform == null) {
  149. switch (defaultTargetPlatform) {
  150. case TargetPlatform.android:
  151. _platform = AndroidWebView();
  152. break;
  153. case TargetPlatform.iOS:
  154. _platform = CupertinoWebView();
  155. break;
  156. default:
  157. throw UnsupportedError(
  158. "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one");
  159. }
  160. }
  161. return _platform;
  162. }
  163. /// If not null invoked once the web view is created.
  164. final WebViewCreatedCallback onWebViewCreated;
  165. /// Which gestures should be consumed by the web view.
  166. ///
  167. /// It is possible for other gesture recognizers to be competing with the web view on pointer
  168. /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
  169. /// vertical drags. The web view will claim gestures that are recognized by any of the
  170. /// recognizers on this list.
  171. ///
  172. /// When this set is empty or null, the web view will only handle pointer events for gestures that
  173. /// were not claimed by any other gesture recognizer.
  174. final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
  175. /// The initial URL to load.
  176. final String initialUrl;
  177. /// Whether Javascript execution is enabled.
  178. final JavascriptMode javascriptMode;
  179. /// The set of [JavascriptChannel]s available to JavaScript code running in the web view.
  180. ///
  181. /// For each [JavascriptChannel] in the set, a channel object is made available for the
  182. /// JavaScript code in a window property named [JavascriptChannel.name].
  183. /// The JavaScript code can then call `postMessage` on that object to send a message that will be
  184. /// passed to [JavascriptChannel.onMessageReceived].
  185. ///
  186. /// For example for the following JavascriptChannel:
  187. ///
  188. /// ```dart
  189. /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); });
  190. /// ```
  191. ///
  192. /// JavaScript code can call:
  193. ///
  194. /// ```javascript
  195. /// Print.postMessage('Hello');
  196. /// ```
  197. ///
  198. /// To asynchronously invoke the message handler which will print the message to standard output.
  199. ///
  200. /// Adding a new JavaScript channel only takes affect after the next page is loaded.
  201. ///
  202. /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple
  203. /// channels in the list.
  204. ///
  205. /// A null value is equivalent to an empty set.
  206. final Set<JavascriptChannel> javascriptChannels;
  207. /// A delegate function that decides how to handle navigation actions.
  208. ///
  209. /// When a navigation is initiated by the WebView (e.g when a user clicks a link)
  210. /// this delegate is called and has to decide how to proceed with the navigation.
  211. ///
  212. /// See [NavigationDecision] for possible decisions the delegate can take.
  213. ///
  214. /// When null all navigation actions are allowed.
  215. ///
  216. /// Caveats on Android:
  217. ///
  218. /// * Navigation actions targeted to the main frame can be intercepted,
  219. /// navigation actions targeted to subframes are allowed regardless of the value
  220. /// returned by this delegate.
  221. /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were
  222. /// triggered by a user gesture, this disables some of Chromium's security mechanisms.
  223. /// A navigationDelegate should only be set when loading trusted content.
  224. /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have
  225. /// a later version):
  226. /// * When a navigationDelegate is set pages with frames are not properly handled by the
  227. /// webview, and frames will be opened in the main frame.
  228. /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
  229. final NavigationDelegate navigationDelegate;
  230. /// Invoked when a page starts loading.
  231. final PageStartedCallback onPageStarted;
  232. /// Invoked when a page has finished loading.
  233. ///
  234. /// This is invoked only for the main frame.
  235. ///
  236. /// When [onPageFinished] is invoked on Android, the page being rendered may
  237. /// not be updated yet.
  238. ///
  239. /// When invoked on iOS or Android, any Javascript code that is embedded
  240. /// directly in the HTML has been loaded and code injected with
  241. /// [WebViewController.evaluateJavascript] can assume this.
  242. final PageFinishedCallback onPageFinished;
  243. /// Controls whether WebView debugging is enabled.
  244. ///
  245. /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
  246. ///
  247. /// WebView debugging is enabled by default in dev builds on iOS.
  248. ///
  249. /// To debug WebViews on iOS:
  250. /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.)
  251. /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> <your webview page>
  252. ///
  253. /// By default `debuggingEnabled` is false.
  254. final bool debuggingEnabled;
  255. /// The value used for the HTTP User-Agent: request header.
  256. /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations.
  257. ///
  258. /// This only works on iOS.
  259. ///
  260. /// By default `gestureNavigationEnabled` is false.
  261. final bool gestureNavigationEnabled;
  262. ///
  263. /// When null the platform's webview default is used for the User-Agent header.
  264. ///
  265. /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent.
  266. ///
  267. /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded.
  268. ///
  269. /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom
  270. /// user agent.
  271. ///
  272. /// By default `userAgent` is null.
  273. final String userAgent;
  274. /// Which restrictions apply on automatic media playback.
  275. ///
  276. /// This initial value is applied to the platform's webview upon creation. Any following
  277. /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved).
  278. ///
  279. /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types].
  280. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy;
  281. @override
  282. State<StatefulWidget> createState() => _WebViewState();
  283. }
  284. class _WebViewState extends State<WebView> {
  285. final Completer<WebViewController> _controller =
  286. Completer<WebViewController>();
  287. _PlatformCallbacksHandler _platformCallbacksHandler;
  288. @override
  289. Widget build(BuildContext context) {
  290. return WebView.platform.build(
  291. context: context,
  292. onWebViewPlatformCreated: _onWebViewPlatformCreated,
  293. webViewPlatformCallbacksHandler: _platformCallbacksHandler,
  294. gestureRecognizers: widget.gestureRecognizers,
  295. creationParams: _creationParamsfromWidget(widget),
  296. );
  297. }
  298. @override
  299. void initState() {
  300. super.initState();
  301. _assertJavascriptChannelNamesAreUnique();
  302. _platformCallbacksHandler = _PlatformCallbacksHandler(widget);
  303. }
  304. @override
  305. void didUpdateWidget(WebView oldWidget) {
  306. super.didUpdateWidget(oldWidget);
  307. _assertJavascriptChannelNamesAreUnique();
  308. _controller.future.then((WebViewController controller) {
  309. _platformCallbacksHandler._widget = widget;
  310. controller._updateWidget(widget);
  311. });
  312. }
  313. void _onWebViewPlatformCreated(WebViewPlatformController webViewPlatform) {
  314. final WebViewController controller =
  315. WebViewController._(widget, webViewPlatform, _platformCallbacksHandler);
  316. _controller.complete(controller);
  317. if (widget.onWebViewCreated != null) {
  318. widget.onWebViewCreated(controller);
  319. }
  320. }
  321. void _assertJavascriptChannelNamesAreUnique() {
  322. if (widget.javascriptChannels == null ||
  323. widget.javascriptChannels.isEmpty) {
  324. return;
  325. }
  326. assert(_extractChannelNames(widget.javascriptChannels).length ==
  327. widget.javascriptChannels.length);
  328. }
  329. }
  330. CreationParams _creationParamsfromWidget(WebView widget) {
  331. return CreationParams(
  332. initialUrl: widget.initialUrl,
  333. webSettings: _webSettingsFromWidget(widget),
  334. javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),
  335. userAgent: widget.userAgent,
  336. autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
  337. );
  338. }
  339. WebSettings _webSettingsFromWidget(WebView widget) {
  340. return WebSettings(
  341. javascriptMode: widget.javascriptMode,
  342. hasNavigationDelegate: widget.navigationDelegate != null,
  343. debuggingEnabled: widget.debuggingEnabled,
  344. gestureNavigationEnabled: widget.gestureNavigationEnabled,
  345. userAgent: WebSetting<String>.of(widget.userAgent),
  346. );
  347. }
  348. // This method assumes that no fields in `currentValue` are null.
  349. WebSettings _clearUnchangedWebSettings(
  350. WebSettings currentValue, WebSettings newValue) {
  351. assert(currentValue.javascriptMode != null);
  352. assert(currentValue.hasNavigationDelegate != null);
  353. assert(currentValue.debuggingEnabled != null);
  354. assert(currentValue.userAgent.isPresent);
  355. assert(newValue.javascriptMode != null);
  356. assert(newValue.hasNavigationDelegate != null);
  357. assert(newValue.debuggingEnabled != null);
  358. assert(newValue.userAgent.isPresent);
  359. JavascriptMode javascriptMode;
  360. bool hasNavigationDelegate;
  361. bool debuggingEnabled;
  362. WebSetting<String> userAgent = WebSetting<String>.absent();
  363. if (currentValue.javascriptMode != newValue.javascriptMode) {
  364. javascriptMode = newValue.javascriptMode;
  365. }
  366. if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) {
  367. hasNavigationDelegate = newValue.hasNavigationDelegate;
  368. }
  369. if (currentValue.debuggingEnabled != newValue.debuggingEnabled) {
  370. debuggingEnabled = newValue.debuggingEnabled;
  371. }
  372. if (currentValue.userAgent != newValue.userAgent) {
  373. userAgent = newValue.userAgent;
  374. }
  375. return WebSettings(
  376. javascriptMode: javascriptMode,
  377. hasNavigationDelegate: hasNavigationDelegate,
  378. debuggingEnabled: debuggingEnabled,
  379. userAgent: userAgent,
  380. );
  381. }
  382. Set<String> _extractChannelNames(Set<JavascriptChannel> channels) {
  383. final Set<String> channelNames = channels == null
  384. // TODO(iskakaushik): Remove this when collection literals makes it to stable.
  385. // ignore: prefer_collection_literals
  386. ? Set<String>()
  387. : channels.map((JavascriptChannel channel) => channel.name).toSet();
  388. return channelNames;
  389. }
  390. class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
  391. _PlatformCallbacksHandler(this._widget) {
  392. _updateJavascriptChannelsFromSet(_widget.javascriptChannels);
  393. }
  394. WebView _widget;
  395. // Maps a channel name to a channel.
  396. final Map<String, JavascriptChannel> _javascriptChannels =
  397. <String, JavascriptChannel>{};
  398. @override
  399. void onJavaScriptChannelMessage(String channel, String message) {
  400. _javascriptChannels[channel].onMessageReceived(JavascriptMessage(message));
  401. }
  402. @override
  403. FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame}) async {
  404. final NavigationRequest request =
  405. NavigationRequest._(url: url, isForMainFrame: isForMainFrame);
  406. final bool allowNavigation = _widget.navigationDelegate == null ||
  407. await _widget.navigationDelegate(request) ==
  408. NavigationDecision.navigate;
  409. return allowNavigation;
  410. }
  411. @override
  412. void onPageStarted(String url) {
  413. if (_widget.onPageStarted != null) {
  414. _widget.onPageStarted(url);
  415. }
  416. }
  417. @override
  418. void onPageFinished(String url) {
  419. if (_widget.onPageFinished != null) {
  420. _widget.onPageFinished(url);
  421. }
  422. }
  423. void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {
  424. _javascriptChannels.clear();
  425. if (channels == null) {
  426. return;
  427. }
  428. for (JavascriptChannel channel in channels) {
  429. _javascriptChannels[channel.name] = channel;
  430. }
  431. }
  432. }
  433. /// Controls a [WebView].
  434. ///
  435. /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated]
  436. /// callback for a [WebView] widget.
  437. class WebViewController {
  438. WebViewController._(
  439. this._widget,
  440. this._webViewPlatformController,
  441. this._platformCallbacksHandler,
  442. ) : assert(_webViewPlatformController != null) {
  443. _settings = _webSettingsFromWidget(_widget);
  444. }
  445. final WebViewPlatformController _webViewPlatformController;
  446. final _PlatformCallbacksHandler _platformCallbacksHandler;
  447. WebSettings _settings;
  448. WebView _widget;
  449. /// Loads the specified URL.
  450. ///
  451. /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will
  452. /// be added as key value pairs of HTTP headers for the request.
  453. ///
  454. /// `url` must not be null.
  455. ///
  456. /// Throws an ArgumentError if `url` is not a valid URL string.
  457. Future<void> loadUrl(
  458. String url, {
  459. Map<String, String> headers,
  460. }) async {
  461. assert(url != null);
  462. _validateUrlString(url);
  463. return _webViewPlatformController.loadUrl(url, headers);
  464. }
  465. /// Accessor to the current URL that the WebView is displaying.
  466. ///
  467. /// If [WebView.initialUrl] was never specified, returns `null`.
  468. /// Note that this operation is asynchronous, and it is possible that the
  469. /// current URL changes again by the time this function returns (in other
  470. /// words, by the time this future completes, the WebView may be displaying a
  471. /// different URL).
  472. Future<String> currentUrl() {
  473. return _webViewPlatformController.currentUrl();
  474. }
  475. /// Checks whether there's a back history item.
  476. ///
  477. /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has
  478. /// changed by the time the future completed.
  479. Future<bool> canGoBack() {
  480. return _webViewPlatformController.canGoBack();
  481. }
  482. /// Checks whether there's a forward history item.
  483. ///
  484. /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has
  485. /// changed by the time the future completed.
  486. Future<bool> canGoForward() {
  487. return _webViewPlatformController.canGoForward();
  488. }
  489. /// Goes back in the history of this WebView.
  490. ///
  491. /// If there is no back history item this is a no-op.
  492. Future<void> goBack() {
  493. return _webViewPlatformController.goBack();
  494. }
  495. /// Goes forward in the history of this WebView.
  496. ///
  497. /// If there is no forward history item this is a no-op.
  498. Future<void> goForward() {
  499. return _webViewPlatformController.goForward();
  500. }
  501. /// Reloads the current URL.
  502. Future<void> reload() {
  503. return _webViewPlatformController.reload();
  504. }
  505. /// Clears all caches used by the [WebView].
  506. ///
  507. /// The following caches are cleared:
  508. /// 1. Browser HTTP Cache.
  509. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches.
  510. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache.
  511. /// 3. Application cache.
  512. /// 4. Local Storage.
  513. ///
  514. /// Note: Calling this method also triggers a reload.
  515. Future<void> clearCache() async {
  516. await _webViewPlatformController.clearCache();
  517. return reload();
  518. }
  519. Future<void> _updateWidget(WebView widget) async {
  520. _widget = widget;
  521. await _updateSettings(_webSettingsFromWidget(widget));
  522. await _updateJavascriptChannels(widget.javascriptChannels);
  523. }
  524. Future<void> _updateSettings(WebSettings newSettings) {
  525. final WebSettings update =
  526. _clearUnchangedWebSettings(_settings, newSettings);
  527. _settings = newSettings;
  528. return _webViewPlatformController.updateSettings(update);
  529. }
  530. Future<void> _updateJavascriptChannels(
  531. Set<JavascriptChannel> newChannels) async {
  532. final Set<String> currentChannels =
  533. _platformCallbacksHandler._javascriptChannels.keys.toSet();
  534. final Set<String> newChannelNames = _extractChannelNames(newChannels);
  535. final Set<String> channelsToAdd =
  536. newChannelNames.difference(currentChannels);
  537. final Set<String> channelsToRemove =
  538. currentChannels.difference(newChannelNames);
  539. if (channelsToRemove.isNotEmpty) {
  540. await _webViewPlatformController
  541. .removeJavascriptChannels(channelsToRemove);
  542. }
  543. if (channelsToAdd.isNotEmpty) {
  544. await _webViewPlatformController.addJavascriptChannels(channelsToAdd);
  545. }
  546. _platformCallbacksHandler._updateJavascriptChannelsFromSet(newChannels);
  547. }
  548. /// Evaluates a JavaScript expression in the context of the current page.
  549. ///
  550. /// On Android returns the evaluation result as a JSON formatted string.
  551. ///
  552. /// On iOS depending on the value type the return value would be one of:
  553. ///
  554. /// - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100').
  555. /// - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.').
  556. /// - Other non-primitive types are not supported on iOS and will complete the Future with an error.
  557. ///
  558. /// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the
  559. /// evaluated expression is not supported as described above.
  560. ///
  561. /// When evaluating Javascript in a [WebView], it is best practice to wait for
  562. /// the [WebView.onPageFinished] callback. This guarantees all the Javascript
  563. /// embedded in the main frame HTML has been loaded.
  564. Future<String> evaluateJavascript(String javascriptString) {
  565. if (_settings.javascriptMode == JavascriptMode.disabled) {
  566. return Future<String>.error(FlutterError(
  567. 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.'));
  568. }
  569. if (javascriptString == null) {
  570. return Future<String>.error(
  571. ArgumentError('The argument javascriptString must not be null.'));
  572. }
  573. // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
  574. // https://github.com/flutter/flutter/issues/26431
  575. // ignore: strong_mode_implicit_dynamic_method
  576. return _webViewPlatformController.evaluateJavascript(javascriptString);
  577. }
  578. /// Returns the title of the currently loaded page.
  579. Future<String> getTitle() {
  580. return _webViewPlatformController.getTitle();
  581. }
  582. }
  583. /// Manages cookies pertaining to all [WebView]s.
  584. class CookieManager {
  585. /// Creates a [CookieManager] -- returns the instance if it's already been called.
  586. factory CookieManager() {
  587. return _instance ??= CookieManager._();
  588. }
  589. CookieManager._();
  590. static CookieManager _instance;
  591. /// Clears all cookies for all [WebView] instances.
  592. ///
  593. /// This is a no op on iOS version smaller than 9.
  594. ///
  595. /// Returns true if cookies were present before clearing, else false.
  596. Future<bool> clearCookies() => WebView.platform.clearCookies();
  597. }
  598. // Throws an ArgumentError if `url` is not a valid URL string.
  599. void _validateUrlString(String url) {
  600. try {
  601. final Uri uri = Uri.parse(url);
  602. if (uri.scheme.isEmpty) {
  603. throw ArgumentError('Missing scheme in URL string: "$url"');
  604. }
  605. } on FormatException catch (e) {
  606. throw ArgumentError(e);
  607. }
  608. }