| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| #import "CDVWKWebViewEngine.h" |
| #import "CDVWKWebViewUIDelegate.h" |
| #import <Cordova/NSDictionary+CordovaPreferences.h> |
| |
| #import <objc/message.h> |
| |
| #define CDV_BRIDGE_NAME @"cordova" |
| |
| @interface CDVWKWebViewEngine () |
| |
| @property (nonatomic, strong, readwrite) UIView* engineWebView; |
| @property (nonatomic, strong, readwrite) id <WKUIDelegate> uiDelegate; |
| |
| @end |
| |
| // see forwardingTargetForSelector: selector comment for the reason for this pragma |
| #pragma clang diagnostic ignored "-Wprotocol" |
| |
| @implementation CDVWKWebViewEngine |
| |
| @synthesize engineWebView = _engineWebView; |
| |
| - (instancetype)initWithFrame:(CGRect)frame |
| { |
| self = [super init]; |
| if (self) { |
| if (!IsAtLeastiOSVersion(@"8.0")) { |
| return nil; |
| } |
| self.uiDelegate = [[CDVWKWebViewUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]]; |
| |
| WKUserContentController* userContentController = [[WKUserContentController alloc] init]; |
| [userContentController addScriptMessageHandler:self name:CDV_BRIDGE_NAME]; |
| |
| WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; |
| configuration.userContentController = userContentController; |
| |
| WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:frame configuration:configuration]; |
| |
| wkWebView.UIDelegate = self.uiDelegate; |
| |
| self.engineWebView = wkWebView; |
| |
| NSLog(@"Using WKWebView"); |
| } |
| |
| return self; |
| } |
| |
| - (void)pluginInitialize |
| { |
| // viewController would be available now. we attempt to set all possible delegates to it, by default |
| |
| WKWebView* wkWebView = (WKWebView*)_engineWebView; |
| |
| if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) { |
| wkWebView.UIDelegate = (id <WKUIDelegate>)self.viewController; |
| } |
| |
| if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) { |
| wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self.viewController; |
| } else { |
| wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self; |
| } |
| |
| if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) { |
| [wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:@"cordova"]; |
| } |
| |
| [self updateSettings:self.commandDelegate.settings]; |
| } |
| |
| - (id)loadRequest:(NSURLRequest*)request |
| { |
| SEL wk_sel = NSSelectorFromString(@"loadFileURL:allowingReadAccessToURL:"); |
| |
| // the URL needs to be a file reference |
| NSURL* url = request.URL; |
| |
| if ([_engineWebView respondsToSelector:wk_sel] && [url isFileReferenceURL]) { |
| // allow the folder containing the file reference to be read as well |
| NSURL* readAccessUrl = [request.URL URLByDeletingLastPathComponent]; |
| return ((id (*)(id, SEL, id, id))objc_msgSend)(_engineWebView, wk_sel, url, readAccessUrl); |
| } else { |
| return [(WKWebView*)_engineWebView loadRequest:request]; |
| } |
| } |
| |
| - (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL |
| { |
| return [(WKWebView*)_engineWebView loadHTMLString:string baseURL:baseURL]; |
| } |
| |
| - (NSURL*) URL |
| { |
| return [(WKWebView*)_engineWebView URL]; |
| } |
| |
| - (void)updateSettings:(NSDictionary*)settings |
| { |
| WKWebView* wkWebView = (WKWebView*)_engineWebView; |
| |
| wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0]; |
| wkWebView.configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:NO]; |
| wkWebView.configuration.mediaPlaybackRequiresUserAction = [settings cordovaBoolSettingForKey:@"MediaPlaybackRequiresUserAction" defaultValue:YES]; |
| wkWebView.configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO]; |
| wkWebView.configuration.mediaPlaybackAllowsAirPlay = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES]; |
| |
| /* |
| wkWebView.configuration.preferences.javaScriptEnabled = [settings cordovaBoolSettingForKey:@"JavaScriptEnabled" default:YES]; |
| wkWebView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = [settings cordovaBoolSettingForKey:@"JavaScriptCanOpenWindowsAutomatically" default:NO]; |
| */ |
| |
| // By default, DisallowOverscroll is false (thus bounce is allowed) |
| BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]); |
| |
| // prevent webView from bouncing |
| if (!bounceAllowed) { |
| if ([wkWebView respondsToSelector:@selector(scrollView)]) { |
| ((UIScrollView*)[wkWebView scrollView]).bounces = NO; |
| } else { |
| for (id subview in wkWebView.subviews) { |
| if ([[subview class] isSubclassOfClass:[UIScrollView class]]) { |
| ((UIScrollView*)subview).bounces = NO; |
| } |
| } |
| } |
| } |
| } |
| |
| - (void)updateWithInfo:(NSDictionary*)info |
| { |
| NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers]; |
| NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences]; |
| id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate]; |
| id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate]; |
| |
| WKWebView* wkWebView = (WKWebView*)_engineWebView; |
| |
| if (scriptMessageHandlers && [scriptMessageHandlers isKindOfClass:[NSDictionary class]]) { |
| NSArray* allKeys = [scriptMessageHandlers allKeys]; |
| |
| for (NSString* key in allKeys) { |
| id object = [scriptMessageHandlers objectForKey:key]; |
| if ([object conformsToProtocol:@protocol(WKScriptMessageHandler)]) { |
| [wkWebView.configuration.userContentController addScriptMessageHandler:object name:key]; |
| } |
| } |
| } |
| |
| if (navigationDelegate && [navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]) { |
| wkWebView.navigationDelegate = navigationDelegate; |
| } |
| |
| if (uiDelegate && [uiDelegate conformsToProtocol:@protocol(WKUIDelegate)]) { |
| wkWebView.UIDelegate = uiDelegate; |
| } |
| |
| if (settings && [settings isKindOfClass:[NSDictionary class]]) { |
| [self updateSettings:settings]; |
| } |
| } |
| |
| // This forwards the methods that are in the header that are not implemented here. |
| // Both WKWebView and UIWebView implement the below: |
| // loadHTMLString:baseURL: |
| // loadRequest: |
| - (id)forwardingTargetForSelector:(SEL)aSelector |
| { |
| return _engineWebView; |
| } |
| |
| #pragma mark WKScriptMessageHandler implementation |
| |
| - (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message |
| { |
| if (![message.name isEqualToString:CDV_BRIDGE_NAME]) { |
| return; |
| } |
| |
| CDVViewController* vc = (CDVViewController*)self.viewController; |
| |
| NSArray* jsonEntry = message.body; // NSString:callbackId, NSString:service, NSString:action, NSArray:args |
| CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; |
| CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName); |
| |
| if (![vc.commandQueue execute:command]) { |
| #ifdef DEBUG |
| NSError* error = nil; |
| NSString* commandJson = nil; |
| NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonEntry |
| options:0 |
| error:&error]; |
| |
| if (error == nil) { |
| commandJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; |
| } |
| |
| static NSUInteger maxLogLength = 1024; |
| NSString* commandString = ([commandJson length] > maxLogLength) ? |
| [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] : |
| commandJson; |
| |
| NSLog(@"FAILED pluginJSON = %@", commandString); |
| #endif |
| } |
| } |
| |
| #pragma mark WKNavigationDelegate implementation |
| |
| - (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation |
| { |
| [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]]; |
| } |
| |
| @end |