[webview_flutter] Support for handling basic authentication requests (iOS) (#5455)

Adds the iOS implementation for basic http authentication.

This PR is part of a series of PRs that aim to close https://github.com/flutter/flutter/issues/83556.
The PR that contains all changes can be found at https://github.com/flutter/packages/pull/4140.
This commit is contained in:
Jeroen Weener
2023-12-19 20:31:20 +01:00
committed by GitHub
parent e2b53347f1
commit 3273017a06
38 changed files with 2366 additions and 132 deletions

View File

@ -1,5 +1,6 @@
## NEXT ## 3.10.0
* Adds support for `PlatformNavigationDelegate.setOnHttpAuthRequest`.
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
## 3.9.4 ## 3.9.4

View File

@ -34,6 +34,15 @@ Future<void> main() async {
request.response.writeln('${request.headers}'); request.response.writeln('${request.headers}');
} else if (request.uri.path == '/favicon.ico') { } else if (request.uri.path == '/favicon.ico') {
request.response.statusCode = HttpStatus.notFound; request.response.statusCode = HttpStatus.notFound;
} else if (request.uri.path == '/http-basic-authentication') {
final bool isAuthenticating = request.headers['Authorization'] != null;
if (isAuthenticating) {
request.response.writeln('Authorized');
} else {
request.response.headers
.add('WWW-Authenticate', 'Basic realm="Test realm"');
request.response.statusCode = HttpStatus.unauthorized;
}
} else { } else {
fail('unexpected request: ${request.method} ${request.uri}'); fail('unexpected request: ${request.method} ${request.uri}');
} }
@ -43,6 +52,7 @@ Future<void> main() async {
final String primaryUrl = '$prefixUrl/hello.txt'; final String primaryUrl = '$prefixUrl/hello.txt';
final String secondaryUrl = '$prefixUrl/secondary.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt';
final String headersUrl = '$prefixUrl/headers'; final String headersUrl = '$prefixUrl/headers';
final String basicAuthUrl = '$prefixUrl/http-basic-authentication';
testWidgets( testWidgets(
'withWeakReferenceTo allows encapsulating class to be garbage collected', 'withWeakReferenceTo allows encapsulating class to be garbage collected',
@ -1127,6 +1137,82 @@ Future<void> main() async {
}); });
}); });
testWidgets('can receive HTTP basic auth requests',
(WidgetTester tester) async {
final Completer<void> authRequested = Completer<void>();
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);
final PlatformNavigationDelegate navigationDelegate =
PlatformNavigationDelegate(
const PlatformNavigationDelegateCreationParams(),
);
await navigationDelegate.setOnHttpAuthRequest(
(HttpAuthRequest request) => authRequested.complete());
await controller.setPlatformNavigationDelegate(navigationDelegate);
// Clear cache so that the auth request is always received and we don't get
// a cached response.
await controller.clearCache();
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
WebKitWebViewWidgetCreationParams(controller: controller),
).build(context);
},
),
);
await controller.loadRequest(
LoadRequestParams(uri: Uri.parse(basicAuthUrl)),
);
await expectLater(authRequested.future, completes);
});
testWidgets('can reply to HTTP basic auth requests',
(WidgetTester tester) async {
final Completer<void> pageFinished = Completer<void>();
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
);
final PlatformNavigationDelegate navigationDelegate =
PlatformNavigationDelegate(
const PlatformNavigationDelegateCreationParams(),
);
await navigationDelegate.setOnPageFinished((_) => pageFinished.complete());
await navigationDelegate.setOnHttpAuthRequest(
(HttpAuthRequest request) => request.onProceed(
const WebViewCredential(user: 'user', password: 'password'),
),
);
await controller.setPlatformNavigationDelegate(navigationDelegate);
// Clear cache so that the auth request is always received and we don't get
// a cached response.
await controller.clearCache();
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
WebKitWebViewWidgetCreationParams(controller: controller),
).build(context);
},
),
);
await controller.loadRequest(
LoadRequestParams(uri: Uri.parse(basicAuthUrl)),
);
await expectLater(pageFinished.future, completes);
});
testWidgets('launches with gestureNavigationEnabled on iOS', testWidgets('launches with gestureNavigationEnabled on iOS',
(WidgetTester tester) async { (WidgetTester tester) async {
final WebKitWebViewController controller = WebKitWebViewController( final WebKitWebViewController controller = WebKitWebViewController(

View File

@ -11,6 +11,9 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; }; 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; };
8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */; }; 8F4FF94B29AC223F000A6586 /* FWFURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */; };
8F562F902A56C02D00C2BED6 /* FWFURLCredentialHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F562F8F2A56C02D00C2BED6 /* FWFURLCredentialHostApiTests.m */; };
8F562F922A56C04F00C2BED6 /* FWFURLProtectionSpaceHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F562F912A56C04F00C2BED6 /* FWFURLProtectionSpaceHostApiTests.m */; };
8F562F942A56C07B00C2BED6 /* FWFURLAuthenticationChallengeHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F562F932A56C07B00C2BED6 /* FWFURLAuthenticationChallengeHostApiTests.m */; };
8F78EAAA2A02CB9100C2E520 /* FWFErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */; }; 8F78EAAA2A02CB9100C2E520 /* FWFErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */; };
8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; }; 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; };
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; }; 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; };
@ -81,6 +84,9 @@
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = "<group>"; }; 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = "<group>"; };
8F4FF94A29AC223F000A6586 /* FWFURLTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLTests.m; sourceTree = "<group>"; }; 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLTests.m; sourceTree = "<group>"; };
8F562F8F2A56C02D00C2BED6 /* FWFURLCredentialHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLCredentialHostApiTests.m; sourceTree = "<group>"; };
8F562F912A56C04F00C2BED6 /* FWFURLProtectionSpaceHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLProtectionSpaceHostApiTests.m; sourceTree = "<group>"; };
8F562F932A56C07B00C2BED6 /* FWFURLAuthenticationChallengeHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFURLAuthenticationChallengeHostApiTests.m; sourceTree = "<group>"; };
8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFErrorTests.m; sourceTree = "<group>"; }; 8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFErrorTests.m; sourceTree = "<group>"; };
8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = "<group>"; }; 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = "<group>"; };
8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = "<group>"; }; 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = "<group>"; };
@ -167,6 +173,9 @@
8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */, 8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */,
8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */, 8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */,
8F4FF94A29AC223F000A6586 /* FWFURLTests.m */, 8F4FF94A29AC223F000A6586 /* FWFURLTests.m */,
8F562F8F2A56C02D00C2BED6 /* FWFURLCredentialHostApiTests.m */,
8F562F912A56C04F00C2BED6 /* FWFURLProtectionSpaceHostApiTests.m */,
8F562F932A56C07B00C2BED6 /* FWFURLAuthenticationChallengeHostApiTests.m */,
8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */, 8F78EAA92A02CB9100C2E520 /* FWFErrorTests.m */,
); );
path = RunnerTests; path = RunnerTests;
@ -318,7 +327,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
DefaultBuildSystemTypeForWorkspace = Original; DefaultBuildSystemTypeForWorkspace = Original;
LastUpgradeCheck = 1300; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "The Flutter Authors"; ORGANIZATIONNAME = "The Flutter Authors";
TargetAttributes = { TargetAttributes = {
68BDCAE823C3F7CB00D9C032 = { 68BDCAE823C3F7CB00D9C032 = {
@ -327,7 +336,6 @@
}; };
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 7624MWN53C;
}; };
F7151F73266057800028CB91 = { F7151F73266057800028CB91 = {
CreatedOnToolsVersion = 12.5; CreatedOnToolsVersion = 12.5;
@ -474,12 +482,15 @@
8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */, 8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */,
8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */, 8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */,
8FB79B672820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m in Sources */, 8FB79B672820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m in Sources */,
8F562F942A56C07B00C2BED6 /* FWFURLAuthenticationChallengeHostApiTests.m in Sources */,
8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */, 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */,
8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */, 8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */,
8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */, 8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */,
8F562F902A56C02D00C2BED6 /* FWFURLCredentialHostApiTests.m in Sources */,
8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */, 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */,
8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */, 8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */,
8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */, 8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */,
8F562F922A56C04F00C2BED6 /* FWFURLProtectionSpaceHostApiTests.m in Sources */,
8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */, 8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */,
8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */, 8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */,
8FB79B6D2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m in Sources */, 8FB79B6D2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m in Sources */,
@ -689,6 +700,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 7624MWN53C;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -715,6 +727,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 7624MWN53C;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1300" LastUpgradeVersion = "1430"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -213,4 +213,58 @@
webViewIdentifier:1 webViewIdentifier:1
completion:OCMOCK_ANY]); completion:OCMOCK_ANY]);
} }
- (void)testDidReceiveAuthenticationChallenge {
FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager
identifier:0];
FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI =
[self mockFlutterApiWithManager:instanceManager];
OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI);
WKWebView *mockWebView = OCMClassMock([WKWebView class]);
[instanceManager addDartCreatedInstance:mockWebView withIdentifier:1];
NSURLAuthenticationChallenge *mockChallenge = OCMClassMock([NSURLAuthenticationChallenge class]);
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"host"
port:0
protocol:nil
realm:@"realm"
authenticationMethod:nil];
OCMStub([mockChallenge protectionSpace]).andReturn(protectionSpace);
[instanceManager addDartCreatedInstance:mockChallenge withIdentifier:2];
NSURLCredential *credential = [NSURLCredential credentialWithUser:@"user"
password:@"password"
persistence:NSURLCredentialPersistenceNone];
[instanceManager addDartCreatedInstance:credential withIdentifier:5];
OCMStub([mockFlutterAPI
didReceiveAuthenticationChallengeForDelegateWithIdentifier:0
webViewIdentifier:1
challengeIdentifier:2
completion:
([OCMArg
invokeBlockWithArgs:
[FWFAuthenticationChallengeResponse
makeWithDisposition:
FWFNSUrlSessionAuthChallengeDispositionCancelAuthenticationChallenge
credentialIdentifier:@(5)],
[NSNull null], nil])]);
NSURLSessionAuthChallengeDisposition __block callbackDisposition = -1;
NSURLCredential *__block callbackCredential;
[mockDelegate webView:mockWebView
didReceiveAuthenticationChallenge:mockChallenge
completionHandler:^(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential) {
callbackDisposition = disposition;
callbackCredential = credential;
}];
XCTAssertEqual(callbackDisposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge);
XCTAssertEqualObjects(callbackCredential, credential);
}
@end @end

View File

@ -0,0 +1,47 @@
// 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 Flutter;
@import XCTest;
@import webview_flutter_wkwebview;
#import <OCMock/OCMock.h>
@interface FWFURLAuthenticationChallengeHostApiTests : XCTestCase
@end
@implementation FWFURLAuthenticationChallengeHostApiTests
- (void)testFlutterApiCreate {
FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
FWFURLAuthenticationChallengeFlutterApiImpl *flutterApi =
[[FWFURLAuthenticationChallengeFlutterApiImpl alloc]
initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger))
instanceManager:instanceManager];
flutterApi.api = OCMClassMock([FWFNSUrlAuthenticationChallengeFlutterApi class]);
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"host"
port:0
protocol:nil
realm:@"realm"
authenticationMethod:nil];
NSURLAuthenticationChallenge *mockChallenge = OCMClassMock([NSURLAuthenticationChallenge class]);
OCMStub([mockChallenge protectionSpace]).andReturn(protectionSpace);
[flutterApi createWithInstance:mockChallenge
protectionSpace:protectionSpace
completion:^(FlutterError *error){
}];
long identifier = [instanceManager identifierWithStrongReferenceForInstance:mockChallenge];
long protectionSpaceIdentifier =
[instanceManager identifierWithStrongReferenceForInstance:protectionSpace];
OCMVerify([flutterApi.api createWithIdentifier:identifier
protectionSpaceIdentifier:protectionSpaceIdentifier
completion:OCMOCK_ANY]);
}
@end

View File

@ -0,0 +1,35 @@
// 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 Flutter;
@import XCTest;
@import webview_flutter_wkwebview;
#import <OCMock/OCMock.h>
@interface FWFURLCredentialHostApiTests : XCTestCase
@end
@implementation FWFURLCredentialHostApiTests
- (void)testHostApiCreate {
FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
FWFURLCredentialHostApiImpl *hostApi = [[FWFURLCredentialHostApiImpl alloc]
initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger))
instanceManager:instanceManager];
FlutterError *error;
[hostApi createWithUserWithIdentifier:0
user:@"user"
password:@"password"
persistence:FWFNSUrlCredentialPersistencePermanent
error:&error];
XCTAssertNil(error);
NSURLCredential *credential = (NSURLCredential *)[instanceManager instanceForIdentifier:0];
XCTAssertEqualObjects(credential.user, @"user");
XCTAssertEqualObjects(credential.password, @"password");
XCTAssertEqual(credential.persistence, NSURLCredentialPersistencePermanent);
}
@end

