Masterarbeit Richard Stern. Flutter App, sich mit einem Bluetooth-Gerät verbindet und Berührungen auf einem Sensor visualisiert.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webview_flutter.dart 21KB

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