// Copyright 2018 The Chromium 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:math'; import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter/src/foundation/basic_types.dart'; import 'package:flutter/src/gestures/recognizer.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter/platform_interface.dart'; import 'package:webview_flutter/webview_flutter.dart'; typedef void VoidCallback(); void main() { TestWidgetsFlutterBinding.ensureInitialized(); final _FakePlatformViewsController fakePlatformViewsController = _FakePlatformViewsController(); final _FakeCookieManager _fakeCookieManager = _FakeCookieManager(); setUpAll(() { SystemChannels.platform_views.setMockMethodCallHandler( fakePlatformViewsController.fakePlatformViewsMethodHandler); SystemChannels.platform .setMockMethodCallHandler(_fakeCookieManager.onMethodCall); }); setUp(() { fakePlatformViewsController.reset(); _fakeCookieManager.reset(); }); testWidgets('Create WebView', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); }); testWidgets('Initial url', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(await controller.currentUrl(), 'https://youtube.com'); }); testWidgets('Javascript mode', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.unrestricted, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.javascriptMode, JavascriptMode.unrestricted); await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.disabled, )); expect(platformWebView.javascriptMode, JavascriptMode.disabled); }); testWidgets('Load url', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller.loadUrl('https://flutter.io'); expect(await controller.currentUrl(), 'https://flutter.io'); }); testWidgets('Invalid urls', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(() => controller.loadUrl(null), throwsA(anything)); expect(await controller.currentUrl(), isNull); expect(() => controller.loadUrl(''), throwsA(anything)); expect(await controller.currentUrl(), isNull); // Missing schema. expect(() => controller.loadUrl('flutter.io'), throwsA(anything)); expect(await controller.currentUrl(), isNull); }); testWidgets('Headers in loadUrl', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final Map headers = { 'CACHE-CONTROL': 'ABC' }; await controller.loadUrl('https://flutter.io', headers: headers); expect(await controller.currentUrl(), equals('https://flutter.io')); }); testWidgets("Can't go back before loading a page", (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final bool canGoBackNoPageLoaded = await controller.canGoBack(); expect(canGoBackNoPageLoaded, false); }); testWidgets("Clear Cache", (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(fakePlatformViewsController.lastCreatedView.hasCache, true); await controller.clearCache(); expect(fakePlatformViewsController.lastCreatedView.hasCache, false); }); testWidgets("Can't go back with no history", (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final bool canGoBackFirstPageLoaded = await controller.canGoBack(); expect(canGoBackFirstPageLoaded, false); }); testWidgets('Can go back', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller.loadUrl('https://www.google.com'); final bool canGoBackSecondPageLoaded = await controller.canGoBack(); expect(canGoBackSecondPageLoaded, true); }); testWidgets("Can't go forward before loading a page", (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final bool canGoForwardNoPageLoaded = await controller.canGoForward(); expect(canGoForwardNoPageLoaded, false); }); testWidgets("Can't go forward with no history", (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final bool canGoForwardFirstPageLoaded = await controller.canGoForward(); expect(canGoForwardFirstPageLoaded, false); }); testWidgets('Can go forward', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller.loadUrl('https://youtube.com'); await controller.goBack(); final bool canGoForwardFirstPageBacked = await controller.canGoForward(); expect(canGoForwardFirstPageBacked, true); }); testWidgets('Go back', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(await controller.currentUrl(), 'https://youtube.com'); await controller.loadUrl('https://flutter.io'); expect(await controller.currentUrl(), 'https://flutter.io'); await controller.goBack(); expect(await controller.currentUrl(), 'https://youtube.com'); }); testWidgets('Go forward', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(await controller.currentUrl(), 'https://youtube.com'); await controller.loadUrl('https://flutter.io'); expect(await controller.currentUrl(), 'https://flutter.io'); await controller.goBack(); expect(await controller.currentUrl(), 'https://youtube.com'); await controller.goForward(); expect(await controller.currentUrl(), 'https://flutter.io'); }); testWidgets('Current URL', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); // Test a WebView without an explicitly set first URL. expect(await controller.currentUrl(), isNull); await controller.loadUrl('https://youtube.com'); expect(await controller.currentUrl(), 'https://youtube.com'); await controller.loadUrl('https://flutter.io'); expect(await controller.currentUrl(), 'https://flutter.io'); await controller.goBack(); expect(await controller.currentUrl(), 'https://youtube.com'); }); testWidgets('Reload url', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.currentUrl, 'https://flutter.io'); expect(platformWebView.amountOfReloadsOnCurrentUrl, 0); await controller.reload(); expect(platformWebView.currentUrl, 'https://flutter.io'); expect(platformWebView.amountOfReloadsOnCurrentUrl, 1); await controller.loadUrl('https://youtube.com'); expect(platformWebView.amountOfReloadsOnCurrentUrl, 0); }); testWidgets('evaluate Javascript', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( await controller.evaluateJavascript("fake js string"), "fake js string", reason: 'should get the argument'); expect( () => controller.evaluateJavascript(null), throwsA(anything), ); }); testWidgets('evaluate Javascript with JavascriptMode disabled', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', javascriptMode: JavascriptMode.disabled, onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( () => controller.evaluateJavascript('fake js string'), throwsA(anything), ); expect( () => controller.evaluateJavascript(null), throwsA(anything), ); }); testWidgets('Cookies can be cleared once', (WidgetTester tester) async { await tester.pumpWidget( const WebView( initialUrl: 'https://flutter.io', ), ); final CookieManager cookieManager = CookieManager(); final bool hasCookies = await cookieManager.clearCookies(); expect(hasCookies, true); }); testWidgets('Second cookie clear does not have cookies', (WidgetTester tester) async { await tester.pumpWidget( const WebView( initialUrl: 'https://flutter.io', ), ); final CookieManager cookieManager = CookieManager(); final bool hasCookies = await cookieManager.clearCookies(); expect(hasCookies, true); final bool hasCookiesSecond = await cookieManager.clearCookies(); expect(hasCookiesSecond, false); }); testWidgets('Initial JavaScript channels', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts', 'Alarm'])); }); test('Only valid JavaScript channel names are allowed', () { final JavascriptMessageHandler noOp = (JavascriptMessage msg) {}; JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); VoidCallback createChannel(String name) { return () { JavascriptChannel(name: name, onMessageReceived: noOp); }; } expect(createChannel('1Alarm'), throwsAssertionError); expect(createChannel('foo.bar'), throwsAssertionError); expect(createChannel(''), throwsAssertionError); }); testWidgets('Unique JavaScript channel names are required', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); expect(tester.takeException(), isNot(null)); }); testWidgets('JavaScript channels update', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts', 'Alarm2', 'Alarm3'])); }); testWidgets('Remove all JavaScript channels and then add', (WidgetTester tester) async { // This covers a specific bug we had where after updating javascriptChannels to null, // updating it again with a subset of the previously registered channels fails as the // widget's cache of current channel wasn't properly updated when updating javascriptChannels to // null. await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); await tester.pumpWidget( const WebView( initialUrl: 'https://youtube.com', ), ); await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), ].toSet(), ), ); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts'])); }); testWidgets('JavaScript channel messages', (WidgetTester tester) async { final List ttsMessagesReceived = []; final List alarmMessagesReceived = []; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannels: [ JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) { ttsMessagesReceived.add(msg.message); }), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) { alarmMessagesReceived.add(msg.message); }), ].toSet(), ), ); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(ttsMessagesReceived, isEmpty); expect(alarmMessagesReceived, isEmpty); platformWebView.fakeJavascriptPostMessage('Tts', 'Hello'); platformWebView.fakeJavascriptPostMessage('Tts', 'World'); expect(ttsMessagesReceived, ['Hello', 'World']); }); group('$PageStartedCallback', () { testWidgets('onPageStarted is not null', (WidgetTester tester) async { String returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) { returnedUrl = url; }, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; platformWebView.fakeOnPageStartedCallback(); expect(platformWebView.currentUrl, returnedUrl); }); testWidgets('onPageStarted is null', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', onPageStarted: null, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; // The platform side will always invoke a call for onPageStarted. This is // to test that it does not crash on a null callback. platformWebView.fakeOnPageStartedCallback(); }); testWidgets('onPageStarted changed', (WidgetTester tester) async { String returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) {}, )); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) { returnedUrl = url; }, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; platformWebView.fakeOnPageStartedCallback(); expect(platformWebView.currentUrl, returnedUrl); }); }); group('$PageFinishedCallback', () { testWidgets('onPageFinished is not null', (WidgetTester tester) async { String returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) { returnedUrl = url; }, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; platformWebView.fakeOnPageFinishedCallback(); expect(platformWebView.currentUrl, returnedUrl); }); testWidgets('onPageFinished is null', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', onPageFinished: null, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; // The platform side will always invoke a call for onPageFinished. This is // to test that it does not crash on a null callback. platformWebView.fakeOnPageFinishedCallback(); }); testWidgets('onPageFinished changed', (WidgetTester tester) async { String returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) {}, )); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) { returnedUrl = url; }, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; platformWebView.fakeOnPageFinishedCallback(); expect(platformWebView.currentUrl, returnedUrl); }); }); group('navigationDelegate', () { testWidgets('hasNavigationDelegate', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.hasNavigationDelegate, false); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', navigationDelegate: (NavigationRequest r) => null, )); expect(platformWebView.hasNavigationDelegate, true); }); testWidgets('Block navigation', (WidgetTester tester) async { final List navigationRequests = []; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', navigationDelegate: (NavigationRequest request) { navigationRequests.add(request); // Only allow navigating to https://flutter.dev return request.url == 'https://flutter.dev' ? NavigationDecision.navigate : NavigationDecision.prevent; })); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.hasNavigationDelegate, true); platformWebView.fakeNavigate('https://www.google.com'); // The navigation delegate only allows navigation to https://flutter.dev // so we should still be in https://youtube.com. expect(platformWebView.currentUrl, 'https://youtube.com'); expect(navigationRequests.length, 1); expect(navigationRequests[0].url, 'https://www.google.com'); expect(navigationRequests[0].isForMainFrame, true); platformWebView.fakeNavigate('https://flutter.dev'); await tester.pump(); expect(platformWebView.currentUrl, 'https://flutter.dev'); }); }); group('debuggingEnabled', () { testWidgets('enable debugging', (WidgetTester tester) async { await tester.pumpWidget(const WebView( debuggingEnabled: true, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.debuggingEnabled, true); }); testWidgets('defaults to false', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.debuggingEnabled, false); }); testWidgets('can be changed', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(WebView(key: key)); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; await tester.pumpWidget(WebView( key: key, debuggingEnabled: true, )); expect(platformWebView.debuggingEnabled, true); await tester.pumpWidget(WebView( key: key, debuggingEnabled: false, )); expect(platformWebView.debuggingEnabled, false); }); }); group('Custom platform implementation', () { setUpAll(() { WebView.platform = MyWebViewPlatform(); }); tearDownAll(() { WebView.platform = null; }); testWidgets('creation', (WidgetTester tester) async { await tester.pumpWidget( const WebView( initialUrl: 'https://youtube.com', gestureNavigationEnabled: true, ), ); final MyWebViewPlatform builder = WebView.platform; final MyWebViewPlatformController platform = builder.lastPlatformBuilt; expect( platform.creationParams, MatchesCreationParams(CreationParams( initialUrl: 'https://youtube.com', webSettings: WebSettings( javascriptMode: JavascriptMode.disabled, hasNavigationDelegate: false, debuggingEnabled: false, userAgent: WebSetting.of(null), gestureNavigationEnabled: true, ), // TODO(iskakaushik): Remove this when collection literals makes it to stable. // ignore: prefer_collection_literals javascriptChannelNames: Set(), ))); }); testWidgets('loadUrl', (WidgetTester tester) async { WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); final MyWebViewPlatform builder = WebView.platform; final MyWebViewPlatformController platform = builder.lastPlatformBuilt; final Map headers = { 'header': 'value', }; await controller.loadUrl('https://google.com', headers: headers); expect(platform.lastUrlLoaded, 'https://google.com'); expect(platform.lastRequestHeaders, headers); }); }); testWidgets('Set UserAgent', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.unrestricted, )); final FakePlatformWebView platformWebView = fakePlatformViewsController.lastCreatedView; expect(platformWebView.userAgent, isNull); await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.unrestricted, userAgent: 'UA', )); expect(platformWebView.userAgent, 'UA'); }); } class FakePlatformWebView { FakePlatformWebView(int id, Map params) { if (params.containsKey('initialUrl')) { final String initialUrl = params['initialUrl']; if (initialUrl != null) { history.add(initialUrl); currentPosition++; } } if (params.containsKey('javascriptChannelNames')) { javascriptChannelNames = List.from(params['javascriptChannelNames']); } javascriptMode = JavascriptMode.values[params['settings']['jsMode']]; hasNavigationDelegate = params['settings']['hasNavigationDelegate'] ?? false; debuggingEnabled = params['settings']['debuggingEnabled']; userAgent = params['settings']['userAgent']; channel = MethodChannel( 'plugins.flutter.io/webview_$id', const StandardMethodCodec()); channel.setMockMethodCallHandler(onMethodCall); } MethodChannel channel; List history = []; int currentPosition = -1; int amountOfReloadsOnCurrentUrl = 0; bool hasCache = true; String get currentUrl => history.isEmpty ? null : history[currentPosition]; JavascriptMode javascriptMode; List javascriptChannelNames; bool hasNavigationDelegate; bool debuggingEnabled; String userAgent; Future onMethodCall(MethodCall call) { switch (call.method) { case 'loadUrl': final Map request = call.arguments; _loadUrl(request['url']); return Future.sync(() {}); case 'updateSettings': if (call.arguments['jsMode'] != null) { javascriptMode = JavascriptMode.values[call.arguments['jsMode']]; } if (call.arguments['hasNavigationDelegate'] != null) { hasNavigationDelegate = call.arguments['hasNavigationDelegate']; } if (call.arguments['debuggingEnabled'] != null) { debuggingEnabled = call.arguments['debuggingEnabled']; } userAgent = call.arguments['userAgent']; break; case 'canGoBack': return Future.sync(() => currentPosition > 0); break; case 'canGoForward': return Future.sync(() => currentPosition < history.length - 1); break; case 'goBack': currentPosition = max(-1, currentPosition - 1); return Future.sync(() {}); break; case 'goForward': currentPosition = min(history.length - 1, currentPosition + 1); return Future.sync(() {}); case 'reload': amountOfReloadsOnCurrentUrl++; return Future.sync(() {}); break; case 'currentUrl': return Future.value(currentUrl); break; case 'evaluateJavascript': return Future.value(call.arguments); break; case 'addJavascriptChannels': final List channelNames = List.from(call.arguments); javascriptChannelNames.addAll(channelNames); break; case 'removeJavascriptChannels': final List channelNames = List.from(call.arguments); javascriptChannelNames .removeWhere((String channel) => channelNames.contains(channel)); break; case 'clearCache': hasCache = false; return Future.sync(() {}); } return Future.sync(() {}); } void fakeJavascriptPostMessage(String jsChannel, String message) { final StandardMethodCodec codec = const StandardMethodCodec(); final Map arguments = { 'channel': jsChannel, 'message': message }; final ByteData data = codec .encodeMethodCall(MethodCall('javascriptChannelMessage', arguments)); ServicesBinding.instance.defaultBinaryMessenger .handlePlatformMessage(channel.name, data, (ByteData data) {}); } // Fakes a main frame navigation that was initiated by the webview, e.g when // the user clicks a link in the currently loaded page. void fakeNavigate(String url) { if (!hasNavigationDelegate) { print('no navigation delegate'); _loadUrl(url); return; } final StandardMethodCodec codec = const StandardMethodCodec(); final Map arguments = { 'url': url, 'isForMainFrame': true }; final ByteData data = codec.encodeMethodCall(MethodCall('navigationRequest', arguments)); ServicesBinding.instance.defaultBinaryMessenger .handlePlatformMessage(channel.name, data, (ByteData data) { final bool allow = codec.decodeEnvelope(data); if (allow) { _loadUrl(url); } }); } void fakeOnPageStartedCallback() { final StandardMethodCodec codec = const StandardMethodCodec(); final ByteData data = codec.encodeMethodCall(MethodCall( 'onPageStarted', {'url': currentUrl}, )); ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( channel.name, data, (ByteData data) {}, ); } void fakeOnPageFinishedCallback() { final StandardMethodCodec codec = const StandardMethodCodec(); final ByteData data = codec.encodeMethodCall(MethodCall( 'onPageFinished', {'url': currentUrl}, )); ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( channel.name, data, (ByteData data) {}, ); } void _loadUrl(String url) { history = history.sublist(0, currentPosition + 1); history.add(url); currentPosition++; amountOfReloadsOnCurrentUrl = 0; } } class _FakePlatformViewsController { FakePlatformWebView lastCreatedView; Future fakePlatformViewsMethodHandler(MethodCall call) { switch (call.method) { case 'create': final Map args = call.arguments; final Map params = _decodeParams(args['params']); lastCreatedView = FakePlatformWebView( args['id'], params, ); return Future.sync(() => 1); default: return Future.sync(() {}); } } void reset() { lastCreatedView = null; } } Map _decodeParams(Uint8List paramsMessage) { final ByteBuffer buffer = paramsMessage.buffer; final ByteData messageBytes = buffer.asByteData( paramsMessage.offsetInBytes, paramsMessage.lengthInBytes, ); return const StandardMessageCodec().decodeMessage(messageBytes); } class _FakeCookieManager { _FakeCookieManager() { final MethodChannel channel = const MethodChannel( 'plugins.flutter.io/cookie_manager', StandardMethodCodec(), ); channel.setMockMethodCallHandler(onMethodCall); } bool hasCookies = true; Future onMethodCall(MethodCall call) { switch (call.method) { case 'clearCookies': bool hadCookies = false; if (hasCookies) { hadCookies = true; hasCookies = false; } return Future.sync(() { return hadCookies; }); break; } return Future.sync(() => null); } void reset() { hasCookies = true; } } class MyWebViewPlatform implements WebViewPlatform { MyWebViewPlatformController lastPlatformBuilt; @override Widget build({ BuildContext context, CreationParams creationParams, @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, @required WebViewPlatformCreatedCallback onWebViewPlatformCreated, Set> gestureRecognizers, }) { assert(onWebViewPlatformCreated != null); lastPlatformBuilt = MyWebViewPlatformController( creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); onWebViewPlatformCreated(lastPlatformBuilt); return Container(); } @override Future clearCookies() { return Future.sync(() => null); } } class MyWebViewPlatformController extends WebViewPlatformController { MyWebViewPlatformController(this.creationParams, this.gestureRecognizers, WebViewPlatformCallbacksHandler platformHandler) : super(platformHandler); CreationParams creationParams; Set> gestureRecognizers; String lastUrlLoaded; Map lastRequestHeaders; @override Future loadUrl(String url, Map headers) { equals(1, 1); lastUrlLoaded = url; lastRequestHeaders = headers; return null; } } class MatchesWebSettings extends Matcher { MatchesWebSettings(this._webSettings); final WebSettings _webSettings; @override Description describe(Description description) => description.add('$_webSettings'); @override bool matches( covariant WebSettings webSettings, Map matchState) { return _webSettings.javascriptMode == webSettings.javascriptMode && _webSettings.hasNavigationDelegate == webSettings.hasNavigationDelegate && _webSettings.debuggingEnabled == webSettings.debuggingEnabled && _webSettings.gestureNavigationEnabled == webSettings.gestureNavigationEnabled && _webSettings.userAgent == webSettings.userAgent; } } class MatchesCreationParams extends Matcher { MatchesCreationParams(this._creationParams); final CreationParams _creationParams; @override Description describe(Description description) => description.add('$_creationParams'); @override bool matches(covariant CreationParams creationParams, Map matchState) { return _creationParams.initialUrl == creationParams.initialUrl && MatchesWebSettings(_creationParams.webSettings) .matches(creationParams.webSettings, matchState) && orderedEquals(_creationParams.javascriptChannelNames) .matches(creationParams.javascriptChannelNames, matchState); } }