View File

@ -0,0 +1,43 @@
// 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 Flutter;
@import XCTest;
@import webview_flutter_wkwebview;
#import <OCMock/OCMock.h>
@interface FWFURLProtectionSpaceHostApiTests : XCTestCase
@end
@implementation FWFURLProtectionSpaceHostApiTests
- (void)testFlutterApiCreate {
FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init];
FWFURLProtectionSpaceFlutterApiImpl *flutterApi = [[FWFURLProtectionSpaceFlutterApiImpl alloc]
initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger))
instanceManager:instanceManager];
flutterApi.api = OCMClassMock([FWFNSUrlProtectionSpaceFlutterApi class]);
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:@"host"
port:0
protocol:nil
realm:@"realm"
authenticationMethod:nil];
[flutterApi createWithInstance:protectionSpace
host:@"host"
realm:@"realm"
authenticationMethod:@"method"
completion:^(FlutterError *error){
}];
long identifier = [instanceManager identifierWithStrongReferenceForInstance:protectionSpace];
OCMVerify([flutterApi.api createWithIdentifier:identifier
host:@"host"
realm:@"realm"
authenticationMethod:@"method"
completion:OCMOCK_ANY]);
}
@end

View File

@ -162,6 +162,9 @@ Page resource error:
}) })
..setOnUrlChange((UrlChange change) { ..setOnUrlChange((UrlChange change) {
debugPrint('url change to ${change.url}'); debugPrint('url change to ${change.url}');
})
..setOnHttpAuthRequest((HttpAuthRequest request) {
openDialog(request);
}), }),
) )
..addJavaScriptChannel(JavaScriptChannelParams( ..addJavaScriptChannel(JavaScriptChannelParams(
@ -220,6 +223,62 @@ Page resource error:
child: const Icon(Icons.favorite), child: const Icon(Icons.favorite),
); );
} }
Future<void> openDialog(HttpAuthRequest httpRequest) async {
final TextEditingController usernameTextController =
TextEditingController();
final TextEditingController passwordTextController =
TextEditingController();
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: const InputDecoration(labelText: 'Username'),
autofocus: true,
controller: usernameTextController,
),
TextField(
decoration: const InputDecoration(labelText: 'Password'),
controller: passwordTextController,
),
],
),
),
actions: <Widget>[
// Explicitly cancel the request on iOS as the OS does not emit new
// requests when a previous request is pending.
TextButton(
onPressed: () {
httpRequest.onCancel();
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
httpRequest.onProceed(
WebViewCredential(
user: usernameTextController.text,
password: passwordTextController.text,
),
);
Navigator.of(context).pop();
},
child: const Text('Authenticate'),
),
],
);
},
);
}
} }
enum MenuOptions { enum MenuOptions {
@ -237,6 +296,7 @@ enum MenuOptions {
transparentBackground, transparentBackground,
setCookie, setCookie,
logExample, logExample,
basicAuthentication,
} }
class SampleMenu extends StatelessWidget { class SampleMenu extends StatelessWidget {
@ -300,6 +360,9 @@ class SampleMenu extends StatelessWidget {
case MenuOptions.logExample: case MenuOptions.logExample:
_onLogExample(); _onLogExample();
break; break;
case MenuOptions.basicAuthentication:
_promptForUrl(context);
break;
} }
}, },
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[ itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@ -360,6 +423,10 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.logExample, value: MenuOptions.logExample,
child: Text('Log example'), child: Text('Log example'),
), ),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.basicAuthentication,
child: Text('Basic Authentication Example'),
),
], ],
); );
} }
@ -518,6 +585,41 @@ class SampleMenu extends StatelessWidget {
return webViewController.loadHtmlString(kLogExamplePage); return webViewController.loadHtmlString(kLogExamplePage);
} }
Future<void> _promptForUrl(BuildContext context) {
final TextEditingController urlTextController =
TextEditingController(text: 'https://');
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Input URL to visit'),
content: TextField(
decoration: const InputDecoration(labelText: 'URL'),
autofocus: true,
controller: urlTextController,
),
actions: <Widget>[
TextButton(
onPressed: () {
if (urlTextController.text.isNotEmpty) {
final Uri? uri = Uri.tryParse(urlTextController.text);
if (uri != null && uri.scheme.isNotEmpty) {
webViewController.loadRequest(
LoadRequestParams(uri: uri),
);
Navigator.pop(context);
}
}
},
child: const Text('Visit'),
),
],
);
},
);
}
} }
class NavigationControls extends StatelessWidget { class NavigationControls extends StatelessWidget {

View File

@ -10,7 +10,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path_provider: ^2.0.6 path_provider: ^2.0.6
webview_flutter_platform_interface: ^2.6.0 webview_flutter_platform_interface: ^2.7.0
webview_flutter_wkwebview: webview_flutter_wkwebview:
# When depending on this package from a real application you should use: # When depending on this package from a real application you should use:
# webview_flutter: ^x.y.z # webview_flutter: ^x.y.z

View File

@ -13,6 +13,7 @@
#import "FWFScrollViewHostApi.h" #import "FWFScrollViewHostApi.h"
#import "FWFUIDelegateHostApi.h" #import "FWFUIDelegateHostApi.h"
#import "FWFUIViewHostApi.h" #import "FWFUIViewHostApi.h"
#import "FWFURLCredentialHostApi.h"
#import "FWFURLHostApi.h" #import "FWFURLHostApi.h"
#import "FWFUserContentControllerHostApi.h" #import "FWFUserContentControllerHostApi.h"
#import "FWFWebViewConfigurationHostApi.h" #import "FWFWebViewConfigurationHostApi.h"
@ -105,6 +106,11 @@
[[FWFURLHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger [[FWFURLHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger
instanceManager:instanceManager]); instanceManager:instanceManager]);
SetUpFWFNSUrlCredentialHostApi(
registrar.messenger,
[[FWFURLCredentialHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger
instanceManager:instanceManager]);
FWFWebViewFactory *webviewFactory = [[FWFWebViewFactory alloc] initWithManager:instanceManager]; FWFWebViewFactory *webviewFactory = [[FWFWebViewFactory alloc] initWithManager:instanceManager];
[registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"];

View File

@ -193,4 +193,26 @@ API_AVAILABLE(ios(15.0))
extern FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType( extern FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType(
WKMediaCaptureType type); WKMediaCaptureType type);
/**
* Converts an FWFNSUrlSessionAuthChallengeDisposition to an NSURLSessionAuthChallengeDisposition.
*
* @param value The object containing information to create an NSURLSessionAuthChallengeDisposition.
*
* @return A NSURLSessionAuthChallengeDisposition or -1 if data could not be converted.
*/
extern NSURLSessionAuthChallengeDisposition
FWFNativeNSURLSessionAuthChallengeDispositionFromFWFNSUrlSessionAuthChallengeDisposition(
FWFNSUrlSessionAuthChallengeDisposition value);
/**
* Converts an FWFNSUrlCredentialPersistence to an NSURLCredentialPersistence.
*
* @param value The object containing information to create an NSURLCredentialPersistence.
*
* @return A NSURLCredentialPersistence or -1 if data could not be converted.
*/
extern NSURLCredentialPersistence
FWFNativeNSURLCredentialPersistenceFromFWFNSUrlCredentialPersistence(
FWFNSUrlCredentialPersistence value);
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -285,3 +285,36 @@ FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType
return nil; return nil;
} }
NSURLSessionAuthChallengeDisposition
FWFNativeNSURLSessionAuthChallengeDispositionFromFWFNSUrlSessionAuthChallengeDisposition(
FWFNSUrlSessionAuthChallengeDisposition value) {
switch (value) {
case FWFNSUrlSessionAuthChallengeDispositionUseCredential:
return NSURLSessionAuthChallengeUseCredential;
case FWFNSUrlSessionAuthChallengeDispositionPerformDefaultHandling:
return NSURLSessionAuthChallengePerformDefaultHandling;
case FWFNSUrlSessionAuthChallengeDispositionCancelAuthenticationChallenge:
return NSURLSessionAuthChallengeCancelAuthenticationChallenge;
case FWFNSUrlSessionAuthChallengeDispositionRejectProtectionSpace:
return NSURLSessionAuthChallengeRejectProtectionSpace;
}
return -1;
}
NSURLCredentialPersistence FWFNativeNSURLCredentialPersistenceFromFWFNSUrlCredentialPersistence(
FWFNSUrlCredentialPersistence value) {
switch (value) {
case FWFNSUrlCredentialPersistenceNone:
return NSURLCredentialPersistenceNone;
case FWFNSUrlCredentialPersistenceSession:
return NSURLCredentialPersistenceForSession;
case FWFNSUrlCredentialPersistencePermanent:
return NSURLCredentialPersistencePermanent;
case FWFNSUrlCredentialPersistenceSynchronizable:
return NSURLCredentialPersistenceSynchronizable;
}
return -1;
}

View File

@ -264,6 +264,73 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) {
- (instancetype)initWithValue:(FWFWKMediaCaptureType)value; - (instancetype)initWithValue:(FWFWKMediaCaptureType)value;
@end @end
/// Responses to an authentication challenge.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition?language=objc.
typedef NS_ENUM(NSUInteger, FWFNSUrlSessionAuthChallengeDisposition) {
/// Use the specified credential, which may be nil.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeusecredential?language=objc.
FWFNSUrlSessionAuthChallengeDispositionUseCredential = 0,
/// Use the default handling for the challenge as though this delegate method
/// were not implemented.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeperformdefaulthandling?language=objc.
FWFNSUrlSessionAuthChallengeDispositionPerformDefaultHandling = 1,
/// Cancel the entire request.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengecancelauthenticationchallenge?language=objc.
FWFNSUrlSessionAuthChallengeDispositionCancelAuthenticationChallenge = 2,
/// Reject this challenge, and call the authentication delegate method again
/// with the next authentication protection space.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengerejectprotectionspace?language=objc.
FWFNSUrlSessionAuthChallengeDispositionRejectProtectionSpace = 3,
};
/// Wrapper for FWFNSUrlSessionAuthChallengeDisposition to allow for nullability.
@interface FWFNSUrlSessionAuthChallengeDispositionBox : NSObject
@property(nonatomic, assign) FWFNSUrlSessionAuthChallengeDisposition value;
- (instancetype)initWithValue:(FWFNSUrlSessionAuthChallengeDisposition)value;
@end
/// Specifies how long a credential will be kept.
typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) {
/// The credential should not be stored.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencenone?language=objc.
FWFNSUrlCredentialPersistenceNone = 0,
/// The credential should be stored only for this session.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistenceforsession?language=objc.
FWFNSUrlCredentialPersistenceSession = 1,
/// The credential should be stored in the keychain.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencepermanent?language=objc.
FWFNSUrlCredentialPersistencePermanent = 2,
/// The credential should be stored permanently in the keychain, and in
/// addition should be distributed to other devices based on the owning Apple
/// ID.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencesynchronizable?language=objc.
FWFNSUrlCredentialPersistenceSynchronizable = 3,
};
/// Wrapper for FWFNSUrlCredentialPersistence to allow for nullability.
@interface FWFNSUrlCredentialPersistenceBox : NSObject
@property(nonatomic, assign) FWFNSUrlCredentialPersistence value;
- (instancetype)initWithValue:(FWFNSUrlCredentialPersistence)value;
@end
@class FWFNSKeyValueObservingOptionsEnumData; @class FWFNSKeyValueObservingOptionsEnumData;
@class FWFNSKeyValueChangeKeyEnumData; @class FWFNSKeyValueChangeKeyEnumData;
@class FWFWKUserScriptInjectionTimeEnumData; @class FWFWKUserScriptInjectionTimeEnumData;
@ -282,6 +349,7 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) {
@class FWFWKSecurityOriginData; @class FWFWKSecurityOriginData;
@class FWFNSHttpCookieData; @class FWFNSHttpCookieData;
@class FWFObjectOrIdentifier; @class FWFObjectOrIdentifier;
@class FWFAuthenticationChallengeResponse;
@interface FWFNSKeyValueObservingOptionsEnumData : NSObject @interface FWFNSKeyValueObservingOptionsEnumData : NSObject
/// `init` unavailable to enforce nonnull fields, see the `make` class method. /// `init` unavailable to enforce nonnull fields, see the `make` class method.
@ -462,6 +530,15 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) {
@property(nonatomic, assign) BOOL isIdentifier; @property(nonatomic, assign) BOOL isIdentifier;
@end @end
@interface FWFAuthenticationChallengeResponse : NSObject
/// `init` unavailable to enforce nonnull fields, see the `make` class method.
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)makeWithDisposition:(FWFNSUrlSessionAuthChallengeDisposition)disposition
credentialIdentifier:(nullable NSNumber *)credentialIdentifier;
@property(nonatomic, assign) FWFNSUrlSessionAuthChallengeDisposition disposition;
@property(nonatomic, strong, nullable) NSNumber *credentialIdentifier;
@end
/// The codec used by FWFWKWebsiteDataStoreHostApi. /// The codec used by FWFWKWebsiteDataStoreHostApi.
NSObject<FlutterMessageCodec> *FWFWKWebsiteDataStoreHostApiGetCodec(void); NSObject<FlutterMessageCodec> *FWFWKWebsiteDataStoreHostApiGetCodec(void);
@ -711,6 +788,14 @@ NSObject<FlutterMessageCodec> *FWFWKNavigationDelegateFlutterApiGetCodec(void);
completion: completion:
(void (^)(FlutterError *_Nullable)) (void (^)(FlutterError *_Nullable))
completion; completion;
- (void)didReceiveAuthenticationChallengeForDelegateWithIdentifier:(NSInteger)identifier
webViewIdentifier:(NSInteger)webViewIdentifier
challengeIdentifier:(NSInteger)challengeIdentifier
completion:
(void (^)(
FWFAuthenticationChallengeResponse
*_Nullable,
FlutterError *_Nullable))completion;
@end @end
/// The codec used by FWFNSObjectHostApi. /// The codec used by FWFNSObjectHostApi.
@ -919,4 +1004,65 @@ NSObject<FlutterMessageCodec> *FWFNSUrlFlutterApiGetCodec(void);
completion:(void (^)(FlutterError *_Nullable))completion; completion:(void (^)(FlutterError *_Nullable))completion;
@end @end
/// The codec used by FWFNSUrlCredentialHostApi.
NSObject<FlutterMessageCodec> *FWFNSUrlCredentialHostApiGetCodec(void);
/// Host API for `NSUrlCredential`.
///
/// This class may handle instantiating and adding native object instances that
/// are attached to a Dart instance or handle method calls on the associated
/// native class or an instance of the class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredential?language=objc.
@protocol FWFNSUrlCredentialHostApi
/// Create a new native instance and add it to the `InstanceManager`.
- (void)createWithUserWithIdentifier:(NSInteger)identifier
user:(NSString *)user
password:(NSString *)password
persistence:(FWFNSUrlCredentialPersistence)persistence
error:(FlutterError *_Nullable *_Nonnull)error;
@end
extern void SetUpFWFNSUrlCredentialHostApi(id<FlutterBinaryMessenger> binaryMessenger,
NSObject<FWFNSUrlCredentialHostApi> *_Nullable api);
/// The codec used by FWFNSUrlProtectionSpaceFlutterApi.
NSObject<FlutterMessageCodec> *FWFNSUrlProtectionSpaceFlutterApiGetCodec(void);
/// Flutter API for `NSUrlProtectionSpace`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlprotectionspace?language=objc.
@interface FWFNSUrlProtectionSpaceFlutterApi : NSObject
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
/// Create a new Dart instance and add it to the `InstanceManager`.
- (void)createWithIdentifier:(NSInteger)identifier
host:(nullable NSString *)host
realm:(nullable NSString *)realm
authenticationMethod:(nullable NSString *)authenticationMethod
completion:(void (^)(FlutterError *_Nullable))completion;
@end
/// The codec used by FWFNSUrlAuthenticationChallengeFlutterApi.
NSObject<FlutterMessageCodec> *FWFNSUrlAuthenticationChallengeFlutterApiGetCodec(void);
/// Flutter API for `NSUrlAuthenticationChallenge`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlauthenticationchallenge?language=objc.
@interface FWFNSUrlAuthenticationChallengeFlutterApi : NSObject
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
/// Create a new Dart instance and add it to the `InstanceManager`.
- (void)createWithIdentifier:(NSInteger)identifier
protectionSpaceIdentifier:(NSInteger)protectionSpaceIdentifier
completion:(void (^)(FlutterError *_Nullable))completion;
@end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -164,6 +164,31 @@
} }
@end @end
/// Responses to an authentication challenge.
///
/// See
/// https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition?language=objc.
@implementation FWFNSUrlSessionAuthChallengeDispositionBox
- (instancetype)initWithValue:(FWFNSUrlSessionAuthChallengeDisposition)value {
self = [super init];
if (self) {
_value = value;
}
return self;
}
@end
/// Specifies how long a credential will be kept.
@implementation FWFNSUrlCredentialPersistenceBox
- (instancetype)initWithValue:(FWFNSUrlCredentialPersistence)value {
self = [super init];
if (self) {
_value = value;
}
return self;
}
@end
static NSArray *wrapResult(id result, FlutterError *error) { static NSArray *wrapResult(id result, FlutterError *error) {
if (error) { if (error) {
return @[ return @[
@ -285,6 +310,12 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
- (NSArray *)toList; - (NSArray *)toList;
@end @end
@interface FWFAuthenticationChallengeResponse ()
+ (FWFAuthenticationChallengeResponse *)fromList:(NSArray *)list;
+ (nullable FWFAuthenticationChallengeResponse *)nullableFromList:(NSArray *)list;
- (NSArray *)toList;
@end
@implementation FWFNSKeyValueObservingOptionsEnumData @implementation FWFNSKeyValueObservingOptionsEnumData
+ (instancetype)makeWithValue:(FWFNSKeyValueObservingOptionsEnum)value { + (instancetype)makeWithValue:(FWFNSKeyValueObservingOptionsEnum)value {
FWFNSKeyValueObservingOptionsEnumData *pigeonResult = FWFNSKeyValueObservingOptionsEnumData *pigeonResult =
@ -727,6 +758,33 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
} }
@end @end
@implementation FWFAuthenticationChallengeResponse
+ (instancetype)makeWithDisposition:(FWFNSUrlSessionAuthChallengeDisposition)disposition
credentialIdentifier:(nullable NSNumber *)credentialIdentifier {
FWFAuthenticationChallengeResponse *pigeonResult =
[[FWFAuthenticationChallengeResponse alloc] init];
pigeonResult.disposition = disposition;
pigeonResult.credentialIdentifier = credentialIdentifier;
return pigeonResult;
}
+ (FWFAuthenticationChallengeResponse *)fromList:(NSArray *)list {
FWFAuthenticationChallengeResponse *pigeonResult =
[[FWFAuthenticationChallengeResponse alloc] init];
pigeonResult.disposition = [GetNullableObjectAtIndex(list, 0) integerValue];
pigeonResult.credentialIdentifier = GetNullableObjectAtIndex(list, 1);
return pigeonResult;
}
+ (nullable FWFAuthenticationChallengeResponse *)nullableFromList:(NSArray *)list {
return (list) ? [FWFAuthenticationChallengeResponse fromList:list] : nil;
}
- (NSArray *)toList {
return @[
@(self.disposition),
self.credentialIdentifier ?: [NSNull null],
];
}
@end
@interface FWFWKWebsiteDataStoreHostApiCodecReader : FlutterStandardReader @interface FWFWKWebsiteDataStoreHostApiCodecReader : FlutterStandardReader
@end @end
@implementation FWFWKWebsiteDataStoreHostApiCodecReader @implementation FWFWKWebsiteDataStoreHostApiCodecReader
@ -1684,14 +1742,16 @@ void SetUpFWFWKNavigationDelegateHostApi(id<FlutterBinaryMessenger> binaryMessen
- (nullable id)readValueOfType:(UInt8)type { - (nullable id)readValueOfType:(UInt8)type {
switch (type) { switch (type) {
case 128: case 128:
return [FWFNSErrorData fromList:[self readValue]]; return [FWFAuthenticationChallengeResponse fromList:[self readValue]];
case 129: case 129:
return [FWFNSUrlRequestData fromList:[self readValue]]; return [FWFNSErrorData fromList:[self readValue]];
case 130: case 130:
return [FWFWKFrameInfoData fromList:[self readValue]]; return [FWFNSUrlRequestData fromList:[self readValue]];
case 131: case 131:
return [FWFWKNavigationActionData fromList:[self readValue]]; return [FWFWKFrameInfoData fromList:[self readValue]];
case 132: case 132:
return [FWFWKNavigationActionData fromList:[self readValue]];
case 133:
return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]];
default: default:
return [super readValueOfType:type]; return [super readValueOfType:type];
@ -1703,21 +1763,24 @@ void SetUpFWFWKNavigationDelegateHostApi(id<FlutterBinaryMessenger> binaryMessen
@end @end
@implementation FWFWKNavigationDelegateFlutterApiCodecWriter @implementation FWFWKNavigationDelegateFlutterApiCodecWriter
- (void)writeValue:(id)value { - (void)writeValue:(id)value {
if ([value isKindOfClass:[FWFNSErrorData class]]) { if ([value isKindOfClass:[FWFAuthenticationChallengeResponse class]]) {
[self writeByte:128]; [self writeByte:128];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { } else if ([value isKindOfClass:[FWFNSErrorData class]]) {
[self writeByte:129]; [self writeByte:129];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
[self writeByte:130]; [self writeByte:130];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
[self writeByte:131]; [self writeByte:131];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
[self writeByte:132]; [self writeByte:132];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
[self writeByte:133];
[self writeValue:[value toList]];
} else { } else {
[super writeValue:value]; [super writeValue:value];
} }
@ -1934,6 +1997,40 @@ NSObject<FlutterMessageCodec> *FWFWKNavigationDelegateFlutterApiGetCodec(void) {
} }
}]; }];
} }
- (void)
didReceiveAuthenticationChallengeForDelegateWithIdentifier:(NSInteger)arg_identifier
webViewIdentifier:(NSInteger)arg_webViewIdentifier
challengeIdentifier:(NSInteger)arg_challengeIdentifier
completion:
(void (^)(FWFAuthenticationChallengeResponse
*_Nullable,
FlutterError *_Nullable))
completion {
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview."
@"WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge"
binaryMessenger:self.binaryMessenger
codec:FWFWKNavigationDelegateFlutterApiGetCodec()];
[channel sendMessage:@[ @(arg_identifier), @(arg_webViewIdentifier), @(arg_challengeIdentifier) ]
reply:^(NSArray<id> *reply) {
if (reply != nil) {
if (reply.count > 1) {
completion(nil, [FlutterError errorWithCode:reply[0]
message:reply[1]
details:reply[2]]);
} else {
FWFAuthenticationChallengeResponse *output =
reply[0] == [NSNull null] ? nil : reply[0];
completion(output, nil);
}
} else {
completion(nil, [FlutterError
errorWithCode:@"channel-error"
message:@"Unable to establish connection on channel."
details:@""]);
}
}];
}
@end @end
@interface FWFNSObjectHostApiCodecReader : FlutterStandardReader @interface FWFNSObjectHostApiCodecReader : FlutterStandardReader
@ -2201,40 +2298,42 @@ NSObject<FlutterMessageCodec> *FWFNSObjectFlutterApiGetCodec(void) {
- (nullable id)readValueOfType:(UInt8)type { - (nullable id)readValueOfType:(UInt8)type {
switch (type) { switch (type) {
case 128: case 128:
return [FWFNSErrorData fromList:[self readValue]]; return [FWFAuthenticationChallengeResponse fromList:[self readValue]];
case 129: case 129:
return [FWFNSHttpCookieData fromList:[self readValue]]; return [FWFNSErrorData fromList:[self readValue]];
case 130: case 130:
return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; return [FWFNSHttpCookieData fromList:[self readValue]];
case 131: case 131:
return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]];
case 132: case 132:
return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]];
case 133: case 133:
return [FWFNSUrlRequestData fromList:[self readValue]]; return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]];
case 134: case 134:
return [FWFObjectOrIdentifier fromList:[self readValue]]; return [FWFNSUrlRequestData fromList:[self readValue]];
case 135: case 135:
return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; return [FWFObjectOrIdentifier fromList:[self readValue]];
case 136: case 136:
return [FWFWKFrameInfoData fromList:[self readValue]]; return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]];
case 137: case 137:
return [FWFWKMediaCaptureTypeData fromList:[self readValue]]; return [FWFWKFrameInfoData fromList:[self readValue]];
case 138: case 138:
return [FWFWKNavigationActionData fromList:[self readValue]]; return [FWFWKMediaCaptureTypeData fromList:[self readValue]];
case 139: case 139:
return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; return [FWFWKNavigationActionData fromList:[self readValue]];
case 140: case 140:
return [FWFWKPermissionDecisionData fromList:[self readValue]]; return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]];
case 141: case 141:
return [FWFWKScriptMessageData fromList:[self readValue]]; return [FWFWKPermissionDecisionData fromList:[self readValue]];
case 142: case 142:
return [FWFWKSecurityOriginData fromList:[self readValue]]; return [FWFWKScriptMessageData fromList:[self readValue]];
case 143: case 143:
return [FWFWKUserScriptData fromList:[self readValue]]; return [FWFWKSecurityOriginData fromList:[self readValue]];
case 144: case 144:
return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; return [FWFWKUserScriptData fromList:[self readValue]];
case 145: case 145:
return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]];
case 146:
return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]];
default: default:
return [super readValueOfType:type]; return [super readValueOfType:type];
@ -2246,60 +2345,63 @@ NSObject<FlutterMessageCodec> *FWFNSObjectFlutterApiGetCodec(void) {
@end @end
@implementation FWFWKWebViewHostApiCodecWriter @implementation FWFWKWebViewHostApiCodecWriter
- (void)writeValue:(id)value { - (void)writeValue:(id)value {
if ([value isKindOfClass:[FWFNSErrorData class]]) { if ([value isKindOfClass:[FWFAuthenticationChallengeResponse class]]) {
[self writeByte:128]; [self writeByte:128];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) { } else if ([value isKindOfClass:[FWFNSErrorData class]]) {
[self writeByte:129]; [self writeByte:129];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { } else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) {
[self writeByte:130]; [self writeByte:130];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) {
[self writeByte:131]; [self writeByte:131];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) {
[self writeByte:132]; [self writeByte:132];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) {
[self writeByte:133]; [self writeByte:133];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) { } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
[self writeByte:134]; [self writeByte:134];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { } else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) {
[self writeByte:135]; [self writeByte:135];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) {
[self writeByte:136]; [self writeByte:136];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) { } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
[self writeByte:137]; [self writeByte:137];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { } else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) {
[self writeByte:138]; [self writeByte:138];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
[self writeByte:139]; [self writeByte:139];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) { } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
[self writeByte:140]; [self writeByte:140];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { } else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) {
[self writeByte:141]; [self writeByte:141];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) { } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) {
[self writeByte:142]; [self writeByte:142];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { } else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) {
[self writeByte:143]; [self writeByte:143];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) {
[self writeByte:144]; [self writeByte:144];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) {
[self writeByte:145]; [self writeByte:145];
[self writeValue:[value toList]]; [self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) {
[self writeByte:146];
[self writeValue:[value toList]];
} else { } else {
[super writeValue:value]; [super writeValue:value];
} }
@ -3181,3 +3283,143 @@ NSObject<FlutterMessageCodec> *FWFNSUrlFlutterApiGetCodec(void) {
}]; }];
} }
@end @end
NSObject<FlutterMessageCodec> *FWFNSUrlCredentialHostApiGetCodec(void) {
static FlutterStandardMessageCodec *sSharedObject = nil;
sSharedObject = [FlutterStandardMessageCodec sharedInstance];
return sSharedObject;
}
void SetUpFWFNSUrlCredentialHostApi(id<FlutterBinaryMessenger> binaryMessenger,
NSObject<FWFNSUrlCredentialHostApi> *api) {
/// Create a new native instance and add it to the `InstanceManager`.
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
initWithName:
@"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser"
binaryMessenger:binaryMessenger
codec:FWFNSUrlCredentialHostApiGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector
(createWithUserWithIdentifier:user:password:persistence:error:)],
@"FWFNSUrlCredentialHostApi api (%@) doesn't respond to "
@"@selector(createWithUserWithIdentifier:user:password:persistence:error:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
NSInteger arg_identifier = [GetNullableObjectAtIndex(args, 0) integerValue];
NSString *arg_user = GetNullableObjectAtIndex(args, 1);
NSString *arg_password = GetNullableObjectAtIndex(args, 2);
FWFNSUrlCredentialPersistence arg_persistence =
[GetNullableObjectAtIndex(args, 3) integerValue];
FlutterError *error;
[api createWithUserWithIdentifier:arg_identifier
user:arg_user
password:arg_password
persistence:arg_persistence
error:&error];
callback(wrapResult(nil, error));
}];
} else {
[channel setMessageHandler:nil];
}
}
}
NSObject<FlutterMessageCodec> *FWFNSUrlProtectionSpaceFlutterApiGetCodec(void) {
static FlutterStandardMessageCodec *sSharedObject = nil;
sSharedObject = [FlutterStandardMessageCodec sharedInstance];
return sSharedObject;
}
@interface FWFNSUrlProtectionSpaceFlutterApi ()
@property(nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;
@end
@implementation FWFNSUrlProtectionSpaceFlutterApi
- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger {
self = [super init];
if (self) {
_binaryMessenger = binaryMessenger;
}
return self;
}
- (void)createWithIdentifier:(NSInteger)arg_identifier
host:(nullable NSString *)arg_host
realm:(nullable NSString *)arg_realm
authenticationMethod:(nullable NSString *)arg_authenticationMethod
completion:(void (^)(FlutterError *_Nullable))completion {
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
messageChannelWithName:
@"dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create"
binaryMessenger:self.binaryMessenger
codec:FWFNSUrlProtectionSpaceFlutterApiGetCodec()];
[channel
sendMessage:@[
@(arg_identifier), arg_host ?: [NSNull null], arg_realm ?: [NSNull null],
arg_authenticationMethod ?: [NSNull null]
]
reply:^(NSArray<id> *reply) {
if (reply != nil) {
if (reply.count > 1) {
completion([FlutterError errorWithCode:reply[0]
message:reply[1]
details:reply[2]]);
} else {
completion(nil);
}
} else {
completion([FlutterError errorWithCode:@"channel-error"
message:@"Unable to establish connection on channel."
details:@""]);
}
}];
}
@end
NSObject<FlutterMessageCodec> *FWFNSUrlAuthenticationChallengeFlutterApiGetCodec(void) {
static FlutterStandardMessageCodec *sSharedObject = nil;
sSharedObject = [FlutterStandardMessageCodec sharedInstance];
return sSharedObject;
}
@interface FWFNSUrlAuthenticationChallengeFlutterApi ()
@property(nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;
@end
@implementation FWFNSUrlAuthenticationChallengeFlutterApi
- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger {
self = [super init];
if (self) {
_binaryMessenger = binaryMessenger;
}
return self;
}
- (void)createWithIdentifier:(NSInteger)arg_identifier
protectionSpaceIdentifier:(NSInteger)arg_protectionSpaceIdentifier
completion:(void (^)(FlutterError *_Nullable))completion {
FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel
messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview."
@"NSUrlAuthenticationChallengeFlutterApi.create"
binaryMessenger:self.binaryMessenger
codec:FWFNSUrlAuthenticationChallengeFlutterApiGetCodec()];
[channel
sendMessage:@[ @(arg_identifier), @(arg_protectionSpaceIdentifier) ]
reply:^(NSArray<id> *reply) {
if (reply != nil) {
if (reply.count > 1) {
completion([FlutterError errorWithCode:reply[0]
message:reply[1]
details:reply[2]]);
} else {
completion(nil);
}
} else {
completion([FlutterError errorWithCode:@"channel-error"
message:@"Unable to establish connection on channel."
details:@""]);
}
}];
}
@end

