Uh oh!
There was an error while loading. Please reload this page.
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
- Notifications
You must be signed in to change notification settings - Fork 9.6k
[webview_flutter]Allow specifying a navigation delegate(Android and Dart).#1236
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
729bcbd
[WebView] Allow specifying a navigation delegate.
amirh 8ac15be
review comments follow-up
amirh 8e2a9e9
remove stray thanks
amirh 46d8bf6
retain HTTP headers when possible when using a navigation delegate
amirh 8d9101c
add more documentation for caveats on Android
amirh a743f8d
format
amirh 6df13d9
use WebViewClientCompat, rename isMainFrame to isForMainFrame
amirh d9d671e
typo fix, update documentation
amirh e3eec87
bump version, update changelog
amirh 530d024
don't call super.shouldOverrideUrlLoading
amirh 151b47d
Merge branch 'master' into navigation_delegate
amirh ad30926
fix nit
amirh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -44,4 +44,8 @@ android { | ||
| lintOptions { | ||
| disable 'InvalidPackage' | ||
| } | ||
| dependencies { | ||
| implementation 'androidx.webkit:webkit:1.0.0' | ||
| } | ||
| } | ||
10 changes: 10 additions & 0 deletions
10 ...bview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124 ...flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| org.gradle.jvmargs=-Xmx1536M | ||
| android.useAndroidX=true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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> | ||
mklim marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| <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 @@ class WebViewExample extends StatelessWidget { | ||
| 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 @@ class WebViewExample extends StatelessWidget { | ||
| enum MenuOptions { | ||
| showUserAgent, | ||
| toast, | ||
| listCookies, | ||
| clearCookies, | ||
| addToCache, | ||
| listCache, | ||
| clearCache, | ||
| navigationDelegate, | ||
| } | ||
| class SampleMenu extends StatelessWidget { | ||
| @@ -102,13 +126,6 @@ class SampleMenu extends StatelessWidget { | ||
| 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 @@ class SampleMenu extends StatelessWidget { | ||
| case MenuOptions.clearCache: | ||
| _onClearCache(controller.data, context); | ||
| break; | ||
| case MenuOptions.navigationDelegate: | ||
| _onNavigationDelegateExample(controller.data, context); | ||
| break; | ||
| } | ||
| }, | ||
| itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[ | ||
| @@ -132,10 +152,6 @@ class SampleMenu extends StatelessWidget { | ||
| child: const Text('Show user agent'), | ||
| 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 @@ class SampleMenu extends StatelessWidget { | ||
| value: MenuOptions.clearCache, | ||
| child: Text('Clear cache'), | ||
| ), | ||
| const PopupMenuItem<MenuOptions>( | ||
| value: MenuOptions.navigationDelegate, | ||
| child: Text('Navigation Delegate example'), | ||
| ), | ||
| ], | ||
| ); | ||
| }, | ||
| @@ -218,6 +238,13 @@ class SampleMenu extends StatelessWidget { | ||
| )); | ||
| } | ||
| 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(); | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.