├── .gitignore ├── demo ├── loadMeByXHR.txt └── index.html ├── screenshots └── UIWebView-vs-WKWebView.png ├── src └── ios │ ├── AppDelegate+WKWebViewPolyfill.h │ ├── ReroutingUIWebView.h │ ├── MyMainViewController.h │ ├── CDVWebViewUIDelegate.h │ ├── CDVWebViewOperationsDelegate.h │ ├── ReroutingUIWebView.m │ ├── CDVWebViewOperationsDelegate.m │ ├── CDVWebViewUIDelegate.m │ ├── AppDelegate+WKWebViewPolyfill.m │ └── MyMainViewController.m ├── package.json ├── www └── wkwebview.js ├── plugin.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml -------------------------------------------------------------------------------- /demo/loadMeByXHR.txt: -------------------------------------------------------------------------------- 1 | Hey there, I live in www/loadMeByXHR.txt :) -------------------------------------------------------------------------------- /screenshots/UIWebView-vs-WKWebView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telerik-Verified-Plugins/WKWebView/HEAD/screenshots/UIWebView-vs-WKWebView.png -------------------------------------------------------------------------------- /src/ios/AppDelegate+WKWebViewPolyfill.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+WKWebViewPolyfill.h 3 | // 4 | // Created by Hein Rutjes on X-mas eve 5 | // 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate (WKWebViewPolyfill) 11 | 12 | - (void) createWindowAndStartWebServer:(BOOL) startWebServer; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /src/ios/ReroutingUIWebView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "MyMainViewController.h" 4 | 5 | @interface ReroutingUIWebView : UIWebView { 6 | } 7 | 8 | @property (nonatomic, readonly, retain, strong) UIScrollView *scrollView; 9 | @property (nonatomic, strong) IBOutlet WKWebView* wkWebView; 10 | @property (nonatomic, strong) MyMainViewController* viewController; 11 | @end 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.10", 3 | "name": "cordova-plugin-wkwebview", 4 | "cordova_name": "WKWebView Polyfill", 5 | "description": "A drop-in replacement of UIWebView for boosted performance and enhanced HTML5 support", 6 | "license": "MIT", 7 | "repo": "https://github.com/Telerik-Verified-Plugins/WKWebView.git", 8 | "issue": "https://github.com/Telerik-Verified-Plugins/WKWebView/issues", 9 | "keywords": [ 10 | "Webkit", 11 | "Webkit webview", 12 | "WKWebView Polyfill", 13 | "WKWebView", 14 | "WebView+", 15 | "ecosystem:cordova" 16 | ], 17 | "platforms": [ 18 | "ios" 19 | ], 20 | "engines": [ 21 | { 22 | "name": "cordova", 23 | "version": ">=3.0.0" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ios/MyMainViewController.h: -------------------------------------------------------------------------------- 1 | #import "MainViewController.h" 2 | #import "CDVWebViewOperationsDelegate.h" 3 | #import 4 | #import 5 | 6 | @interface MyMainViewController : MainViewController { 7 | @protected CDVWebViewOperationsDelegate* _webViewOperationsDelegate; 8 | } 9 | 10 | @property (nonatomic, strong) IBOutlet WKWebView* wkWebView; 11 | 12 | @property (nonatomic, readwrite, copy) NSString* uiWebViewLS; 13 | @property (nonatomic, readwrite, copy) NSString* wkWebViewLS; 14 | 15 | @property (nonatomic, strong) NSURL* url; 16 | @property (nonatomic, assign) BOOL pageLoaded; 17 | 18 | @property (nonatomic, readwrite, assign) BOOL alreadyLoaded; 19 | @property (nonatomic, assign) unsigned short port; 20 | 21 | - (void)loadURL:(NSURL*)URL; 22 | - (void)copyLS:(unsigned short)httpPort; 23 | - (void)setServerPort:(unsigned short) port; 24 | - (NSURL*)fixURL:(NSString*)URL; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /src/ios/CDVWebViewUIDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #ifdef __IPHONE_8_0 21 | #import 22 | #endif 23 | 24 | @interface CDVWebViewUIDelegate : NSObject 25 | #ifdef __IPHONE_8_0 26 | 27 | #endif 28 | 29 | @property (nonatomic, copy) NSString* title; 30 | 31 | - (instancetype)initWithTitle:(NSString*)title; 32 | 33 | @end -------------------------------------------------------------------------------- /src/ios/CDVWebViewOperationsDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | #import 22 | 23 | @interface CDVWebViewOperationsDelegate : NSObject { 24 | @private 25 | __weak UIView* _webView; 26 | } 27 | 28 | - (instancetype) initWithWebView:(UIView*)webView; 29 | 30 | - (void)loadRequest:(NSURLRequest*)request; 31 | - (void)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL; 32 | - (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler; 33 | 34 | @end -------------------------------------------------------------------------------- /src/ios/ReroutingUIWebView.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "ReroutingUIWebView.h" 3 | 4 | // note that this is not the most elegant solution, but as a pragmatic fix it works nicely for my usecases 5 | @implementation ReroutingUIWebView 6 | 7 | @synthesize scrollView = _scrollView; 8 | 9 | // Override the referenced scrollView to use WKWebView's scroll view, this helps fix plugins that alter 10 | // the size of the UIScrollView (like the Keyboard Plugin) 11 | - (void) setWkWebView:(WKWebView *)wkWebView{ 12 | _scrollView = wkWebView.scrollView; 13 | _wkWebView = wkWebView; 14 | } 15 | 16 | // because plugins send their result to the UIWebView, this method reroutes this data to the WKWebView 17 | - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script { 18 | [self.wkWebView evaluateJavaScript:script completionHandler:nil]; 19 | return nil; 20 | } 21 | 22 | // Ionic's Keyboard Plugin 'close()' function uses this 23 | - (BOOL)endEditing:(BOOL)force { 24 | return [self.wkWebView endEditing:force]; 25 | } 26 | 27 | // the Toast plugin (for one) adds a subview to the webview which needs to propagate to the wkwebview 28 | - (void)addSubview:(UIView *)view { 29 | [self.wkWebView addSubview:view]; 30 | } 31 | - (void)layoutSubviews { 32 | self.wkWebView.frame = self.frame; 33 | } 34 | 35 | // Ionic's Deploy plugin uses this 36 | - (void)loadRequest:(NSURLRequest*)request { 37 | NSMutableURLRequest *mutableRequest = [request mutableCopy]; 38 | NSString *urlString = [request.URL absoluteString]; 39 | mutableRequest.URL = [self.viewController fixURL:urlString]; 40 | [self.wkWebView loadRequest:mutableRequest]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /www/wkwebview.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | if(!cordova.exec.jsToNativeModes.WK_WEBVIEW_BINDING) { 23 | 24 | // 25 | //intercept iframe bridge 26 | // 27 | 28 | var exec = require('cordova/exec'); 29 | 30 | //force bridge to be created with invalid message 31 | try{ 32 | exec(null, null, 'WKWebView', '', []); 33 | } catch(e) {} 34 | 35 | //wrap nativeFetchMessages with redirect to wkwebview bridge 36 | var origNativeFetchMessages = exec.nativeFetchMessages; 37 | exec.nativeFetchMessages = function() { 38 | var cmds = origNativeFetchMessages(); 39 | cmds = JSON.parse(cmds); 40 | for(var i=0;i 2 | 5 | 6 | WKWebView Polyfill 7 | 8 | 9 | A drop-in replacement of UIWebView for boosted performance and enhanced HTML5 support 10 | 11 | 12 | Eddy Verbruggen / Telerik 13 | 14 | MIT 15 | 16 | Webkit, Webkit webview, WKWebView Polyfill, WKWebView, WebView+ 17 | 18 | https://github.com/Telerik-Verified-Plugins/WKWebView.git 19 | 20 | https://github.com/Telerik-Verified-Plugins/WKWebView/issues 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | NSAllowsArbitraryLoads 48 | 49 | 50 | 51 | 52 | 53 | $WKWEBVIEW_SERVER_PORT 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/ios/CDVWebViewOperationsDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | #import "CDVWebViewOperationsDelegate.h" 22 | 23 | @implementation CDVWebViewOperationsDelegate 24 | 25 | - (instancetype) initWithWebView:(UIView*)webView 26 | { 27 | self = [super init]; 28 | if (self) { 29 | Class wk_class = NSClassFromString(@"WKWebView"); 30 | if ( !([webView isKindOfClass:wk_class] || [webView isKindOfClass:[UIWebView class]] )) { 31 | return nil; 32 | } 33 | _webView = webView; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | - (void)loadRequest:(NSURLRequest*)request 40 | { 41 | SEL selector = NSSelectorFromString(@"loadRequest:"); 42 | if ([_webView respondsToSelector:selector]) { 43 | // UIKit operations have to be on the main thread. and this method is synchronous 44 | [_webView performSelectorOnMainThread:selector withObject:request waitUntilDone:YES]; 45 | } 46 | } 47 | 48 | - (void)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL 49 | { 50 | SEL selector = NSSelectorFromString(@"loadHTMLString:baseURL:"); 51 | 52 | dispatch_block_t invoke = ^(void) { 53 | ((void (*)(id, SEL, id, id))objc_msgSend)(_webView, selector, string, baseURL); 54 | }; 55 | 56 | if ([_webView respondsToSelector:selector]) { 57 | // UIKit operations have to be on the main thread. 58 | // perform a synchronous invoke on the main thread without deadlocking 59 | if ([NSThread isMainThread]) { 60 | invoke(); 61 | } else { 62 | dispatch_sync(dispatch_get_main_queue(), invoke); 63 | } 64 | } 65 | } 66 | 67 | - (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler 68 | { 69 | SEL ui_sel = NSSelectorFromString(@"stringByEvaluatingJavaScriptFromString:"); 70 | SEL wk_sel = NSSelectorFromString(@"evaluateJavaScript:completionHandler:"); 71 | 72 | // UIKit operations have to be on the main thread. This method does not need to be synchronous 73 | dispatch_async(dispatch_get_main_queue(), ^{ 74 | if ([_webView respondsToSelector:ui_sel]) { 75 | NSString* ret = ((NSString* (*)(id, SEL, id))objc_msgSend)(_webView, ui_sel, javaScriptString); 76 | completionHandler(ret, nil); 77 | } else if ([_webView respondsToSelector:wk_sel]) { 78 | ((void (*)(id, SEL, id, id))objc_msgSend)(_webView, wk_sel, javaScriptString, completionHandler); 79 | } 80 | }); 81 | } 82 | 83 | @end -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Hello World 29 | 30 | 31 |
32 |

