diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.h b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.h new file mode 100644 index 0000000000..f4f8816e04 --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.h @@ -0,0 +1,27 @@ +/* + 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 + +@interface CDVWKProcessPoolFactory : NSObject +@property (nonatomic, retain) WKProcessPool* sharedPool; + ++(instancetype) sharedFactory; +-(WKProcessPool*) sharedProcessPool; +@end diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.m b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.m new file mode 100644 index 0000000000..48ac09e665 --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKProcessPoolFactory.m @@ -0,0 +1,49 @@ +/* + 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 +#import +#import "CDVWKProcessPoolFactory.h" + +static CDVWKProcessPoolFactory *factory = nil; + +@implementation CDVWKProcessPoolFactory + ++ (instancetype)sharedFactory +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + factory = [[CDVWKProcessPoolFactory alloc] init]; + }); + + return factory; +} + +- (instancetype)init +{ + if (self = [super init]) { + _sharedPool = [[WKProcessPool alloc] init]; + } + return self; +} + +- (WKProcessPool*) sharedProcessPool { + return _sharedPool; +} +@end diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.h b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.h new file mode 100644 index 0000000000..829653a56a --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.h @@ -0,0 +1,28 @@ +/* + 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 + +@interface CDVWKWebViewDelegate : NSObject + +@property (nonatomic, copy) NSString* title; + +- (instancetype)initWithTitle:(NSString*)title; + +@end diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.m b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.m new file mode 100644 index 0000000000..ea3bf91c94 --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewDelegate.m @@ -0,0 +1,123 @@ +/* + 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 "CDVWKWebViewDelegate.h" + +@implementation CDVWKWebViewDelegate + +- (instancetype)initWithTitle:(NSString*)title +{ + self = [super init]; + if (self) { + self.title = title; + } + + return self; +} + +- (void) webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message + initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +- (void) webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message + initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(YES); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(NO); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + [alert addAction:cancel]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +- (void) webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt + defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame + completionHandler:(void (^)(NSString* result))completionHandler +{ + UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title + message:prompt + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(alert.textFields[0].text); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + + [alert addAction:ok]; + + UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction* action) + { + completionHandler(nil); + [alert dismissViewControllerAnimated:YES completion:nil]; + }]; + [alert addAction:cancel]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) { + textField.text = defaultText; + }]; + + UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootController presentViewController:alert animated:YES completion:nil]; +} + +@end diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.h b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.h new file mode 100644 index 0000000000..c71f4e21b9 --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.h @@ -0,0 +1,29 @@ +/* + 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 +#import + +@interface CDVWKWebViewEngine : CDVPlugin + +@property (nonatomic, strong, readonly) id uiDelegate; + +- (void)allowsBackForwardNavigationGestures:(CDVInvokedUrlCommand*)command; + +@end diff --git a/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.m new file mode 100644 index 0000000000..2ee565d99f --- /dev/null +++ b/CordovaLib/Classes/Private/Plugins/CDVWKWebViewEngine/CDVWKWebViewEngine.m @@ -0,0 +1,463 @@ +/* + 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 "CDVWKWebViewDelegate.h" +#import "CDVWKProcessPoolFactory.h" +#import + +#import + +#define CDV_BRIDGE_NAME @"cordova" +#define CDV_WKWEBVIEW_FILE_URL_LOAD_SELECTOR @"loadFileURL:allowingReadAccessToURL:" + + +@interface CDVWKWeakScriptMessageHandler : NSObject + +@property(nonatomic, weak, readonly) id scriptMessageHandler; + +- (instancetype)initWithScriptMessageHandler:(id )scriptMessageHandler; + +@end + + +@interface CDVWKWebViewEngine () + +@property(nonatomic, strong, readwrite) UIView *engineWebView; +@property(nonatomic, strong, readwrite) id uiDelegate; +@property(nonatomic, weak) id weakScriptMessageHandler; + +@end + +// see forwardingTargetForSelector: selector comment for the reason for this pragma +#pragma clang diagnostic ignored "-Wprotocol" + +@implementation CDVWKWebViewEngine { + CGRect _frame; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super init]; + if (self) { + if (NSClassFromString(@"WKWebView") == nil) { + return nil; + } + + _frame = frame; + } + + return self; +} + +- (WKWebViewConfiguration *)createConfigurationFromSettings:(NSDictionary *)settings { + WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; + configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool]; + if (settings == nil) { + return configuration; + } + + configuration.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:NO]; + configuration.mediaPlaybackRequiresUserAction = [settings cordovaBoolSettingForKey:@"MediaPlaybackRequiresUserAction" defaultValue:YES]; + configuration.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO]; + configuration.mediaPlaybackAllowsAirPlay = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES]; + return configuration; +} + +- (void)pluginInitialize { + // viewController would be available now. we attempt to set all possible delegates to it, by default + NSDictionary *settings = self.commandDelegate.settings; + + self.uiDelegate = [[CDVWKWebViewDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]]; + + CDVWKWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWKWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self]; + + WKUserContentController *userContentController = [[WKUserContentController alloc] init]; + [userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME]; + + WKWebViewConfiguration *configuration = [self createConfigurationFromSettings:settings]; + configuration.userContentController = userContentController; + + // re-create WKWebView, since we need to update configuration + WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:_frame configuration:configuration]; + wkWebView.UIDelegate = self.uiDelegate; + self.engineWebView = wkWebView; + + if (IsAtLeastiOSVersion(@"9.0") && [self.viewController isKindOfClass:[CDVViewController class]]) { + wkWebView.customUserAgent = ((CDVViewController *) self.viewController).userAgent; + } + + if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) { + wkWebView.UIDelegate = (id ) self.viewController; + } + + if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) { + wkWebView.navigationDelegate = (id ) self.viewController; + } else { + wkWebView.navigationDelegate = (id ) self; + } + + if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) { + [wkWebView.configuration.userContentController addScriptMessageHandler:(id ) self.viewController name:CDV_BRIDGE_NAME]; + } + + [self updateSettings:settings]; + + // check if content thread has died on resume + NSLog(@"%@", @"CDVWKWebViewEngine will reload WKWebView if required on resume"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onAppWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + + NSLog(@"Using WKWebView"); + + [self addURLObserver]; +} + +- (void)onReset { + [self addURLObserver]; +} + +static void *KVOContext = &KVOContext; + +- (void)addURLObserver { + if (!IsAtLeastiOSVersion(@"9.0")) { + [self.webView addObserver:self forKeyPath:@"URL" options:0 context:KVOContext]; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == KVOContext) { + if (object == [self webView] && [keyPath isEqualToString:@"URL"] && [object valueForKeyPath:keyPath] == nil) { + NSLog(@"URL is nil. Reloading WKWebView"); + [(WKWebView *) _engineWebView reload]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)onAppWillEnterForeground:(NSNotification *)notification { + if ([self shouldReloadWebView]) { + NSLog(@"%@", @"CDVWKWebViewEngine reloading!"); + [(WKWebView *) _engineWebView reload]; + } +} + +- (BOOL)shouldReloadWebView { + WKWebView *wkWebView = (WKWebView *) _engineWebView; + return [self shouldReloadWebView:wkWebView.URL title:wkWebView.title]; +} + +- (BOOL)shouldReloadWebView:(NSURL *)location title:(NSString *)title { + BOOL title_is_nil = (title == nil); + BOOL location_is_blank = [[location absoluteString] isEqualToString:@"about:blank"]; + + BOOL reload = (title_is_nil || location_is_blank); + +#ifdef DEBUG + NSLog(@"%@", @"CDVWKWebViewEngine shouldReloadWebView::"); + NSLog(@"CDVWKWebViewEngine shouldReloadWebView title: %@", title); + NSLog(@"CDVWKWebViewEngine shouldReloadWebView location: %@", [location absoluteString]); + NSLog(@"CDVWKWebViewEngine shouldReloadWebView reload: %u", reload); +#endif + + return reload; +} + + +- (id)loadRequest:(NSURLRequest *)request { + if ([self canLoadRequest:request]) { // can load, differentiate between file urls and other schemes + if (request.URL.fileURL) { + NSURL *readAccessUrl = [request.URL URLByDeletingLastPathComponent]; + return [(id) _engineWebView loadFileURL:request.URL allowingReadAccessToURL:readAccessUrl]; + } else { + return [(WKWebView *) _engineWebView loadRequest:request]; + } + } else { // can't load, print out error + NSString *errorHtml = [NSString stringWithFormat: + @"" + @"Error" + @"
" + @"

The WebView engine '%@' is unable to load the request: %@

" + @"

Most likely the cause of the error is that the loading of file urls is not supported in iOS %@.

" + @"
", + NSStringFromClass([self class]), + [request.URL description], + [[UIDevice currentDevice] systemVersion] + ]; + return [self loadHTMLString:errorHtml baseURL:nil]; + } +} + +- (id)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL { + return [(WKWebView *) _engineWebView loadHTMLString:string baseURL:baseURL]; +} + +- (NSURL *)URL { + return [(WKWebView *) _engineWebView URL]; +} + +- (BOOL)canLoadRequest:(NSURLRequest *)request { + // See: https://issues.apache.org/jira/browse/CB-9636 + SEL wk_sel = NSSelectorFromString(CDV_WKWEBVIEW_FILE_URL_LOAD_SELECTOR); + + // if it's a file URL, check whether WKWebView has the selector (which is in iOS 9 and up only) + if (request.URL.fileURL) { + return [_engineWebView respondsToSelector:wk_sel]; + } else { + return YES; + } +} + +- (void)updateSettings:(NSDictionary *)settings { + WKWebView *wkWebView = (WKWebView *) _engineWebView; + + wkWebView.configuration.preferences.minimumFontSize = [settings cordovaFloatSettingForKey:@"MinimumFontSize" defaultValue:0.0]; + + /* + 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; + } + } + } + } + + NSString *decelerationSetting = [settings cordovaSettingForKey:@"WKWebViewDecelerationSpeed"]; + if (!decelerationSetting) { + // Fallback to the UIWebView-named preference + decelerationSetting = [settings cordovaSettingForKey:@"UIWebViewDecelerationSpeed"]; + } + + if (![@"fast" isEqualToString:decelerationSetting]) { + [wkWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal]; + } else { + [wkWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateFast]; + } + + wkWebView.allowsBackForwardNavigationGestures = [settings cordovaBoolSettingForKey:@"AllowBackForwardNavigationGestures" defaultValue:NO]; + wkWebView.allowsLinkPreview = [settings cordovaBoolSettingForKey:@"Allow3DTouchLinkPreview" defaultValue:YES]; +} + +- (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 != nil) { + [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; +} + +- (UIView *)webView { + return self.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 didStartProvisionalNavigation:(WKNavigation *)navigation { + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:webView]]; +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + CDVViewController *vc = (CDVViewController *) self.viewController; + [CDVUserAgentUtil releaseLock:vc.userAgentLockToken]; + + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:webView]]; +} + +- (void)webView:(WKWebView *)theWebView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { + [self webView:theWebView didFailNavigation:navigation withError:error]; +} + +- (void)webView:(WKWebView *)theWebView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { + CDVViewController *vc = (CDVViewController *) self.viewController; + [CDVUserAgentUtil releaseLock:vc.userAgentLockToken]; + + NSString *message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]]; + NSLog(@"%@", message); + + NSURL *errorUrl = vc.errorURL; + if (errorUrl) { + errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl]; + NSLog(@"%@", [errorUrl absoluteString]); + [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]]; + } +} + +- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView { + [webView reload]; +} + +- (BOOL)defaultResourcePolicyForURL:(NSURL *)url { + // all file:// urls are allowed + if ([url isFileURL]) { + return YES; + } + + return NO; +} + +- (void) webView:(WKWebView *)webView +decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction + decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + NSURL *url = [navigationAction.request URL]; + CDVViewController *vc = (CDVViewController *) self.viewController; + + /* + * Give plugins the chance to handle the url + */ + + for (NSString *pluginName in vc.pluginObjects) { + CDVPlugin *plugin = [vc.pluginObjects objectForKey:pluginName]; + SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:"); + if ([plugin respondsToSelector:selector]) { + WKNavigationType navType = navigationAction.navigationType; + if (WKNavigationTypeOther == navigationAction.navigationType) { + navType = UIWebViewNavigationTypeOther; + } + if((((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, navigationAction.request, navType))) { + return decisionHandler(WKNavigationActionPolicyAllow); + } else { + break; + } + } + } + + /* + * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. + */ + if ([self defaultResourcePolicyForURL:url]) { + return decisionHandler(WKNavigationActionPolicyAllow); + } else { + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; + } + + return decisionHandler(WKNavigationActionPolicyCancel); +} + +#pragma mark - Plugin interface + +- (void)allowsBackForwardNavigationGestures:(CDVInvokedUrlCommand *)command; { + id value = [command argumentAtIndex:0]; + if (!([value isKindOfClass:[NSNumber class]])) { + value = [NSNumber numberWithBool:NO]; + } + + WKWebView *wkWebView = (WKWebView *) _engineWebView; + wkWebView.allowsBackForwardNavigationGestures = [value boolValue]; +} + +@end + +#pragma mark - CDVWKWeakScriptMessageHandler + +@implementation CDVWKWeakScriptMessageHandler + +- (instancetype)initWithScriptMessageHandler:(id )scriptMessageHandler { + self = [super init]; + if (self) { + _scriptMessageHandler = scriptMessageHandler; + } + return self; +} + +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { + [self.scriptMessageHandler userContentController:userContentController didReceiveScriptMessage:message]; +} + +@end diff --git a/CordovaLib/Classes/Public/CDVViewController.m b/CordovaLib/Classes/Public/CDVViewController.m index 75e74add02..cf9be0def5 100644 --- a/CordovaLib/Classes/Public/CDVViewController.m +++ b/CordovaLib/Classes/Public/CDVViewController.m @@ -27,6 +27,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVLocalStorage.h" #import "CDVCommandDelegateImpl.h" #import +#import @interface CDVViewController () { NSInteger _userAgentLockToken; @@ -286,12 +287,6 @@ - (void)viewDidLoad [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType]; - // // Instantiate the WebView /////////////// - - if (!self.webView) { - [self createGapView]; - } - // ///////////////// /* @@ -300,7 +295,7 @@ - (void)viewDidLoad */ if ([backupWebStorageType isEqualToString:@"local"]) { NSString* localStorageFeatureName = @"localstorage"; - if ([self.pluginsMap objectForKey:localStorageFeatureName]) { // plugin specified in config + if (self.pluginsMap[localStorageFeatureName]) { // plugin specified in config [self.startupPluginNames addObject:localStorageFeatureName]; } } @@ -316,6 +311,21 @@ - (void)viewDidLoad [CDVTimer stop:@"TotalPluginStartup"]; } +} + +- (void)setLockToken:(NSInteger)lockToken +{ + _userAgentLockToken = lockToken; + [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; +} + +-(void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if (!self.webView) { + [self createGapView]; + } // ///////////////// NSURL* appURL = [self appUrl]; @@ -343,22 +353,11 @@ - (void)viewDidLoad } }]; - // ///////////////// NSString* bgColorString = [self.settings cordovaSettingForKey:@"BackgroundColor"]; UIColor* bgColor = [self colorFromColorString:bgColorString]; [self.webView setBackgroundColor:bgColor]; -} -- (void)setLockToken:(NSInteger)lockToken -{ - _userAgentLockToken = lockToken; - [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; -} - --(void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVViewWillAppearNotification object:nil]]; } @@ -514,7 +513,7 @@ - (UIView*)newCordovaViewWithFrame:(CGRect)bounds NSString* webViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"]; if (!defaultWebViewEngineClass) { - defaultWebViewEngineClass = @"CDVUIWebViewEngine"; + defaultWebViewEngineClass = @"CDVWKWebViewEngine"; } if (!webViewEngineClass) { webViewEngineClass = defaultWebViewEngineClass; @@ -565,14 +564,18 @@ - (NSString*)userAgent - (void)createGapView { CGRect webViewBounds = self.view.bounds; - webViewBounds.origin = self.view.bounds.origin; + UIView* webView = [self newCordovaViewWithFrame:webViewBounds]; + + self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + + [self.view addSubview:webView]; + [self.view sendSubviewToBack:webView]; - UIView* view = [self newCordovaViewWithFrame:webViewBounds]; + webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - [self.view addSubview:view]; - [self.view sendSubviewToBack:view]; + self.view.backgroundColor = UIColor.brownColor; + webView.backgroundColor = UIColor.greenColor; } - (void)didReceiveMemoryWarning diff --git a/CordovaLib/Cordova/Cordova.h b/CordovaLib/Cordova/Cordova.h index 8f3107709d..5c2562d9a3 100644 --- a/CordovaLib/Cordova/Cordova.h +++ b/CordovaLib/Cordova/Cordova.h @@ -6,9 +6,9 @@ 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 @@ -44,6 +44,7 @@ FOUNDATION_EXPORT const unsigned char CordovaVersionString[]; #import #import #import +#import "Cordova/CDVWKWebViewDelegate.h" #import #import #import diff --git a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index cb67163887..a43ffd26a8 100644 --- a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -95,6 +95,18 @@ 9052DE902150D06B008E83D4 /* CDVLocalStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CFB1AB9028C008C4574 /* CDVLocalStorage.h */; }; 9052DE912150D06B008E83D4 /* CDVUIWebViewNavigationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30193A4F1AE6350A0069A75F /* CDVUIWebViewNavigationDelegate.h */; }; 9052DE922150D06B008E83D4 /* CDVUIWebViewEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95D001AB9028C008C4574 /* CDVUIWebViewEngine.h */; }; + 99EE54E2163B9C6A704A3E49 /* CDVWKProcessPoolFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE5504B72C826326EC253E /* CDVWKProcessPoolFactory.h */; }; + 99EE55903ED79FD1D0591C6E /* CDVWKWebViewEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE54FCA7FE62F1D27D53EF /* CDVWKWebViewEngine.m */; }; + 99EE55C343055C2266640E20 /* CDVWKWebViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE59786FCE16F077EA2558 /* CDVWKWebViewDelegate.h */; }; + 99EE55E8F5A9F926FFEF96DD /* CDVWKWebViewEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE5AC13A8C8E8736073B17 /* CDVWKWebViewEngine.h */; }; + 99EE563B27A1FDB8E454C415 /* CDVWKWebViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE59786FCE16F077EA2558 /* CDVWKWebViewDelegate.h */; }; + 99EE5835FB7FD487366780F9 /* CDVWKWebViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE586A26D8C4EBDCE5DE75 /* CDVWKWebViewDelegate.m */; }; + 99EE5C2C3BAE481AB0AB8B0C /* CDVWKWebViewEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE54FCA7FE62F1D27D53EF /* CDVWKWebViewEngine.m */; }; + 99EE5E5525C367BD7AF435CF /* CDVWKProcessPoolFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE5504B72C826326EC253E /* CDVWKProcessPoolFactory.h */; }; + 99EE5E86307B5D4E9AA3E8A0 /* CDVWKProcessPoolFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE52ADD237A22CE3D23482 /* CDVWKProcessPoolFactory.m */; }; + 99EE5F2F4A92F9A70DAE82FF /* CDVWKProcessPoolFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE52ADD237A22CE3D23482 /* CDVWKProcessPoolFactory.m */; }; + 99EE5F4FE3EAF8D03E59B260 /* CDVWKWebViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 99EE586A26D8C4EBDCE5DE75 /* CDVWKWebViewDelegate.m */; }; + 99EE5F7DCAB912B45AA761B2 /* CDVWKWebViewEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 99EE5AC13A8C8E8736073B17 /* CDVWKWebViewEngine.h */; }; A3B082D41BB15CEA00D8DC35 /* CDVGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */; }; A3B082D51BB15CEA00D8DC35 /* CDVGestureHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */; }; C0C01EB61E3911D50056E6CB /* Cordova.h in Headers */ = {isa = PBXBuildFile; fileRef = C0C01EB41E3911D50056E6CB /* Cordova.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -178,6 +190,12 @@ 7ED95D321AB9029B008C4574 /* NSDictionary+CordovaPreferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+CordovaPreferences.m"; sourceTree = ""; }; 7ED95D331AB9029B008C4574 /* NSMutableArray+QueueAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+QueueAdditions.h"; sourceTree = ""; }; 7ED95D341AB9029B008C4574 /* NSMutableArray+QueueAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+QueueAdditions.m"; sourceTree = ""; }; + 99EE52ADD237A22CE3D23482 /* CDVWKProcessPoolFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWKProcessPoolFactory.m; sourceTree = ""; }; + 99EE54FCA7FE62F1D27D53EF /* CDVWKWebViewEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWKWebViewEngine.m; sourceTree = ""; }; + 99EE5504B72C826326EC253E /* CDVWKProcessPoolFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWKProcessPoolFactory.h; sourceTree = ""; }; + 99EE586A26D8C4EBDCE5DE75 /* CDVWKWebViewDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVWKWebViewDelegate.m; sourceTree = ""; }; + 99EE59786FCE16F077EA2558 /* CDVWKWebViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWKWebViewDelegate.h; sourceTree = ""; }; + 99EE5AC13A8C8E8736073B17 /* CDVWKWebViewEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVWKWebViewEngine.h; sourceTree = ""; }; A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVGestureHandler.h; sourceTree = ""; }; A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVGestureHandler.m; sourceTree = ""; }; AA747D9E0F9514B9006C5449 /* CordovaLib_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CordovaLib_Prefix.pch; sourceTree = SOURCE_ROOT; }; @@ -264,6 +282,7 @@ 7ED95CF71AB9028C008C4574 /* CDVHandleOpenURL */, 7ED95CFA1AB9028C008C4574 /* CDVLocalStorage */, 7ED95CFD1AB9028C008C4574 /* CDVUIWebViewEngine */, + 99EE5FE3B0BAE79CA9E104E2 /* CDVWKWebViewEngine */, ); path = Plugins; sourceTree = ""; @@ -343,6 +362,19 @@ path = Classes/Public; sourceTree = ""; }; + 99EE5FE3B0BAE79CA9E104E2 /* CDVWKWebViewEngine */ = { + isa = PBXGroup; + children = ( + 99EE5504B72C826326EC253E /* CDVWKProcessPoolFactory.h */, + 99EE52ADD237A22CE3D23482 /* CDVWKProcessPoolFactory.m */, + 99EE5AC13A8C8E8736073B17 /* CDVWKWebViewEngine.h */, + 99EE54FCA7FE62F1D27D53EF /* CDVWKWebViewEngine.m */, + 99EE59786FCE16F077EA2558 /* CDVWKWebViewDelegate.h */, + 99EE586A26D8C4EBDCE5DE75 /* CDVWKWebViewDelegate.m */, + ); + path = CDVWKWebViewEngine; + sourceTree = ""; + }; A3B082D11BB15CEA00D8DC35 /* CDVGestureHandler */ = { isa = PBXGroup; children = ( @@ -401,6 +433,9 @@ 9052DE912150D06B008E83D4 /* CDVUIWebViewNavigationDelegate.h in Headers */, C0C01ED01E3913610056E6CB /* CDVUIWebViewDelegate.h in Headers */, 9052DE922150D06B008E83D4 /* CDVUIWebViewEngine.h in Headers */, + 99EE54E2163B9C6A704A3E49 /* CDVWKProcessPoolFactory.h in Headers */, + 99EE5F7DCAB912B45AA761B2 /* CDVWKWebViewEngine.h in Headers */, + 99EE563B27A1FDB8E454C415 /* CDVWKWebViewDelegate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -440,6 +475,9 @@ 30193A511AE6350A0069A75F /* CDVUIWebViewNavigationDelegate.h in Headers */, 7ED95D0A1AB9028C008C4574 /* CDVUIWebViewDelegate.h in Headers */, 7E7F69B81ABA368F007546F4 /* CDVUIWebViewEngine.h in Headers */, + 99EE5E5525C367BD7AF435CF /* CDVWKProcessPoolFactory.h in Headers */, + 99EE55E8F5A9F926FFEF96DD /* CDVWKWebViewEngine.h in Headers */, + 99EE55C343055C2266640E20 /* CDVWKWebViewDelegate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -545,6 +583,9 @@ 9052DE862150D040008E83D4 /* CDVUIWebViewNavigationDelegate.m in Sources */, 9052DE872150D040008E83D4 /* CDVUIWebViewDelegate.m in Sources */, 9052DE882150D040008E83D4 /* CDVUIWebViewEngine.m in Sources */, + 99EE5F2F4A92F9A70DAE82FF /* CDVWKProcessPoolFactory.m in Sources */, + 99EE5C2C3BAE481AB0AB8B0C /* CDVWKWebViewEngine.m in Sources */, + 99EE5F4FE3EAF8D03E59B260 /* CDVWKWebViewDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -576,6 +617,9 @@ 30193A501AE6350A0069A75F /* CDVUIWebViewNavigationDelegate.m in Sources */, 7ED95D0B1AB9028C008C4574 /* CDVUIWebViewDelegate.m in Sources */, 7ED95D0D1AB9028C008C4574 /* CDVUIWebViewEngine.m in Sources */, + 99EE5E86307B5D4E9AA3E8A0 /* CDVWKProcessPoolFactory.m in Sources */, + 99EE55903ED79FD1D0591C6E /* CDVWKWebViewEngine.m in Sources */, + 99EE5835FB7FD487366780F9 /* CDVWKWebViewDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CordovaLib/cordova.js b/CordovaLib/cordova.js index 32961a9159..34abe0c593 100644 --- a/CordovaLib/cordova.js +++ b/CordovaLib/cordova.js @@ -970,19 +970,23 @@ function iOSExec () { actionArgs = massageArgsJsToNative(actionArgs); - var command = [callbackId, service, action, actionArgs]; - - // Stringify and queue the command. We stringify to command now to - // effectively clone the command arguments in case they are mutated before - // the command is executed. - commandQueue.push(JSON.stringify(command)); - - // If we're in the context of a stringByEvaluatingJavaScriptFromString call, - // then the queue will be flushed when it returns; no need for a poke. - // Also, if there is already a command in the queue, then we've already - // poked the native side, so there is no reason to do so again. - if (!isInContextOfEvalJs && commandQueue.length === 1) { - pokeNative(); + if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) { + var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))]; + window.webkit.messageHandlers.cordova.postMessage(command); + } else { + var command = [callbackId, service, action, actionArgs]; + // Stringify and queue the command. We stringify to command now to + // effectively clone the command arguments in case they are mutated before + // the command is executed. + commandQueue.push(JSON.stringify(command)); + + if (!isInContextOfEvalJs && commandQueue.length === 1) { + // If we're in the context of a stringByEvaluatingJavaScriptFromString call, + // then the queue will be flushed when it returns; no need for a poke. + // Also, if there is already a command in the queue, then we've already + // poked the native side, so there is no reason to do so again. + pokeNative(); + } } } @@ -1113,6 +1117,14 @@ execProxy.nativeCallback = function () { module.exports = execProxy; +if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) { + // unregister the old bridge + cordova.define.remove('cordova/exec'); + // redefine bridge to our new bridge + cordova.define('cordova/exec', function (require, exports, module) { + module.exports = execProxy; + }); +} }); // file: src/common/exec/proxy.js diff --git a/cordova-js-src/exec.js b/cordova-js-src/exec.js index 422582e742..20fd358e4e 100644 --- a/cordova-js-src/exec.js +++ b/cordova-js-src/exec.js @@ -33,7 +33,7 @@ var commandQueue = []; // Contains pending JS->Native messages. var isInContextOfEvalJs = 0; var failSafeTimerId = 0; -function massageArgsJsToNative (args) { +function messageArgsJsToNative (args) { if (!args || utils.typeName(args) !== 'Array') { return args; } @@ -51,7 +51,7 @@ function massageArgsJsToNative (args) { return ret; } -function massageMessageNativeToJs (message) { +function nativeMessageToJs (message) { if (message.CDVType === 'ArrayBuffer') { var stringToArrayBuffer = function (str) { var ret = new Uint8Array(str.length); @@ -68,16 +68,16 @@ function massageMessageNativeToJs (message) { return message; } -function convertMessageToArgsNativeToJs (message) { +function convertMessageNativeArgsToJs (message) { var args = []; if (!message || !Object.prototype.hasOwnProperty.call(message, 'CDVType')) { args.push(message); } else if (message.CDVType === 'MultiPart') { message.messages.forEach(function (e) { - args.push(massageMessageNativeToJs(e)); + args.push(nativeMessageToJs(e)); }); } else { - args.push(massageMessageNativeToJs(message)); + args.push(nativeMessageToJs(message)); } return args; } @@ -115,21 +115,25 @@ function iOSExec () { { success: successCallback, fail: failCallback }; } - actionArgs = massageArgsJsToNative(actionArgs); + actionArgs = messageArgsJsToNative(actionArgs); - var command = [callbackId, service, action, actionArgs]; - - // Stringify and queue the command. We stringify to command now to - // effectively clone the command arguments in case they are mutated before - // the command is executed. - commandQueue.push(JSON.stringify(command)); + if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova && window.webkit.messageHandlers.cordova.postMessage) { + var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))]; + window.webkit.messageHandlers.cordova.postMessage(command); + } else { + var command = [callbackId, service, action, actionArgs]; + // Stringify and queue the command. We stringify to command now to + // effectively clone the command arguments in case they are mutated before + // the command is executed. + commandQueue.push(JSON.stringify(command)); - // If we're in the context of a stringByEvaluatingJavaScriptFromString call, - // then the queue will be flushed when it returns; no need for a poke. - // Also, if there is already a command in the queue, then we've already - // poked the native side, so there is no reason to do so again. - if (!isInContextOfEvalJs && commandQueue.length === 1) { - pokeNative(); + if (!isInContextOfEvalJs && commandQueue.length === 1) { + // If we're in the context of a stringByEvaluatingJavaScriptFromString call, + // then the queue will be flushed when it returns; no need for a poke. + // Also, if there is already a command in the queue, then we've already + // poked the native side, so there is no reason to do so again. + pokeNative(); + } } } @@ -215,7 +219,7 @@ iOSExec.nativeFetchMessages = function () { iOSExec.nativeCallback = function (callbackId, status, message, keepCallback, debug) { return iOSExec.nativeEvalAndFetch(function () { var success = status === 0 || status === 1; - var args = convertMessageToArgsNativeToJs(message); + var args = convertMessageNativeArgsToJs(message); function nc2 () { cordova.callbackFromNative(callbackId, success, status, args, keepCallback); } @@ -259,3 +263,12 @@ execProxy.nativeCallback = function () { }; module.exports = execProxy; + +if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.cordova) { + // unregister the old bridge + cordova.define.remove('cordova/exec'); + // redefine bridge to our new bridge + cordova.define('cordova/exec', function (require, exports, module) { + module.exports = execProxy; + }); +} diff --git a/cordova-js-src/platform.js b/cordova-js-src/platform.js index 2345fa5b7b..a8623326f5 100644 --- a/cordova-js-src/platform.js +++ b/cordova-js-src/platform.js @@ -22,9 +22,15 @@ module.exports = { id: 'ios', bootstrap: function () { + let modulemapper = require('cordova/modulemapper'); + // Attach the console polyfill that is iOS-only to window.console // see the file under plugin/ios/console.js - require('cordova/modulemapper').clobbers('cordova/plugin/ios/console', 'window.console'); + modulemapper.clobbers('cordova/plugin/ios/console', 'window.console'); + + // Attach the WKWebView extension methods + // see the file under plugin/ios/wkwebview.js + modulemapper.clobbers('cordova/plugin/ios/wkwebview', 'window.WKWebView'); require('cordova/channel').onNativeReady.fire(); } diff --git a/cordova-js-src/plugin/ios/wkwebview.js b/cordova-js-src/plugin/ios/wkwebview.js new file mode 100644 index 0000000000..4504993b34 --- /dev/null +++ b/cordova-js-src/plugin/ios/wkwebview.js @@ -0,0 +1,5 @@ +module.exports = { + allowsBackForwardNavigationGestures: function (allow) { + require('cordova/exec')(null, null, 'CDVWKWebViewEngine', 'allowsBackForwardNavigationGestures', [allow]); + } +}