View File

@ -4,9 +4,13 @@
#import "FWFNavigationDelegateHostApi.h" #import "FWFNavigationDelegateHostApi.h"
#import "FWFDataConverters.h" #import "FWFDataConverters.h"
#import "FWFURLAuthenticationChallengeHostApi.h"
#import "FWFWebViewConfigurationHostApi.h" #import "FWFWebViewConfigurationHostApi.h"
@interface FWFNavigationDelegateFlutterApiImpl () @interface FWFNavigationDelegateFlutterApiImpl ()
// BinaryMessenger must be weak to prevent a circular reference with the host API it
// references.
@property(nonatomic, weak) id<FlutterBinaryMessenger> binaryMessenger;
// InstanceManager must be weak to prevent a circular reference with the object it stores. // InstanceManager must be weak to prevent a circular reference with the object it stores.
@property(nonatomic, weak) FWFInstanceManager *instanceManager; @property(nonatomic, weak) FWFInstanceManager *instanceManager;
@end @end
@ -16,6 +20,7 @@
instanceManager:(FWFInstanceManager *)instanceManager { instanceManager:(FWFInstanceManager *)instanceManager {
self = [self initWithBinaryMessenger:binaryMessenger]; self = [self initWithBinaryMessenger:binaryMessenger];
if (self) { if (self) {
_binaryMessenger = binaryMessenger;
_instanceManager = instanceManager; _instanceManager = instanceManager;
} }
return self; return self;
@ -102,6 +107,37 @@
webViewIdentifier:webViewIdentifier webViewIdentifier:webViewIdentifier
completion:completion]; completion:completion];
} }
- (void)
didReceiveAuthenticationChallengeForDelegate:(FWFNavigationDelegate *)instance
webView:(WKWebView *)webView
challenge:(NSURLAuthenticationChallenge *)challenge
completion:
(void (^)(FWFAuthenticationChallengeResponse *_Nullable,
FlutterError *_Nullable))completion {
NSInteger webViewIdentifier =
[self.instanceManager identifierWithStrongReferenceForInstance:webView];
FWFURLAuthenticationChallengeFlutterApiImpl *challengeApi =
[[FWFURLAuthenticationChallengeFlutterApiImpl alloc]
initWithBinaryMessenger:self.binaryMessenger
instanceManager:self.instanceManager];
[challengeApi createWithInstance:challenge
protectionSpace:challenge.protectionSpace
completion:^(FlutterError *error) {
NSAssert(!error, @"%@", error);
}];
[self
didReceiveAuthenticationChallengeForDelegateWithIdentifier:[self
identifierForDelegate:instance]
webViewIdentifier:webViewIdentifier
challengeIdentifier:
[self.instanceManager
identifierWithStrongReferenceForInstance:
challenge]
completion:completion];
}
@end @end
@implementation FWFNavigationDelegate @implementation FWFNavigationDelegate
@ -144,8 +180,13 @@
completion:^(FWFWKNavigationActionPolicyEnumData *policy, completion:^(FWFWKNavigationActionPolicyEnumData *policy,
FlutterError *error) { FlutterError *error) {
NSAssert(!error, @"%@", error); NSAssert(!error, @"%@", error);
if (!error) {
decisionHandler( decisionHandler(
FWFNativeWKNavigationActionPolicyFromEnumData(policy)); FWFNativeWKNavigationActionPolicyFromEnumData(
policy));
} else {
decisionHandler(WKNavigationActionPolicyCancel);
}
}]; }];
} }
@ -179,6 +220,40 @@
NSAssert(!error, @"%@", error); NSAssert(!error, @"%@", error);
}]; }];
} }
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
NSURLCredential *_Nullable))completionHandler {
[self.navigationDelegateAPI
didReceiveAuthenticationChallengeForDelegate:self
webView:webView
challenge:challenge
completion:^(FWFAuthenticationChallengeResponse *response,
FlutterError *error) {
NSAssert(!error, @"%@", error);
if (!error) {
NSURLSessionAuthChallengeDisposition disposition =
FWFNativeNSURLSessionAuthChallengeDispositionFromFWFNSUrlSessionAuthChallengeDisposition(
response.disposition);
NSURLCredential *credential =
response.credentialIdentifier
? (NSURLCredential *)[self.navigationDelegateAPI
.instanceManager
instanceForIdentifier:
response.credentialIdentifier
.longValue]
: nil;
completionHandler(disposition, credential);
} else {
completionHandler(
NSURLSessionAuthChallengeCancelAuthenticationChallenge,
nil);
}
}];
}
@end @end
@interface FWFNavigationDelegateHostApiImpl () @interface FWFNavigationDelegateHostApiImpl ()

