[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.
## 3.9.4

View File

@ -34,6 +34,15 @@ Future<void> main() async {
request.response.writeln('${request.headers}');
} else if (request.uri.path == '/favicon.ico') {
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 {
fail('unexpected request: ${request.method} ${request.uri}');
}
@ -43,6 +52,7 @@ Future<void> main() async {
final String primaryUrl = '$prefixUrl/hello.txt';
final String secondaryUrl = '$prefixUrl/secondary.txt';
final String headersUrl = '$prefixUrl/headers';
final String basicAuthUrl = '$prefixUrl/http-basic-authentication';
testWidgets(
'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',
(WidgetTester tester) async {
final WebKitWebViewController controller = WebKitWebViewController(

View File

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

View File

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

View File

@ -213,4 +213,58 @@
webViewIdentifier:1
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

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) {
debugPrint('url change to ${change.url}');
})
..setOnHttpAuthRequest((HttpAuthRequest request) {
openDialog(request);
}),
)
..addJavaScriptChannel(JavaScriptChannelParams(
@ -220,6 +223,62 @@ Page resource error:
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 {
@ -237,6 +296,7 @@ enum MenuOptions {
transparentBackground,
setCookie,
logExample,
basicAuthentication,
}
class SampleMenu extends StatelessWidget {
@ -300,6 +360,9 @@ class SampleMenu extends StatelessWidget {
case MenuOptions.logExample:
_onLogExample();
break;
case MenuOptions.basicAuthentication:
_promptForUrl(context);
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@ -360,6 +423,10 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.logExample,
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);
}
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 {

View File

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

View File

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

View File

@ -193,4 +193,26 @@ API_AVAILABLE(ios(15.0))
extern FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType(
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

View File

@ -285,3 +285,36 @@ FWFWKMediaCaptureTypeData *FWFWKMediaCaptureTypeDataFromNativeWKMediaCaptureType
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;
@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 FWFNSKeyValueChangeKeyEnumData;
@class FWFWKUserScriptInjectionTimeEnumData;
@ -282,6 +349,7 @@ typedef NS_ENUM(NSUInteger, FWFWKMediaCaptureType) {
@class FWFWKSecurityOriginData;
@class FWFNSHttpCookieData;
@class FWFObjectOrIdentifier;
@class FWFAuthenticationChallengeResponse;
@interface FWFNSKeyValueObservingOptionsEnumData : NSObject
/// `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;
@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.
NSObject<FlutterMessageCodec> *FWFWKWebsiteDataStoreHostApiGetCodec(void);
@ -711,6 +788,14 @@ NSObject<FlutterMessageCodec> *FWFWKNavigationDelegateFlutterApiGetCodec(void);
completion:
(void (^)(FlutterError *_Nullable))
completion;
- (void)didReceiveAuthenticationChallengeForDelegateWithIdentifier:(NSInteger)identifier
webViewIdentifier:(NSInteger)webViewIdentifier
challengeIdentifier:(NSInteger)challengeIdentifier
completion:
(void (^)(
FWFAuthenticationChallengeResponse
*_Nullable,
FlutterError *_Nullable))completion;
@end
/// The codec used by FWFNSObjectHostApi.
@ -919,4 +1004,65 @@ NSObject<FlutterMessageCodec> *FWFNSUrlFlutterApiGetCodec(void);
completion:(void (^)(FlutterError *_Nullable))completion;
@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

View File

@ -164,6 +164,31 @@
}
@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) {
if (error) {
return @[
@ -285,6 +310,12 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
- (NSArray *)toList;
@end
@interface FWFAuthenticationChallengeResponse ()
+ (FWFAuthenticationChallengeResponse *)fromList:(NSArray *)list;
+ (nullable FWFAuthenticationChallengeResponse *)nullableFromList:(NSArray *)list;
- (NSArray *)toList;
@end
@implementation FWFNSKeyValueObservingOptionsEnumData
+ (instancetype)makeWithValue:(FWFNSKeyValueObservingOptionsEnum)value {
FWFNSKeyValueObservingOptionsEnumData *pigeonResult =
@ -727,6 +758,33 @@ static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
}
@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
@end
@implementation FWFWKWebsiteDataStoreHostApiCodecReader
@ -1684,14 +1742,16 @@ void SetUpFWFWKNavigationDelegateHostApi(id<FlutterBinaryMessenger> binaryMessen
- (nullable id)readValueOfType:(UInt8)type {
switch (type) {
case 128:
return [FWFNSErrorData fromList:[self readValue]];
return [FWFAuthenticationChallengeResponse fromList:[self readValue]];
case 129:
return [FWFNSUrlRequestData fromList:[self readValue]];
return [FWFNSErrorData fromList:[self readValue]];
case 130:
return [FWFWKFrameInfoData fromList:[self readValue]];
return [FWFNSUrlRequestData fromList:[self readValue]];
case 131:
return [FWFWKNavigationActionData fromList:[self readValue]];
return [FWFWKFrameInfoData fromList:[self readValue]];
case 132:
return [FWFWKNavigationActionData fromList:[self readValue]];
case 133:
return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]];
default:
return [super readValueOfType:type];
@ -1703,21 +1763,24 @@ void SetUpFWFWKNavigationDelegateHostApi(id<FlutterBinaryMessenger> binaryMessen
@end
@implementation FWFWKNavigationDelegateFlutterApiCodecWriter
- (void)writeValue:(id)value {
if ([value isKindOfClass:[FWFNSErrorData class]]) {
if ([value isKindOfClass:[FWFAuthenticationChallengeResponse class]]) {
[self writeByte:128];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
} else if ([value isKindOfClass:[FWFNSErrorData class]]) {
[self writeByte:129];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
[self writeByte:130];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
[self writeByte:131];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
[self writeByte:132];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
[self writeByte:133];
[self writeValue:[value toList]];
} else {
[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
@interface FWFNSObjectHostApiCodecReader : FlutterStandardReader
@ -2201,40 +2298,42 @@ NSObject<FlutterMessageCodec> *FWFNSObjectFlutterApiGetCodec(void) {
- (nullable id)readValueOfType:(UInt8)type {
switch (type) {
case 128:
return [FWFNSErrorData fromList:[self readValue]];
return [FWFAuthenticationChallengeResponse fromList:[self readValue]];
case 129:
return [FWFNSHttpCookieData fromList:[self readValue]];
return [FWFNSErrorData fromList:[self readValue]];
case 130:
return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]];
return [FWFNSHttpCookieData fromList:[self readValue]];
case 131:
return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]];
return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]];
case 132:
return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]];
return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]];
case 133:
return [FWFNSUrlRequestData fromList:[self readValue]];
return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]];
case 134:
return [FWFObjectOrIdentifier fromList:[self readValue]];
return [FWFNSUrlRequestData fromList:[self readValue]];
case 135:
return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]];
return [FWFObjectOrIdentifier fromList:[self readValue]];
case 136:
return [FWFWKFrameInfoData fromList:[self readValue]];
return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]];
case 137:
return [FWFWKMediaCaptureTypeData fromList:[self readValue]];
return [FWFWKFrameInfoData fromList:[self readValue]];
case 138:
return [FWFWKNavigationActionData fromList:[self readValue]];
return [FWFWKMediaCaptureTypeData fromList:[self readValue]];
case 139:
return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]];
return [FWFWKNavigationActionData fromList:[self readValue]];
case 140:
return [FWFWKPermissionDecisionData fromList:[self readValue]];
return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]];
case 141:
return [FWFWKScriptMessageData fromList:[self readValue]];
return [FWFWKPermissionDecisionData fromList:[self readValue]];
case 142:
return [FWFWKSecurityOriginData fromList:[self readValue]];
return [FWFWKScriptMessageData fromList:[self readValue]];
case 143:
return [FWFWKUserScriptData fromList:[self readValue]];
return [FWFWKSecurityOriginData fromList:[self readValue]];
case 144:
return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]];
return [FWFWKUserScriptData fromList:[self readValue]];
case 145:
return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]];
case 146:
return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]];
default:
return [super readValueOfType:type];
@ -2246,60 +2345,63 @@ NSObject<FlutterMessageCodec> *FWFNSObjectFlutterApiGetCodec(void) {
@end
@implementation FWFWKWebViewHostApiCodecWriter
- (void)writeValue:(id)value {
if ([value isKindOfClass:[FWFNSErrorData class]]) {
if ([value isKindOfClass:[FWFAuthenticationChallengeResponse class]]) {
[self writeByte:128];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) {
} else if ([value isKindOfClass:[FWFNSErrorData class]]) {
[self writeByte:129];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) {
} else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) {
[self writeByte:130];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) {
} else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) {
[self writeByte:131];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) {
} else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) {
[self writeByte:132];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
} else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) {
[self writeByte:133];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) {
} else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) {
[self writeByte:134];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) {
} else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) {
[self writeByte:135];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
} else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) {
[self writeByte:136];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) {
} else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) {
[self writeByte:137];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
} else if ([value isKindOfClass:[FWFWKMediaCaptureTypeData class]]) {
[self writeByte:138];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
} else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) {
[self writeByte:139];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) {
} else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) {
[self writeByte:140];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) {
} else if ([value isKindOfClass:[FWFWKPermissionDecisionData class]]) {
[self writeByte:141];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) {
} else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) {
[self writeByte:142];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKUserScriptData class]]) {
} else if ([value isKindOfClass:[FWFWKSecurityOriginData class]]) {
[self writeByte:143];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) {
} else if ([value isKindOfClass:[FWFWKUserScriptData class]]) {
[self writeByte:144];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) {
} else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) {
[self writeByte:145];
[self writeValue:[value toList]];
} else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) {
[self writeByte:146];
[self writeValue:[value toList]];
} else {
[super writeValue:value];
}
@ -3181,3 +3283,143 @@ NSObject<FlutterMessageCodec> *FWFNSUrlFlutterApiGetCodec(void) {
}];
}
@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 "FWFDataConverters.h"
#import "FWFURLAuthenticationChallengeHostApi.h"
#import "FWFWebViewConfigurationHostApi.h"
@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.
@property(nonatomic, weak) FWFInstanceManager *instanceManager;
@end
@ -16,6 +20,7 @@
instanceManager:(FWFInstanceManager *)instanceManager {
self = [self initWithBinaryMessenger:binaryMessenger];
if (self) {
_binaryMessenger = binaryMessenger;
_instanceManager = instanceManager;
}
return self;
@ -102,6 +107,37 @@
webViewIdentifier:webViewIdentifier
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
@implementation FWFNavigationDelegate
@ -144,8 +180,13 @@
completion:^(FWFWKNavigationActionPolicyEnumData *policy,
FlutterError *error) {
NSAssert(!error, @"%@", error);
decisionHandler(
FWFNativeWKNavigationActionPolicyFromEnumData(policy));
if (!error) {
decisionHandler(
FWFNativeWKNavigationActionPolicyFromEnumData(
policy));
} else {
decisionHandler(WKNavigationActionPolicyCancel);
}
}];
}
@ -179,6 +220,40 @@
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
@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 "FWFUIDelegateHostApi.h"
#import "FWFUIViewHostApi.h"
#import "FWFURLAuthenticationChallengeHostApi.h"
#import "FWFURLCredentialHostApi.h"
#import "FWFURLHostApi.h"
#import "FWFURLProtectionSpaceHostApi.h"
#import "FWFUserContentControllerHostApi.h"
#import "FWFWebViewConfigurationHostApi.h"
#import "FWFWebViewFlutterWKWebViewExternalAPI.h"

