[webview_flutter]Allow specifying a navigation delegate(Android and Dart). (#1236)
This allows the app to prevent specific navigations(e.g prevent
navigating to specific URLs).
flutter/flutter#25329
iOS implementation in #1323
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 889a6a1..7a3256a 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.4
+
+* Support specifying navigation delegates that can prevent navigations from being executed.
+
## 0.3.3+2
* Exclude LongPress handler from semantics tree since it does nothing.
diff --git a/packages/webview_flutter/android/build.gradle b/packages/webview_flutter/android/build.gradle
index 098c2f4..45ab74d 100644
--- a/packages/webview_flutter/android/build.gradle
+++ b/packages/webview_flutter/android/build.gradle
@@ -44,4 +44,8 @@
lintOptions {
disable 'InvalidPackage'
}
+
+ dependencies {
+ implementation 'androidx.webkit:webkit:1.0.0'
+ }
}
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index 44fe31f..070ba74 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -4,7 +4,9 @@
package io.flutter.plugins.webviewflutter;
+import android.annotation.TargetApi;
import android.content.Context;
+import android.os.Build;
import android.view.View;
import android.webkit.WebStorage;
import android.webkit.WebView;
@@ -21,6 +23,7 @@
private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
private final WebView webView;
private final MethodChannel methodChannel;
+ private final FlutterWebViewClient flutterWebViewClient;
@SuppressWarnings("unchecked")
FlutterWebView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) {
@@ -31,12 +34,15 @@
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
methodChannel.setMethodCallHandler(this);
+ flutterWebViewClient = new FlutterWebViewClient(methodChannel);
applySettings((Map<String, Object>) params.get("settings"));
if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
registerJavaScriptChannelNames((List<String>) params.get(JS_CHANNEL_NAMES_FIELD));
}
+ webView.setWebViewClient(flutterWebViewClient);
+
if (params.containsKey("initialUrl")) {
String url = (String) params.get("initialUrl");
webView.loadUrl(url);
@@ -135,6 +141,7 @@
result.success(null);
}
+ @TargetApi(Build.VERSION_CODES.KITKAT)
private void evaluateJavaScript(MethodCall methodCall, final Result result) {
String jsString = (String) methodCall.arguments;
if (jsString == null) {
@@ -178,6 +185,9 @@
case "jsMode":
updateJsMode((Integer) settings.get(key));
break;
+ case "hasNavigationDelegate":
+ flutterWebViewClient.setHasNavigationDelegate((boolean) settings.get(key));
+ break;
default:
throw new IllegalArgumentException("Unknown WebView setting: " + key);
}
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
new file mode 100644
index 0000000..fe4482c
--- /dev/null
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
@@ -0,0 +1,124 @@
+// Copyright 2019 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.
+
+package io.flutter.plugins.webviewflutter;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.Log;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebView;
+import androidx.webkit.WebViewClientCompat;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+import java.util.Map;
+
+// We need to use WebViewClientCompat to get
+// shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
+// invoked by the webview on older Android devices, without it pages that use iframes will
+// be broken when a navigationDelegate is set on Android version earlier than N.
+class FlutterWebViewClient extends WebViewClientCompat {
+ private static final String TAG = "FlutterWebViewClient";
+ private final MethodChannel methodChannel;
+ private boolean hasNavigationDelegate;
+
+ FlutterWebViewClient(MethodChannel methodChannel) {
+ this.methodChannel = methodChannel;
+ }
+
+ void setHasNavigationDelegate(boolean hasNavigationDelegate) {
+ this.hasNavigationDelegate = hasNavigationDelegate;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
+ if (!hasNavigationDelegate) {
+ return false;
+ }
+ notifyOnNavigationRequest(
+ request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame());
+ // We must make a synchronous decision here whether to allow the navigation or not,
+ // if the Dart code has set a navigation delegate we want that delegate to decide whether
+ // to navigate or not, and as we cannot get a response from the Dart delegate synchronously we
+ // return true here to block the navigation, if the Dart delegate decides to allow the
+ // navigation the plugin will later make an addition loadUrl call for this url.
+ //
+ // Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop
+ // navigations that target the main frame, if the request is not for the main frame
+ // we just return false to allow the navigation.
+ //
+ // For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209
+ return request.isForMainFrame();
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (!hasNavigationDelegate) {
+ return false;
+ }
+ // This version of shouldOverrideUrlLoading is only invoked by the webview on devices with
+ // webview versions earlier than 67(it is also invoked when hasNavigationDelegate is false).
+ // On these devices we cannot tell whether the navigation is targeted to the main frame or not.
+ // We proceed assuming that the navigation is targeted to the main frame. If the page had any
+ // frames they will be loaded in the main frame instead.
+ Log.w(
+ TAG,
+ "Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work");
+ notifyOnNavigationRequest(url, null, view, true);
+ return true;
+ }
+
+ private void notifyOnNavigationRequest(
+ String url, Map<String, String> headers, WebView webview, boolean isMainFrame) {
+ HashMap<String, Object> args = new HashMap<>();
+ args.put("url", url);
+ args.put("isForMainFrame", isMainFrame);
+ if (isMainFrame) {
+ methodChannel.invokeMethod(
+ "navigationRequest", args, new OnNavigationRequestResult(url, headers, webview));
+ } else {
+ methodChannel.invokeMethod("navigationRequest", args);
+ }
+ }
+
+ private static class OnNavigationRequestResult implements MethodChannel.Result {
+ private final String url;
+ private final Map<String, String> headers;
+ private final WebView webView;
+
+ private OnNavigationRequestResult(String url, Map<String, String> headers, WebView webView) {
+ this.url = url;
+ this.headers = headers;
+ this.webView = webView;
+ }
+
+ @Override
+ public void success(Object shouldLoad) {
+ Boolean typedShouldLoad = (Boolean) shouldLoad;
+ if (typedShouldLoad) {
+ loadUrl();
+ }
+ }
+
+ @Override
+ public void error(String errorCode, String s1, Object o) {
+ throw new IllegalStateException("navigationRequest calls must succeed");
+ }
+
+ @Override
+ public void notImplemented() {
+ throw new IllegalStateException(
+ "navigationRequest must be implemented by the webview method channel");
+ }
+
+ private void loadUrl() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ webView.loadUrl(url, headers);
+ } else {
+ webView.loadUrl(url);
+ }
+ }
+ }
+}
diff --git a/packages/webview_flutter/example/android/gradle.properties b/packages/webview_flutter/example/android/gradle.properties
index 8bd86f6..ad8917e 100644
--- a/packages/webview_flutter/example/android/gradle.properties
+++ b/packages/webview_flutter/example/android/gradle.properties
@@ -1 +1,2 @@
org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
\ No newline at end of file
diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart
index f688b60..7d6ce10 100644
--- a/packages/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/example/lib/main.dart
@@ -3,11 +3,27 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(MaterialApp(home: WebViewExample()));
+const String kNavigationExamplePage = '''
+<!DOCTYPE html><html>
+<head><title>Navigation Delegate Example</title></head>
+<body>
+<p>
+The navigation delegate is set to block navigation to the youtube website.
+</p>
+<ul>
+<ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul>
+<ul><a href="https://www.google.com/">https://www.google.com/</a></ul>
+</ul>
+</body>
+</html>
+''';
+
class WebViewExample extends StatelessWidget {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@@ -37,6 +53,14 @@
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
+ navigationDelegate: (NavigationRequest request) {
+ if (request.url.startsWith('https://www.youtube.com/')) {
+ print('blocking navigation to $request}');
+ return NavigationDecision.prevent;
+ }
+ print('allowing navigation to $request');
+ return NavigationDecision.navigate;
+ },
);
}),
floatingActionButton: favoriteButton(),
@@ -76,12 +100,12 @@
enum MenuOptions {
showUserAgent,
- toast,
listCookies,
clearCookies,
addToCache,
listCache,
clearCache,
+ navigationDelegate,
}
class SampleMenu extends StatelessWidget {
@@ -102,13 +126,6 @@
case MenuOptions.showUserAgent:
_onShowUserAgent(controller.data, context);
break;
- case MenuOptions.toast:
- Scaffold.of(context).showSnackBar(
- SnackBar(
- content: Text('You selected: $value'),
- ),
- );
- break;
case MenuOptions.listCookies:
_onListCookies(controller.data, context);
break;
@@ -124,6 +141,9 @@
case MenuOptions.clearCache:
_onClearCache(controller.data, context);
break;
+ case MenuOptions.navigationDelegate:
+ _onNavigationDelegateExample(controller.data, context);
+ break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@@ -133,10 +153,6 @@
enabled: controller.hasData,
),
const PopupMenuItem<MenuOptions>(
- value: MenuOptions.toast,
- child: Text('Make a toast'),
- ),
- const PopupMenuItem<MenuOptions>(
value: MenuOptions.listCookies,
child: Text('List cookies'),
),
@@ -156,6 +172,10 @@
value: MenuOptions.clearCache,
child: Text('Clear cache'),
),
+ const PopupMenuItem<MenuOptions>(
+ value: MenuOptions.navigationDelegate,
+ child: Text('Navigation Delegate example'),
+ ),
],
);
},
@@ -218,6 +238,13 @@
));
}
+ void _onNavigationDelegateExample(
+ WebViewController controller, BuildContext context) async {
+ final String contentBase64 =
+ base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
+ controller.loadUrl('data:text/html;base64,$contentBase64');
+ }
+
Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index 4f02852..8833cb4 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -33,6 +33,39 @@
/// Callback type for handling messages sent from Javascript running in a web view.
typedef void JavascriptMessageHandler(JavascriptMessage message);
+/// Information about a navigation action that is about to be executed.
+class NavigationRequest {
+ NavigationRequest._({this.url, 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 '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)';
+ }
+}
+
+/// 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,
+}
+
+/// 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 NavigationDecision NavigationDelegate(NavigationRequest navigation);
+
final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$');
/// A named channel for receiving messaged from JavaScript code running inside a web view.
@@ -78,6 +111,7 @@
this.initialUrl,
this.javascriptMode = JavascriptMode.disabled,
this.javascriptChannels,
+ this.navigationDelegate,
this.gestureRecognizers,
}) : assert(javascriptMode != null),
super(key: key);
@@ -131,6 +165,30 @@
/// A null value is equivalent to an empty set.
final Set<JavascriptChannel> 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;
+
@override
State<StatefulWidget> createState() => _WebViewState();
}
@@ -197,6 +255,7 @@
final WebViewController controller = await _controller.future;
controller._updateSettings(settings);
controller._updateJavascriptChannels(widget.javascriptChannels);
+ controller._navigationDelegate = widget.navigationDelegate;
}
void _onPlatformViewCreated(int id) {
@@ -204,6 +263,7 @@
id,
_WebSettings.fromWidget(widget),
widget.javascriptChannels,
+ widget.navigationDelegate,
);
_controller.complete(controller);
if (widget.onWebViewCreated != null) {
@@ -261,27 +321,35 @@
class _WebSettings {
_WebSettings({
this.javascriptMode,
+ this.hasNavigationDelegate,
});
static _WebSettings fromWidget(WebView widget) {
- return _WebSettings(javascriptMode: widget.javascriptMode);
+ return _WebSettings(
+ javascriptMode: widget.javascriptMode,
+ hasNavigationDelegate: widget.navigationDelegate != null,
+ );
}
final JavascriptMode javascriptMode;
+ final bool hasNavigationDelegate;
Map<String, dynamic> toMap() {
return <String, dynamic>{
'jsMode': javascriptMode.index,
+ 'hasNavigationDelegate': hasNavigationDelegate,
};
}
Map<String, dynamic> updatesMap(_WebSettings newSettings) {
- if (javascriptMode == newSettings.javascriptMode) {
- return null;
+ final Map<String, dynamic> updates = <String, dynamic>{};
+ if (javascriptMode != newSettings.javascriptMode) {
+ updates['jsMode'] = newSettings.javascriptMode.index;
}
- return <String, dynamic>{
- 'jsMode': newSettings.javascriptMode.index,
- };
+ if (hasNavigationDelegate != newSettings.hasNavigationDelegate) {
+ updates['hasNavigationDelegate'] = newSettings.hasNavigationDelegate;
+ }
+ return updates;
}
}
@@ -291,29 +359,48 @@
/// callback for a [WebView] widget.
class WebViewController {
WebViewController._(
- int id, this._settings, Set<JavascriptChannel> javascriptChannels)
- : _channel = MethodChannel('plugins.flutter.io/webview_$id') {
+ int id,
+ this._settings,
+ Set<JavascriptChannel> javascriptChannels,
+ this._navigationDelegate,
+ ) : _channel = MethodChannel('plugins.flutter.io/webview_$id') {
_updateJavascriptChannelsFromSet(javascriptChannels);
_channel.setMethodCallHandler(_onMethodCall);
}
final MethodChannel _channel;
+ NavigationDelegate _navigationDelegate;
+
_WebSettings _settings;
// Maps a channel name to a channel.
Map<String, JavascriptChannel> _javascriptChannels =
<String, JavascriptChannel>{};
- Future<void> _onMethodCall(MethodCall call) async {
+ Future<bool> _onMethodCall(MethodCall call) async {
switch (call.method) {
case 'javascriptChannelMessage':
final String channel = call.arguments['channel'];
final String message = call.arguments['message'];
_javascriptChannels[channel]
.onMessageReceived(JavascriptMessage(message));
- break;
+ return true;
+ case 'navigationRequest':
+ final NavigationRequest request = NavigationRequest._(
+ url: call.arguments['url'],
+ isForMainFrame: call.arguments['isForMainFrame'],
+ );
+
+ // _navigationDelegate can be null if the widget was rebuilt with no
+ // navigation delegate after a navigation happened and just before we
+ // got the navigationRequest message.
+ final bool allowNavigation = _navigationDelegate == null ||
+ _navigationDelegate(request) == NavigationDecision.navigate;
+ return allowNavigation;
}
+ throw MissingPluginException(
+ '${call.method} was invoked but has no handler');
}
/// Loads the specified URL.
@@ -417,7 +504,7 @@
Future<void> _updateSettings(_WebSettings setting) async {
final Map<String, dynamic> updateMap = _settings.updatesMap(setting);
- if (updateMap == null) {
+ if (updateMap == null || updateMap.isEmpty) {
return null;
}
_settings = setting;
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index 8307e1f..84484a2 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
-version: 0.3.3+2
+version: 0.3.4
author: Flutter Team <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart
index 4fcdc7f..8635875 100644
--- a/packages/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/test/webview_flutter_test.dart
@@ -575,6 +575,57 @@
expect(ttsMessagesReceived, <String>['Hello', 'World']);
});
+
+ 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<NavigationRequest> navigationRequests = <NavigationRequest>[];
+
+ 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');
+ });
+ });
}
class FakePlatformWebView {
@@ -585,12 +636,14 @@
history.add(initialUrl);
currentPosition++;
}
- javascriptMode = JavascriptMode.values[params['settings']['jsMode']];
}
if (params.containsKey('javascriptChannelNames')) {
javascriptChannelNames =
List<String>.from(params['javascriptChannelNames']);
}
+ javascriptMode = JavascriptMode.values[params['settings']['jsMode']];
+ hasNavigationDelegate =
+ params['settings']['hasNavigationDelegate'] ?? false;
channel = MethodChannel(
'plugins.flutter.io/webview_$id', const StandardMethodCodec());
channel.setMockMethodCallHandler(onMethodCall);
@@ -607,20 +660,21 @@
JavascriptMode javascriptMode;
List<String> javascriptChannelNames;
+ bool hasNavigationDelegate;
+
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
case 'loadUrl':
final String url = call.arguments;
- history = history.sublist(0, currentPosition + 1);
- history.add(url);
- currentPosition++;
- amountOfReloadsOnCurrentUrl = 0;
+ _loadUrl(url);
return Future<void>.sync(() {});
case 'updateSettings':
- if (call.arguments['jsMode'] == null) {
- break;
+ if (call.arguments['jsMode'] != null) {
+ javascriptMode = JavascriptMode.values[call.arguments['jsMode']];
}
- javascriptMode = JavascriptMode.values[call.arguments['jsMode']];
+ if (call.arguments['hasNavigationDelegate'] != null) {
+ hasNavigationDelegate = call.arguments['hasNavigationDelegate'];
+ }
break;
case 'canGoBack':
return Future<bool>.sync(() => currentPosition > 0);
@@ -672,6 +726,36 @@
BinaryMessages.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<String, dynamic> arguments = <String, dynamic>{
+ 'url': url,
+ 'isForMainFrame': true
+ };
+ final ByteData data =
+ codec.encodeMethodCall(MethodCall('navigationRequest', arguments));
+ BinaryMessages.handlePlatformMessage(channel.name, data, (ByteData data) {
+ final bool allow = codec.decodeEnvelope(data);
+ if (allow) {
+ _loadUrl(url);
+ }
+ });
+ }
+
+ void _loadUrl(String url) {
+ history = history.sublist(0, currentPosition + 1);
+ history.add(url);
+ currentPosition++;
+ amountOfReloadsOnCurrentUrl = 0;
+ }
}
class _FakePlatformViewsController {