View File

@ -0,0 +1,33 @@
// 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 <Flutter/Flutter.h>
#import <Foundation/Foundation.h>
#import "FWFGeneratedWebKitApis.h"
#import "FWFInstanceManager.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Flutter API implementation for `NSURLAuthenticationChallenge`.
*
* This class may handle instantiating and adding Dart instances that are attached to a native
* instance or sending callback methods from an overridden native class.
*/
@interface FWFURLAuthenticationChallengeFlutterApiImpl : NSObject
/**
* The Flutter API used to send messages back to Dart.
*/
@property FWFNSUrlAuthenticationChallengeFlutterApi *api;
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager;
/**
* Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`.
*/
- (void)createWithInstance:(NSURLAuthenticationChallenge *)instance
protectionSpace:(NSURLProtectionSpace *)protectionSpace
completion:(void (^)(FlutterError *_Nullable))completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
// 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 "FWFURLAuthenticationChallengeHostApi.h"
#import "FWFURLProtectionSpaceHostApi.h"
@interface FWFURLAuthenticationChallengeFlutterApiImpl ()
// BinaryMessenger must be weak to prevent a circular reference with the host API it
// references.
@property(nonatomic, weak) id<FlutterBinaryMessenger> binaryMessenger;
// InstanceManager must be weak to prevent a circular reference with the object it stores.
@property(nonatomic, weak) FWFInstanceManager *instanceManager;
@end
@implementation FWFURLAuthenticationChallengeFlutterApiImpl
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager {
self = [self init];
if (self) {
_binaryMessenger = binaryMessenger;
_instanceManager = instanceManager;
_api =
[[FWFNSUrlAuthenticationChallengeFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
}
return self;
}
- (void)createWithInstance:(NSURLAuthenticationChallenge *)instance
protectionSpace:(NSURLProtectionSpace *)protectionSpace
completion:(void (^)(FlutterError *_Nullable))completion {
if ([self.instanceManager containsInstance:instance]) {
return;
}
FWFURLProtectionSpaceFlutterApiImpl *protectionSpaceApi =
[[FWFURLProtectionSpaceFlutterApiImpl alloc] initWithBinaryMessenger:self.binaryMessenger
instanceManager:self.instanceManager];
[protectionSpaceApi createWithInstance:protectionSpace
host:protectionSpace.host
realm:protectionSpace.realm
authenticationMethod:protectionSpace.authenticationMethod
completion:^(FlutterError *error) {
NSAssert(!error, @"%@", error);
}];
[self.api createWithIdentifier:[self.instanceManager addHostCreatedInstance:instance]
protectionSpaceIdentifier:[self.instanceManager
identifierWithStrongReferenceForInstance:protectionSpace]
completion:completion];
}
@end

View File

@ -0,0 +1,24 @@
// 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 <Flutter/Flutter.h>
#import <Foundation/Foundation.h>
#import "FWFDataConverters.h"
#import "FWFGeneratedWebKitApis.h"
#import "FWFInstanceManager.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Host API implementation for `NSURLCredential`.
*
* This class may handle instantiating and adding native object instances that are attached to a
* Dart instance or method calls on the associated native class or an instance of the class.
*/
@interface FWFURLCredentialHostApiImpl : NSObject <FWFNSUrlCredentialHostApi>
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,58 @@
// 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 "FWFURLCredentialHostApi.h"
@interface FWFURLCredentialHostApiImpl ()
// BinaryMessenger must be weak to prevent a circular reference with the host API it
// references.
@property(nonatomic, weak) id<FlutterBinaryMessenger> binaryMessenger;
// InstanceManager must be weak to prevent a circular reference with the object it stores.
@property(nonatomic, weak) FWFInstanceManager *instanceManager;
@end
@implementation FWFURLCredentialHostApiImpl
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager {
self = [self init];
if (self) {
_binaryMessenger = binaryMessenger;
_instanceManager = instanceManager;
}
return self;
}
- (void)createWithUserWithIdentifier:(NSInteger)identifier
user:(nonnull NSString *)user
password:(nonnull NSString *)password
persistence:(FWFNSUrlCredentialPersistence)persistence
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
[self.instanceManager
addDartCreatedInstance:
[NSURLCredential
credentialWithUser:user
password:password
persistence:
FWFNativeNSURLCredentialPersistenceFromFWFNSUrlCredentialPersistence(
persistence)]
withIdentifier:identifier];
}
- (nullable NSURL *)credentialForIdentifier:(NSNumber *)identifier
error:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSURL *instance = (NSURL *)[self.instanceManager instanceForIdentifier:identifier.longValue];
if (!instance) {
NSString *message =
[NSString stringWithFormat:@"InstanceManager does not contain an NSURL with identifier: %@",
identifier];
*error = [FlutterError errorWithCode:NSInternalInconsistencyException
message:message
details:nil];
}
return instance;
}
@end

