diff --git a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m index a07bf290a..5be8a41e7 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m +++ b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m @@ -45,6 +45,7 @@ @interface CDVWebViewEngine () @property (nonatomic, strong) CDVURLSchemeHandler * schemeHandler; @property (nonatomic, readwrite) NSString *CDV_ASSETS_URL; @property (nonatomic, readwrite) Boolean cdvIsFileScheme; +@property (nullable, nonatomic, strong, readwrite) WKWebViewConfiguration *configuration; @end @@ -55,24 +56,36 @@ @implementation CDVWebViewEngine @synthesize engineWebView = _engineWebView; -- (instancetype)initWithFrame:(CGRect)frame +- (nullable instancetype)initWithFrame:(CGRect)frame configuration:(nullable WKWebViewConfiguration *)configuration { self = [super init]; if (self) { if (NSClassFromString(@"WKWebView") == nil) { return nil; } - - self.engineWebView = [[WKWebView alloc] initWithFrame:frame]; + + self.configuration = configuration; + self.engineWebView = configuration ? [[WKWebView alloc] initWithFrame:frame configuration:configuration] : [[WKWebView alloc] initWithFrame:frame]; } return self; } +- (nullable instancetype)initWithFrame:(CGRect)frame +{ + return [self initWithFrame:frame configuration:nil]; +} + - (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings { - WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; - configuration.processPool = [[CDVWebViewProcessPoolFactory sharedFactory] sharedProcessPool]; + WKWebViewConfiguration* configuration; + if (_configuration) { + configuration = _configuration; + } else { + configuration = [[WKWebViewConfiguration alloc] init]; + configuration.processPool = [[CDVWebViewProcessPoolFactory sharedFactory] sharedProcessPool]; + } + if (settings == nil) { return configuration; } diff --git a/CordovaLib/Classes/Public/CDVViewController.h b/CordovaLib/Classes/Public/CDVViewController.h index 81c7128f1..8e595e30f 100644 --- a/CordovaLib/Classes/Public/CDVViewController.h +++ b/CordovaLib/Classes/Public/CDVViewController.h @@ -26,6 +26,25 @@ #import "CDVScreenOrientationDelegate.h" #import "CDVPlugin.h" #import "CDVWebViewEngineProtocol.h" +@import WebKit; + +@protocol CDVWebViewEngineConfigurationDelegate + +@optional +/// Provides a fully configured WKWebViewConfiguration which will be overriden with +/// any related settings you add to config.xml (e.g., `PreferredContentMode`). +/// Useful for more complex configuration, including websiteDataStore. +/// +/// Example usage: +/// +/// extension CDVViewController: CDVWebViewEngineConfigurationDelegate { +/// public func configuration() -> WKWebViewConfiguration { +/// // return your config here +/// } +/// } +- (nonnull WKWebViewConfiguration*)configuration; + +@end @interface CDVViewController : UIViewController { @protected @@ -36,10 +55,12 @@ CDVCommandQueue* _commandQueue; } +NS_ASSUME_NONNULL_BEGIN + @property (nonatomic, readonly, weak) IBOutlet UIView* webView; @property (nonatomic, readonly, strong) UIView* launchView; -@property (nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; +@property (nullable, nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; @property (nonatomic, readonly, strong) NSDictionary* pluginsMap; @property (nonatomic, readonly, strong) NSMutableDictionary* settings; @property (nonatomic, readonly, strong) NSXMLParser* configParser; @@ -53,21 +74,21 @@ @property (nonatomic, readonly, strong) id commandDelegate; /** - Takes/Gives an array of UIInterfaceOrientation (int) objects - ex. UIInterfaceOrientationPortrait + Takes/Gives an array of UIInterfaceOrientation (int) objects + ex. UIInterfaceOrientationPortrait */ @property (nonatomic, readwrite, strong) NSArray* supportedOrientations; - (UIView*)newCordovaViewWithFrame:(CGRect)bounds; -- (NSString*)appURLScheme; -- (NSURL*)errorURL; +- (nullable NSString*)appURLScheme; +- (nullable NSURL*)errorURL; - (UIColor*)colorFromColorString:(NSString*)colorString CDV_DEPRECATED(7.0.0, "Use BackgroundColor in xcassets"); - (NSArray*)parseInterfaceOrientations:(NSArray*)orientations; - (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation; -- (id)getCommandInstance:(NSString*)pluginName; +- (nullable id)getCommandInstance:(NSString*)pluginName; - (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className; - (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName; @@ -75,4 +96,6 @@ - (void)showLaunchScreen:(BOOL)visible; +NS_ASSUME_NONNULL_END + @end diff --git a/CordovaLib/Classes/Public/CDVViewController.m b/CordovaLib/Classes/Public/CDVViewController.m index dcec9589e..106630d98 100644 --- a/CordovaLib/Classes/Public/CDVViewController.m +++ b/CordovaLib/Classes/Public/CDVViewController.m @@ -27,7 +27,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVCommandDelegateImpl.h" #import -@interface CDVViewController () { } +@interface CDVViewController () { } @property (nonatomic, readwrite, strong) NSXMLParser* configParser; @property (nonatomic, readwrite, strong) NSMutableDictionary* settings; @@ -238,7 +238,7 @@ - (NSURL*)appUrl return appURL; } -- (NSURL*)errorURL +- (nullable NSURL*)errorURL { NSURL* errorUrl = nil; @@ -485,36 +485,61 @@ - (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation return [self.supportedOrientations containsObject:@(orientation)]; } -- (UIView*)newCordovaViewWithFrame:(CGRect)bounds +/// Retrieves the view from a newwly initialized webViewEngine +/// @param bounds The bounds with which the webViewEngine will be initialized +- (nonnull UIView*)newCordovaViewWithFrame:(CGRect)bounds { - NSString* defaultWebViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaDefaultWebViewEngine"]; - NSString* webViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"]; + NSString* defaultWebViewEngineClassName = [self.settings cordovaSettingForKey:@"CordovaDefaultWebViewEngine"]; + NSString* webViewEngineClassName = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"]; - if (!defaultWebViewEngineClass) { - defaultWebViewEngineClass = @"CDVWebViewEngine"; + if (!defaultWebViewEngineClassName) { + defaultWebViewEngineClassName = @"CDVWebViewEngine"; } - if (!webViewEngineClass) { - webViewEngineClass = defaultWebViewEngineClass; + if (!webViewEngineClassName) { + webViewEngineClassName = defaultWebViewEngineClassName; } - // Find webViewEngine - if (NSClassFromString(webViewEngineClass)) { - self.webViewEngine = [[NSClassFromString(webViewEngineClass) alloc] initWithFrame:bounds]; - // if a webView engine returns nil (not supported by the current iOS version) or doesn't conform to the protocol, or can't load the request, we use WKWebView - if (!self.webViewEngine || ![self.webViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)] || ![self.webViewEngine canLoadRequest:[NSURLRequest requestWithURL:self.appUrl]]) { - self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; + // Determine if a provided custom web view engine is sufficient + id engine; + Class customWebViewEngineClass = NSClassFromString(webViewEngineClassName); + if (customWebViewEngineClass) { + id customWebViewEngine = [self initWebViewEngine:customWebViewEngineClass bounds:bounds]; + BOOL customConformsToProtocol = [customWebViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)]; + BOOL customCanLoad = [customWebViewEngine canLoadRequest:[NSURLRequest requestWithURL:self.appUrl]]; + if (customConformsToProtocol && customCanLoad) { + engine = customWebViewEngine; } - } else { - self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; } - - if ([self.webViewEngine isKindOfClass:[CDVPlugin class]]) { - [self registerPlugin:(CDVPlugin*)self.webViewEngine withClassName:webViewEngineClass]; + + // Otherwise use the default web view engine + if (!engine) { + Class defaultWebViewEngineClass = NSClassFromString(defaultWebViewEngineClassName); + id defaultWebViewEngine = [self initWebViewEngine:defaultWebViewEngineClass bounds:bounds]; + NSAssert([defaultWebViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)], + @"we expected the default web view engine to conform to the CDVWebViewEngineProtocol"); + engine = defaultWebViewEngine; + } + + if ([engine isKindOfClass:[CDVPlugin class]]) { + [self registerPlugin:(CDVPlugin*)engine withClassName:webViewEngineClassName]; } + self.webViewEngine = engine; return self.webViewEngine.engineWebView; } +/// Initialiizes the webViewEngine, with config, if supported and provided +/// @param engineClass A class that must conform to the `CDVWebViewEngineProtocol` +/// @param bounds with which the webview will be initialized +- (id _Nullable) initWebViewEngine:(nonnull Class)engineClass bounds:(CGRect)bounds { + WKWebViewConfiguration *config = [self respondsToSelector:@selector(configuration)] ? [self configuration] : nil; + if (config && [engineClass respondsToSelector:@selector(initWithFrame:configuration:)]) { + return [[engineClass alloc] initWithFrame:bounds configuration:config]; + } else { + return [[engineClass alloc] initWithFrame:bounds]; + } +} + - (void)createLaunchView { CGRect webViewBounds = self.view.bounds; @@ -608,7 +633,7 @@ - (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName /** Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned. */ -- (id)getCommandInstance:(NSString*)pluginName +- (nullable id)getCommandInstance:(NSString*)pluginName { // first, we try to find the pluginName in the pluginsMap // (acts as a whitelist as well) if it does not exist, we return nil @@ -642,7 +667,7 @@ - (id)getCommandInstance:(NSString*)pluginName #pragma mark - -- (NSString*)appURLScheme +- (nullable NSString*)appURLScheme { NSString* URLScheme = nil; diff --git a/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h b/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h index e6126a4b4..8a562a704 100644 --- a/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h +++ b/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h @@ -18,6 +18,7 @@ */ #import +@import WebKit; #define kCDVWebViewEngineScriptMessageHandlers @"kCDVWebViewEngineScriptMessageHandlers" #define kCDVWebViewEngineWKNavigationDelegate @"kCDVWebViewEngineWKNavigationDelegate" @@ -26,16 +27,25 @@ @protocol CDVWebViewEngineProtocol +NS_ASSUME_NONNULL_BEGIN + @property (nonatomic, strong, readonly) UIView* engineWebView; - (id)loadRequest:(NSURLRequest*)request; -- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL; -- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler; +- (id)loadHTMLString:(NSString*)string baseURL:(nullable NSURL*)baseURL; +- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^_Nullable)(id, NSError*))completionHandler; - (NSURL*)URL; - (BOOL)canLoadRequest:(NSURLRequest*)request; +- (nullable instancetype)initWithFrame:(CGRect)frame; + +/// Convenience Initializer +/// @param frame The frame for the new web view. +/// @param configuration The configuration for the new web view. +- (nullable instancetype)initWithFrame:(CGRect)frame configuration:(nullable WKWebViewConfiguration *)configuration; -- (instancetype)initWithFrame:(CGRect)frame; - (void)updateWithInfo:(NSDictionary*)info; +NS_ASSUME_NONNULL_END + @end diff --git a/tests/CordovaLibTests/CDVWebViewEngineTest.m b/tests/CordovaLibTests/CDVWebViewEngineTest.m index 0facc70fa..962d9434d 100644 --- a/tests/CordovaLibTests/CDVWebViewEngineTest.m +++ b/tests/CordovaLibTests/CDVWebViewEngineTest.m @@ -126,7 +126,7 @@ - (void) testUpdateInfo { - (void) testConfigurationFromSettings { // we need to re-set the plugin from the "setup" to take in the app settings we need - self.plugin = [[CDVWebViewEngine alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + self.plugin = [[CDVWebViewEngine alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:nil]; self.viewController = [[CDVViewController alloc] init]; // generate the app settings