diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index afccf41481d5..fdc448279982 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -87,14 +87,11 @@ public void onReceivedError( @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { - if (!request.isForMainFrame()) { - // The client is only allowed to stop navigations that target the main frame because - // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. - return false; - } - flutterApi.requestLoading(this, view, request, reply -> {}); - return returnValueForShouldOverrideUrlLoading; + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; } // Legacy codepath for < 24; newer versions use the variant above. @@ -192,14 +189,11 @@ public void onReceivedError( @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { - if (!request.isForMainFrame()) { - // The client is only allowed to stop navigations that target the main frame because - // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. - return false; - } - flutterApi.requestLoading(this, view, request, reply -> {}); - return returnValueForShouldOverrideUrlLoading; + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; } // Legacy codepath for < Lollipop; newer versions use the variant above. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java index d3849274fbba..8bdb6fd46481 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatImplTest.java @@ -11,7 +11,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.net.Uri; @@ -128,7 +127,8 @@ public void urlLoadingNotForMainFrame() { when(mockRequest.isForMainFrame()).thenReturn(false); assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); - verifyNoInteractions(mockFlutterApi); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); } @Test @@ -139,7 +139,8 @@ public void urlLoadingNotForMainFrameWithOverride() { when(mockRequest.isForMainFrame()).thenReturn(false); assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); - verifyNoInteractions(mockFlutterApi); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java index 492caf4874c7..cddd089ab4b8 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientImplTest.java @@ -11,7 +11,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.net.Uri; @@ -125,7 +124,8 @@ public void urlLoadingNotForMainFrame() { when(mockRequest.isForMainFrame()).thenReturn(false); assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); - verifyNoInteractions(mockFlutterApi); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); } @Test @@ -136,7 +136,8 @@ public void urlLoadingNotForMainFrameWithOverride() { when(mockRequest.isForMainFrame()).thenReturn(false); assertFalse(webViewClient.shouldOverrideUrlLoading(mockWebView, mockRequest)); - verifyNoInteractions(mockFlutterApi); + verify(mockFlutterApi) + .requestLoading(eq(webViewClient), eq(mockWebView), eq(mockRequest), any()); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index f353532bc5dd..01560956f23e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -1461,6 +1461,12 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { required bool isForMainFrame, Map headers = const {}, }) { + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + if (!isForMainFrame) { + return; + } + final LoadRequestCallback? onLoadRequest = _onLoadRequest; final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 85b99dec2327..1adc7e64356c 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -163,6 +163,66 @@ void main() { expect(callbackNavigationRequest, isNull); }); + test( + 'onNavigationRequest from requestLoading should be called when request is for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest, isNotNull); + }); + + test( + 'onNavigationRequest from requestLoading should not be called when request is not for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + ); + + expect(callbackNavigationRequest, isNull); + }); + test( 'onLoadRequest from requestLoading should not be called when navigationRequestCallback is not specified', () { @@ -598,6 +658,7 @@ class CapturingWebChromeClient extends android_webview.WebChromeClient { }) : super.detached() { lastCreatedDelegate = this; } + static CapturingWebChromeClient lastCreatedDelegate = CapturingWebChromeClient(); } @@ -611,6 +672,7 @@ class CapturingDownloadListener extends android_webview.DownloadListener { }) : super.detached() { lastCreatedListener = this; } + static CapturingDownloadListener lastCreatedListener = CapturingDownloadListener(onDownloadStart: (_, __, ___, ____, _____) {}); }