View File

@ -202,6 +202,58 @@ enum WKMediaCaptureType {
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 {
NSKeyValueObservingOptionsEnumData({
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 {
const _WKWebsiteDataStoreHostApiCodec();
@override
@ -1576,21 +1655,24 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec {
const _WKNavigationDelegateFlutterApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) {
if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) {
} else if (value is NSErrorData) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) {
} else if (value is NSUrlRequestData) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) {
} else if (value is WKFrameInfoData) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) {
} else if (value is WKNavigationActionData) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@ -1600,14 +1682,16 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return NSErrorData.decode(readValue(buffer)!);
return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129:
return NSUrlRequestData.decode(readValue(buffer)!);
return NSErrorData.decode(readValue(buffer)!);
case 130:
return WKFrameInfoData.decode(readValue(buffer)!);
return NSUrlRequestData.decode(readValue(buffer)!);
case 131:
return WKNavigationActionData.decode(readValue(buffer)!);
return WKFrameInfoData.decode(readValue(buffer)!);
case 132:
return WKNavigationActionData.decode(readValue(buffer)!);
case 133:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@ -1641,6 +1725,9 @@ abstract class WKNavigationDelegateFlutterApi {
void webViewWebContentProcessDidTerminate(
int identifier, int webViewIdentifier);
Future<AuthenticationChallengeResponse> didReceiveAuthenticationChallenge(
int identifier, int webViewIdentifier, int challengeIdentifier);
static void setup(WKNavigationDelegateFlutterApi? api,
{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();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) {
if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else if (value is NSHttpCookieData) {
} else if (value is NSErrorData) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is NSHttpCookiePropertyKeyEnumData) {
} else if (value is NSHttpCookieData) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is NSKeyValueChangeKeyEnumData) {
} else if (value is NSHttpCookiePropertyKeyEnumData) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is NSKeyValueObservingOptionsEnumData) {
} else if (value is NSKeyValueChangeKeyEnumData) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) {
} else if (value is NSKeyValueObservingOptionsEnumData) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is ObjectOrIdentifier) {
} else if (value is NSUrlRequestData) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is WKAudiovisualMediaTypeEnumData) {
} else if (value is ObjectOrIdentifier) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) {
} else if (value is WKAudiovisualMediaTypeEnumData) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else if (value is WKMediaCaptureTypeData) {
} else if (value is WKFrameInfoData) {
buffer.putUint8(137);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) {
} else if (value is WKMediaCaptureTypeData) {
buffer.putUint8(138);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) {
} else if (value is WKNavigationActionData) {
buffer.putUint8(139);
writeValue(buffer, value.encode());
} else if (value is WKPermissionDecisionData) {
} else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(140);
writeValue(buffer, value.encode());
} else if (value is WKScriptMessageData) {
} else if (value is WKPermissionDecisionData) {
buffer.putUint8(141);
writeValue(buffer, value.encode());
} else if (value is WKSecurityOriginData) {
} else if (value is WKScriptMessageData) {
buffer.putUint8(142);
writeValue(buffer, value.encode());
} else if (value is WKUserScriptData) {
} else if (value is WKSecurityOriginData) {
buffer.putUint8(143);
writeValue(buffer, value.encode());
} else if (value is WKUserScriptInjectionTimeEnumData) {
} else if (value is WKUserScriptData) {
buffer.putUint8(144);
writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
} else if (value is WKUserScriptInjectionTimeEnumData) {
buffer.putUint8(145);
writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
buffer.putUint8(146);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@ -2145,40 +2270,42 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return NSErrorData.decode(readValue(buffer)!);
return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129:
return NSHttpCookieData.decode(readValue(buffer)!);
return NSErrorData.decode(readValue(buffer)!);
case 130:
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
return NSHttpCookieData.decode(readValue(buffer)!);
case 131:
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
case 132:
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
case 133:
return NSUrlRequestData.decode(readValue(buffer)!);
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
case 134:
return ObjectOrIdentifier.decode(readValue(buffer)!);
return NSUrlRequestData.decode(readValue(buffer)!);
case 135:
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
return ObjectOrIdentifier.decode(readValue(buffer)!);
case 136:
return WKFrameInfoData.decode(readValue(buffer)!);
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
case 137:
return WKMediaCaptureTypeData.decode(readValue(buffer)!);
return WKFrameInfoData.decode(readValue(buffer)!);
case 138:
return WKNavigationActionData.decode(readValue(buffer)!);
return WKMediaCaptureTypeData.decode(readValue(buffer)!);
case 139:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
return WKNavigationActionData.decode(readValue(buffer)!);
case 140:
return WKPermissionDecisionData.decode(readValue(buffer)!);
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
case 141:
return WKScriptMessageData.decode(readValue(buffer)!);
return WKPermissionDecisionData.decode(readValue(buffer)!);
case 142:
return WKSecurityOriginData.decode(readValue(buffer)!);
return WKScriptMessageData.decode(readValue(buffer)!);
case 143:
return WKUserScriptData.decode(readValue(buffer)!);
return WKSecurityOriginData.decode(readValue(buffer)!);
case 144:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
return WKUserScriptData.decode(readValue(buffer)!);
case 145:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
case 146:
return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!);
default:
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 'foundation_api_impls.dart';
export 'foundation_api_impls.dart'
show NSUrlSessionAuthChallengeDisposition, NSUrlCredentialPersistence;
/// The values that can be returned in a change map.
///
/// 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 'foundation.dart';
export '../common/web_kit.g.dart'
show NSUrlSessionAuthChallengeDisposition, NSUrlCredentialPersistence;
Iterable<NSKeyValueObservingOptionsEnumData>
_toNSKeyValueObservingOptionsEnumData(
Iterable<NSKeyValueObservingOptions> options,
@ -56,6 +59,14 @@ class FoundationFlutterApis {
url = NSUrlFlutterApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
urlProtectionSpace = NSUrlProtectionSpaceFlutterApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
urlAuthenticationChallenge = NSUrlAuthenticationChallengeFlutterApiImpl(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
);
static FoundationFlutterApis _instance = FoundationFlutterApis();
@ -82,6 +93,14 @@ class FoundationFlutterApis {
@visibleForTesting
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.
void ensureSetUp() {
if (!_hasBeenSetUp) {
@ -90,6 +109,14 @@ class FoundationFlutterApis {
binaryMessenger: _binaryMessenger,
);
NSUrlFlutterApi.setup(url, binaryMessenger: _binaryMessenger);
NSUrlProtectionSpaceFlutterApi.setup(
urlProtectionSpace,
binaryMessenger: _binaryMessenger,
);
NSUrlAuthenticationChallengeFlutterApi.setup(
urlAuthenticationChallenge,
binaryMessenger: _binaryMessenger,
);
_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.didFailProvisionalNavigation,
this.webViewWebContentProcessDidTerminate,
this.didReceiveAuthenticationChallenge,
super.observeValue,
super.binaryMessenger,
super.instanceManager,
@ -855,6 +856,7 @@ class WKNavigationDelegate extends NSObject {
this.didFailNavigation,
this.didFailProvisionalNavigation,
this.webViewWebContentProcessDidTerminate,
this.didReceiveAuthenticationChallenge,
super.observeValue,
super.binaryMessenger,
super.instanceManager,
@ -901,6 +903,16 @@ class WKNavigationDelegate extends NSObject {
/// {@macro webview_flutter_wkwebview.foundation.callbacks}
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
WKNavigationDelegate copy() {
return WKNavigationDelegate.detached(
@ -911,6 +923,7 @@ class WKNavigationDelegate extends NSObject {
didFailProvisionalNavigation: didFailProvisionalNavigation,
webViewWebContentProcessDidTerminate:
webViewWebContentProcessDidTerminate,
didReceiveAuthenticationChallenge: didReceiveAuthenticationChallenge,
observeValue: observeValue,
binaryMessenger: _navigationDelegateApi.binaryMessenger,
instanceManager: _navigationDelegateApi.instanceManager,

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@ -904,6 +906,53 @@ class WKNavigationDelegateFlutterApiImpl
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].

View File

@ -71,6 +71,14 @@ class WebKitProxy {
void Function(WKWebView webView, NSError error)?
didFailProvisionalNavigation,
void Function(WKWebView webView)? webViewWebContentProcessDidTerminate,
void Function(
WKWebView webView,
NSUrlAuthenticationChallenge challenge,
void Function(
NSUrlSessionAuthChallengeDisposition disposition,
NSUrlCredential? credential,
) completionHandler,
)? didReceiveAuthenticationChallenge,
}) createNavigationDelegate;
/// 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;
NavigationRequestCallback? _onNavigationRequest;
UrlChangeCallback? _onUrlChange;
HttpAuthRequestCallback? _onHttpAuthRequest;
@override
Future<void> setOnPageFinished(PageEventCallback onPageFinished) async {
@ -994,6 +1043,13 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async {
_onUrlChange = onUrlChange;
}
@override
Future<void> setOnHttpAuthRequest(
HttpAuthRequestCallback onHttpAuthRequest,
) async {
_onHttpAuthRequest = onHttpAuthRequest;
}
}
/// WebKit implementation of [PlatformWebViewPermissionRequest].

View File

@ -258,6 +258,58 @@ class WKMediaCaptureTypeData {
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.
///
/// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc.
@ -343,6 +395,11 @@ class ObjectOrIdentifier {
late bool isIdentifier;
}
class AuthenticationChallengeResponse {
late NSUrlSessionAuthChallengeDisposition disposition;
late int? credentialIdentifier;
}
/// Mirror of WKWebsiteDataStore.
///
/// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc.
@ -582,6 +639,16 @@ abstract class WKNavigationDelegateFlutterApi {
int identifier,
int webViewIdentifier,
);
@async
@ObjCSelector(
'didReceiveAuthenticationChallengeForDelegateWithIdentifier:webViewIdentifier:challengeIdentifier:',
)
AuthenticationChallengeResponse didReceiveAuthenticationChallenge(
int identifier,
int webViewIdentifier,
int challengeIdentifier,
);
}
/// Mirror of NSObject.
@ -781,3 +848,57 @@ abstract class NSUrlFlutterApi {
@ObjCSelector('createWithIdentifier:')
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.
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
version: 3.9.4
version: 3.10.0
environment:
sdk: ">=3.0.0 <4.0.0"
@ -20,7 +20,7 @@ dependencies:
flutter:
sdk: flutter
path: ^1.8.0
webview_flutter_platform_interface: ^2.6.0
webview_flutter_platform_interface: ^2.7.0
dev_dependencies:
build_runner: ^2.1.5

View File

@ -1199,60 +1199,63 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec {
const _TestWKWebViewHostApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is NSErrorData) {
if (value is AuthenticationChallengeResponse) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else if (value is NSHttpCookieData) {
} else if (value is NSErrorData) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is NSHttpCookiePropertyKeyEnumData) {
} else if (value is NSHttpCookieData) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is NSKeyValueChangeKeyEnumData) {
} else if (value is NSHttpCookiePropertyKeyEnumData) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is NSKeyValueObservingOptionsEnumData) {
} else if (value is NSKeyValueChangeKeyEnumData) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is NSUrlRequestData) {
} else if (value is NSKeyValueObservingOptionsEnumData) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is ObjectOrIdentifier) {
} else if (value is NSUrlRequestData) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is WKAudiovisualMediaTypeEnumData) {
} else if (value is ObjectOrIdentifier) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is WKFrameInfoData) {
} else if (value is WKAudiovisualMediaTypeEnumData) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else if (value is WKMediaCaptureTypeData) {
} else if (value is WKFrameInfoData) {
buffer.putUint8(137);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionData) {
} else if (value is WKMediaCaptureTypeData) {
buffer.putUint8(138);
writeValue(buffer, value.encode());
} else if (value is WKNavigationActionPolicyEnumData) {
} else if (value is WKNavigationActionData) {
buffer.putUint8(139);
writeValue(buffer, value.encode());
} else if (value is WKPermissionDecisionData) {
} else if (value is WKNavigationActionPolicyEnumData) {
buffer.putUint8(140);
writeValue(buffer, value.encode());
} else if (value is WKScriptMessageData) {
} else if (value is WKPermissionDecisionData) {
buffer.putUint8(141);
writeValue(buffer, value.encode());
} else if (value is WKSecurityOriginData) {
} else if (value is WKScriptMessageData) {
buffer.putUint8(142);
writeValue(buffer, value.encode());
} else if (value is WKUserScriptData) {
} else if (value is WKSecurityOriginData) {
buffer.putUint8(143);
writeValue(buffer, value.encode());
} else if (value is WKUserScriptInjectionTimeEnumData) {
} else if (value is WKUserScriptData) {
buffer.putUint8(144);
writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
} else if (value is WKUserScriptInjectionTimeEnumData) {
buffer.putUint8(145);
writeValue(buffer, value.encode());
} else if (value is WKWebsiteDataTypeEnumData) {
buffer.putUint8(146);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@ -1262,40 +1265,42 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return NSErrorData.decode(readValue(buffer)!);
return AuthenticationChallengeResponse.decode(readValue(buffer)!);
case 129:
return NSHttpCookieData.decode(readValue(buffer)!);
return NSErrorData.decode(readValue(buffer)!);
case 130:
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
return NSHttpCookieData.decode(readValue(buffer)!);
case 131:
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!);
case 132:
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!);
case 133:
return NSUrlRequestData.decode(readValue(buffer)!);
return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!);
case 134:
return ObjectOrIdentifier.decode(readValue(buffer)!);
return NSUrlRequestData.decode(readValue(buffer)!);
case 135:
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
return ObjectOrIdentifier.decode(readValue(buffer)!);
case 136:
return WKFrameInfoData.decode(readValue(buffer)!);
return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!);
case 137:
return WKMediaCaptureTypeData.decode(readValue(buffer)!);
return WKFrameInfoData.decode(readValue(buffer)!);
case 138:
return WKNavigationActionData.decode(readValue(buffer)!);
return WKMediaCaptureTypeData.decode(readValue(buffer)!);
case 139:
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
return WKNavigationActionData.decode(readValue(buffer)!);
case 140:
return WKPermissionDecisionData.decode(readValue(buffer)!);
return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!);
case 141:
return WKScriptMessageData.decode(readValue(buffer)!);
return WKPermissionDecisionData.decode(readValue(buffer)!);
case 142:
return WKSecurityOriginData.decode(readValue(buffer)!);
return WKScriptMessageData.decode(readValue(buffer)!);
case 143:
return WKUserScriptData.decode(readValue(buffer)!);
return WKSecurityOriginData.decode(readValue(buffer)!);
case 144:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
return WKUserScriptData.decode(readValue(buffer)!);
case 145:
return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!);
case 146:
return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!);
default:
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>[
TestNSObjectHostApi,
TestNSUrlCredentialHostApi,
TestNSUrlHostApi,
])
void main() {
@ -245,6 +246,102 @@ void main() {
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', () {

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].
///
/// 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]));
});
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', () {

View File

@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/web_kit/web_kit.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.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.decidePolicyForNavigationAction,
super.webViewWebContentProcessDidTerminate,
super.didReceiveAuthenticationChallenge,
}) : super.detached() {
lastCreatedDelegate = this;
}

View File

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