View File

@ -0,0 +1,35 @@
// 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 <Flutter/Flutter.h>
#import <Foundation/Foundation.h>
#import "FWFGeneratedWebKitApis.h"
#import "FWFInstanceManager.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Flutter API implementation for `NSURLProtectionSpace`.
*
* This class may handle instantiating and adding Dart instances that are attached to a native
* instance or sending callback methods from an overridden native class.
*/
@interface FWFURLProtectionSpaceFlutterApiImpl : NSObject
/**
* The Flutter API used to send messages back to Dart.
*/
@property FWFNSUrlProtectionSpaceFlutterApi *api;
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager;
/**
* Sends a message to Dart to create a new Dart instance and add it to the `InstanceManager`.
*/
- (void)createWithInstance:(NSURLProtectionSpace *)instance
host:(nullable NSString *)host
realm:(nullable NSString *)realm
authenticationMethod:(nullable NSString *)authenticationMethod
completion:(void (^)(FlutterError *_Nullable))completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,36 @@
// 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 "FWFURLProtectionSpaceHostApi.h"
@interface FWFURLProtectionSpaceFlutterApiImpl ()
// InstanceManager must be weak to prevent a circular reference with the object it stores.
@property(nonatomic, weak) FWFInstanceManager *instanceManager;
@end
@implementation FWFURLProtectionSpaceFlutterApiImpl
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
instanceManager:(FWFInstanceManager *)instanceManager {
self = [self init];
if (self) {
_instanceManager = instanceManager;
_api = [[FWFNSUrlProtectionSpaceFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
}
return self;
}
- (void)createWithInstance:(NSURLProtectionSpace *)instance
host:(nullable NSString *)host
realm:(nullable NSString *)realm
authenticationMethod:(nullable NSString *)authenticationMethod
completion:(void (^)(FlutterError *_Nullable))completion {
if (![self.instanceManager containsInstance:instance]) {
[self.api createWithIdentifier:[self.instanceManager addHostCreatedInstance:instance]
host:host
realm:realm
authenticationMethod:authenticationMethod
completion:completion];
}
}
@end

View File

@ -16,7 +16,10 @@
#import "FWFScrollViewHostApi.h" #import "FWFScrollViewHostApi.h"
#import "FWFUIDelegateHostApi.h" #import "FWFUIDelegateHostApi.h"
#import "FWFUIViewHostApi.h" #import "FWFUIViewHostApi.h"
#import "FWFURLAuthenticationChallengeHostApi.h"
#import "FWFURLCredentialHostApi.h"
#import "FWFURLHostApi.h" #import "FWFURLHostApi.h"
#import "FWFURLProtectionSpaceHostApi.h"
#import "FWFUserContentControllerHostApi.h" #import "FWFUserContentControllerHostApi.h"
#import "FWFWebViewConfigurationHostApi.h" #import "FWFWebViewConfigurationHostApi.h"
#import "FWFWebViewFlutterWKWebViewExternalAPI.h" #import "FWFWebViewFlutterWKWebViewExternalAPI.h"

View File

@ -202,6 +202,58 @@ enum WKMediaCaptureType {
unknown, unknown,
} }
/// Responses to an authentication challenge.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition?language=objc.
enum NSUrlSessionAuthChallengeDisposition {
/// Use the specified credential, which may be nil.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeusecredential?language=objc.
useCredential,
/// Use the default handling for the challenge as though this delegate method
/// were not implemented.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeperformdefaulthandling?language=objc.
performDefaultHandling,
/// Cancel the entire request.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengecancelauthenticationchallenge?language=objc.
cancelAuthenticationChallenge,
/// Reject this challenge, and call the authentication delegate method again
/// with the next authentication protection space.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengerejectprotectionspace?language=objc.
rejectProtectionSpace,
}
/// Specifies how long a credential will be kept.
enum NSUrlCredentialPersistence {
/// The credential should not be stored.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencenone?language=objc.
none,
/// The credential should be stored only for this session.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistenceforsession?language=objc.
session,
/// The credential should be stored in the keychain.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencepermanent?language=objc.
permanent,
/// The credential should be stored permanently in the keychain, and in
/// addition should be distributed to other devices based on the owning Apple
/// ID.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencesynchronizable?language=objc.
synchronizable,
}
class NSKeyValueObservingOptionsEnumData { class NSKeyValueObservingOptionsEnumData {
NSKeyValueObservingOptionsEnumData({ NSKeyValueObservingOptionsEnumData({
required this.value, required this.value,
@ -684,6 +736,33 @@ class ObjectOrIdentifier {
} }
} }
class AuthenticationChallengeResponse {
AuthenticationChallengeResponse({
required this.disposition,
this.credentialIdentifier,
});
NSUrlSessionAuthChallengeDisposition disposition;
int? credentialIdentifier;
Object encode() {
return <Object?>[
disposition.index,
credentialIdentifier,
];
}
static AuthenticationChallengeResponse decode(Object result) {
result as List<Object?>;
return AuthenticationChallengeResponse(
disposition:
NSUrlSessionAuthChallengeDisposition.values[result[0]! as int],
credentialIdentifier: result[1] as int?,
);
}
}
class _WKWebsiteDataStoreHostApiCodec extends StandardMessageCodec { class _WKWebsiteDataStoreHostApiCodec extends StandardMessageCodec {
const _WKWebsiteDataStoreHostApiCodec(); const _WKWebsiteDataStoreHostApiCodec();
@override @override
@ -1576,21 +1655,24 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec {
const _WKNavigationDelegateFlutterApiCodec(); const _WKNavigationDelegateFlutterApiCodec();
@override @override
void writeValue(WriteBuffer buffer, Object? value) { void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) { if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128); buffer.putUint8(128);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) { } else if (value is NSErrorData) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) { } else if (value is NSUrlRequestData) {
buffer.putUint8(130); buffer.putUint8(130);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) { } else if (value is WKFrameInfoData) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) { } else if (value is WKNavigationActionData) {
buffer.putUint8(132); buffer.putUint8(132);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -1600,14 +1682,16 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 128: case 128:
return NSErrorData.decode(readValue(buffer)!); return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129: case 129:
return NSUrlRequestData.decode(readValue(buffer)!); return NSErrorData.decode(readValue(buffer)!);
case 130: case 130:
return WKFrameInfoData.decode(readValue(buffer)!); return NSUrlRequestData.decode(readValue(buffer)!);
case 131: case 131:
return WKNavigationActionData.decode(readValue(buffer)!); return WKFrameInfoData.decode(readValue(buffer)!);
case 132: case 132:
return WKNavigationActionData.decode(readValue(buffer)!);
case 133:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@ -1641,6 +1725,9 @@ abstract class WKNavigationDelegateFlutterApi {
void webViewWebContentProcessDidTerminate( void webViewWebContentProcessDidTerminate(
int identifier, int webViewIdentifier); int identifier, int webViewIdentifier);
Future<AuthenticationChallengeResponse> didReceiveAuthenticationChallenge(
int identifier, int webViewIdentifier, int challengeIdentifier);
static void setup(WKNavigationDelegateFlutterApi? api, static void setup(WKNavigationDelegateFlutterApi? api,
{BinaryMessenger? binaryMessenger}) { {BinaryMessenger? binaryMessenger}) {
{ {
@ -1842,6 +1929,41 @@ abstract class WKNavigationDelegateFlutterApi {
}); });
} }
} }
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge was null, expected non-null int.');
final int? arg_webViewIdentifier = (args[1] as int?);
assert(arg_webViewIdentifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge was null, expected non-null int.');
final int? arg_challengeIdentifier = (args[2] as int?);
assert(arg_challengeIdentifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegateFlutterApi.didReceiveAuthenticationChallenge was null, expected non-null int.');
try {
final AuthenticationChallengeResponse output =
await api.didReceiveAuthenticationChallenge(arg_identifier!,
arg_webViewIdentifier!, arg_challengeIdentifier!);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
} }
} }
@ -2082,60 +2204,63 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec {
const _WKWebViewHostApiCodec(); const _WKWebViewHostApiCodec();
@override @override
void writeValue(WriteBuffer buffer, Object? value) { void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) { if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128); buffer.putUint8(128);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSHttpCookieData) { } else if (value is NSErrorData) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSHttpCookiePropertyKeyEnumData) { } else if (value is NSHttpCookieData) {
buffer.putUint8(130); buffer.putUint8(130);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSKeyValueChangeKeyEnumData) { } else if (value is NSHttpCookiePropertyKeyEnumData) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSKeyValueObservingOptionsEnumData) { } else if (value is NSKeyValueChangeKeyEnumData) {
buffer.putUint8(132); buffer.putUint8(132);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) { } else if (value is NSKeyValueObservingOptionsEnumData) {
buffer.putUint8(133); buffer.putUint8(133);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is ObjectOrIdentifier) { } else if (value is NSUrlRequestData) {
buffer.putUint8(134); buffer.putUint8(134);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKAudiovisualMediaTypeEnumData) { } else if (value is ObjectOrIdentifier) {
buffer.putUint8(135); buffer.putUint8(135);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) { } else if (value is WKAudiovisualMediaTypeEnumData) {
buffer.putUint8(136); buffer.putUint8(136);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKMediaCaptureTypeData) { } else if (value is WKFrameInfoData) {
buffer.putUint8(137); buffer.putUint8(137);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) { } else if (value is WKMediaCaptureTypeData) {
buffer.putUint8(138); buffer.putUint8(138);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) { } else if (value is WKNavigationActionData) {
buffer.putUint8(139); buffer.putUint8(139);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKPermissionDecisionData) { } else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(140); buffer.putUint8(140);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKScriptMessageData) { } else if (value is WKPermissionDecisionData) {
buffer.putUint8(141); buffer.putUint8(141);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKSecurityOriginData) { } else if (value is WKScriptMessageData) {
buffer.putUint8(142); buffer.putUint8(142);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKUserScriptData) { } else if (value is WKSecurityOriginData) {
buffer.putUint8(143); buffer.putUint8(143);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKUserScriptInjectionTimeEnumData) { } else if (value is WKUserScriptData) {
buffer.putUint8(144); buffer.putUint8(144);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) { } else if (value is WKUserScriptInjectionTimeEnumData) {
buffer.putUint8(145); buffer.putUint8(145);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
buffer.putUint8(146);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -2145,40 +2270,42 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 128: case 128:
return NSErrorData.decode(readValue(buffer)!); return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129: case 129:
return NSHttpCookieData.decode(readValue(buffer)!); return NSErrorData.decode(readValue(buffer)!);
case 130: case 130:
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); return NSHttpCookieData.decode(readValue(buffer)!);
case 131: case 131:
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
case 132: case 132:
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
case 133: case 133:
return NSUrlRequestData.decode(readValue(buffer)!); return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
case 134: case 134:
return ObjectOrIdentifier.decode(readValue(buffer)!); return NSUrlRequestData.decode(readValue(buffer)!);
case 135: case 135:
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); return ObjectOrIdentifier.decode(readValue(buffer)!);
case 136: case 136:
return WKFrameInfoData.decode(readValue(buffer)!); return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
case 137: case 137:
return WKMediaCaptureTypeData.decode(readValue(buffer)!); return WKFrameInfoData.decode(readValue(buffer)!);
case 138: case 138:
return WKNavigationActionData.decode(readValue(buffer)!); return WKMediaCaptureTypeData.decode(readValue(buffer)!);
case 139: case 139:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); return WKNavigationActionData.decode(readValue(buffer)!);
case 140: case 140:
return WKPermissionDecisionData.decode(readValue(buffer)!); return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
case 141: case 141:
return WKScriptMessageData.decode(readValue(buffer)!); return WKPermissionDecisionData.decode(readValue(buffer)!);
case 142: case 142:
return WKSecurityOriginData.decode(readValue(buffer)!); return WKScriptMessageData.decode(readValue(buffer)!);
case 143: case 143:
return WKUserScriptData.decode(readValue(buffer)!); return WKSecurityOriginData.decode(readValue(buffer)!);
case 144: case 144:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); return WKUserScriptData.decode(readValue(buffer)!);
case 145: case 145:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
case 146:
return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@ -3052,3 +3179,148 @@ abstract class NSUrlFlutterApi {
} }
} }
} }
/// Host API for `NSUrlCredential`.
///
/// This class may handle instantiating and adding native object instances that
/// are attached to a Dart instance or handle method calls on the associated
/// native class or an instance of the class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredential?language=objc.
class NSUrlCredentialHostApi {
/// Constructor for [NSUrlCredentialHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
NSUrlCredentialHostApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = StandardMessageCodec();
/// Create a new native instance and add it to the `InstanceManager`.
Future<void> createWithUser(int arg_identifier, String arg_user,
String arg_password, NSUrlCredentialPersistence arg_persistence) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(<Object?>[
arg_identifier,
arg_user,
arg_password,
arg_persistence.index
]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
/// Flutter API for `NSUrlProtectionSpace`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlprotectionspace?language=objc.
abstract class NSUrlProtectionSpaceFlutterApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
/// Create a new Dart instance and add it to the `InstanceManager`.
void create(int identifier, String? host, String? realm,
String? authenticationMethod);
static void setup(NSUrlProtectionSpaceFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlProtectionSpaceFlutterApi.create was null, expected non-null int.');
final String? arg_host = (args[1] as String?);
final String? arg_realm = (args[2] as String?);
final String? arg_authenticationMethod = (args[3] as String?);
try {
api.create(
arg_identifier!, arg_host, arg_realm, arg_authenticationMethod);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
}
}
/// Flutter API for `NSUrlAuthenticationChallenge`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlauthenticationchallenge?language=objc.
abstract class NSUrlAuthenticationChallengeFlutterApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
/// Create a new Dart instance and add it to the `InstanceManager`.
void create(int identifier, int protectionSpaceIdentifier);
static void setup(NSUrlAuthenticationChallengeFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlAuthenticationChallengeFlutterApi.create',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlAuthenticationChallengeFlutterApi.create was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlAuthenticationChallengeFlutterApi.create was null, expected non-null int.');
final int? arg_protectionSpaceIdentifier = (args[1] as int?);
assert(arg_protectionSpaceIdentifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlAuthenticationChallengeFlutterApi.create was null, expected non-null int.');
try {
api.create(arg_identifier!, arg_protectionSpaceIdentifier!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
}
}

View File

@ -9,6 +9,9 @@ import '../common/instance_manager.dart';
import '../common/weak_reference_utils.dart'; import '../common/weak_reference_utils.dart';
import 'foundation_api_impls.dart'; import 'foundation_api_impls.dart';
export 'foundation_api_impls.dart'
show NSUrlSessionAuthChallengeDisposition, NSUrlCredentialPersistence;
/// The values that can be returned in a change map. /// The values that can be returned in a change map.
/// ///
/// Wraps [NSKeyValueObservingOptions](https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc). /// Wraps [NSKeyValueObservingOptions](https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc).
@ -387,3 +390,131 @@ class NSObject with Copyable {
); );
} }
} }
/// An authentication credential consisting of information specific to the type
/// of credential and the type of persistent storage to use, if any.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredential?language=objc.
class NSUrlCredential extends NSObject {
/// Creates a URL credential instance for internet password authentication
/// with a given user name and password, using a given persistence setting.
NSUrlCredential.withUser({
required String user,
required String password,
required NSUrlCredentialPersistence persistence,
@visibleForTesting super.binaryMessenger,
@visibleForTesting super.instanceManager,
}) : _urlCredentialApi = NSUrlCredentialHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager),
super.detached() {
// Ensures Flutter Apis are setup.
FoundationFlutterApis.instance.ensureSetUp();
_urlCredentialApi.createWithUserFromInstances(
this,
user,
password,
persistence,
);
}
/// Instantiates a [NSUrlCredential] without creating and attaching to an
/// instance of the associated native class.
///
/// This should only be used outside of tests by subclasses created by this
/// library or to create a copy for an [InstanceManager].
@protected
NSUrlCredential.detached({super.binaryMessenger, super.instanceManager})
: _urlCredentialApi = NSUrlCredentialHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager),
super.detached();
final NSUrlCredentialHostApiImpl _urlCredentialApi;
@override
NSObject copy() {
return NSUrlCredential.detached(
binaryMessenger: _urlCredentialApi.binaryMessenger,
instanceManager: _urlCredentialApi.instanceManager,
);
}
}
/// A server or an area on a server, commonly referred to as a realm, that
/// requires authentication.
///
/// See https://developer.apple.com/documentation/foundation/nsurlprotectionspace?language=objc.
class NSUrlProtectionSpace extends NSObject {
/// Instantiates a [NSUrlProtectionSpace] without creating and attaching to an
/// instance of the associated native class.
///
/// This should only be used outside of tests by subclasses created by this
/// library or to create a copy for an [InstanceManager].
@protected
NSUrlProtectionSpace.detached({
required this.host,
required this.realm,
required this.authenticationMethod,
super.binaryMessenger,
super.instanceManager,
}) : super.detached();
/// The receivers host.
final String? host;
/// The receivers authentication realm.
final String? realm;
/// The authentication method used by the receiver.
final String? authenticationMethod;
@override
NSUrlProtectionSpace copy() {
return NSUrlProtectionSpace.detached(
host: host,
realm: realm,
authenticationMethod: authenticationMethod,
);
}
}
/// The authentication method used by the receiver.
class NSUrlAuthenticationMethod {
/// Use the default authentication method for a protocol.
static const String default_ = 'NSURLAuthenticationMethodDefault';
/// Use HTML form authentication for this protection space.
static const String htmlForm = 'NSURLAuthenticationMethodHTMLForm';
/// Use HTTP basic authentication for this protection space.
static const String httpBasic = 'NSURLAuthenticationMethodHTTPBasic';
/// Use HTTP digest authentication for this protection space.
static const String httpDigest = 'NSURLAuthenticationMethodHTTPDigest';
}
/// A challenge from a server requiring authentication from the client.
///
/// See https://developer.apple.com/documentation/foundation/nsurlauthenticationchallenge?language=objc.
class NSUrlAuthenticationChallenge extends NSObject {
/// Instantiates a [NSUrlAuthenticationChallenge] without creating and
/// attaching to an instance of the associated native class.
///
/// This should only be used outside of tests by subclasses created by this
/// library or to create a copy for an [InstanceManager].
@protected
NSUrlAuthenticationChallenge.detached({
required this.protectionSpace,
super.binaryMessenger,
super.instanceManager,
}) : super.detached();
/// The receivers protection space.
late final NSUrlProtectionSpace protectionSpace;
@override
NSUrlAuthenticationChallenge copy() {
return NSUrlAuthenticationChallenge.detached(
protectionSpace: protectionSpace,
);
}
}

