diff --git a/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h b/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h index 1e4bb3f76..3f6a1262b 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h +++ b/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h @@ -20,15 +20,13 @@ #import #import "CDVAllowList.h" -#define CDVWebViewNavigationType int - typedef NS_ENUM(NSInteger, CDVIntentAndNavigationFilterValue) { CDVIntentAndNavigationFilterValueIntentAllowed, CDVIntentAndNavigationFilterValueNavigationAllowed, CDVIntentAndNavigationFilterValueNoneAllowed }; -@interface CDVIntentAndNavigationFilter : CDVPlugin +@interface CDVIntentAndNavigationFilter : CDVPlugin + (CDVIntentAndNavigationFilterValue) filterUrl:(NSURL*)url allowIntentsList:(CDVAllowList*)allowIntentsList navigationsAllowList:(CDVAllowList*)navigationsAllowList; + (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType filterValue:(CDVIntentAndNavigationFilterValue)filterValue; diff --git a/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m b/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m index bf8479e73..511d618dd 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m +++ b/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.m @@ -143,7 +143,7 @@ + (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDV } } -- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType +- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest*)request navigationType:(CDVWebViewNavigationType)navigationType info:(NSDictionary *)navInfo { return [[self class] shouldOverrideLoadWithRequest:request navigationType:navigationType filterValue:[self filterUrl:request.URL]]; } diff --git a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m index 45ad8ff8c..d2314c82e 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m +++ b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m @@ -477,7 +477,7 @@ - (CDVWebViewPermissionGrantType)parsePermissionGrantType:(NSString*)optionStrin return result; } -#pragma mark WKScriptMessageHandler implementation +#pragma mark - WKScriptMessageHandler implementation - (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message { @@ -513,7 +513,7 @@ - (void)userContentController:(WKUserContentController*)userContentController di } } -#pragma mark WKNavigationDelegate implementation +#pragma mark - WKNavigationDelegate implementation - (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation { @@ -561,45 +561,80 @@ - (BOOL)defaultResourcePolicyForURL:(NSURL*)url return NO; } -- (void) webView: (WKWebView *) webView decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - NSURL* url = [navigationAction.request URL]; - CDVViewController* vc = (CDVViewController*)self.viewController; + CDVViewController *vc = (CDVViewController *)self.viewController; - /* - * Give plugins the chance to handle the url - */ - BOOL anyPluginsResponded = NO; - BOOL shouldAllowRequest = NO; + NSURLRequest *request = navigationAction.request; + CDVWebViewNavigationType navType = (CDVWebViewNavigationType)navigationAction.navigationType; + NSMutableDictionary *info = [NSMutableDictionary dictionary]; + info[@"sourceFrame"] = navigationAction.sourceFrame; + info[@"targetFrame"] = navigationAction.targetFrame; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500 + if (@available(iOS 14.5, *)) { + info[@"shouldPerformDownload"] = [NSNumber numberWithBool:navigationAction.shouldPerformDownload]; + } +#endif - for (CDVPlugin *plugin in vc.enumerablePlugins) { - SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:"); - if ([plugin respondsToSelector:selector]) { - anyPluginsResponded = YES; - // https://issues.apache.org/jira/browse/CB-12497 - int navType = (int)navigationAction.navigationType; - shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, navigationAction.request, navType)); - if (!shouldAllowRequest) { - break; + // Give plugins the chance to handle the url, as long as this WebViewEngine is still the WKNavigationDelegate. + // This allows custom delegates to choose to call this method for `default` cordova behavior without querying all plugins. + if (webView.navigationDelegate == self) { + BOOL anyPluginsResponded = NO; + BOOL shouldAllowRequest = NO; + + for (CDVPlugin *plugin in vc.enumerablePlugins) { + if ([plugin respondsToSelector:@selector(shouldOverrideLoadWithRequest:navigationType:info:)] || [plugin respondsToSelector:@selector(shouldOverrideLoadWithRequest:navigationType:)]) { + CDVPlugin *navPlugin = (CDVPlugin *)plugin; + anyPluginsResponded = YES; + + if ([navPlugin respondsToSelector:@selector(shouldOverrideLoadWithRequest:navigationType:info:)]) { + shouldAllowRequest = [navPlugin shouldOverrideLoadWithRequest:request navigationType:navType info:info]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + shouldAllowRequest = [navPlugin shouldOverrideLoadWithRequest:request navigationType:navType]; +#pragma clang diagnostic pop + } + + if (!shouldAllowRequest) { + break; + } } } + + if (anyPluginsResponded) { + return decisionHandler(shouldAllowRequest ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); + } + } else { + CDVPlugin *intentAndNavFilter = (CDVPlugin *)[vc getCommandInstance:@"IntentAndNavigationFilter"]; + if (intentAndNavFilter) { + BOOL shouldAllowRequest = [intentAndNavFilter shouldOverrideLoadWithRequest:request navigationType:navType info:info]; + return decisionHandler(shouldAllowRequest ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); + } } - if (anyPluginsResponded) { - return decisionHandler(shouldAllowRequest); + // Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. + BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:request.URL]; + if (!shouldAllowNavigation) { + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:request.URL userInfo:@{}]]; } + return decisionHandler(shouldAllowNavigation ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); +} - /* - * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. - */ - BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url]; - if (shouldAllowNavigation) { - return decisionHandler(YES); - } else { - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; +- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler +{ + CDVViewController* vc = (CDVViewController*)self.viewController; + + for (CDVPlugin *plugin in vc.enumerablePlugins) { + if ([plugin respondsToSelector:@selector(willHandleAuthenticationChallenge:completionHandler:)]) { + CDVPlugin *challengePlugin = (CDVPlugin *)plugin; + if ([challengePlugin willHandleAuthenticationChallenge:challenge completionHandler:completionHandler]) { + return; + } + } } - return decisionHandler(NO); + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } #pragma mark - Plugin interface diff --git a/CordovaLib/Classes/Public/CDVPlugin.m b/CordovaLib/Classes/Public/CDVPlugin.m index 2a657703e..3eac82097 100644 --- a/CordovaLib/Classes/Public/CDVPlugin.m +++ b/CordovaLib/Classes/Public/CDVPlugin.m @@ -142,7 +142,7 @@ - (void)handleOpenURL:(NSNotification*)notification /* NOTE: calls into JavaScript must not call or trigger any blocking UI, like alerts */ -- (void)handleOpenURLWithApplicationSourceAndAnnotation: (NSNotification*)notification +- (void)handleOpenURLWithApplicationSourceAndAnnotation:(NSNotification*)notification { // override to handle urls sent to your app diff --git a/CordovaLib/CordovaLib.docc/CordovaLib.md b/CordovaLib/CordovaLib.docc/CordovaLib.md index f06ad8f2a..aa04deba8 100644 --- a/CordovaLib/CordovaLib.docc/CordovaLib.md +++ b/CordovaLib/CordovaLib.docc/CordovaLib.md @@ -36,6 +36,8 @@ For more information about Apache Cordova, visit [https://cordova.apache.org](ht ### Cordova plugins - ``CDVPlugin`` +- ``CDVPluginAuthenticationHandler`` +- ``CDVPluginNavigationHandler`` - ``CDVPluginSchemeHandler`` ### Plugin communication diff --git a/CordovaLib/CordovaLib.docc/upgrading-8.md b/CordovaLib/CordovaLib.docc/upgrading-8.md index 471a73295..285a38a50 100644 --- a/CordovaLib/CordovaLib.docc/upgrading-8.md +++ b/CordovaLib/CordovaLib.docc/upgrading-8.md @@ -259,6 +259,12 @@ The following headers are deprecated due to adding global category extensions to * The ``CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification`` notification is now deprecated. The existing ``CDVPluginHandleOpenURLNotification`` notification now includes the source and annotation in its `userInfo` dictionary. +* ``CDVPluginAuthenticationHandler`` + * Newly added protocol for plugins wishing to handle server authentication requests. + +* ``CDVPluginNavigationHandler`` + * Newly added protocol for plugins wishing to handle navigation request permitting or denying within the webview. + * ``CDVPluginSchemeHandler`` * Newly added protocol for plugins wishing to override WebKit scheme handling for web requests. diff --git a/CordovaLib/include/Cordova/CDVPlugin.h b/CordovaLib/include/Cordova/CDVPlugin.h index 981675fc5..db4b2cf3c 100644 --- a/CordovaLib/include/Cordova/CDVPlugin.h +++ b/CordovaLib/include/Cordova/CDVPlugin.h @@ -30,16 +30,20 @@ // Forward declaration to avoid bringing WebKit API into public headers @protocol WKURLSchemeTask; +typedef int CDVWebViewNavigationType; + #ifndef __swift__ // This global extension to the UIView class causes issues for Swift subclasses // of UIView with their own scrollView properties, so we're removing it from // the exposed Swift API and marking it as deprecated // TODO: Remove in Cordova 9 @interface UIView (org_apache_cordova_UIView_Extension) -@property (nonatomic, weak) UIScrollView* scrollView CDV_DEPRECATED(8, "Check for a scrollView property on the view object at runtime and invoke it dynamically."); +@property (nonatomic, weak, nullable) UIScrollView* scrollView CDV_DEPRECATED(8, "Check for a scrollView property on the view object at runtime and invoke it dynamically."); @end #endif +NS_ASSUME_NONNULL_BEGIN + extern const NSNotificationName CDVPageDidLoadNotification; extern const NSNotificationName CDVPluginHandleOpenURLNotification; extern const NSNotificationName CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification CDV_DEPRECATED(8, "Find sourceApplication and annotations in the userInfo of the CDVPluginHandleOpenURLNotification notification."); @@ -85,6 +89,74 @@ extern const NSNotificationName CDVViewWillTransitionToSizeNotification; #pragma mark - Plugin protocols +/** + A protocol for Cordova plugins to intercept and respond to server + authentication challenges through WebKit. + + Your plugin should implement this protocol and the + ``willHandleAuthenticationChallenge:completionHandler:`` method to return + `YES` if it wants to support responses to server-side authentication + challenges, otherwise the default NSURLSession handling for authentication + challenges will be used. + */ +@protocol CDVPluginAuthenticationHandler + +/** + Asks your plugin to respond to an authentication challenge. + + Return `YES` if the plugin is handling the challenge, and `NO` to fallback to + the default handling. + + - Parameters: + - challenge: The authentication challenge. + - completionHandler: A completion handler block to execute with the response. + This handler has no return value and takes the following parameters: + - disposition: The option to use to handle the challenge. For a list of + options, see `NSURLSessionAuthChallengeDisposition`. + - credential: The credential to use for authentication when the + `disposition` parameter contains the value + `NSURLSessionAuthChallengeUseCredential`. Specify `nil` to continue + without a credential. + - Returns: A Boolean value indicating if the plugin is handling the request. + */ +- (BOOL)willHandleAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler; + +@end + + +/** + A protocol for Cordova plugins to manage permitting and denying of webview + navigations. + + You plugin should implement this protocol if it wants to control whether the + webview is allowed to navigate to a requested URL. + */ +@protocol CDVPluginNavigationHandler + +/** + Asks your plugin to decide whether a navigation request should be permitted or + denied. + + - Parameters: + - request: The navigation request. + - navigationType: The type of action triggering the navigation. + - navInfo: Descriptive information about the action triggering the navigation. + + - Returns: A Boolean representing whether the navigation should be allowed or not. + */ +- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest *)request navigationType:(CDVWebViewNavigationType)navigationType info:(NSDictionary *)navInfo; + +@optional +/** + @DeprecationSummary { + Use ``shouldOverrideLoadWithRequest:navigationType:info:`` instead. + } + */ +- (BOOL)shouldOverrideLoadWithRequest:(NSURLRequest *)request navigationType:(CDVWebViewNavigationType)navigationType CDV_DEPRECATED_WITH_REPLACEMENT(8, "Use shouldOverrideLoadWithRequest:navigationType:info: instead", "shouldOverrideLoadWithRequest:navigationType:info:"); + +@end + + /** A protocol for Cordova plugins to intercept handling of WebKit resource loading for a custom URL scheme. @@ -128,3 +200,5 @@ extern const NSNotificationName CDVViewWillTransitionToSizeNotification; */ - (void)stopSchemeTask:(id )task; @end + +NS_ASSUME_NONNULL_END