First page

33 | 58 |
59 | 60 | 61 | 62 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/ios/CDVWebViewUIDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #ifdef __IPHONE_8_0 21 | 22 | #import "CDVWebViewUIDelegate.h" 23 | 24 | @implementation CDVWebViewUIDelegate 25 | 26 | - (instancetype)initWithTitle:(NSString*)title 27 | { 28 | self = [super init]; 29 | if (self) { 30 | self.title = title; 31 | } 32 | 33 | return self; 34 | } 35 | 36 | - (void) webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message 37 | initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler 38 | { 39 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title 40 | message:message 41 | preferredStyle:UIAlertControllerStyleAlert]; 42 | 43 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") 44 | style:UIAlertActionStyleDefault 45 | handler:^(UIAlertAction* action) 46 | { 47 | completionHandler(); 48 | [alert dismissViewControllerAnimated:YES completion:nil]; 49 | }]; 50 | 51 | [alert addAction:ok]; 52 | 53 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; 54 | 55 | [rootController presentViewController:alert animated:YES completion:nil]; 56 | } 57 | 58 | - (void) webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message 59 | initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler 60 | { 61 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title 62 | message:message 63 | preferredStyle:UIAlertControllerStyleAlert]; 64 | 65 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") 66 | style:UIAlertActionStyleDefault 67 | handler:^(UIAlertAction* action) 68 | { 69 | completionHandler(YES); 70 | [alert dismissViewControllerAnimated:YES completion:nil]; 71 | }]; 72 | 73 | [alert addAction:ok]; 74 | 75 | UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") 76 | style:UIAlertActionStyleDefault 77 | handler:^(UIAlertAction* action) 78 | { 79 | completionHandler(NO); 80 | [alert dismissViewControllerAnimated:YES completion:nil]; 81 | }]; 82 | [alert addAction:cancel]; 83 | 84 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; 85 | 86 | [rootController presentViewController:alert animated:YES completion:nil]; 87 | } 88 | 89 | - (void) webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt 90 | defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame 91 | completionHandler:(void (^)(NSString* result))completionHandler 92 | { 93 | UIAlertController* alert = [UIAlertController alertControllerWithTitle:self.title 94 | message:prompt 95 | preferredStyle:UIAlertControllerStyleAlert]; 96 | 97 | UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK") 98 | style:UIAlertActionStyleDefault 99 | handler:^(UIAlertAction* action) 100 | { 101 | completionHandler(((UITextField*)alert.textFields[0]).text); 102 | [alert dismissViewControllerAnimated:YES completion:nil]; 103 | }]; 104 | 105 | [alert addAction:ok]; 106 | 107 | UIAlertAction* cancel = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") 108 | style:UIAlertActionStyleDefault 109 | handler:^(UIAlertAction* action) 110 | { 111 | completionHandler(nil); 112 | [alert dismissViewControllerAnimated:YES completion:nil]; 113 | }]; 114 | [alert addAction:cancel]; 115 | 116 | [alert addTextFieldWithConfigurationHandler:^(UITextField* textField) { 117 | textField.text = defaultText; 118 | }]; 119 | 120 | UIViewController* rootController = [UIApplication sharedApplication].delegate.window.rootViewController; 121 | 122 | [rootController presentViewController:alert animated:YES completion:nil]; 123 | } 124 | 125 | @end 126 | #endif /* ifdef __IPHONE_8_0 */ -------------------------------------------------------------------------------- /src/ios/AppDelegate+WKWebViewPolyfill.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "AppDelegate.h" 3 | #import "MyMainViewController.h" 4 | #import 5 | #import 6 | #import 7 | 8 | // need to swap out a method, so swizzling it here 9 | static void swizzleMethod(Class class, SEL destinationSelector, SEL sourceSelector); 10 | 11 | @implementation AppDelegate (WKWebViewPolyfill) 12 | 13 | NSString *const FileSchemaConstant = @"file://"; 14 | NSString *const ServerCreatedNotificationName = @"WKWebView.WebServer.Created"; 15 | GCDWebServer* _webServer; 16 | NSMutableDictionary* _webServerOptions; 17 | NSString* appDataFolder; 18 | 19 | + (void)load { 20 | // Swap in our own viewcontroller which loads the wkwebview, but only in case we're running iOS 8+ 21 | if (IsAtLeastiOSVersion(@"8.0")) { 22 | swizzleMethod([AppDelegate class], 23 | @selector(application:didFinishLaunchingWithOptions:), 24 | @selector(my_application:didFinishLaunchingWithOptions:)); 25 | } 26 | } 27 | 28 | - (BOOL)my_application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 29 | [self createWindowAndStartWebServer:true]; 30 | return YES; 31 | } 32 | 33 | - (void) createWindowAndStartWebServer:(BOOL) startWebServer { 34 | CGRect screenBounds = [[UIScreen mainScreen] bounds]; 35 | 36 | self.window = [[UIWindow alloc] initWithFrame:screenBounds]; 37 | self.window.autoresizesSubviews = YES; 38 | MyMainViewController *myMainViewController = [[MyMainViewController alloc] init]; 39 | self.viewController = myMainViewController; 40 | self.window.rootViewController = myMainViewController; 41 | [self.window makeKeyAndVisible]; 42 | appDataFolder = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByDeletingLastPathComponent]; 43 | 44 | // Note: the embedded webserver is still needed for iOS 9. It's not needed to load index.html, 45 | // but we need it to ajax-load files (file:// protocol has no origin, leading to CORS issues). 46 | NSString *directoryPath = myMainViewController.wwwFolderName; 47 | 48 | // don't restart the webserver if we don't have to (fi. after a crash, see #223) 49 | if (_webServer != nil && [_webServer isRunning]) { 50 | [myMainViewController setServerPort:_webServer.port]; 51 | return; 52 | } 53 | 54 | _webServer = [[GCDWebServer alloc] init]; 55 | _webServerOptions = [NSMutableDictionary dictionary]; 56 | 57 | // Add GET handler for local "www/" directory 58 | [_webServer addGETHandlerForBasePath:@"/" 59 | directoryPath:directoryPath 60 | indexFilename:nil 61 | cacheAge:30 62 | allowRangeRequests:YES]; 63 | 64 | [self addHandlerForPath:@"/Library/"]; 65 | [self addHandlerForPath:@"/Documents/"]; 66 | [self addHandlerForPath:@"/tmp/"]; 67 | 68 | // Initialize Server startup 69 | if (startWebServer) { 70 | [self startServer]; 71 | [myMainViewController copyLS:_webServer.port]; 72 | } 73 | 74 | // Update Swizzled ViewController with port currently used by local Server 75 | [myMainViewController setServerPort:_webServer.port]; 76 | } 77 | 78 | - (void)addHandlerForPath:(NSString *) path { 79 | [_webServer addHandlerForMethod:@"GET" 80 | pathRegex: [NSString stringWithFormat:@"^%@.*", path] 81 | requestClass:[GCDWebServerRequest class] 82 | processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { 83 | NSString *fileLocation = request.URL.path; 84 | if ([fileLocation hasPrefix:path]) { 85 | fileLocation = [appDataFolder stringByAppendingString:request.URL.path]; 86 | } 87 | 88 | fileLocation = [fileLocation stringByReplacingOccurrencesOfString:FileSchemaConstant withString:@""]; 89 | if (![[NSFileManager defaultManager] fileExistsAtPath:fileLocation]) { 90 | return nil; 91 | } 92 | 93 | GCDWebServerResponse* response = [GCDWebServerFileResponse responseWithFile:fileLocation byteRange:request.byteRange]; 94 | [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; 95 | return response; 96 | } 97 | ]; 98 | } 99 | 100 | - (BOOL)identity_application: (UIApplication *)application 101 | openURL: (NSURL *)url 102 | sourceApplication: (NSString *)sourceApplication 103 | annotation: (id)annotation { 104 | 105 | // call super 106 | return [self identity_application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; 107 | } 108 | 109 | - (void)startServer 110 | { 111 | NSError *error = nil; 112 | 113 | // Enable this option to force the Server also to run when suspended 114 | //[_webServerOptions setObject:[NSNumber numberWithBool:NO] forKey:GCDWebServerOption_AutomaticallySuspendInBackground]; 115 | 116 | [_webServerOptions setObject:[NSNumber numberWithBool:YES] 117 | forKey:GCDWebServerOption_BindToLocalhost]; 118 | 119 | // If a fixed port is passed in, use that one, otherwise use 12344. 120 | // If the port is taken though, look for a free port by adding 1 to the port until we find one. 121 | int httpPort = 12344; 122 | 123 | // first we check any passed-in variable during plugin install (which is copied to plist, see plugin.xml) 124 | NSNumber *plistPort = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WKWebViewPluginEmbeddedServerPort"]; 125 | if (plistPort != nil) { 126 | httpPort = [plistPort intValue]; 127 | } 128 | 129 | // now check if it was set in config.xml - this one wins if set. 130 | // (note that the settings can be in any casing, but they are stored in lowercase) 131 | if ([self.viewController.settings objectForKey:@"wkwebviewpluginembeddedserverport"]) { 132 | httpPort = [[self.viewController.settings objectForKey:@"wkwebviewpluginembeddedserverport"] intValue]; 133 | } 134 | 135 | _webServer.delegate = (id)self; 136 | do { 137 | [_webServerOptions setObject:[NSNumber numberWithInteger:httpPort++] 138 | forKey:GCDWebServerOption_Port]; 139 | } while(![_webServer startWithOptions:_webServerOptions error:&error]); 140 | 141 | if (error) { 142 | NSLog(@"Error starting http daemon: %@", error); 143 | } else { 144 | [GCDWebServer setLogLevel:kGCDWebServerLoggingLevel_Warning]; 145 | NSLog(@"Started http daemon: %@ ", _webServer.serverURL); 146 | } 147 | } 148 | 149 | //MARK:GCDWebServerDelegate 150 | - (void)webServerDidStart:(GCDWebServer*)server { 151 | [NSNotificationCenter.defaultCenter postNotificationName:ServerCreatedNotificationName 152 | object: @[self.viewController, _webServer]]; 153 | } 154 | 155 | @end 156 | 157 | 158 | #pragma mark Swizzling 159 | 160 | static void swizzleMethod(Class class, SEL destinationSelector, SEL sourceSelector) { 161 | Method destinationMethod = class_getInstanceMethod(class, destinationSelector); 162 | Method sourceMethod = class_getInstanceMethod(class, sourceSelector); 163 | 164 | // If the method doesn't exist, add it. If it does exist, replace it with the given implementation. 165 | if (class_addMethod(class, destinationSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod))) { 166 | class_replaceMethod(class, destinationSelector, method_getImplementation(destinationMethod), method_getTypeEncoding(destinationMethod)); 167 | } else { 168 | method_exchangeImplementations(destinationMethod, sourceMethod); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cordova WKWebView Polyfill Plugin 2 | by [Eddy Verbruggen](http://twitter.com/eddyverbruggen) / [Telerik](http://www.telerik.com) 3 | 4 | 5 | __[Jan 17, 2016] iOS 9.3 BETA seems to require you to set the minimum deployment target to 8.0, otherwise you may see a hanging splashscreen or white startup page. I hope that's fixed in later BETA's, otherwise it may be wise to drop iOS 7 support in your app.__ 6 | 7 | __[Jan 10, 2016] At the moment this plugin is NOT compatible with Cordova-iOS-4 (so use 3.x), see [#209](https://github.com/Telerik-Verified-Plugins/WKWebView/issues/209) for details.__ 8 | 9 | 10 | ## 0. Index 11 | 12 | 1. [Description](#1-description) 13 | 2. [Screenshot](#2-screenshot) 14 | 3. [Installation](#3-installation) 15 | 4. [Changelog](#4-changelog) 16 | 5. [Credits](#5-credits) 17 | 6. [License](#6-license) 18 | 19 | ## 1. Description 20 | 21 | _BETA_ - things may break, [please post your feedback :)](https://github.com/EddyVerbruggen/cordova-plugin-wkwebview/issues) 22 | 23 | * Allows you to use the new WKWebView on iOS 8 (the simulator is supported as well). 24 | * Falls back to UIWebView on iOS 7 and lower. 25 | * Will hopefully cease to exist soon (when Apple releases a fixed WKWebView so Cordova can use it without the hacks I needed to apply). 26 | * As a matter of fact, [Apache is working on a similar plugin (which you can't use at the moment of writing)](https://github.com/apache/cordova-plugins/tree/master/wkwebview-engine) which I came across after releasing version 0.1.1. It targets Cordova 3.7.0 and up whereas this plugin is supported on 3.0.0 an up. 27 | 28 | ### Take note! 29 | 30 | * For a seamless upgrade to iOS9 this plugin wipes any existing `NSAppTransportSecurity` configuration you may have done (a new feature in iOS9) to allow communication with even HTTP (non-S) backends, like previous iOS versions did. You can and should configure access rules (`config.xml`) and use the whitelist plugin as before. 31 | * [Ionic](http://ionicframework.com/) tip: to prevent flashes of a black background, make sure you set `ion-nav-view`'s `background-color` to `transparent`. 32 | * If you need the [device plugin](https://github.com/apache/cordova-plugin-device), use at least Cordova-iOS 3.6.3 (deviceready never fires with 3.5.0 due to a currently unknown reason). 33 | * When making AJAX requests to a remote server make sure it supports CORS. See the [Telerik Verified Marketplace documentation](http://plugins.telerik.com/plugin/wkwebview) for more details on this and other valuable pointers. As a last resort you can add [this CORS-Proxy](https://github.com/gr2m/CORS-Proxy) between your app and the server. 34 | * You can load files from the app's cache folders by using the entire path (/var/.../Library/...) or simply '/Library/..' (or '/Documents/..'). 35 | * This plugin features crash recovery: if the WKWebView crashes, it will auto-restart (otherwise you'd have an app with a blank page as it doesn't crash the app itself). Crash recovery requires a filled `anything` tag in your html files. If you want to disable this feature, set the `config.xml` property `DisableCrashRecovery` to `true`. 36 | * In order to open links like `tel:` and `mailto:` you need to add `target="_blank"`: `call!` 37 | * If you're trying to use `HideFormAccessoryBar` with the `cordova-plugin-keyboard` plugin, please [use version 1.1.3+ of this fork](https://www.npmjs.com/package/cordova-plugin-keyboard) which is compatbile with WKWebView. 38 | 39 | ## 2. Screenshot 40 | This image shows the [SocialSharing plugin](https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin) in action while running [a performance test](https://www.scirra.com/demos/c2/particles/) in an iframe on my iPhone 6 (older devices show an even larger difference). 41 | It's a screenshot of the [demo app](demo/index.html). 42 | 43 | 44 | 45 | ## 3. Installation 46 | 47 | From npm 48 | ``` 49 | $ cordova plugin add cordova-plugin-wkwebview 50 | ``` 51 | 52 | Specify a custom port (default 12344), you can omit this if you want to use the default in case you use a recent Cordova CLI version 53 | ``` 54 | $ cordova plugin add cordova-plugin-wkwebview --variable WKWEBVIEW_SERVER_PORT=12344 55 | ``` 56 | 57 | No need for anything else - you can now open the project in XCode 6 if you like. 58 | 59 | ## 4. Changelog 60 | * __0.6.9__ Don't start a new webserver after a crash if one is still running. See #223. 61 | * __0.6.8__ Compatibility with Telerik's LivePatch plugin. See #202. 62 | * __0.6.7__ Compatibility with `file://` protocol for usage in plugins like [cordova-hot-code-push](https://github.com/nordnet/cordova-hot-code-push), thanks #195 and #196! 63 | * __0.6.5__ `KeyboardDisplayRequiresUserAction` works! So set to `false` if you want the keyboard to pop up when programmatically focussing an input field. 64 | * __0.6.4__ On top of the port preference introduced in 0.6.3 you can now override the default variable when installing this plugin (see 'Installation'). 65 | * __0.6.3__ By default the embedded webserver uses port `12344`, but if you want to you can now override that port by setting f.i. `` in `config.xml`. 66 | * __0.6.2__ LocalStorage is copied from UIWebView to WKWebView again (iOS location was recently changed as it appears). 67 | * __0.6.1__ Allow reading files from /tmp, so the camera plugin file URI's work. Thx #155. 68 | * __0.6.0__ iOS9 (GM) compatibility. Also, compatibility with iOS8 devices when building with XCode 7 (iOS9 SDK). Dialogs (alert, prompt, confirm) were broken. 69 | * __0.5.1__ Added support for `config.xml` property `DisableLocalStorageSyncWithUIWebView` (default `false`). Set it to `true` if you want to switch back to UIWebView and retain LS changes made while running WKWebView. 70 | * __0.5.0__ iOS9 (beta) compatibility, keyboard scroll fix, white keyboard background if no specific color is specified (was black). 71 | * __0.4.0__ Compatibility with Telerik LiveSync and LivePatch. Disabled the horizontal and vertical scrollbars. Added support for `config.xml` property `DisableCrashRecovery` (default `false`). 72 | * __0.3.8__ Adding a way to access files in '/Library/' and '/Documents/' (simply use those prefixes), thanks #88! 73 | * __0.3.7__ Custom URL Schemes did not work, see #98, also this version includes crash recovery, thanks #62! 74 | * __0.3.6__ Bind embedded webserver to localhost so it can't be reached from the outside, thanks #64! 75 | * __0.3.5__ Compatibility with the statusbar plugin: allow the statusbar to not overlay the webview, thanks #6 and #20! 76 | * __0.3.4__ The GCDWebServer is now compatible with all iOS architectures, thanks #47 and #48! 77 | * __0.3.2__ Switched embedded HTTP server from CocoaHTTP server to GCDWebServer, thanks #43! 78 | * __0.3.1__ Compatibility with the SplashScreen plugin 79 | * __0.3.0__ Enhanced loading files with the embedded HTTP server, thanks #36! 80 | * __0.2.7__ Cut app startup time in half - not noticable unless you have a lot of files in your app, see #32 81 | * __0.2.6__ `Config.xml` settings like `MediaPlaybackRequiresUserAction` (autoplay HTML 5 video) are now supported, see #25. 82 | * __0.2.5__ Fixed a script error for Cordova 3.5.0 and lower, see #17. 83 | * __0.2.4__ Compatibility with the `device` plugin on Cordova 3.5.0 and lower, see #17. 84 | * __0.2.3__ Compatibility with the `close` function of the [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard). 85 | * __0.2.2__ Compatibility with plugins which use the superview of the 'classic' webview, like [ActivityIndicator](https://github.com/Initsogar/cordova-activityindicator) 86 | * __0.2.1__ LocalStorage sync between UIWebView and WKWebView - on a real device as well 87 | * __0.2.0__ LocalStorage sync between UIWebView and WKWebView - on a simulator only 88 | * __0.1.3__ Compatibility with [InAppBrowser](https://github.com/apache/cordova-plugin-inappbrowser) 89 | * __0.1.2__ Compatibility with plugins like [Toast](https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin) which add a subview to the webview (they didn't show) 90 | * __0.1.1__ Cleanup to get rid of a few (deprecation) warnings - lots left (on purpose) because they're thrown by a 3rd party framework and can be safely ignored 91 | * __0.1.0__ Added support for loading local files via XHR. This should now transparently work for $.ajax, AngularJS templateUrl's, etc. To this end the plugin adds an embedded HTTP server on port 12344 which is stopped when the app is put to sleep or exits. 92 | * __0.0.1__ Initial version 93 | 94 | ## 5. Credits 95 | This plugin was inspired by the hard work of the Apache Cordova team [(and most notably Shazron)](https://github.com/shazron/WKWebViewFIleUrlTest). 96 | 97 | ## 6. License 98 | 99 | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) 100 | 101 | Permission is hereby granted, free of charge, to any person obtaining a copy 102 | of this software and associated documentation files (the "Software"), to deal 103 | in the Software without restriction, including without limitation the rights 104 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 105 | copies of the Software, and to permit persons to whom the Software is 106 | furnished to do so, subject to the following conditions: 107 | 108 | The above copyright notice and this permission notice shall be included in 109 | all copies or substantial portions of the Software. 110 | 111 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 112 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 113 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 114 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 115 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 116 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 117 | THE SOFTWARE. 118 | -------------------------------------------------------------------------------- /src/ios/MyMainViewController.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "MyMainViewController.h" 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import "CDVWebViewUIDelegate.h" 10 | #import "ReroutingUIWebView.h" 11 | #import "AppDelegate+WKWebViewPolyfill.h" 12 | 13 | @interface CDVViewController () 14 | @property (nonatomic, readwrite, retain) NSArray *startupPluginNames; 15 | @end 16 | 17 | @interface MyMainViewController () { 18 | NSInteger _userAgentLockToken; 19 | CDVWebViewDelegate* _webViewDelegate; 20 | CDVWebViewUIDelegate* _webViewUIDelegate; 21 | BOOL _targetExistsLocally; 22 | NSTimer* _crashRecoveryTimer; 23 | BOOL _crashRecoveryActive; 24 | NSURL *_startURL; 25 | } 26 | @end 27 | 28 | @implementation MyMainViewController 29 | 30 | @synthesize alreadyLoaded; 31 | 32 | - (id)init 33 | { 34 | self = [super init]; 35 | if (self) { 36 | // Save the path for our /www/ files 37 | NSURL* startURL = [NSURL URLWithString:self.startPage]; 38 | NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]]; 39 | _targetExistsLocally = [[NSFileManager defaultManager] 40 | fileExistsAtPath:startFilePath]; 41 | startFilePath = [startFilePath stringByDeletingLastPathComponent]; 42 | self.wwwFolderName = startFilePath; 43 | self.alreadyLoaded = false; 44 | } 45 | 46 | if (![self settingForKey:@"DisableLocalStorageSyncWithUIWebView"] || ![[self settingForKey:@"DisableLocalStorageSyncWithUIWebView"] boolValue]) { 47 | // configure listeners which fires when the application goes away 48 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(copyLocalStorageToUIWebView:) 49 | name:UIApplicationWillTerminateNotification object:nil]; 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(copyLocalStorageToUIWebView:) 51 | name:UIApplicationWillResignActiveNotification object:nil]; 52 | } 53 | 54 | // and some more for custom url schemes 55 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationLaunchedWithUrl:) name:CDVPluginHandleOpenURLNotification object:nil]; 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationPageDidLoad:) name:CDVPageDidLoadNotification object:nil]; 57 | 58 | return self; 59 | } 60 | 61 | - (void)applicationLaunchedWithUrl:(NSNotification*)notification { 62 | NSURL *url = [notification object]; 63 | self.url = url; 64 | 65 | // warm-start handler 66 | if (self.pageLoaded) { 67 | [self processOpenUrl:self.url pageLoaded:YES]; 68 | self.url = nil; 69 | } 70 | } 71 | 72 | - (void)applicationPageDidLoad:(NSNotification*)notification { 73 | // cold-start handler 74 | 75 | self.pageLoaded = YES; 76 | 77 | if (self.url) { 78 | [self processOpenUrl:self.url pageLoaded:YES]; 79 | self.url = nil; 80 | } 81 | } 82 | 83 | - (void)processOpenUrl:(NSURL*)url pageLoaded:(BOOL)pageLoaded { 84 | if (!pageLoaded) { 85 | // query the webview for readystate 86 | NSString* readyState = [self.webView stringByEvaluatingJavaScriptFromString:@"document.readyState"]; 87 | pageLoaded = [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"]; 88 | } 89 | 90 | if (pageLoaded) { 91 | // calls into javascript global function 'handleOpenURL' 92 | NSString* jsString = [NSString stringWithFormat:@"document.addEventListener('deviceready',function(){if (typeof handleOpenURL === 'function') { handleOpenURL(\"%@\");}});", url]; 93 | [self.wkWebView evaluateJavaScript:jsString completionHandler:nil]; 94 | } else { 95 | // save for when page has loaded 96 | self.url = url; 97 | } 98 | } 99 | 100 | - (void)copyLocalStorageToUIWebView:(NSNotification*)notification { 101 | if (self.uiWebViewLS != nil && self.wkWebViewLS != nil) { 102 | [[CDVLocalStorage class] copyFrom:self.wkWebViewLS to:self.uiWebViewLS error:nil]; 103 | } 104 | } 105 | 106 | #pragma mark View Rotation Event Handling 107 | 108 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator 109 | { 110 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 111 | 112 | [coordinator animateAlongsideTransition:^(id context) { 113 | self.wkWebView.frame = self.view.bounds; 114 | [self.wkWebView layoutIfNeeded]; 115 | } completion:nil]; 116 | } 117 | 118 | #pragma mark View lifecycle 119 | 120 | - (void)createGapView:(WKWebViewConfiguration*) config 121 | { 122 | CGRect webViewBounds = self.view.bounds; 123 | webViewBounds.origin = self.view.bounds.origin; 124 | 125 | self.wkWebView = [self newCordovaWKWebViewWithFrame:webViewBounds wkWebViewConfig:config]; 126 | self.wkWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); 127 | 128 | /* 129 | #ifdef __IPHONE_9_0 130 | // 3D touch-triggered URL preview feature - 131 | // setting this to the default value, just to remind myself to play with it once I have a 6s. 132 | self.wkWebView.allowsLinkPreview = NO; 133 | #endif 134 | */ 135 | 136 | [self.wkWebView setOpaque:NO]; 137 | NSString* setting = @"BackgroundColor"; 138 | if ([self settingForKey:setting]) { 139 | self.wkWebView.backgroundColor = [self colorFromHexString:[self settingForKey:setting]]; 140 | self.view.backgroundColor = self.wkWebView.backgroundColor; 141 | } else { 142 | self.wkWebView.backgroundColor = [UIColor clearColor]; 143 | self.view.backgroundColor = [UIColor whiteColor]; 144 | } 145 | 146 | _webViewOperationsDelegate = [[CDVWebViewOperationsDelegate alloc] initWithWebView:self.webView]; 147 | 148 | [self.view addSubview:self.wkWebView]; 149 | [self.view sendSubviewToBack:self.wkWebView]; 150 | 151 | // plugins may do self.webView.superview which would evaluate to nil if we don't do this 152 | self.webView.hidden = true; 153 | [self.view addSubview:self.webView]; 154 | [self.view sendSubviewToBack:self.webView]; 155 | } 156 | 157 | - (void)recoverFromCrash 158 | { 159 | // When an empty title is returned, WkWebView has crashed 160 | NSString* title = self.wkWebView.title; 161 | if ((title == nil) || [title isEqualToString:@""]) { 162 | if (_crashRecoveryActive) { 163 | NSLog(@"WkWebView crash detected, recovering... "); 164 | _crashRecoveryActive = false; 165 | [_crashRecoveryTimer invalidate]; 166 | _crashRecoveryTimer = nil; 167 | AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate]; 168 | [appDelegate createWindowAndStartWebServer:true]; 169 | } 170 | } else { 171 | 172 | // Once the title has been reported back as valid, activate crash recovery 173 | _crashRecoveryActive = true; 174 | } 175 | } 176 | 177 | - (id)settingForKey:(NSString*)key 178 | { 179 | return [[self settings] objectForKey:[key lowercaseString]]; 180 | } 181 | 182 | - (void)setSetting:(id)setting forKey:(NSString*)key 183 | { 184 | [[self settings] setObject:setting forKey:[key lowercaseString]]; 185 | } 186 | 187 | // THIS FUNCTION WILL BE REMOVED WHEN WKWEBVIEW SUPPORTS FILE LOADING FROM OUTSIDE OF /tmp 188 | - (NSURL *) copyToTMP:(NSURL *)url { 189 | NSString *fullPath = url.path; 190 | NSString *src = url.lastPathComponent; 191 | // Does file already exist at tmp? 192 | NSError *copyError = nil; 193 | if ([[NSFileManager defaultManager] fileExistsAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:src]]) { 194 | if (![[NSFileManager defaultManager] removeItemAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:src] error:©Error]) { 195 | NSLog(@"Error deleting file: %@", [copyError localizedDescription]); 196 | } 197 | } 198 | 199 | // Copy to tmp 200 | if (![[NSFileManager defaultManager] copyItemAtPath:fullPath toPath:[NSTemporaryDirectory() stringByAppendingPathComponent:src] error:©Error]) { 201 | NSLog(@"Error copying file: %@", [copyError localizedDescription]); 202 | return url; 203 | } 204 | // Load from tmp 205 | return [[NSURL alloc] initFileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:src]]; 206 | } 207 | 208 | - (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError* __autoreleasing*)error 209 | { 210 | NSFileManager* fileManager = [NSFileManager defaultManager]; 211 | 212 | if (![fileManager fileExistsAtPath:src]) { 213 | NSString* errorString = [NSString stringWithFormat:@"%@ file does not exist.", src]; 214 | if (error != NULL) { 215 | (*error) = [NSError errorWithDomain:@"TestDomainTODO" 216 | code:1 217 | userInfo:[NSDictionary dictionaryWithObject:errorString 218 | forKey:NSLocalizedDescriptionKey]]; 219 | } 220 | return NO; 221 | } 222 | 223 | BOOL destExists = [fileManager fileExistsAtPath:dest]; 224 | 225 | // remove the dest 226 | if (destExists && ![fileManager removeItemAtPath:dest error:error]) { 227 | return NO; 228 | } 229 | 230 | // create path to dest 231 | if (!destExists && ![fileManager createDirectoryAtPath:[dest stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:error]) { 232 | return NO; 233 | } 234 | 235 | // copy src to dest 236 | return [fileManager copyItemAtPath:src toPath:dest error:error]; 237 | } 238 | 239 | - (void)loadURL:(NSURL*)URL 240 | { 241 | self.alreadyLoaded = true; 242 | // ///////////////// 243 | [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { 244 | _userAgentLockToken = lockToken; 245 | [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; 246 | NSURLRequest* appReq = [NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; 247 | [self.wkWebView loadRequest:appReq]; 248 | }]; 249 | } 250 | 251 | - (NSURL*)fixURL:(NSString*)URL 252 | { 253 | if ([URL hasPrefix:@"http"]) { 254 | return [NSURL URLWithString:URL]; 255 | } else if ([URL hasPrefix:@"file"]) { 256 | NSString* fixedStartPage = [URL stringByReplacingOccurrencesOfString:@"file://" withString:@""]; 257 | fixedStartPage = [fixedStartPage stringByReplacingOccurrencesOfString:NSHomeDirectory() withString:@""]; 258 | return [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%hu%@", self.port, fixedStartPage]]; 259 | } else { 260 | return [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%hu/%@", self.port, URL]]; 261 | } 262 | } 263 | 264 | - (void)setServerPort:(unsigned short)port 265 | { 266 | if (self.alreadyLoaded) { 267 | // If we already loaded for some reason, we don't care about the local port. 268 | return; 269 | } else { 270 | self.port = port; 271 | _startURL = [self fixURL:self.startPage]; 272 | [self loadURL:_startURL]; 273 | } 274 | } 275 | 276 | - (void)viewDidLoad 277 | { 278 | NSURL* appURL = nil; 279 | NSString* loadErr = nil; 280 | 281 | if ([self.startPage rangeOfString:@"://"].location != NSNotFound) { 282 | appURL = [NSURL URLWithString:self.startPage]; 283 | } else if ([self.wwwFolderName rangeOfString:@"://"].location != NSNotFound) { 284 | appURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.wwwFolderName, self.startPage]]; 285 | } 286 | 287 | // iOS9 (runtime) compatibility 288 | if (IsAtLeastiOSVersion(@"9.0")) { 289 | appURL = _startURL; 290 | } 291 | 292 | //// Fix the iOS 5.1 SECURITY_ERR bug (CB-347), this must be before the webView is instantiated //// 293 | NSString* backupWebStorageType = @"cloud"; // default value 294 | 295 | id backupWebStorage = [self settingForKey:@"BackupWebStorage"]; 296 | if ([backupWebStorage isKindOfClass:[NSString class]]) { 297 | backupWebStorageType = backupWebStorage; 298 | } 299 | [self setSetting:backupWebStorageType forKey:@"BackupWebStorage"]; 300 | 301 | if (IsAtLeastiOSVersion(@"5.1")) { 302 | [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType]; 303 | } 304 | 305 | NSNumber* allowInlineMediaPlayback = [self settingForKey:@"AllowInlineMediaPlayback"]; 306 | BOOL mediaPlaybackRequiresUserAction = YES; // default value 307 | if ([self settingForKey:@"MediaPlaybackRequiresUserAction"]) { 308 | mediaPlaybackRequiresUserAction = [(NSNumber*)[self settingForKey:@"MediaPlaybackRequiresUserAction"] boolValue]; 309 | } 310 | 311 | // // Instantiate the WebView /////////////// 312 | 313 | if (!self.wkWebView) { 314 | WKUserContentController* userContentController = [[WKUserContentController alloc] init]; 315 | if ([self conformsToProtocol:@protocol(WKScriptMessageHandler)]) { 316 | [userContentController addScriptMessageHandler:self name:@"cordova"]; 317 | } 318 | WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; 319 | if ([allowInlineMediaPlayback boolValue]) { 320 | config.allowsInlineMediaPlayback = YES; 321 | } 322 | config.mediaPlaybackRequiresUserAction = mediaPlaybackRequiresUserAction; 323 | config.userContentController = userContentController; 324 | BOOL suppressesIncrementalRendering = NO; // SuppressesIncrementalRendering - defaults to NO 325 | if ([self settingForKey:@"SuppressesIncrementalRendering"] != nil) { 326 | if ([self settingForKey:@"SuppressesIncrementalRendering"]) { 327 | suppressesIncrementalRendering = [(NSNumber*)[self settingForKey:@"SuppressesIncrementalRendering"] boolValue]; 328 | } 329 | } 330 | config.suppressesIncrementalRendering = suppressesIncrementalRendering; 331 | [self createGapView:config]; 332 | } 333 | 334 | // Configure WebView 335 | self.wkWebView.navigationDelegate = self; 336 | 337 | // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set 338 | [CDVURLProtocol registerViewController:self]; 339 | 340 | // ///////////////// 341 | 342 | NSString* enableViewportScale = [self settingForKey:@"EnableViewportScale"]; 343 | 344 | // NOTE: setting these because this is largely a copy-paste of the super class, it's not actually used of course because this is the 'old' webView 345 | self.webView.scalesPageToFit = [enableViewportScale boolValue]; 346 | 347 | // Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup. 348 | if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorageType isEqualToString:@"local"]) || 349 | ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { 350 | [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])]; 351 | }; 352 | 353 | /* 354 | * This is for iOS 4.x, where you can allow inline