From 5b72b8e89aa35235b0b45453b59fb71acde8f9fc Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Sat, 14 Dec 2024 01:15:46 -0800 Subject: [PATCH] refactor(plugins): Add CDVPluginNavigationHandler protocol This also adds a dictionary parameter containing the other navigation action details so that plugins can make choices based on frames. Closes GH-1272. Closes GH-1333. Co-Authored-By: Michael Tamburro <61243400+msmtamburro@users.noreply.github.com> --- .../CDVIntentAndNavigationFilter.h | 4 +- .../CDVIntentAndNavigationFilter.m | 2 +- .../CDVWebViewEngine/CDVWebViewEngine.m | 83 +++++++++------- CordovaLib/CordovaLib.docc/CordovaLib.md | 3 +- CordovaLib/CordovaLib.docc/upgrading-8.md | 3 + CordovaLib/include/Cordova/CDVPlugin.h | 95 +++++++++++++------ 6 files changed, 123 insertions(+), 67 deletions(-) diff --git a/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h b/CordovaLib/Classes/Private/Plugins/CDVIntentAndNavigationFilter/CDVIntentAndNavigationFilter.h index 1e4bb3f762..3f6a1262bd 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 bf8479e733..511d618dd4 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 96715c0b55..dc39707878 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,64 @@ - (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); - } - /* - * 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); + if (anyPluginsResponded) { + return decisionHandler(shouldAllowRequest ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); + } } else { - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + CDVPlugin *intentAndNavFilter = (CDVPlugin *)[vc getCommandInstance:@"IntentAndNavigationFilter"]; + if (intentAndNavFilter) { + BOOL shouldAllowRequest = [intentAndNavFilter shouldOverrideLoadWithRequest:request navigationType:navType info:info]; + return decisionHandler(shouldAllowRequest ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel); + } } - return decisionHandler(NO); + // 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); } - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler diff --git a/CordovaLib/CordovaLib.docc/CordovaLib.md b/CordovaLib/CordovaLib.docc/CordovaLib.md index 8ee6f753b1..aa04deba8f 100644 --- a/CordovaLib/CordovaLib.docc/CordovaLib.md +++ b/CordovaLib/CordovaLib.docc/CordovaLib.md @@ -36,8 +36,9 @@ For more information about Apache Cordova, visit [https://cordova.apache.org](ht ### Cordova plugins - ``CDVPlugin`` -- ``CDVPluginSchemeHandler`` - ``CDVPluginAuthenticationHandler`` +- ``CDVPluginNavigationHandler`` +- ``CDVPluginSchemeHandler`` ### Plugin communication - ``CDVPluginResult`` diff --git a/CordovaLib/CordovaLib.docc/upgrading-8.md b/CordovaLib/CordovaLib.docc/upgrading-8.md index 67c8ca358c..285a38a50f 100644 --- a/CordovaLib/CordovaLib.docc/upgrading-8.md +++ b/CordovaLib/CordovaLib.docc/upgrading-8.md @@ -262,6 +262,9 @@ The following headers are deprecated due to adding global category extensions to * ``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 e423c6d316..4fad01c507 100644 --- a/CordovaLib/include/Cordova/CDVPlugin.h +++ b/CordovaLib/include/Cordova/CDVPlugin.h @@ -30,6 +30,8 @@ // 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 @@ -87,6 +89,69 @@ 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 + ``didReceiveAuthenticationChallenge:completionHandler:`` 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. + + - 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. + */ +- (void)didReceiveAuthenticationChallenge:(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. @@ -131,34 +196,4 @@ extern const NSNotificationName CDVViewWillTransitionToSizeNotification; - (void)stopSchemeTask:(id )task; @end - -/** - A protocol for Cordova plugins to intercept and respond to server - authentication challenges through WebKit. - - Your plugin should implement this protocol and the - ``didReceiveAuthenticationChallenge:completionHandler:`` 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. - - - 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. - */ -- (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler; - -@end - NS_ASSUME_NONNULL_END