View File

@ -9,6 +9,9 @@ import '../common/instance_manager.dart';
import '../common/web_kit.g.dart'; import '../common/web_kit.g.dart';
import 'foundation.dart'; import 'foundation.dart';
export '../common/web_kit.g.dart'
show NSUrlSessionAuthChallengeDisposition, NSUrlCredentialPersistence;
Iterable<NSKeyValueObservingOptionsEnumData> Iterable<NSKeyValueObservingOptionsEnumData>
_toNSKeyValueObservingOptionsEnumData( _toNSKeyValueObservingOptionsEnumData(
Iterable<NSKeyValueObservingOptions> options, Iterable<NSKeyValueObservingOptions> options,
@ -56,6 +59,14 @@ class FoundationFlutterApis {
url = NSUrlFlutterApiImpl( url = NSUrlFlutterApiImpl(
binaryMessenger: binaryMessenger, binaryMessenger: binaryMessenger,
instanceManager: instanceManager, instanceManager: instanceManager,
),
urlProtectionSpace = NSUrlProtectionSpaceFlutterApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
urlAuthenticationChallenge = NSUrlAuthenticationChallengeFlutterApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
); );
static FoundationFlutterApis _instance = FoundationFlutterApis(); static FoundationFlutterApis _instance = FoundationFlutterApis();
@ -82,6 +93,14 @@ class FoundationFlutterApis {
@visibleForTesting @visibleForTesting
final NSUrlFlutterApiImpl url; final NSUrlFlutterApiImpl url;
/// Flutter Api for [NSUrlProtectionSpace].
@visibleForTesting
final NSUrlProtectionSpaceFlutterApiImpl urlProtectionSpace;
/// Flutter Api for [NSUrlAuthenticationChallenge].
@visibleForTesting
final NSUrlAuthenticationChallengeFlutterApiImpl urlAuthenticationChallenge;
/// Ensures all the Flutter APIs have been set up to receive calls from native code. /// Ensures all the Flutter APIs have been set up to receive calls from native code.
void ensureSetUp() { void ensureSetUp() {
if (!_hasBeenSetUp) { if (!_hasBeenSetUp) {
@ -90,6 +109,14 @@ class FoundationFlutterApis {
binaryMessenger: _binaryMessenger, binaryMessenger: _binaryMessenger,
); );
NSUrlFlutterApi.setup(url, binaryMessenger: _binaryMessenger); NSUrlFlutterApi.setup(url, binaryMessenger: _binaryMessenger);
NSUrlProtectionSpaceFlutterApi.setup(
urlProtectionSpace,
binaryMessenger: _binaryMessenger,
);
NSUrlAuthenticationChallengeFlutterApi.setup(
urlAuthenticationChallenge,
binaryMessenger: _binaryMessenger,
);
_hasBeenSetUp = true; _hasBeenSetUp = true;
} }
} }
@ -249,3 +276,121 @@ class NSUrlFlutterApiImpl implements NSUrlFlutterApi {
); );
} }
} }
/// Host api implementation for [NSUrlCredential].
class NSUrlCredentialHostApiImpl extends NSUrlCredentialHostApi {
/// Constructs an [NSUrlCredentialHostApiImpl].
NSUrlCredentialHostApiImpl({
this.binaryMessenger,
InstanceManager? instanceManager,
}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager,
super(binaryMessenger: binaryMessenger);
/// Sends binary data across the Flutter platform barrier.
///
/// If it is null, the default BinaryMessenger will be used which routes to
/// the host platform.
final BinaryMessenger? binaryMessenger;
/// Maintains instances stored to communicate with Objective-C objects.
final InstanceManager instanceManager;
/// Calls [createWithUser] with the ids of the provided object instances.
Future<void> createWithUserFromInstances(
NSUrlCredential instance,
String user,
String password,
NSUrlCredentialPersistence persistence,
) {
return createWithUser(
instanceManager.addDartCreatedInstance(instance),
user,
password,
persistence,
);
}
}
/// Flutter API implementation for [NSUrlProtectionSpace].
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
@protected
class NSUrlProtectionSpaceFlutterApiImpl
implements NSUrlProtectionSpaceFlutterApi {
/// Constructs a [NSUrlProtectionSpaceFlutterApiImpl].
NSUrlProtectionSpaceFlutterApiImpl({
this.binaryMessenger,
InstanceManager? instanceManager,
}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager;
/// Receives binary data across the Flutter platform barrier.
///
/// If it is null, the default BinaryMessenger will be used which routes to
/// the host platform.
final BinaryMessenger? binaryMessenger;
/// Maintains instances stored to communicate with native language objects.
final InstanceManager instanceManager;
@override
void create(
int identifier,
String? host,
String? realm,
String? authenticationMethod,
) {
instanceManager.addHostCreatedInstance(
NSUrlProtectionSpace.detached(
host: host,
realm: realm,
authenticationMethod: authenticationMethod,
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
identifier,
);
}
}
/// Flutter API implementation for [NSUrlAuthenticationChallenge].
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
@protected
class NSUrlAuthenticationChallengeFlutterApiImpl
implements NSUrlAuthenticationChallengeFlutterApi {
/// Constructs a [NSUrlAuthenticationChallengeFlutterApiImpl].
NSUrlAuthenticationChallengeFlutterApiImpl({
this.binaryMessenger,
InstanceManager? instanceManager,
}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager;
/// Receives binary data across the Flutter platform barrier.
///
/// If it is null, the default BinaryMessenger will be used which routes to
/// the host platform.
final BinaryMessenger? binaryMessenger;
/// Maintains instances stored to communicate with native language objects.
final InstanceManager instanceManager;
@override
void create(
int identifier,
int protectionSpaceIdentifier,
) {
instanceManager.addHostCreatedInstance(
NSUrlAuthenticationChallenge.detached(
protectionSpace: instanceManager.getInstanceWithWeakReference(
protectionSpaceIdentifier,
)!,
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
identifier,
);
}
}

View File

@ -830,6 +830,7 @@ class WKNavigationDelegate extends NSObject {
this.didFailNavigation, this.didFailNavigation,
this.didFailProvisionalNavigation, this.didFailProvisionalNavigation,
this.webViewWebContentProcessDidTerminate, this.webViewWebContentProcessDidTerminate,
this.didReceiveAuthenticationChallenge,
super.observeValue, super.observeValue,
super.binaryMessenger, super.binaryMessenger,
super.instanceManager, super.instanceManager,
@ -855,6 +856,7 @@ class WKNavigationDelegate extends NSObject {
this.didFailNavigation, this.didFailNavigation,
this.didFailProvisionalNavigation, this.didFailProvisionalNavigation,
this.webViewWebContentProcessDidTerminate, this.webViewWebContentProcessDidTerminate,
this.didReceiveAuthenticationChallenge,
super.observeValue, super.observeValue,
super.binaryMessenger, super.binaryMessenger,
super.instanceManager, super.instanceManager,
@ -901,6 +903,16 @@ class WKNavigationDelegate extends NSObject {
/// {@macro webview_flutter_wkwebview.foundation.callbacks} /// {@macro webview_flutter_wkwebview.foundation.callbacks}
final void Function(WKWebView webView)? webViewWebContentProcessDidTerminate; final void Function(WKWebView webView)? webViewWebContentProcessDidTerminate;
/// Called when the delegate needs a response to an authentication challenge.
final void Function(
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) completionHandler,
)? didReceiveAuthenticationChallenge;
@override @override
WKNavigationDelegate copy() { WKNavigationDelegate copy() {
return WKNavigationDelegate.detached( return WKNavigationDelegate.detached(
@ -911,6 +923,7 @@ class WKNavigationDelegate extends NSObject {
didFailProvisionalNavigation: didFailProvisionalNavigation, didFailProvisionalNavigation: didFailProvisionalNavigation,
webViewWebContentProcessDidTerminate: webViewWebContentProcessDidTerminate:
webViewWebContentProcessDidTerminate, webViewWebContentProcessDidTerminate,
didReceiveAuthenticationChallenge: didReceiveAuthenticationChallenge,
observeValue: observeValue, observeValue: observeValue,
binaryMessenger: _navigationDelegateApi.binaryMessenger, binaryMessenger: _navigationDelegateApi.binaryMessenger,
instanceManager: _navigationDelegateApi.instanceManager, instanceManager: _navigationDelegateApi.instanceManager,

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -904,6 +906,53 @@ class WKNavigationDelegateFlutterApiImpl
as WKWebView, as WKWebView,
); );
} }
@override
Future<AuthenticationChallengeResponse> didReceiveAuthenticationChallenge(
int identifier,
int webViewIdentifier,
int challengeIdentifier,
) async {
final void Function(
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
),
)? function = _getDelegate(identifier).didReceiveAuthenticationChallenge;
if (function == null) {
return AuthenticationChallengeResponse(
disposition: NSUrlSessionAuthChallengeDisposition.rejectProtectionSpace,
);
}
final Completer<AuthenticationChallengeResponse> responseCompleter =
Completer<AuthenticationChallengeResponse>();
function.call(
instanceManager.getInstanceWithWeakReference(webViewIdentifier)!
as WKWebView,
instanceManager.getInstanceWithWeakReference(challengeIdentifier)!
as NSUrlAuthenticationChallenge,
(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) {
responseCompleter.complete(
AuthenticationChallengeResponse(
disposition: disposition,
credentialIdentifier: credential != null
? instanceManager.getIdentifier(credential)
: null,
),
);
},
);
return responseCompleter.future;
}
} }
/// Host api implementation for [WKWebView]. /// Host api implementation for [WKWebView].

View File

@ -71,6 +71,14 @@ class WebKitProxy {
void Function(WKWebView webView, NSError error)? void Function(WKWebView webView, NSError error)?
didFailProvisionalNavigation, didFailProvisionalNavigation,
void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, void Function(WKWebView webView)? webViewWebContentProcessDidTerminate,
void Function(
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) completionHandler,
)? didReceiveAuthenticationChallenge,
}) createNavigationDelegate; }) createNavigationDelegate;
/// Constructs a [WKUIDelegate]. /// Constructs a [WKUIDelegate].

View File

@ -948,6 +948,54 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
); );
} }
}, },
didReceiveAuthenticationChallenge: (
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) completionHandler,
) {
if (challenge.protectionSpace.authenticationMethod ==
NSUrlAuthenticationMethod.httpBasic) {
final void Function(HttpAuthRequest)? callback =
weakThis.target?._onHttpAuthRequest;
final String? host = challenge.protectionSpace.host;
final String? realm = challenge.protectionSpace.realm;
if (callback != null && host != null) {
callback(
HttpAuthRequest(
onProceed: (WebViewCredential credential) {
completionHandler(
NSUrlSessionAuthChallengeDisposition.useCredential,
NSUrlCredential.withUser(
user: credential.user,
password: credential.password,
persistence: NSUrlCredentialPersistence.session,
),
);
},
onCancel: () {
completionHandler(
NSUrlSessionAuthChallengeDisposition
.cancelAuthenticationChallenge,
null,
);
},
host: host,
realm: realm,
),
);
return;
}
}
completionHandler(
NSUrlSessionAuthChallengeDisposition.performDefaultHandling,
null,
);
},
); );
} }
@ -960,6 +1008,7 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
WebResourceErrorCallback? _onWebResourceError; WebResourceErrorCallback? _onWebResourceError;
NavigationRequestCallback? _onNavigationRequest; NavigationRequestCallback? _onNavigationRequest;
UrlChangeCallback? _onUrlChange; UrlChangeCallback? _onUrlChange;
HttpAuthRequestCallback? _onHttpAuthRequest;
@override @override
Future<void> setOnPageFinished(PageEventCallback onPageFinished) async { Future<void> setOnPageFinished(PageEventCallback onPageFinished) async {
@ -994,6 +1043,13 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async { Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async {
_onUrlChange = onUrlChange; _onUrlChange = onUrlChange;
} }
@override
Future<void> setOnHttpAuthRequest(
HttpAuthRequestCallback onHttpAuthRequest,
) async {
_onHttpAuthRequest = onHttpAuthRequest;
}
} }
/// WebKit implementation of [PlatformWebViewPermissionRequest]. /// WebKit implementation of [PlatformWebViewPermissionRequest].

View File

@ -258,6 +258,58 @@ class WKMediaCaptureTypeData {
late WKMediaCaptureType value; late WKMediaCaptureType value;
} }
/// Responses to an authentication challenge.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition?language=objc.
enum NSUrlSessionAuthChallengeDisposition {
/// Use the specified credential, which may be nil.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeusecredential?language=objc.
useCredential,
/// Use the default handling for the challenge as though this delegate method
/// were not implemented.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengeperformdefaulthandling?language=objc.
performDefaultHandling,
/// Cancel the entire request.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengecancelauthenticationchallenge?language=objc.
cancelAuthenticationChallenge,
/// Reject this challenge, and call the authentication delegate method again
/// with the next authentication protection space.
///
/// See https://developer.apple.com/documentation/foundation/nsurlsessionauthchallengedisposition/nsurlsessionauthchallengerejectprotectionspace?language=objc.
rejectProtectionSpace,
}
/// Specifies how long a credential will be kept.
enum NSUrlCredentialPersistence {
/// The credential should not be stored.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencenone?language=objc.
none,
/// The credential should be stored only for this session.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistenceforsession?language=objc.
session,
/// The credential should be stored in the keychain.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencepermanent?language=objc.
permanent,
/// The credential should be stored permanently in the keychain, and in
/// addition should be distributed to other devices based on the owning Apple
/// ID.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredentialpersistence/nsurlcredentialpersistencesynchronizable?language=objc.
synchronizable,
}
/// Mirror of NSURLRequest. /// Mirror of NSURLRequest.
/// ///
/// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc. /// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc.
@ -343,6 +395,11 @@ class ObjectOrIdentifier {
late bool isIdentifier; late bool isIdentifier;
} }
class AuthenticationChallengeResponse {
late NSUrlSessionAuthChallengeDisposition disposition;
late int? credentialIdentifier;
}
/// Mirror of WKWebsiteDataStore. /// Mirror of WKWebsiteDataStore.
/// ///
/// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc.
@ -582,6 +639,16 @@ abstract class WKNavigationDelegateFlutterApi {
int identifier, int identifier,
int webViewIdentifier, int webViewIdentifier,
); );
@async
@ObjCSelector(
'didReceiveAuthenticationChallengeForDelegateWithIdentifier:webViewIdentifier:challengeIdentifier:',
)
AuthenticationChallengeResponse didReceiveAuthenticationChallenge(
int identifier,
int webViewIdentifier,
int challengeIdentifier,
);
} }
/// Mirror of NSObject. /// Mirror of NSObject.
@ -781,3 +848,57 @@ abstract class NSUrlFlutterApi {
@ObjCSelector('createWithIdentifier:') @ObjCSelector('createWithIdentifier:')
void create(int identifier); void create(int identifier);
} }
/// Host API for `NSUrlCredential`.
///
/// This class may handle instantiating and adding native object instances that
/// are attached to a Dart instance or handle method calls on the associated
/// native class or an instance of the class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredential?language=objc.
@HostApi(dartHostTestHandler: 'TestNSUrlCredentialHostApi')
abstract class NSUrlCredentialHostApi {
/// Create a new native instance and add it to the `InstanceManager`.
@ObjCSelector(
'createWithUserWithIdentifier:user:password:persistence:',
)
void createWithUser(
int identifier,
String user,
String password,
NSUrlCredentialPersistence persistence,
);
}
/// Flutter API for `NSUrlProtectionSpace`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlprotectionspace?language=objc.
@FlutterApi()
abstract class NSUrlProtectionSpaceFlutterApi {
/// Create a new Dart instance and add it to the `InstanceManager`.
@ObjCSelector('createWithIdentifier:host:realm:authenticationMethod:')
void create(
int identifier,
String? host,
String? realm,
String? authenticationMethod,
);
}
/// Flutter API for `NSUrlAuthenticationChallenge`.
///
/// This class may handle instantiating and adding Dart instances that are
/// attached to a native instance or receiving callback methods from an
/// overridden native class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlauthenticationchallenge?language=objc.
@FlutterApi()
abstract class NSUrlAuthenticationChallengeFlutterApi {
/// Create a new Dart instance and add it to the `InstanceManager`.
@ObjCSelector('createWithIdentifier:protectionSpaceIdentifier:')
void create(int identifier, int protectionSpaceIdentifier);
}

View File

@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 3.9.4 version: 3.10.0
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
@ -20,7 +20,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path: ^1.8.0 path: ^1.8.0
webview_flutter_platform_interface: ^2.6.0 webview_flutter_platform_interface: ^2.7.0
dev_dependencies: dev_dependencies:
build_runner: ^2.1.5 build_runner: ^2.1.5

View File

@ -1199,60 +1199,63 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec {
const _TestWKWebViewHostApiCodec(); const _TestWKWebViewHostApiCodec();
@override @override
void writeValue(WriteBuffer buffer, Object? value) { void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) { if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128); buffer.putUint8(128);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSHttpCookieData) { } else if (value is NSErrorData) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSHttpCookiePropertyKeyEnumData) { } else if (value is NSHttpCookieData) {
buffer.putUint8(130); buffer.putUint8(130);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSKeyValueChangeKeyEnumData) { } else if (value is NSHttpCookiePropertyKeyEnumData) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSKeyValueObservingOptionsEnumData) { } else if (value is NSKeyValueChangeKeyEnumData) {
buffer.putUint8(132); buffer.putUint8(132);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) { } else if (value is NSKeyValueObservingOptionsEnumData) {
buffer.putUint8(133); buffer.putUint8(133);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is ObjectOrIdentifier) { } else if (value is NSUrlRequestData) {
buffer.putUint8(134); buffer.putUint8(134);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKAudiovisualMediaTypeEnumData) { } else if (value is ObjectOrIdentifier) {
buffer.putUint8(135); buffer.putUint8(135);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) { } else if (value is WKAudiovisualMediaTypeEnumData) {
buffer.putUint8(136); buffer.putUint8(136);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKMediaCaptureTypeData) { } else if (value is WKFrameInfoData) {
buffer.putUint8(137); buffer.putUint8(137);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) { } else if (value is WKMediaCaptureTypeData) {
buffer.putUint8(138); buffer.putUint8(138);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) { } else if (value is WKNavigationActionData) {
buffer.putUint8(139); buffer.putUint8(139);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKPermissionDecisionData) { } else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(140); buffer.putUint8(140);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKScriptMessageData) { } else if (value is WKPermissionDecisionData) {
buffer.putUint8(141); buffer.putUint8(141);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKSecurityOriginData) { } else if (value is WKScriptMessageData) {
buffer.putUint8(142); buffer.putUint8(142);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKUserScriptData) { } else if (value is WKSecurityOriginData) {
buffer.putUint8(143); buffer.putUint8(143);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKUserScriptInjectionTimeEnumData) { } else if (value is WKUserScriptData) {
buffer.putUint8(144); buffer.putUint8(144);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) { } else if (value is WKUserScriptInjectionTimeEnumData) {
buffer.putUint8(145); buffer.putUint8(145);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
buffer.putUint8(146);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -1262,40 +1265,42 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 128: case 128:
return NSErrorData.decode(readValue(buffer)!); return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129: case 129:
return NSHttpCookieData.decode(readValue(buffer)!); return NSErrorData.decode(readValue(buffer)!);
case 130: case 130:
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); return NSHttpCookieData.decode(readValue(buffer)!);
case 131: case 131:
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
case 132: case 132:
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
case 133: case 133:
return NSUrlRequestData.decode(readValue(buffer)!); return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
case 134: case 134:
return ObjectOrIdentifier.decode(readValue(buffer)!); return NSUrlRequestData.decode(readValue(buffer)!);
case 135: case 135:
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); return ObjectOrIdentifier.decode(readValue(buffer)!);
case 136: case 136:
return WKFrameInfoData.decode(readValue(buffer)!); return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
case 137: case 137:
return WKMediaCaptureTypeData.decode(readValue(buffer)!); return WKFrameInfoData.decode(readValue(buffer)!);
case 138: case 138:
return WKNavigationActionData.decode(readValue(buffer)!); return WKMediaCaptureTypeData.decode(readValue(buffer)!);
case 139: case 139:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); return WKNavigationActionData.decode(readValue(buffer)!);
case 140: case 140:
return WKPermissionDecisionData.decode(readValue(buffer)!); return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
case 141: case 141:
return WKScriptMessageData.decode(readValue(buffer)!); return WKPermissionDecisionData.decode(readValue(buffer)!);
case 142: case 142:
return WKSecurityOriginData.decode(readValue(buffer)!); return WKScriptMessageData.decode(readValue(buffer)!);
case 143: case 143:
return WKUserScriptData.decode(readValue(buffer)!); return WKSecurityOriginData.decode(readValue(buffer)!);
case 144: case 144:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); return WKUserScriptData.decode(readValue(buffer)!);
case 145: case 145:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
case 146:
return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@ -2196,3 +2201,66 @@ abstract class TestNSUrlHostApi {
} }
} }
} }
/// Host API for `NSUrlCredential`.
///
/// This class may handle instantiating and adding native object instances that
/// are attached to a Dart instance or handle method calls on the associated
/// native class or an instance of the class.
///
/// See https://developer.apple.com/documentation/foundation/nsurlcredential?language=objc.
abstract class TestNSUrlCredentialHostApi {
static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = StandardMessageCodec();
/// Create a new native instance and add it to the `InstanceManager`.
void createWithUser(int identifier, String user, String password,
NSUrlCredentialPersistence persistence);
static void setup(TestNSUrlCredentialHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser was null, expected non-null int.');
final String? arg_user = (args[1] as String?);
assert(arg_user != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser was null, expected non-null String.');
final String? arg_password = (args[2] as String?);
assert(arg_password != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser was null, expected non-null String.');
final NSUrlCredentialPersistence? arg_persistence = args[3] == null
? null
: NSUrlCredentialPersistence.values[args[3]! as int];
assert(arg_persistence != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.NSUrlCredentialHostApi.createWithUser was null, expected non-null NSUrlCredentialPersistence.');
try {
api.createWithUser(
arg_identifier!, arg_user!, arg_password!, arg_persistence!);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
}
}

View File

@ -17,6 +17,7 @@ import 'foundation_test.mocks.dart';
@GenerateMocks(<Type>[ @GenerateMocks(<Type>[
TestNSObjectHostApi, TestNSObjectHostApi,
TestNSUrlCredentialHostApi,
TestNSUrlHostApi, TestNSUrlHostApi,
]) ])
void main() { void main() {
@ -245,6 +246,102 @@ void main() {
expect(instanceManager.getInstanceWithWeakReference(0), isA<NSUrl>()); expect(instanceManager.getInstanceWithWeakReference(0), isA<NSUrl>());
}); });
}); });
group('NSUrlCredential', () {
tearDown(() {
TestNSUrlCredentialHostApi.setup(null);
});
test('HostApi createWithUser', () {
final MockTestNSUrlCredentialHostApi mockApi =
MockTestNSUrlCredentialHostApi();
TestNSUrlCredentialHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const String user = 'testString';
const String password = 'testString2';
const NSUrlCredentialPersistence persistence =
NSUrlCredentialPersistence.permanent;
final NSUrlCredential instance = NSUrlCredential.withUser(
user: user,
password: password,
persistence: persistence,
instanceManager: instanceManager,
);
verify(mockApi.createWithUser(
instanceManager.getIdentifier(instance),
user,
password,
persistence,
));
});
});
group('NSUrlProtectionSpace', () {
test('FlutterAPI create', () {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
final NSUrlProtectionSpaceFlutterApiImpl api =
NSUrlProtectionSpaceFlutterApiImpl(
instanceManager: instanceManager,
);
const int instanceIdentifier = 0;
api.create(
instanceIdentifier,
'testString',
'testString',
'testAuthenticationMethod',
);
expect(
instanceManager.getInstanceWithWeakReference(instanceIdentifier),
isA<NSUrlProtectionSpace>(),
);
});
});
group('NSUrlAuthenticationChallenge', () {
test('FlutterAPI create', () {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
final NSUrlAuthenticationChallengeFlutterApiImpl api =
NSUrlAuthenticationChallengeFlutterApiImpl(
instanceManager: instanceManager,
);
const int instanceIdentifier = 0;
const int protectionSpaceIdentifier = 1;
instanceManager.addHostCreatedInstance(
NSUrlProtectionSpace.detached(
host: null,
realm: null,
authenticationMethod: null,
instanceManager: instanceManager,
),
protectionSpaceIdentifier,
);
api.create(instanceIdentifier, protectionSpaceIdentifier);
expect(
instanceManager.getInstanceWithWeakReference(instanceIdentifier),
isA<NSUrlAuthenticationChallenge>(),
);
});
});
}); });
test('NSError', () { test('NSError', () {

View File

@ -78,6 +78,36 @@ class MockTestNSObjectHostApi extends _i1.Mock
); );
} }
/// A class which mocks [TestNSUrlCredentialHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestNSUrlCredentialHostApi extends _i1.Mock
implements _i2.TestNSUrlCredentialHostApi {
MockTestNSUrlCredentialHostApi() {
_i1.throwOnMissingStub(this);
}
@override
void createWithUser(
int? identifier,
String? user,
String? password,
_i3.NSUrlCredentialPersistence? persistence,
) =>
super.noSuchMethod(
Invocation.method(
#createWithUser,
[
identifier,
user,
password,
persistence,
],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TestNSUrlHostApi]. /// A class which mocks [TestNSUrlHostApi].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.

View File

@ -683,6 +683,63 @@ void main() {
expect(argsCompleter.future, completion(<Object?>[webView])); expect(argsCompleter.future, completion(<Object?>[webView]));
}); });
test('didReceiveAuthenticationChallenge', () async {
WebKitFlutterApis.instance = WebKitFlutterApis(
instanceManager: instanceManager,
);
const int credentialIdentifier = 3;
final NSUrlCredential credential = NSUrlCredential.detached(
instanceManager: instanceManager,
);
instanceManager.addHostCreatedInstance(
credential,
credentialIdentifier,
);
navigationDelegate = WKNavigationDelegate(
instanceManager: instanceManager,
didReceiveAuthenticationChallenge: (
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) completionHandler,
) {
completionHandler(
NSUrlSessionAuthChallengeDisposition.useCredential,
credential,
);
},
);
const int challengeIdentifier = 27;
instanceManager.addHostCreatedInstance(
NSUrlAuthenticationChallenge.detached(
protectionSpace: NSUrlProtectionSpace.detached(
host: null,
realm: null,
authenticationMethod: null,
),
instanceManager: instanceManager,
),
challengeIdentifier,
);
final AuthenticationChallengeResponse response = await WebKitFlutterApis
.instance.navigationDelegate
.didReceiveAuthenticationChallenge(
instanceManager.getIdentifier(navigationDelegate)!,
instanceManager.getIdentifier(webView)!,
challengeIdentifier,
);
expect(response.disposition,
NSUrlSessionAuthChallengeDisposition.useCredential);
expect(response.credentialIdentifier, credentialIdentifier);
});
}); });
group('WKWebView', () { group('WKWebView', () {

View File

@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart';
import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart';
import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';
import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart'; import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart';
@ -214,6 +215,44 @@ void main() {
expect(callbackRequest.url, 'https://www.google.com'); expect(callbackRequest.url, 'https://www.google.com');
expect(callbackRequest.isMainFrame, isFalse); expect(callbackRequest.isMainFrame, isFalse);
}); });
test('onHttpBasicAuthRequest emits host and realm', () {
final WebKitNavigationDelegate iosNavigationDelegate =
WebKitNavigationDelegate(
const WebKitNavigationDelegateCreationParams(
webKitProxy: WebKitProxy(
createNavigationDelegate: CapturingNavigationDelegate.new,
),
),
);
String? callbackHost;
String? callbackRealm;
iosNavigationDelegate.setOnHttpAuthRequest((HttpAuthRequest request) {
callbackHost = request.host;
callbackRealm = request.realm;
});
const String expectedHost = 'expectedHost';
const String expectedRealm = 'expectedRealm';
CapturingNavigationDelegate
.lastCreatedDelegate.didReceiveAuthenticationChallenge!(
WKWebView.detached(),
NSUrlAuthenticationChallenge.detached(
protectionSpace: NSUrlProtectionSpace.detached(
host: expectedHost,
realm: expectedRealm,
authenticationMethod: NSUrlAuthenticationMethod.httpBasic,
),
),
(NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential) {});
expect(callbackHost, expectedHost);
expect(callbackRealm, expectedRealm);
});
}); });
} }
@ -226,6 +265,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate {
super.didFailProvisionalNavigation, super.didFailProvisionalNavigation,
super.decidePolicyForNavigationAction, super.decidePolicyForNavigationAction,
super.webViewWebContentProcessDidTerminate, super.webViewWebContentProcessDidTerminate,
super.didReceiveAuthenticationChallenge,
}) : super.detached() { }) : super.detached() {
lastCreatedDelegate = this; lastCreatedDelegate = this;
} }

View File

@ -1366,6 +1366,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate {
super.didFailProvisionalNavigation, super.didFailProvisionalNavigation,
super.decidePolicyForNavigationAction, super.decidePolicyForNavigationAction,
super.webViewWebContentProcessDidTerminate, super.webViewWebContentProcessDidTerminate,
super.didReceiveAuthenticationChallenge,
}) : super.detached() { }) : super.detached() {
lastCreatedDelegate = this; lastCreatedDelegate = this;
} }