diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index 02ee1976eb67..42b45434b66d 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -523,58 +523,6 @@ Future main() async { await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); - - // allowsInlineMediaPlayback is a noop on Android, so it is skipped. - testWidgets( - 'Video plays full screen when allowsInlineMediaPlayback is false', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'VideoTestTime', - onMessageReceived: (JavascriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ), - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: false, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - // Pump once to trigger the video play. - await tester.pump(); - - // Makes sure we get the correct event that indicates the video is actually playing. - await videoPlaying.future; - - final String fullScreen = - await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(true)); - }, skip: Platform.isAndroid); }); group('Audio playback policy', () { @@ -876,42 +824,6 @@ Future main() async { }, skip: Platform.isAndroid && _skipDueToIssue86757); }); - // Minimial end-to-end testing of the legacy Android implementation. - group('AndroidWebView (virtual display)', () { - setUpAll(() { - WebView.platform = AndroidWebView(); - }); - - tearDownAll(() { - WebView.platform = null; - }); - - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer loadCompleter = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - loadCompleter.complete(); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await loadCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - }, skip: !Platform.isAndroid || _skipDueToIssue86757); - group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + @@ -1123,32 +1035,6 @@ Future main() async { }); }); - testWidgets('launches with gestureNavigationEnabled on iOS', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 400, - height: 300, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - gestureNavigationEnabled: true, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 testWidgets('target _blank opens in same window', (WidgetTester tester) async { diff --git a/packages/webview_flutter/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/webview_flutter/example/pubspec.yaml index ae3b57e07a89..496b07e431fe 100644 --- a/packages/webview_flutter/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/example/pubspec.yaml @@ -18,6 +18,14 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ +dependency_overrides: + webview_flutter_android: + path: ../../webview_flutter_android + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface + webview_flutter_wkwebview: + path: ../../webview_flutter_wkwebview + dev_dependencies: espresso: ^0.1.0+2 flutter_driver: diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 58f2f369bcf5..f2b983ddf595 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -9,20 +9,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:webview_flutter_android/webview_android.dart'; -import 'package:webview_flutter_android/webview_surface_android.dart'; -import 'package:webview_flutter_android_example/navigation_decision.dart'; -import 'package:webview_flutter_android_example/navigation_request.dart'; -import 'package:webview_flutter_android_example/web_view.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -47,779 +40,42 @@ Future main() async { final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; - final String headersUrl = '$prefixUrl/headers'; - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - MaterialApp( - home: Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }, skip: _skipDueToIssue86757); - - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets('loadUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await controller.loadUrl(secondaryUrl); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, secondaryUrl); - }, skip: _skipDueToIssue86757); - - testWidgets('evaluateJavascript', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String result = await controller.evaluateJavascript('1 + 1'); - expect(result, equals('2')); - }); - - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets('loadUrl with headers', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageStarts = StreamController(); - final StreamController pageLoads = StreamController(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarts.add(url); - }, - onPageFinished: (String url) { - pageLoads.add(url); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final Map headers = { - 'test_header': 'flutter_test_header' - }; - await controller.loadUrl(headersUrl, headers: headers); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, headersUrl); - - await pageStarts.stream.firstWhere((String url) => url == currentUrl); - await pageLoads.stream.firstWhere((String url) => url == currentUrl); - - final String content = await controller - .runJavascriptReturningResult('document.documentElement.innerText'); - expect(content.contains('flutter_test_header'), isTrue); - }, skip: _skipDueToIssue86757); - - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets('JavascriptChannel', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); - final List messagesReceived = []; - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - // This is the data URL for: '' - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'Echo', - onMessageReceived: (JavascriptMessage message) { - messagesReceived.add(message.message); - }, - ), - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - expect(messagesReceived, isEmpty); - await controller.runJavascript('Echo.postMessage("hello");'); - expect(messagesReceived, equals(['hello'])); - }, skip: _skipDueToIssue86757); - - testWidgets('resize webview', (WidgetTester tester) async { - final Completer initialResizeCompleter = Completer(); - final Completer buttonTapResizeCompleter = Completer(); - final Completer onPageFinished = Completer(); - - bool resizeButtonTapped = false; - await tester.pumpWidget(ResizableWebView( - onResize: (_) { - if (resizeButtonTapped) { - buttonTapResizeCompleter.complete(); - } else { - initialResizeCompleter.complete(); - } - }, - onPageFinished: () => onPageFinished.complete(), - )); - await onPageFinished.future; - // Wait for a potential call to resize after page is loaded. - await initialResizeCompleter.future.timeout( - const Duration(seconds: 3), - onTimeout: () => null, - ); - - resizeButtonTapped = true; - await tester.tap(find.byKey(const ValueKey('resizeButton'))); - await tester.pumpAndSettle(); - expect(buttonTapResizeCompleter.future, completes); - }); - - testWidgets('set custom userAgent', (WidgetTester tester) async { - final Completer controllerCompleter1 = - Completer(); - final GlobalKey _globalKey = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent1', - onWebViewCreated: (WebViewController controller) { - controllerCompleter1.complete(controller); - }, - ), - ), - ); - final WebViewController controller1 = await controllerCompleter1.future; - final String customUserAgent1 = await _getUserAgent(controller1); - expect(customUserAgent1, 'Custom_User_Agent1'); - // rebuild the WebView with a different user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent2', - ), - ), - ); - - final String customUserAgent2 = await _getUserAgent(controller1); - expect(customUserAgent2, 'Custom_User_Agent2'); - }); - - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets('use default platform userAgent after webView is rebuilt', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final GlobalKey _globalKey = GlobalKey(); - // Build the webView with no user agent to get the default platform user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String defaultPlatformUserAgent = await _getUserAgent(controller); - // rebuild the WebView with a custom user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent', - ), - ), - ); - final String customUserAgent = await _getUserAgent(controller); - expect(customUserAgent, 'Custom_User_Agent'); - // rebuilds the WebView with no user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - - final String customUserAgent2 = await _getUserAgent(controller); - expect(customUserAgent2, defaultPlatformUserAgent); - }, skip: _skipDueToIssue86757); - - group('Video playback policy', () { - late String videoTestBase64; - setUpAll(() async { - final ByteData videoData = - await rootBundle.load('assets/sample_video.mp4'); - final String base64VideoData = - base64Encode(Uint8List.view(videoData.buffer)); - final String videoTest = ''' - - Video auto play - - - - - - - '''; - videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); - }); - - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - controllerCompleter = Completer(); - pageLoaded = Completer(); - - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - controller = await controllerCompleter.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); + // Minimial end-to-end testing of the legacy Android implementation. + group('AndroidWebView (virtual display)', () { + setUpAll(() { + WebView.platform = AndroidWebView(); }); - testWidgets('Changes to initialMediaPlaybackPolicy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - await controller.reload(); - - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + tearDownAll(() { + WebView.platform = null; }); - testWidgets('Video plays inline when allowsInlineMediaPlayback is true', - (WidgetTester tester) async { + testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'VideoTestTime', - onMessageReceived: (JavascriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ), - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: true, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - // Pump once to trigger the video play. - await tester.pump(); - - // Makes sure we get the correct event that indicates the video is actually playing. - await videoPlaying.future; - - final String fullScreen = - await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(false)); - }); - }); - - group('Audio playback policy', () { - late String audioTestBase64; - setUpAll(() async { - final ByteData audioData = - await rootBundle.load('assets/sample_audio.ogg'); - final String base64AudioData = - base64Encode(Uint8List.view(audioData.buffer)); - final String audioTest = ''' - - Audio auto play - - - - - - - '''; - audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); - }); - - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); - Completer pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - controllerCompleter = Completer(); - pageStarted = Completer(); - pageLoaded = Completer(); - - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + final Completer loadCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); - }); - - testWidgets('Changes to initialMediaPlaybackPolocy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); - Completer pageLoaded = Completer(); - - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, onPageFinished: (String url) { - pageLoaded.complete(null); + loadCompleter.complete(); }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - pageStarted = Completer(); - pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - await controller.reload(); - - await pageStarted.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); + await loadCompleter.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); }); - }); - - testWidgets('getTitle', (WidgetTester tester) async { - const String getTitleTest = ''' - - Some title - - - - - '''; - final String getTitleTestBase64 = - base64Encode(const Utf8Encoder().convert(getTitleTest)); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - final String? title = await controller.getTitle(); - expect(title, 'Some title'); - }); - - group('Programmatic Scroll', () { - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - const String scrollTestPage = ''' - - - - - - -
- - - '''; - - final String scrollTestPageBase64 = - base64Encode(const Utf8Encoder().convert(scrollTestPage)); - - final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - await tester.pumpAndSettle(const Duration(seconds: 3)); - - int scrollPosX = await controller.getScrollX(); - int scrollPosY = await controller.getScrollY(); - - // Check scrollTo() - const int X_SCROLL = 123; - const int Y_SCROLL = 321; - // Get the initial position; this ensures that scrollTo is actually - // changing something, but also gives the native view's scroll position - // time to settle. - expect(scrollPosX, isNot(X_SCROLL)); - expect(scrollPosX, isNot(Y_SCROLL)); - - await controller.scrollTo(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL); - expect(scrollPosY, Y_SCROLL); - - // Check scrollBy() (on top of scrollTo()) - await controller.scrollBy(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL * 2); - expect(scrollPosY, Y_SCROLL * 2); - }, skip: _skipDueToIssue86757); - }); + }, skip: _skipDueToIssue86757); group('SurfaceAndroidWebView', () { setUpAll(() { @@ -1013,170 +269,6 @@ Future main() async { final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + base64Encode(const Utf8Encoder().convert(blankPage)); - testWidgets('can allow requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); - - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); - - await pageLoads.stream.first; // Wait for the next page load. - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, secondaryUrl); - }); - - testWidgets('onWebResourceError', (WidgetTester tester) async { - final Completer errorCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'https://www.notawebsite..com', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - ), - ), - ); - - final WebResourceError error = await errorCompleter.future; - expect(error, isNotNull); - - expect(error.errorType, isNotNull); - expect( - error.failingUrl?.startsWith('https://www.notawebsite..com'), isTrue); - }); - - testWidgets('onWebResourceError is not called with valid url', - (WidgetTester tester) async { - final Completer errorCompleter = - Completer(); - final Completer pageFinishCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); - - expect(errorCompleter.future, doesNotComplete); - await pageFinishCompleter.future; - }); - - testWidgets( - 'onWebResourceError only called for main frame', - (WidgetTester tester) async { - const String iframeTest = ''' - - - - WebResourceError test - - - - - - '''; - final String iframeTestBase64 = - base64Encode(const Utf8Encoder().convert(iframeTest)); - - final Completer errorCompleter = - Completer(); - final Completer pageFinishCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,$iframeTestBase64', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); - - expect(errorCompleter.future, doesNotComplete); - await pageFinishCompleter.future; - }, - ); - - testWidgets('can block requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); - - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller - .runJavascript('location.href = "https://www.youtube.com/"'); - - // There should never be any second page load, since our new URL is - // blocked. Still wait for a potential page change for some time in order - // to give the test a chance to fail. - await pageLoads.stream.first - .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, isNot(contains('youtube.com'))); - }); - testWidgets('supports asynchronous decisions', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); @@ -1214,102 +306,6 @@ Future main() async { }); }); - testWidgets('launches with gestureNavigationEnabled on iOS', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 400, - height: 300, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - gestureNavigationEnabled: true, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - - testWidgets('target _blank opens in same window', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('window.open("$primaryUrl", "_blank")'); - await pageLoaded.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }, - // Flaky on Android: https://github.com/flutter/flutter/issues/86757 - skip: _skipDueToIssue86757); - - // TODO(bparrishMines): skipped due to https://github.com/flutter/flutter/issues/86757 - testWidgets( - 'can open new window and go back', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(); - }, - initialUrl: primaryUrl, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - expect(controller.currentUrl(), completion(primaryUrl)); - await pageLoaded.future; - pageLoaded = Completer(); - - await controller.runJavascript('window.open("$secondaryUrl")'); - await pageLoaded.future; - pageLoaded = Completer(); - expect(controller.currentUrl(), completion(secondaryUrl)); - - expect(controller.canGoBack(), completion(true)); - await controller.goBack(); - await pageLoaded.future; - expect(controller.currentUrl(), completion(primaryUrl)); - }, - skip: _skipDueToIssue86757, - ); - testWidgets( 'JavaScript does not run in parent window', (WidgetTester tester) async { @@ -1379,20 +375,6 @@ Future main() async { ); } -// JavaScript booleans evaluate to different string values on Android and iOS. -// This utility method returns the string boolean value of the current platform. -String _webviewBool(bool value) { - if (defaultTargetPlatform == TargetPlatform.iOS) { - return value ? '1' : '0'; - } - return value ? 'true' : 'false'; -} - -/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. -Future _getUserAgent(WebViewController controller) async { - return _runJavaScriptReturningResult(controller, 'navigator.userAgent;'); -} - Future _runJavaScriptReturningResult( WebViewController controller, String js, diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index 0d0cd59af796..36e7246fc2fb 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -12,12 +12,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:webview_flutter_android/webview_surface_android.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'navigation_decision.dart'; -import 'navigation_request.dart'; -import 'web_view.dart'; +import 'package:webview_flutter/webview_flutter.dart'; void appMain() { enableFlutterDriverExtension(); @@ -195,9 +190,10 @@ enum _MenuOptions { } class _SampleMenu extends StatelessWidget { - const _SampleMenu(this.controller); + _SampleMenu(this.controller); final Future controller; + final CookieManager cookieManager = CookieManager(); @override Widget build(BuildContext context) { @@ -364,7 +360,7 @@ class _SampleMenu extends StatelessWidget { Future _onClearCookies( WebViewController controller, BuildContext context) async { - final bool hadCookies = await WebViewCookieManager.instance.clearCookies(); + final bool hadCookies = await cookieManager.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; @@ -377,7 +373,7 @@ class _SampleMenu extends StatelessWidget { Future _onSetCookie( WebViewController controller, BuildContext context) async { - await WebViewCookieManager.instance.setCookie( + await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), ); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_decision.dart b/packages/webview_flutter/webview_flutter_android/example/lib/navigation_decision.dart deleted file mode 100644 index d8178acd8096..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_decision.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// A decision on how to handle a navigation request. -enum NavigationDecision { - /// Prevent the navigation from taking place. - prevent, - - /// Allow the navigation to take place. - navigate, -} diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_request.dart b/packages/webview_flutter/webview_flutter_android/example/lib/navigation_request.dart deleted file mode 100644 index 6d33126b7c53..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/lib/navigation_request.dart +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// Information about a navigation action that is about to be executed. -class NavigationRequest { - NavigationRequest._({required this.url, required this.isForMainFrame}); - - /// The URL that will be loaded if the navigation is executed. - final String url; - - /// Whether the navigation request is to be loaded as the main frame. - final bool isForMainFrame; - - @override - String toString() { - return '$NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart deleted file mode 100644 index 91ea66376904..000000000000 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ /dev/null @@ -1,701 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:webview_flutter_android/webview_android.dart'; -import 'package:webview_flutter_android/webview_android_cookie_manager.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'navigation_decision.dart'; -import 'navigation_request.dart'; - -/// Optional callback invoked when a web view is first created. [controller] is -/// the [WebViewController] for the created web view. -typedef WebViewCreatedCallback = void Function(WebViewController controller); - -/// Decides how to handle a specific navigation request. -/// -/// The returned [NavigationDecision] determines how the navigation described by -/// `navigation` should be handled. -/// -/// See also: [WebView.navigationDelegate]. -typedef NavigationDelegate = FutureOr Function( - NavigationRequest navigation); - -/// Signature for when a [WebView] has started loading a page. -typedef PageStartedCallback = void Function(String url); - -/// Signature for when a [WebView] has finished loading a page. -typedef PageFinishedCallback = void Function(String url); - -/// Signature for when a [WebView] is loading a page. -typedef PageLoadingCallback = void Function(int progress); - -/// Signature for when a [WebView] has failed to load a resource. -typedef WebResourceErrorCallback = void Function(WebResourceError error); - -/// A web view widget for showing html content. -/// -/// The [WebView] widget wraps around the [AndroidWebView] or -/// [SurfaceAndroidWebView] classes and acts like a facade which makes it easier -/// to inject a [AndroidWebView] or [SurfaceAndroidWebView] control into the -/// widget tree. -/// -/// The [WebView] widget is controlled using the [WebViewController] which is -/// provided through the `onWebViewCreated` callback. -/// -/// In this example project it's main purpose is to facilitate integration -/// testing of the `webview_flutter_android` package. -class WebView extends StatefulWidget { - /// Creates a new web view. - /// - /// The web view can be controlled using a `WebViewController` that is passed to the - /// `onWebViewCreated` callback once the web view is created. - /// - /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. - const WebView({ - Key? key, - this.onWebViewCreated, - this.initialUrl, - this.initialCookies = const [], - this.javascriptMode = JavascriptMode.disabled, - this.javascriptChannels, - this.navigationDelegate, - this.gestureRecognizers, - this.onPageStarted, - this.onPageFinished, - this.onProgress, - this.onWebResourceError, - this.debuggingEnabled = false, - this.gestureNavigationEnabled = false, - this.userAgent, - this.zoomEnabled = true, - this.initialMediaPlaybackPolicy = - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - this.allowsInlineMediaPlayback = false, - this.backgroundColor, - }) : assert(javascriptMode != null), - assert(initialMediaPlaybackPolicy != null), - assert(allowsInlineMediaPlayback != null), - super(key: key); - - /// The WebView platform that's used by this WebView. - /// - /// The default value is [AndroidWebView]. - static WebViewPlatform platform = AndroidWebView(); - - /// If not null invoked once the web view is created. - final WebViewCreatedCallback? onWebViewCreated; - - /// Which gestures should be consumed by the web view. - /// - /// It is possible for other gesture recognizers to be competing with the web view on pointer - /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle - /// vertical drags. The web view will claim gestures that are recognized by any of the - /// recognizers on this list. - /// - /// When this set is empty or null, the web view will only handle pointer events for gestures that - /// were not claimed by any other gesture recognizer. - final Set>? gestureRecognizers; - - /// The initial URL to load. - final String? initialUrl; - - /// The initial cookies to set. - final List initialCookies; - - /// Whether JavaScript execution is enabled. - final JavascriptMode javascriptMode; - - /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. - /// - /// For each [JavascriptChannel] in the set, a channel object is made available for the - /// JavaScript code in a window property named [JavascriptChannel.name]. - /// The JavaScript code can then call `postMessage` on that object to send a message that will be - /// passed to [JavascriptChannel.onMessageReceived]. - /// - /// For example for the following [JavascriptChannel]: - /// - /// ```dart - /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); - /// ``` - /// - /// JavaScript code can call: - /// - /// ```javascript - /// Print.postMessage('Hello'); - /// ``` - /// - /// To asynchronously invoke the message handler which will print the message to standard output. - /// - /// Adding a new JavaScript channel only takes affect after the next page is loaded. - /// - /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple - /// channels in the list. - /// - /// A null value is equivalent to an empty set. - final Set? javascriptChannels; - - /// A delegate function that decides how to handle navigation actions. - /// - /// When a navigation is initiated by the WebView (e.g when a user clicks a link) - /// this delegate is called and has to decide how to proceed with the navigation. - /// - /// See [NavigationDecision] for possible decisions the delegate can take. - /// - /// When null all navigation actions are allowed. - /// - /// Caveats on Android: - /// - /// * Navigation actions targeted to the main frame can be intercepted, - /// navigation actions targeted to subframes are allowed regardless of the value - /// returned by this delegate. - /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were - /// triggered by a user gesture, this disables some of Chromium's security mechanisms. - /// A navigationDelegate should only be set when loading trusted content. - /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have - /// a later version): - /// * When a navigationDelegate is set pages with frames are not properly handled by the - /// webview, and frames will be opened in the main frame. - /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. - final NavigationDelegate? navigationDelegate; - - /// Controls whether inline playback of HTML5 videos is allowed on iOS. - /// - /// This field is ignored on Android because Android allows it by default. - /// - /// By default `allowsInlineMediaPlayback` is false. - final bool allowsInlineMediaPlayback; - - /// Invoked when a page starts loading. - final PageStartedCallback? onPageStarted; - - /// Invoked when a page has finished loading. - /// - /// This is invoked only for the main frame. - /// - /// When [onPageFinished] is invoked on Android, the page being rendered may - /// not be updated yet. - /// - /// When invoked on iOS or Android, any JavaScript code that is embedded - /// directly in the HTML has been loaded and code injected with - /// [WebViewController.evaluateJavascript] can assume this. - final PageFinishedCallback? onPageFinished; - - /// Invoked when a page is loading. - final PageLoadingCallback? onProgress; - - /// Invoked when a web resource has failed to load. - /// - /// This callback is only called for the main page. - final WebResourceErrorCallback? onWebResourceError; - - /// Controls whether WebView debugging is enabled. - /// - /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). - /// - /// WebView debugging is enabled by default in dev builds on iOS. - /// - /// To debug WebViews on iOS: - /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) - /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> - /// - /// By default `debuggingEnabled` is false. - final bool debuggingEnabled; - - /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. - /// - /// This only works on iOS. - /// - /// By default `gestureNavigationEnabled` is false. - final bool gestureNavigationEnabled; - - /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. - /// - /// By default 'zoomEnabled' is true - final bool zoomEnabled; - - /// The value used for the HTTP User-Agent: request header. - /// - /// When null the platform's webview default is used for the User-Agent header. - /// - /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. - /// - /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. - /// - /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom - /// user agent. - /// - /// By default `userAgent` is null. - final String? userAgent; - - /// Which restrictions apply on automatic media playback. - /// - /// This initial value is applied to the platform's webview upon creation. Any following - /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). - /// - /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. - final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; - - /// The background color of the [WebView]. - /// - /// When `null` the platform's webview default background color is used. By - /// default [backgroundColor] is `null`. - final Color? backgroundColor; - - @override - _WebViewState createState() => _WebViewState(); -} - -class _WebViewState extends State { - final Completer _controller = - Completer(); - late final JavascriptChannelRegistry _javascriptChannelRegistry; - late final _PlatformCallbacksHandler _platformCallbacksHandler; - - @override - void initState() { - super.initState(); - _platformCallbacksHandler = _PlatformCallbacksHandler(widget); - _javascriptChannelRegistry = - JavascriptChannelRegistry(widget.javascriptChannels); - } - - @override - void didUpdateWidget(WebView oldWidget) { - super.didUpdateWidget(oldWidget); - _controller.future.then((WebViewController controller) { - controller.updateWidget(widget); - }); - } - - @override - Widget build(BuildContext context) { - return WebView.platform.build( - context: context, - onWebViewPlatformCreated: - (WebViewPlatformController? webViewPlatformController) { - final WebViewController controller = WebViewController( - widget, - webViewPlatformController!, - _javascriptChannelRegistry, - ); - _controller.complete(controller); - - if (widget.onWebViewCreated != null) { - widget.onWebViewCreated!(controller); - } - }, - webViewPlatformCallbacksHandler: _platformCallbacksHandler, - creationParams: CreationParams( - initialUrl: widget.initialUrl, - webSettings: _webSettingsFromWidget(widget), - javascriptChannelNames: - _javascriptChannelRegistry.channels.keys.toSet(), - autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, - userAgent: widget.userAgent, - backgroundColor: widget.backgroundColor, - cookies: widget.initialCookies, - ), - javascriptChannelRegistry: _javascriptChannelRegistry, - ); - } -} - -class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { - _PlatformCallbacksHandler(this._webView); - - final WebView _webView; - - @override - FutureOr onNavigationRequest({ - required String url, - required bool isForMainFrame, - }) async { - if (url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $url'); - return false; - } - print('allowing navigation to $url'); - return true; - } - - @override - void onPageStarted(String url) { - if (_webView.onPageStarted != null) { - _webView.onPageStarted!(url); - } - } - - @override - void onPageFinished(String url) { - if (_webView.onPageFinished != null) { - _webView.onPageFinished!(url); - } - } - - @override - void onProgress(int progress) { - if (_webView.onProgress != null) { - _webView.onProgress!(progress); - } - } - - @override - void onWebResourceError(WebResourceError error) { - if (_webView.onWebResourceError != null) { - _webView.onWebResourceError!(error); - } - } -} - -/// Controls a [WebView]. -/// -/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] -/// callback for a [WebView] widget. -class WebViewController { - /// Creates a [WebViewController] which can be used to control the provided - /// [WebView] widget. - WebViewController( - this._widget, - this._webViewPlatformController, - this._javascriptChannelRegistry, - ) : assert(_webViewPlatformController != null) { - _settings = _webSettingsFromWidget(_widget); - } - - final JavascriptChannelRegistry _javascriptChannelRegistry; - - final WebViewPlatformController _webViewPlatformController; - - late WebSettings _settings; - - WebView _widget; - - /// Loads the file located on the specified [absoluteFilePath]. - /// - /// The [absoluteFilePath] parameter should contain the absolute path to the - /// file as it is stored on the device. For example: - /// `/Users/username/Documents/www/index.html`. - /// - /// Throws an ArgumentError if the [absoluteFilePath] does not exist. - Future loadFile(String absoluteFilePath) { - return _webViewPlatformController.loadFile(absoluteFilePath); - } - - /// Loads the Flutter asset specified in the pubspec.yaml file. - /// - /// Throws an ArgumentError if [key] is not part of the specified assets - /// in the pubspec.yaml file. - Future loadFlutterAsset(String key) { - return _webViewPlatformController.loadFlutterAsset(key); - } - - /// Loads the supplied HTML string. - /// - /// The [baseUrl] parameter is used when resolving relative URLs within the - /// HTML string. - Future loadHtmlString(String html, {String? baseUrl}) { - return _webViewPlatformController.loadHtmlString( - html, - baseUrl: baseUrl, - ); - } - - /// Loads the specified URL. - /// - /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will - /// be added as key value pairs of HTTP headers for the request. - /// - /// `url` must not be null. - /// - /// Throws an ArgumentError if `url` is not a valid URL string. - Future loadUrl( - String url, { - Map? headers, - }) async { - assert(url != null); - _validateUrlString(url); - return _webViewPlatformController.loadUrl(url, headers); - } - - /// Loads a page by making the specified request. - Future loadRequest(WebViewRequest request) async { - return _webViewPlatformController.loadRequest(request); - } - - /// Accessor to the current URL that the WebView is displaying. - /// - /// If [WebView.initialUrl] was never specified, returns `null`. - /// Note that this operation is asynchronous, and it is possible that the - /// current URL changes again by the time this function returns (in other - /// words, by the time this future completes, the WebView may be displaying a - /// different URL). - Future currentUrl() { - return _webViewPlatformController.currentUrl(); - } - - /// Checks whether there's a back history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has - /// changed by the time the future completed. - Future canGoBack() { - return _webViewPlatformController.canGoBack(); - } - - /// Checks whether there's a forward history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has - /// changed by the time the future completed. - Future canGoForward() { - return _webViewPlatformController.canGoForward(); - } - - /// Goes back in the history of this WebView. - /// - /// If there is no back history item this is a no-op. - Future goBack() { - return _webViewPlatformController.goBack(); - } - - /// Goes forward in the history of this WebView. - /// - /// If there is no forward history item this is a no-op. - Future goForward() { - return _webViewPlatformController.goForward(); - } - - /// Reloads the current URL. - Future reload() { - return _webViewPlatformController.reload(); - } - - /// Clears all caches used by the [WebView]. - /// - /// The following caches are cleared: - /// 1. Browser HTTP Cache. - /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. - /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. - /// 3. Application cache. - /// 4. Local Storage. - /// - /// Note: Calling this method also triggers a reload. - Future clearCache() async { - await _webViewPlatformController.clearCache(); - return reload(); - } - - /// Update the widget managed by the [WebViewController]. - Future updateWidget(WebView widget) async { - _widget = widget; - await _updateSettings(_webSettingsFromWidget(widget)); - await _updateJavascriptChannels( - _javascriptChannelRegistry.channels.values.toSet()); - } - - Future _updateSettings(WebSettings newSettings) { - final WebSettings update = - _clearUnchangedWebSettings(_settings, newSettings); - _settings = newSettings; - return _webViewPlatformController.updateSettings(update); - } - - Future _updateJavascriptChannels( - Set? newChannels) async { - final Set currentChannels = - _javascriptChannelRegistry.channels.keys.toSet(); - final Set newChannelNames = _extractChannelNames(newChannels); - final Set channelsToAdd = - newChannelNames.difference(currentChannels); - final Set channelsToRemove = - currentChannels.difference(newChannelNames); - if (channelsToRemove.isNotEmpty) { - await _webViewPlatformController - .removeJavascriptChannels(channelsToRemove); - } - if (channelsToAdd.isNotEmpty) { - await _webViewPlatformController.addJavascriptChannels(channelsToAdd); - } - _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); - } - - @visibleForTesting - // ignore: public_member_api_docs - Future evaluateJavascript(String javascriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); - } - return _webViewPlatformController.evaluateJavascript(javascriptString); - } - - /// Runs the given JavaScript in the context of the current page. - /// If you are looking for the result, use [runJavascriptReturningResult] instead. - /// The Future completes with an error if a JavaScript error occurred. - /// - /// When running JavaScript in a [WebView], it is best practice to wait for - // the [WebView.onPageFinished] callback. This guarantees all the JavaScript - // embedded in the main frame HTML has been loaded. - Future runJavascript(String javaScriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); - } - return _webViewPlatformController.runJavascript(javaScriptString); - } - - /// Runs the given JavaScript in the context of the current page, and returns the result. - /// - /// Returns the evaluation result as a JSON formatted string. - /// The Future completes with an error if a JavaScript error occurred. - /// - /// When evaluating JavaScript in a [WebView], it is best practice to wait for - /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript - /// embedded in the main frame HTML has been loaded. - Future runJavascriptReturningResult(String javaScriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); - } - return _webViewPlatformController - .runJavascriptReturningResult(javaScriptString); - } - - /// Returns the title of the currently loaded page. - Future getTitle() { - return _webViewPlatformController.getTitle(); - } - - /// Sets the WebView's content scroll position. - /// - /// The parameters `x` and `y` specify the scroll position in WebView pixels. - Future scrollTo(int x, int y) { - return _webViewPlatformController.scrollTo(x, y); - } - - /// Move the scrolled position of this view. - /// - /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. - Future scrollBy(int x, int y) { - return _webViewPlatformController.scrollBy(x, y); - } - - /// Return the horizontal scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from left. - Future getScrollX() { - return _webViewPlatformController.getScrollX(); - } - - /// Return the vertical scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from top. - Future getScrollY() { - return _webViewPlatformController.getScrollY(); - } - - // This method assumes that no fields in `currentValue` are null. - WebSettings _clearUnchangedWebSettings( - WebSettings currentValue, WebSettings newValue) { - assert(currentValue.javascriptMode != null); - assert(currentValue.hasNavigationDelegate != null); - assert(currentValue.hasProgressTracking != null); - assert(currentValue.debuggingEnabled != null); - assert(currentValue.userAgent != null); - assert(newValue.javascriptMode != null); - assert(newValue.hasNavigationDelegate != null); - assert(newValue.debuggingEnabled != null); - assert(newValue.userAgent != null); - assert(newValue.zoomEnabled != null); - - JavascriptMode? javascriptMode; - bool? hasNavigationDelegate; - bool? hasProgressTracking; - bool? debuggingEnabled; - WebSetting userAgent = const WebSetting.absent(); - bool? zoomEnabled; - if (currentValue.javascriptMode != newValue.javascriptMode) { - javascriptMode = newValue.javascriptMode; - } - if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { - hasNavigationDelegate = newValue.hasNavigationDelegate; - } - if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { - hasProgressTracking = newValue.hasProgressTracking; - } - if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { - debuggingEnabled = newValue.debuggingEnabled; - } - if (currentValue.userAgent != newValue.userAgent) { - userAgent = newValue.userAgent; - } - if (currentValue.zoomEnabled != newValue.zoomEnabled) { - zoomEnabled = newValue.zoomEnabled; - } - - return WebSettings( - javascriptMode: javascriptMode, - hasNavigationDelegate: hasNavigationDelegate, - hasProgressTracking: hasProgressTracking, - debuggingEnabled: debuggingEnabled, - userAgent: userAgent, - zoomEnabled: zoomEnabled, - ); - } - - Set _extractChannelNames(Set? channels) { - final Set channelNames = channels == null - ? {} - : channels.map((JavascriptChannel channel) => channel.name).toSet(); - return channelNames; - } - - // Throws an ArgumentError if `url` is not a valid URL string. - void _validateUrlString(String url) { - try { - final Uri uri = Uri.parse(url); - if (uri.scheme.isEmpty) { - throw ArgumentError('Missing scheme in URL string: "$url"'); - } - } on FormatException catch (e) { - throw ArgumentError(e); - } - } -} - -WebSettings _webSettingsFromWidget(WebView widget) { - return WebSettings( - javascriptMode: widget.javascriptMode, - hasNavigationDelegate: widget.navigationDelegate != null, - hasProgressTracking: widget.onProgress != null, - debuggingEnabled: widget.debuggingEnabled, - gestureNavigationEnabled: widget.gestureNavigationEnabled, - allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, - userAgent: WebSetting.of(widget.userAgent), - zoomEnabled: widget.zoomEnabled, - ); -} - -/// App-facing cookie manager that exposes the correct platform implementation. -class WebViewCookieManager extends WebViewCookieManagerPlatform { - WebViewCookieManager._(); - - /// Returns an instance of the cookie manager for the current platform. - static WebViewCookieManagerPlatform get instance { - if (WebViewCookieManagerPlatform.instance == null) { - if (Platform.isAndroid) { - WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); - } else { - throw AssertionError( - 'This platform is currently unsupported for webview_flutter_android.'); - } - } - return WebViewCookieManagerPlatform.instance!; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 85990bd02139..dde236029e80 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -10,14 +10,19 @@ dependencies: sdk: flutter path_provider: ^2.0.6 - - webview_flutter_android: + webview_flutter: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. + path: ../../webview_flutter + +dependency_overrides: + webview_flutter_android: path: ../ + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface dev_dependencies: espresso: ^0.1.0+2 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 924a6caa0f85..a3f0e95f7432 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -17,10 +17,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'package:webview_flutter_wkwebview_example/navigation_decision.dart'; -import 'package:webview_flutter_wkwebview_example/navigation_request.dart'; -import 'package:webview_flutter_wkwebview_example/web_view.dart'; +import 'package:webview_flutter/webview_flutter.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -42,264 +39,6 @@ Future main() async { }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; - final String secondaryUrl = '$prefixUrl/secondary.txt'; - final String headersUrl = '$prefixUrl/headers'; - - testWidgets('initialUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - - testWidgets('loadUrl', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await controller.loadUrl(secondaryUrl); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, secondaryUrl); - }); - - testWidgets('evaluateJavascript', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String result = await controller.evaluateJavascript('1 + 1'); - expect(result, equals('2')); - }); - - testWidgets('loadUrl with headers', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageStarts = StreamController(); - final StreamController pageLoads = StreamController(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarts.add(url); - }, - onPageFinished: (String url) { - pageLoads.add(url); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final Map headers = { - 'test_header': 'flutter_test_header' - }; - await controller.loadUrl(headersUrl, headers: headers); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, headersUrl); - - await pageStarts.stream.firstWhere((String url) => url == currentUrl); - await pageLoads.stream.firstWhere((String url) => url == currentUrl); - - final String content = await controller - .runJavascriptReturningResult('document.documentElement.innerText'); - expect(content.contains('flutter_test_header'), isTrue); - }); - - testWidgets('JavascriptChannel', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); - final List messagesReceived = []; - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - // This is the data URL for: '' - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'Echo', - onMessageReceived: (JavascriptMessage message) { - messagesReceived.add(message.message); - }, - ), - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - expect(messagesReceived, isEmpty); - await controller.runJavascript('Echo.postMessage("hello");'); - expect(messagesReceived, equals(['hello'])); - }); - - testWidgets('resize webview', (WidgetTester tester) async { - final Completer buttonTapResizeCompleter = Completer(); - final Completer onPageFinished = Completer(); - - bool resizeButtonTapped = false; - await tester.pumpWidget(ResizableWebView( - onResize: (_) { - if (resizeButtonTapped) { - buttonTapResizeCompleter.complete(); - } - }, - onPageFinished: () => onPageFinished.complete(), - )); - await onPageFinished.future; - - resizeButtonTapped = true; - await tester.tap(find.byKey(const ValueKey('resizeButton'))); - await tester.pumpAndSettle(); - expect(buttonTapResizeCompleter.future, completes); - }); - - testWidgets('set custom userAgent', (WidgetTester tester) async { - final Completer controllerCompleter1 = - Completer(); - final GlobalKey _globalKey = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent1', - onWebViewCreated: (WebViewController controller) { - controllerCompleter1.complete(controller); - }, - ), - ), - ); - final WebViewController controller1 = await controllerCompleter1.future; - final String customUserAgent1 = await _getUserAgent(controller1); - expect(customUserAgent1, 'Custom_User_Agent1'); - // rebuild the WebView with a different user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent2', - ), - ), - ); - - final String customUserAgent2 = await _getUserAgent(controller1); - expect(customUserAgent2, 'Custom_User_Agent2'); - }); - - testWidgets('use default platform userAgent after webView is rebuilt', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final GlobalKey _globalKey = GlobalKey(); - // Build the webView with no user agent to get the default platform user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: primaryUrl, - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String defaultPlatformUserAgent = await _getUserAgent(controller); - // rebuild the WebView with a custom user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - userAgent: 'Custom_User_Agent', - ), - ), - ); - final String customUserAgent = await _getUserAgent(controller); - expect(customUserAgent, 'Custom_User_Agent'); - // rebuilds the WebView with no user agent. - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: _globalKey, - initialUrl: 'about:blank', - javascriptMode: JavascriptMode.unrestricted, - ), - ), - ); - - final String customUserAgent2 = await _getUserAgent(controller); - expect(customUserAgent2, defaultPlatformUserAgent); - }); group('Video playback policy', () { late String videoTestBase64; @@ -341,126 +80,8 @@ Future main() async { videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - controllerCompleter = Completer(); - pageLoaded = Completer(); - - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - controller = await controllerCompleter.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); - }); - - testWidgets('Changes to initialMediaPlaybackPolicy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - await controller.reload(); - - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - }); - - testWidgets('Video plays inline when allowsInlineMediaPlayback is true', + testWidgets( + 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); @@ -492,7 +113,7 @@ Future main() async { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: true, + allowsInlineMediaPlayback: false, ), ), ); @@ -507,664 +128,36 @@ Future main() async { final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(false)); + expect(fullScreen, _webviewBool(true)); }); + }); - testWidgets( - 'Video plays full screen when allowsInlineMediaPlayback is false', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageLoaded = Completer(); - final Completer videoPlaying = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, + testWidgets('launches with gestureNavigationEnabled', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 400, + height: 300, child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + key: GlobalKey(), + initialUrl: primaryUrl, + gestureNavigationEnabled: true, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, - javascriptMode: JavascriptMode.unrestricted, - javascriptChannels: { - JavascriptChannel( - name: 'VideoTestTime', - onMessageReceived: (JavascriptMessage message) { - final double currentTime = double.parse(message.message); - // Let it play for at least 1 second to make sure the related video's properties are set. - if (currentTime > 1 && !videoPlaying.isCompleted) { - videoPlaying.complete(null); - } - }, - ), - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - allowsInlineMediaPlayback: false, ), ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - // Pump once to trigger the video play. - await tester.pump(); - - // Makes sure we get the correct event that indicates the video is actually playing. - await videoPlaying.future; - - final String fullScreen = - await controller.runJavascriptReturningResult('isFullScreen();'); - expect(fullScreen, _webviewBool(true)); - }); - }); - - group('Audio playback policy', () { - late String audioTestBase64; - setUpAll(() async { - final ByteData audioData = - await rootBundle.load('assets/sample_audio.ogg'); - final String base64AudioData = - base64Encode(Uint8List.view(audioData.buffer)); - final String audioTest = ''' - - Audio auto play - - - - - - - '''; - audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); - }); - - testWidgets('Auto media playback', (WidgetTester tester) async { - Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); - Completer pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - controllerCompleter = Completer(); - pageStarted = Completer(); - pageLoaded = Completer(); - - // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(true)); - }); - - testWidgets('Changes to initialMediaPlaybackPolocy are ignored', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageStarted = Completer(); - Completer pageLoaded = Completer(); - - final GlobalKey key = GlobalKey(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - String isPaused = - await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - - pageStarted = Completer(); - pageLoaded = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: key, - initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - initialMediaPlaybackPolicy: - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - ), - ), - ); - - await controller.reload(); - - await pageStarted.future; - await pageLoaded.future; - - isPaused = await controller.runJavascriptReturningResult('isPaused();'); - expect(isPaused, _webviewBool(false)); - }); - }); - - testWidgets('getTitle', (WidgetTester tester) async { - const String getTitleTest = ''' - - Some title - - - - - '''; - final String getTitleTestBase64 = - base64Encode(const Utf8Encoder().convert(getTitleTest)); - final Completer pageStarted = Completer(); - final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageStarted: (String url) { - pageStarted.complete(null); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await pageStarted.future; - await pageLoaded.future; - - // On at least iOS, it does not appear to be guaranteed that the native - // code has the title when the page load completes. Execute some JavaScript - // before checking the title to ensure that the page has been fully parsed - // and processed. - await controller.runJavascript('1;'); - - final String? title = await controller.getTitle(); - expect(title, 'Some title'); - }); - - group('Programmatic Scroll', () { - testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { - const String scrollTestPage = ''' - - - - - - -
- - - '''; - - final String scrollTestPageBase64 = - base64Encode(const Utf8Encoder().convert(scrollTestPage)); - - final Completer pageLoaded = Completer(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - initialUrl: - 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - - final WebViewController controller = await controllerCompleter.future; - await pageLoaded.future; - - await tester.pumpAndSettle(const Duration(seconds: 3)); - - int scrollPosX = await controller.getScrollX(); - int scrollPosY = await controller.getScrollY(); - - // Check scrollTo() - const int X_SCROLL = 123; - const int Y_SCROLL = 321; - // Get the initial position; this ensures that scrollTo is actually - // changing something, but also gives the native view's scroll position - // time to settle. - expect(scrollPosX, isNot(X_SCROLL)); - expect(scrollPosX, isNot(Y_SCROLL)); - - await controller.scrollTo(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL); - expect(scrollPosY, Y_SCROLL); - - // Check scrollBy() (on top of scrollTo()) - await controller.scrollBy(X_SCROLL, Y_SCROLL); - scrollPosX = await controller.getScrollX(); - scrollPosY = await controller.getScrollY(); - expect(scrollPosX, X_SCROLL * 2); - expect(scrollPosY, Y_SCROLL * 2); - }); - }); - - group('NavigationDelegate', () { - const String blankPage = ''; - final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + - base64Encode(const Utf8Encoder().convert(blankPage)); - - testWidgets('can allow requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); - - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); - - await pageLoads.stream.first; // Wait for the next page load. - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, secondaryUrl); - }); - - testWidgets('onWebResourceError', (WidgetTester tester) async { - final Completer errorCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: 'https://www.notawebsite..com', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - ), - ), - ); - - final WebResourceError error = await errorCompleter.future; - expect(error, isNotNull); - - if (Platform.isIOS) { - expect(error.domain, isNotNull); - expect(error.failingUrl, isNull); - } else if (Platform.isAndroid) { - expect(error.errorType, isNotNull); - expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), - isTrue); - } - }); - - testWidgets('onWebResourceError is not called with valid url', - (WidgetTester tester) async { - final Completer errorCompleter = - Completer(); - final Completer pageFinishCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); - - expect(errorCompleter.future, doesNotComplete); - await pageFinishCompleter.future; - }); - - testWidgets( - 'onWebResourceError only called for main frame', - (WidgetTester tester) async { - const String iframeTest = ''' - - - - WebResourceError test - - - - - - '''; - final String iframeTestBase64 = - base64Encode(const Utf8Encoder().convert(iframeTest)); - - final Completer errorCompleter = - Completer(); - final Completer pageFinishCompleter = Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: - 'data:text/html;charset=utf-8;base64,$iframeTestBase64', - onWebResourceError: (WebResourceError error) { - errorCompleter.complete(error); - }, - onPageFinished: (_) => pageFinishCompleter.complete(), - ), - ), - ); - - expect(errorCompleter.future, doesNotComplete); - await pageFinishCompleter.future; - }, - ); - - testWidgets('can block requests', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) { - return (request.url.contains('youtube.com')) - ? NavigationDecision.prevent - : NavigationDecision.navigate; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); - - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller - .runJavascript('location.href = "https://www.youtube.com/"'); - - // There should never be any second page load, since our new URL is - // blocked. Still wait for a potential page change for some time in order - // to give the test a chance to fail. - await pageLoads.stream.first - .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, isNot(contains('youtube.com'))); - }); - - testWidgets('supports asynchronous decisions', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final StreamController pageLoads = - StreamController.broadcast(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - initialUrl: blankPageEncoded, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - navigationDelegate: (NavigationRequest request) async { - NavigationDecision decision = NavigationDecision.prevent; - decision = await Future.delayed( - const Duration(milliseconds: 10), - () => NavigationDecision.navigate); - return decision; - }, - onPageFinished: (String url) => pageLoads.add(url), - ), - ), - ); - - await pageLoads.stream.first; // Wait for initial page load. - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('location.href = "$secondaryUrl"'); - - await pageLoads.stream.first; // Wait for second page to load. - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, secondaryUrl); - }); - }); - - testWidgets('launches with gestureNavigationEnabled on iOS', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: SizedBox( - width: 400, - height: 300, - child: WebView( - key: GlobalKey(), - initialUrl: primaryUrl, - gestureNavigationEnabled: true, - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - ), - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - - testWidgets('target _blank opens in same window', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - final Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(null); - }, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - await controller.runJavascript('window.open("$primaryUrl", "_blank")'); - await pageLoaded.future; - final String? currentUrl = await controller.currentUrl(); - expect(currentUrl, primaryUrl); - }); - - testWidgets( - 'can open new window and go back', - (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WebView( - key: GlobalKey(), - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller); - }, - javascriptMode: JavascriptMode.unrestricted, - onPageFinished: (String url) { - pageLoaded.complete(); - }, - initialUrl: primaryUrl, - ), - ), - ); - final WebViewController controller = await controllerCompleter.future; - expect(controller.currentUrl(), completion(primaryUrl)); - await pageLoaded.future; - pageLoaded = Completer(); - - await controller.runJavascript('window.open("$secondaryUrl")'); - await pageLoaded.future; - pageLoaded = Completer(); - expect(controller.currentUrl(), completion(secondaryUrl)); - - expect(controller.canGoBack(), completion(true)); - await controller.goBack(); - await pageLoaded.future; - expect(controller.currentUrl(), completion(primaryUrl)); - }, - // Flaky; see https://github.com/flutter/flutter/issues/90976 - skip: true, - ); -} + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); +} // JavaScript booleans evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. @@ -1175,11 +168,6 @@ String _webviewBool(bool value) { return value ? 'true' : 'false'; } -/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. -Future _getUserAgent(WebViewController controller) async { - return await controller.runJavascriptReturningResult('navigator.userAgent;'); -} - class ResizableWebView extends StatefulWidget { const ResizableWebView( {required this.onResize, required this.onPageFinished}); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index d4e0ec4aba0c..be26d2c15f7f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -12,11 +12,7 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'navigation_decision.dart'; -import 'navigation_request.dart'; -import 'web_view.dart'; +import 'package:webview_flutter/webview_flutter.dart'; void main() { runApp(const MaterialApp(home: _WebViewExample())); @@ -180,9 +176,10 @@ enum _MenuOptions { } class _SampleMenu extends StatelessWidget { - const _SampleMenu(this.controller); + _SampleMenu(this.controller); final Future controller; + final CookieManager cookieManager = CookieManager(); @override Widget build(BuildContext context) { @@ -393,7 +390,7 @@ class _SampleMenu extends StatelessWidget { Future _onSetCookie( WebViewController controller, BuildContext context) async { - await WebViewCookieManager.instance.setCookie( + await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'), ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_decision.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_decision.dart deleted file mode 100644 index d8178acd8096..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_decision.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// A decision on how to handle a navigation request. -enum NavigationDecision { - /// Prevent the navigation from taking place. - prevent, - - /// Allow the navigation to take place. - navigate, -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart deleted file mode 100644 index 2f6d7c9f8cdd..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/navigation_request.dart +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// Information about a navigation action that is about to be executed. -class NavigationRequest { - NavigationRequest._({required this.url, required this.isForMainFrame}); - - /// The URL that will be loaded if the navigation is executed. - final String url; - - /// Whether the navigation request is to be loaded as the main frame. - final bool isForMainFrame; - - @override - String toString() { - return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; - } -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart deleted file mode 100644 index 4d479f943d62..000000000000 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ /dev/null @@ -1,694 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; - -import 'navigation_decision.dart'; -import 'navigation_request.dart'; - -/// Optional callback invoked when a web view is first created. [controller] is -/// the [WebViewController] for the created web view. -typedef WebViewCreatedCallback = void Function(WebViewController controller); - -/// Decides how to handle a specific navigation request. -/// -/// The returned [NavigationDecision] determines how the navigation described by -/// `navigation` should be handled. -/// -/// See also: [WebView.navigationDelegate]. -typedef NavigationDelegate = FutureOr Function( - NavigationRequest navigation); - -/// Signature for when a [WebView] has started loading a page. -typedef PageStartedCallback = void Function(String url); - -/// Signature for when a [WebView] has finished loading a page. -typedef PageFinishedCallback = void Function(String url); - -/// Signature for when a [WebView] is loading a page. -typedef PageLoadingCallback = void Function(int progress); - -/// Signature for when a [WebView] has failed to load a resource. -typedef WebResourceErrorCallback = void Function(WebResourceError error); - -/// A web view widget for showing html content. -/// -/// There is a known issue that on iOS 13.4 and 13.5, other flutter widgets covering -/// the `WebView` is not able to block the `WebView` from receiving touch events. -/// See https://github.com/flutter/flutter/issues/53490. -class WebView extends StatefulWidget { - /// Creates a new web view. - /// - /// The web view can be controlled using a `WebViewController` that is passed to the - /// `onWebViewCreated` callback once the web view is created. - /// - /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. - const WebView({ - Key? key, - this.onWebViewCreated, - this.initialUrl, - this.initialCookies = const [], - this.javascriptMode = JavascriptMode.disabled, - this.javascriptChannels, - this.navigationDelegate, - this.gestureRecognizers, - this.onPageStarted, - this.onPageFinished, - this.onProgress, - this.onWebResourceError, - this.debuggingEnabled = false, - this.gestureNavigationEnabled = false, - this.userAgent, - this.zoomEnabled = true, - this.initialMediaPlaybackPolicy = - AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, - this.allowsInlineMediaPlayback = false, - this.backgroundColor, - }) : assert(javascriptMode != null), - assert(initialMediaPlaybackPolicy != null), - assert(allowsInlineMediaPlayback != null), - super(key: key); - - /// The WebView platform that's used by this WebView. - static final WebViewPlatform platform = CupertinoWebView(); - - /// If not null invoked once the web view is created. - final WebViewCreatedCallback? onWebViewCreated; - - /// Which gestures should be consumed by the web view. - /// - /// It is possible for other gesture recognizers to be competing with the web view on pointer - /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle - /// vertical drags. The web view will claim gestures that are recognized by any of the - /// recognizers on this list. - /// - /// When this set is empty or null, the web view will only handle pointer events for gestures that - /// were not claimed by any other gesture recognizer. - final Set>? gestureRecognizers; - - /// The initial URL to load. - final String? initialUrl; - - /// The initial cookies to set. - final List initialCookies; - - /// Whether JavaScript execution is enabled. - final JavascriptMode javascriptMode; - - /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. - /// - /// For each [JavascriptChannel] in the set, a channel object is made available for the - /// JavaScript code in a window property named [JavascriptChannel.name]. - /// The JavaScript code can then call `postMessage` on that object to send a message that will be - /// passed to [JavascriptChannel.onMessageReceived]. - /// - /// For example for the following JavascriptChannel: - /// - /// ```dart - /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); - /// ``` - /// - /// JavaScript code can call: - /// - /// ```javascript - /// Print.postMessage('Hello'); - /// ``` - /// - /// To asynchronously invoke the message handler which will print the message to standard output. - /// - /// Adding a new JavaScript channel only takes affect after the next page is loaded. - /// - /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple - /// channels in the list. - /// - /// A null value is equivalent to an empty set. - final Set? javascriptChannels; - - /// A delegate function that decides how to handle navigation actions. - /// - /// When a navigation is initiated by the WebView (e.g when a user clicks a link) - /// this delegate is called and has to decide how to proceed with the navigation. - /// - /// See [NavigationDecision] for possible decisions the delegate can take. - /// - /// When null all navigation actions are allowed. - /// - /// Caveats on Android: - /// - /// * Navigation actions targeted to the main frame can be intercepted, - /// navigation actions targeted to subframes are allowed regardless of the value - /// returned by this delegate. - /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were - /// triggered by a user gesture, this disables some of Chromium's security mechanisms. - /// A navigationDelegate should only be set when loading trusted content. - /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have - /// a later version): - /// * When a navigationDelegate is set pages with frames are not properly handled by the - /// webview, and frames will be opened in the main frame. - /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. - final NavigationDelegate? navigationDelegate; - - /// Controls whether inline playback of HTML5 videos is allowed on iOS. - /// - /// This field is ignored on Android because Android allows it by default. - /// - /// By default `allowsInlineMediaPlayback` is false. - final bool allowsInlineMediaPlayback; - - /// Invoked when a page starts loading. - final PageStartedCallback? onPageStarted; - - /// Invoked when a page has finished loading. - /// - /// This is invoked only for the main frame. - /// - /// When [onPageFinished] is invoked on Android, the page being rendered may - /// not be updated yet. - /// - /// When invoked on iOS or Android, any JavaScript code that is embedded - /// directly in the HTML has been loaded and code injected with - /// [WebViewController.evaluateJavascript] can assume this. - final PageFinishedCallback? onPageFinished; - - /// Invoked when a page is loading. - final PageLoadingCallback? onProgress; - - /// Invoked when a web resource has failed to load. - /// - /// This callback is only called for the main page. - final WebResourceErrorCallback? onWebResourceError; - - /// Controls whether WebView debugging is enabled. - /// - /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). - /// - /// WebView debugging is enabled by default in dev builds on iOS. - /// - /// To debug WebViews on iOS: - /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) - /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> - /// - /// By default `debuggingEnabled` is false. - final bool debuggingEnabled; - - /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. - /// - /// This only works on iOS. - /// - /// By default `gestureNavigationEnabled` is false. - final bool gestureNavigationEnabled; - - /// The value used for the HTTP User-Agent: request header. - /// - /// When null the platform's webview default is used for the User-Agent header. - /// - /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. - /// - /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. - /// - /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom - /// user agent. - /// - /// By default `userAgent` is null. - final String? userAgent; - - /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. - /// - /// By default 'zoomEnabled' is true - final bool zoomEnabled; - - /// Which restrictions apply on automatic media playback. - /// - /// This initial value is applied to the platform's webview upon creation. Any following - /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). - /// - /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. - final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; - - /// The background color of the [WebView]. - /// - /// When `null` the platform's webview default background color is used. By - /// default [backgroundColor] is `null`. - final Color? backgroundColor; - - @override - _WebViewState createState() => _WebViewState(); -} - -class _WebViewState extends State { - final Completer _controller = - Completer(); - late final JavascriptChannelRegistry _javascriptChannelRegistry; - late final _PlatformCallbacksHandler _platformCallbacksHandler; - - @override - void initState() { - super.initState(); - _platformCallbacksHandler = _PlatformCallbacksHandler(widget); - _javascriptChannelRegistry = - JavascriptChannelRegistry(widget.javascriptChannels); - } - - @override - void didUpdateWidget(WebView oldWidget) { - super.didUpdateWidget(oldWidget); - _controller.future.then((WebViewController controller) { - controller._updateWidget(widget); - }); - } - - @override - Widget build(BuildContext context) { - return WebView.platform.build( - context: context, - onWebViewPlatformCreated: - (WebViewPlatformController? webViewPlatformController) { - final WebViewController controller = WebViewController._( - widget, - webViewPlatformController!, - _javascriptChannelRegistry, - ); - _controller.complete(controller); - - if (widget.onWebViewCreated != null) { - widget.onWebViewCreated!(controller); - } - }, - webViewPlatformCallbacksHandler: _platformCallbacksHandler, - creationParams: CreationParams( - initialUrl: widget.initialUrl, - webSettings: _webSettingsFromWidget(widget), - javascriptChannelNames: - _javascriptChannelRegistry.channels.keys.toSet(), - autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, - userAgent: widget.userAgent, - cookies: widget.initialCookies, - backgroundColor: widget.backgroundColor, - ), - javascriptChannelRegistry: _javascriptChannelRegistry, - ); - } -} - -/// Controls a [WebView]. -/// -/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] -/// callback for a [WebView] widget. -class WebViewController { - WebViewController._( - this._widget, - this._webViewPlatformController, - this._javascriptChannelRegistry, - ) : assert(_webViewPlatformController != null) { - _settings = _webSettingsFromWidget(_widget); - } - - final JavascriptChannelRegistry _javascriptChannelRegistry; - - final WebViewPlatformController _webViewPlatformController; - - late WebSettings _settings; - - WebView _widget; - - /// Loads the Flutter asset specified in the pubspec.yaml file. - /// - /// Throws an ArgumentError if [key] is not part of the specified assets - /// in the pubspec.yaml file. - Future loadFlutterAsset(String key) { - return _webViewPlatformController.loadFlutterAsset(key); - } - - /// Loads the file located on the specified [absoluteFilePath]. - /// - /// The [absoluteFilePath] parameter should contain the absolute path to the - /// file as it is stored on the device. For example: - /// `/Users/username/Documents/www/index.html`. - /// - /// Throws an ArgumentError if the [absoluteFilePath] does not exist. - Future loadFile( - String absoluteFilePath, - ) { - assert(absoluteFilePath.isNotEmpty); - return _webViewPlatformController.loadFile(absoluteFilePath); - } - - /// Loads the supplied HTML string. - /// - /// The [baseUrl] parameter is used when resolving relative URLs within the - /// HTML string. - Future loadHtmlString( - String html, { - String? baseUrl, - }) { - assert(html.isNotEmpty); - return _webViewPlatformController.loadHtmlString( - html, - baseUrl: baseUrl, - ); - } - - /// Loads the specified URL. - /// - /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will - /// be added as key value pairs of HTTP headers for the request. - /// - /// `url` must not be null. - /// - /// Throws an ArgumentError if `url` is not a valid URL string. - Future loadUrl( - String url, { - Map? headers, - }) async { - assert(url != null); - _validateUrlString(url); - return _webViewPlatformController.loadUrl(url, headers); - } - - /// Loads a page by making the specified request. - Future loadRequest(WebViewRequest request) async { - return _webViewPlatformController.loadRequest(request); - } - - /// Accessor to the current URL that the WebView is displaying. - /// - /// If [WebView.initialUrl] was never specified, returns `null`. - /// Note that this operation is asynchronous, and it is possible that the - /// current URL changes again by the time this function returns (in other - /// words, by the time this future completes, the WebView may be displaying a - /// different URL). - Future currentUrl() { - return _webViewPlatformController.currentUrl(); - } - - /// Checks whether there's a back history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has - /// changed by the time the future completed. - Future canGoBack() { - return _webViewPlatformController.canGoBack(); - } - - /// Checks whether there's a forward history item. - /// - /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has - /// changed by the time the future completed. - Future canGoForward() { - return _webViewPlatformController.canGoForward(); - } - - /// Goes back in the history of this WebView. - /// - /// If there is no back history item this is a no-op. - Future goBack() { - return _webViewPlatformController.goBack(); - } - - /// Goes forward in the history of this WebView. - /// - /// If there is no forward history item this is a no-op. - Future goForward() { - return _webViewPlatformController.goForward(); - } - - /// Reloads the current URL. - Future reload() { - return _webViewPlatformController.reload(); - } - - /// Clears all caches used by the [WebView]. - /// - /// The following caches are cleared: - /// 1. Browser HTTP Cache. - /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. - /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. - /// 3. Application cache. - /// 4. Local Storage. - /// - /// Note: Calling this method also triggers a reload. - Future clearCache() async { - await _webViewPlatformController.clearCache(); - return reload(); - } - - Future _updateWidget(WebView widget) async { - _widget = widget; - await _updateSettings(_webSettingsFromWidget(widget)); - await _updateJavascriptChannels( - _javascriptChannelRegistry.channels.values.toSet()); - } - - Future _updateSettings(WebSettings newSettings) { - final WebSettings update = - _clearUnchangedWebSettings(_settings, newSettings); - _settings = newSettings; - return _webViewPlatformController.updateSettings(update); - } - - Future _updateJavascriptChannels( - Set? newChannels) async { - final Set currentChannels = - _javascriptChannelRegistry.channels.keys.toSet(); - final Set newChannelNames = _extractChannelNames(newChannels); - final Set channelsToAdd = - newChannelNames.difference(currentChannels); - final Set channelsToRemove = - currentChannels.difference(newChannelNames); - if (channelsToRemove.isNotEmpty) { - await _webViewPlatformController - .removeJavascriptChannels(channelsToRemove); - } - if (channelsToAdd.isNotEmpty) { - await _webViewPlatformController.addJavascriptChannels(channelsToAdd); - } - _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); - } - - @visibleForTesting - // ignore: public_member_api_docs - Future evaluateJavascript(String javascriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); - } - return _webViewPlatformController.evaluateJavascript(javascriptString); - } - - /// Runs the given JavaScript in the context of the current page. - /// If you are looking for the result, use [runJavascriptReturningResult] instead. - /// The Future completes with an error if a JavaScript error occurred. - /// - /// When running JavaScript in a [WebView], it is best practice to wait for - /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript - /// embedded in the main frame HTML has been loaded. - Future runJavascript(String javaScriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); - } - return _webViewPlatformController.runJavascript(javaScriptString); - } - - /// Runs the given JavaScript in the context of the current page, and returns the result. - /// - /// Depending on the value type the return value would be one of: - /// - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100'). - /// - 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.'). - /// - /// The Future completes with an error if a JavaScript error occurred, or if the - /// type the given expression evaluates to is unsupported. Unsupported values include - /// certain non primitive types, as well as `undefined` or `null` on iOS 14+. - /// - /// When evaluating JavaScript in a [WebView], it is best practice to wait for - /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript - /// embedded in the main frame HTML has been loaded. - Future runJavascriptReturningResult(String javaScriptString) { - if (_settings.javascriptMode == JavascriptMode.disabled) { - return Future.error(FlutterError( - 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); - } - return _webViewPlatformController - .runJavascriptReturningResult(javaScriptString); - } - - /// Returns the title of the currently loaded page. - Future getTitle() { - return _webViewPlatformController.getTitle(); - } - - /// Sets the WebView's content scroll position. - /// - /// The parameters `x` and `y` specify the scroll position in WebView pixels. - Future scrollTo(int x, int y) { - return _webViewPlatformController.scrollTo(x, y); - } - - /// Move the scrolled position of this view. - /// - /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. - Future scrollBy(int x, int y) { - return _webViewPlatformController.scrollBy(x, y); - } - - /// Return the horizontal scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from left. - Future getScrollX() { - return _webViewPlatformController.getScrollX(); - } - - /// Return the vertical scroll position, in WebView pixels, of this view. - /// - /// Scroll position is measured from top. - Future getScrollY() { - return _webViewPlatformController.getScrollY(); - } - - // This method assumes that no fields in `currentValue` are null. - WebSettings _clearUnchangedWebSettings( - WebSettings currentValue, WebSettings newValue) { - assert(currentValue.javascriptMode != null); - assert(currentValue.hasNavigationDelegate != null); - assert(currentValue.hasProgressTracking != null); - assert(currentValue.debuggingEnabled != null); - assert(currentValue.userAgent != null); - assert(newValue.javascriptMode != null); - assert(newValue.hasNavigationDelegate != null); - assert(newValue.debuggingEnabled != null); - assert(newValue.userAgent != null); - - JavascriptMode? javascriptMode; - bool? hasNavigationDelegate; - bool? hasProgressTracking; - bool? debuggingEnabled; - WebSetting userAgent = const WebSetting.absent(); - if (currentValue.javascriptMode != newValue.javascriptMode) { - javascriptMode = newValue.javascriptMode; - } - if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { - hasNavigationDelegate = newValue.hasNavigationDelegate; - } - if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { - hasProgressTracking = newValue.hasProgressTracking; - } - if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { - debuggingEnabled = newValue.debuggingEnabled; - } - if (currentValue.userAgent != newValue.userAgent) { - userAgent = newValue.userAgent; - } - - return WebSettings( - javascriptMode: javascriptMode, - hasNavigationDelegate: hasNavigationDelegate, - hasProgressTracking: hasProgressTracking, - debuggingEnabled: debuggingEnabled, - userAgent: userAgent, - ); - } - - Set _extractChannelNames(Set? channels) { - final Set channelNames = channels == null - ? {} - : channels.map((JavascriptChannel channel) => channel.name).toSet(); - return channelNames; - } - -// Throws an ArgumentError if `url` is not a valid URL string. - void _validateUrlString(String url) { - try { - final Uri uri = Uri.parse(url); - if (uri.scheme.isEmpty) { - throw ArgumentError('Missing scheme in URL string: "$url"'); - } - } on FormatException catch (e) { - throw ArgumentError(e); - } - } -} - -WebSettings _webSettingsFromWidget(WebView widget) { - return WebSettings( - javascriptMode: widget.javascriptMode, - hasNavigationDelegate: widget.navigationDelegate != null, - hasProgressTracking: widget.onProgress != null, - debuggingEnabled: widget.debuggingEnabled, - gestureNavigationEnabled: widget.gestureNavigationEnabled, - allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, - userAgent: WebSetting.of(widget.userAgent), - zoomEnabled: widget.zoomEnabled, - ); -} - -class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { - _PlatformCallbacksHandler(this._webView); - - final WebView _webView; - - @override - FutureOr onNavigationRequest({ - required String url, - required bool isForMainFrame, - }) async { - if (url.startsWith('https://www.youtube.com/')) { - print('blocking navigation to $url'); - return false; - } - print('allowing navigation to $url'); - return true; - } - - @override - void onPageStarted(String url) { - if (_webView.onPageStarted != null) { - _webView.onPageStarted!(url); - } - } - - @override - void onPageFinished(String url) { - if (_webView.onPageFinished != null) { - _webView.onPageFinished!(url); - } - } - - @override - void onProgress(int progress) { - if (_webView.onProgress != null) { - _webView.onProgress!(progress); - } - } - - @override - void onWebResourceError(WebResourceError error) { - if (_webView.onWebResourceError != null) { - _webView.onWebResourceError!(error); - } - } -} - -/// App-facing cookie manager that exposes the correct platform implementation. -class WebViewCookieManager extends WebViewCookieManagerPlatform { - WebViewCookieManager._(); - - /// Returns an instance of the cookie manager for the current platform. - static WebViewCookieManagerPlatform get instance { - if (WebViewCookieManagerPlatform.instance == null) { - if (Platform.isIOS) { - WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); - } else { - throw AssertionError( - 'This platform is currently unsupported for webview_flutter_wkwebview.'); - } - } - return WebViewCookieManagerPlatform.instance!; - } -} diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index b8c2464eb051..57a93e36e81b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -10,13 +10,18 @@ dependencies: sdk: flutter path_provider: ^2.0.6 - - webview_flutter_wkwebview: + webview_flutter: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. + path: ../../webview_flutter + +dependency_overrides: + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface + webview_flutter_wkwebview: path: ../ dev_dependencies: