From c2c50bb657bece9035f4e49be5e1d174e57b2df1 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 13 Oct 2015 02:40:50 +0800 Subject: [PATCH 01/44] Rewrite the embedded HTTP server in Swift Fixed #17 --- XWebView.podspec | 5 +- XWebView.xcodeproj/project.pbxproj | 32 +-- XWebView/XWVHttpConnection.h | 44 ---- XWebView/XWVHttpConnection.m | 393 ----------------------------- XWebView/XWVHttpConnection.swift | 280 ++++++++++++++++++++ XWebView/XWVHttpServer.h | 32 --- XWebView/XWVHttpServer.m | 127 ---------- XWebView/XWVHttpServer.swift | 217 ++++++++++++++++ XWebView/XWebView.h | 2 - XWebView/XWebView.swift | 48 ++-- 10 files changed, 530 insertions(+), 650 deletions(-) delete mode 100644 XWebView/XWVHttpConnection.h delete mode 100644 XWebView/XWVHttpConnection.m create mode 100644 XWebView/XWVHttpConnection.swift delete mode 100644 XWebView/XWVHttpServer.h delete mode 100644 XWebView/XWVHttpServer.m create mode 100644 XWebView/XWVHttpServer.swift diff --git a/XWebView.podspec b/XWebView.podspec index 80ab167..01692f9 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -90,7 +90,7 @@ Pod::Spec.new do |s| # s.source_files = "XWebView/*.swift" - s.exclude_files = "XWebView/XWVInvocation.swift" + s.exclude_files = "XWebView/XWVInvocation.swift", "XWebView/XWVHttp*.swift" # s.public_header_files = "Classes/**/*.h" @@ -138,8 +138,7 @@ Pod::Spec.new do |s| end s.subspec "HttpServer" do |sp| - sp.source_files = "XWebView/XWVHttp*.{h,m}" - sp.private_header_files = "XWebView/XWVHttpConnection.h" + sp.source_files = "XWebView/XWVHttp*.swift" sp.framework = "MobileCoreServices" end end diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 7cd6e87..3450aab 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -23,10 +23,8 @@ EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; - EE71648F1A716C9F00078FF9 /* XWVHttpConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */; }; - EE7164901A716C9F00078FF9 /* XWVHttpConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = EE71648C1A716C9F00078FF9 /* XWVHttpConnection.m */; }; - EE7164911A716C9F00078FF9 /* XWVHttpServer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE71648D1A716C9F00078FF9 /* XWVHttpServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE7164921A716C9F00078FF9 /* XWVHttpServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EE71648E1A716C9F00078FF9 /* XWVHttpServer.m */; }; + EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; settings = {ASSET_TAGS = (); }; }; + EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; settings = {ASSET_TAGS = (); }; }; EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */; }; EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; @@ -78,10 +76,8 @@ EE62691319FA52D100EFC3F8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = XWebView/Info.plist; sourceTree = ""; }; EE62691C19FA52FC00EFC3F8 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWebView.h; path = XWebView/XWebView.h; sourceTree = ""; }; EE62692019FA52FC00EFC3F8 /* XWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWebView.swift; path = XWebView/XWebView.swift; sourceTree = ""; }; - EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWVHttpConnection.h; path = XWebView/XWVHttpConnection.h; sourceTree = ""; }; - EE71648C1A716C9F00078FF9 /* XWVHttpConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XWVHttpConnection.m; path = XWebView/XWVHttpConnection.m; sourceTree = ""; }; - EE71648D1A716C9F00078FF9 /* XWVHttpServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWVHttpServer.h; path = XWebView/XWVHttpServer.h; sourceTree = ""; }; - EE71648E1A716C9F00078FF9 /* XWVHttpServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XWVHttpServer.m; path = XWebView/XWVHttpServer.m; sourceTree = ""; }; + EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVHttpConnection.swift; path = XWebView/XWVHttpConnection.swift; sourceTree = ""; }; + EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVHttpServer.swift; path = XWebView/XWVHttpServer.swift; sourceTree = ""; }; EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVMetaObject.swift; path = XWebView/XWVMetaObject.swift; sourceTree = ""; }; EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; EEDF305F1B6555B900A21659 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVInvocation.swift; path = XWebView/XWVInvocation.swift; sourceTree = ""; }; @@ -158,13 +154,11 @@ EE62683719FA323900EFC3F8 /* XWebView */ = { isa = PBXGroup; children = ( + EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */, + EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */, EEDF305F1B6555B900A21659 /* XWVInvocation.swift */, EE131CA61B5F900400A9E790 /* XWVUserScript.swift */, EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */, - EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */, - EE71648C1A716C9F00078FF9 /* XWVHttpConnection.m */, - EE71648D1A716C9F00078FF9 /* XWVHttpServer.h */, - EE71648E1A716C9F00078FF9 /* XWVHttpServer.m */, EE62691C19FA52FC00EFC3F8 /* XWebView.h */, EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */, EE62692019FA52FC00EFC3F8 /* XWebView.swift */, @@ -194,8 +188,6 @@ buildActionMask = 2147483647; files = ( ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */, - EE7164911A716C9F00078FF9 /* XWVHttpServer.h in Headers */, - EE71648F1A716C9F00078FF9 /* XWVHttpConnection.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -317,16 +309,16 @@ buildActionMask = 2147483647; files = ( EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */, + EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */, EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */, EEE6F9A81AE02F5000A2EC89 /* XWVBindingObject.swift in Sources */, EE131CA71B5F900400A9E790 /* XWVUserScript.swift in Sources */, - EE7164921A716C9F00078FF9 /* XWVHttpServer.m in Sources */, EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */, + EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */, EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */, EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */, EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */, EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */, - EE7164901A716C9F00078FF9 /* XWVHttpConnection.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -345,9 +337,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -364,9 +354,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = XWebViewTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; diff --git a/XWebView/XWVHttpConnection.h b/XWebView/XWVHttpConnection.h deleted file mode 100644 index 98ec799..0000000 --- a/XWebView/XWVHttpConnection.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed 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. -*/ - -#ifndef XWebView_XWVHttpConnection_h -#define XWebView_XWVHttpConnection_h - -#import - -@class XWVHttpConnection; - -@protocol XWVHttpConnectionDelegate - -@optional -@property(nonatomic, readonly) NSString* documentRoot; -- (void)didOpenConnection:(XWVHttpConnection *)connection; -- (void)didCloseConnection:(XWVHttpConnection *)connection; - -@end - -@interface XWVHttpConnection : NSObject - -@property(nonatomic, weak) id delegate; - -- (id)initWithNativeHandle:(CFSocketNativeHandle)handle; -- (BOOL)open; -- (void)close; - -@end - - -#endif diff --git a/XWebView/XWVHttpConnection.m b/XWebView/XWVHttpConnection.m deleted file mode 100644 index b6165ab..0000000 --- a/XWebView/XWVHttpConnection.m +++ /dev/null @@ -1,393 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed 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. - */ - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#include -#include -#include - -#import -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -#import "XWVHttpConnection.h" - -static NSMutableURLRequest *parseRequest(NSMutableURLRequest *request, NSData *line); -static NSHTTPURLResponse *buildResponse(NSURLRequest *request, NSURL *rootURL); -static NSData *serializeResponse(const NSHTTPURLResponse *response); -static NSString *getMIMETypeByExtension(NSString *extension); - - -@implementation XWVHttpConnection { - CFSocketNativeHandle _socket; - NSInputStream *_input; - NSOutputStream *_output; - NSMutableArray *_requestQueue; - - // output state - NSFileHandle *_file; - size_t _fileSize; - NSData* _outputBuf; - size_t _bytesRemain; - - // input state - NSMutableURLRequest *_request; - NSUInteger _cursor; - NSMutableData *_inputBuf; -} - -- (id)initWithNativeHandle:(CFSocketNativeHandle)handle { - if (self = [super init]) - _socket = handle; - return self; -} - -- (BOOL)open { - if (_requestQueue != nil) return NO; // reopen is forbidden - - CFReadStreamRef input = NULL; - CFWriteStreamRef output = NULL; - CFStreamCreatePairWithSocket(kCFAllocatorDefault, _socket, &input, &output); - if (input == NULL || output == NULL) { - return NO; - } - CFReadStreamSetProperty(input, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - CFWriteStreamSetProperty(output, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - - _input = CFBridgingRelease(input); - _output = CFBridgingRelease(output); - [_input setDelegate:self]; - [_output setDelegate:self]; - [_input scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - [_output scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - [_input open]; - [_output open]; - - if (_delegate && [_delegate respondsToSelector:@selector(didOpenConnection:)]) - [_delegate didOpenConnection:self]; - return YES; -} - -- (void)close { - [_input close]; - [_output close]; - _input = nil; - _output = nil; - - _file = nil; - _inputBuf = nil; - _outputBuf = nil; - - if (_delegate && [_delegate respondsToSelector:@selector(didCloseConnection:)]) - [_delegate didCloseConnection:self]; -} - -- (NSURL *)rootURL { - NSURL *root; - if (_delegate && [_delegate respondsToSelector:@selector(documentRoot)]) { - root = [NSURL fileURLWithPath:_delegate.documentRoot isDirectory:YES]; - NSAssert(root != nil, @" you must set a valid documentRoot"); - } else { - NSBundle *bundle = [NSBundle mainBundle]; - root = bundle.resourceURL ?: bundle.bundleURL; - [root URLByAppendingPathComponent:@"www"]; - } - return root; -} - -- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { - switch(eventCode) { - case NSStreamEventOpenCompleted: { - // Initialize input/output state. - if (aStream == _input) { - _cursor = 0; - _request = nil; - _inputBuf = [[NSMutableData alloc] initWithLength:512]; - _requestQueue = [[NSMutableArray alloc] init]; - } else { - _file = nil; - _fileSize = 0; - _outputBuf = nil; - } - break; - } - case NSStreamEventHasBytesAvailable: { - NSInteger len = [_input read:(_inputBuf.mutableBytes + _cursor) maxLength:(_inputBuf.length - _cursor)]; - if (len <= 0) break; - len += _cursor; - _cursor = 0; - uint8_t *buf = _inputBuf.mutableBytes; - for (int i = 1; i < len; ++i) { - if (buf[i] == '\n' && buf[i - 1] == '\r') { - // End of line - if (_cursor == i - 1 && _request != nil) { - // End of request header. - [_requestQueue insertObject:_request atIndex:0]; - _request = nil; - } else if (_request == nil || _request.URL != nil) { - NSData *line = [NSData dataWithBytesNoCopy:(buf + _cursor) length:(i - _cursor - 1)]; - _request = parseRequest(_request, line); - if (_request == nil) // bad request - _request = [NSMutableURLRequest new]; - } - _cursor = i + 1; - } - } - if (_cursor > 0) { - // Move unparsed data to the begining. - memmove(buf, buf + _cursor, len - _cursor); - } else { - // Enlarge input buffer. - _inputBuf.length <<= 1; - } - _cursor = len - _cursor; - if (!_output.hasSpaceAvailable) - break; - } - case NSStreamEventHasSpaceAvailable: { - if (!_outputBuf) { - if (!_requestQueue.count) break; - NSURLRequest *request = _requestQueue.lastObject; - [_requestQueue removeLastObject]; - NSHTTPURLResponse *response = buildResponse(request, [self rootURL]); - if ([request.HTTPMethod compare:@"GET"] == NSOrderedSame) { - _file = [NSFileHandle fileHandleForReadingFromURL:response.URL error:nil]; - _fileSize = (size_t)_file.seekToEndOfFile; - } - _outputBuf = serializeResponse(response); - _bytesRemain = _outputBuf.length + _fileSize; - } - -#define CHUNK_SIZE (128 * 1024) - NSInteger len; - do { - size_t off; - if (_bytesRemain > _fileSize) { - // Send response header - off = _outputBuf.length - (_bytesRemain - _fileSize); - } else if (!(off = (_fileSize - _bytesRemain) % CHUNK_SIZE)) { - // Send file content - [_file seekToFileOffset:(_fileSize - _bytesRemain)]; - _outputBuf = [_file readDataOfLength:CHUNK_SIZE]; - } - len = [_output write:(_outputBuf.bytes + off) maxLength:(_outputBuf.length - off)]; - _bytesRemain -= len; - } while (_bytesRemain && _output.hasSpaceAvailable && len > 0); - if (_bytesRemain == 0) { - // Response has been sent completely. - _file = nil; - _fileSize = 0; - _outputBuf = nil; - } - if (len >= 0) break; - } - case NSStreamEventErrorOccurred: - NSLog(@"ERROR: %@", aStream.streamError.localizedDescription); - case NSStreamEventEndEncountered: - [self close]; - case NSStreamEventNone: - break; - } -} - -@end - - -static const char HttpVersion[] = "HTTP/1.1"; -static const char* HttpRequestMethodToken[] = { - "GET", - "HEAD", - "POST", - "PUT", - "DELETE", - "CONNECT", - "OPTIONS", - "TRACE" -}; -static const char* HttpResponseReasonPhrase[5][6] = { - { - "Continue" // 100 - }, - { - "OK" // 200 - }, - { - "Multiple Choices" // 300 - }, - { - "Bad Request", // 400 - NULL, - NULL, - NULL, - "Not Found", // 404 - "Method Not Allowed" // 405 - }, - { - "Internal Server Error", // 500 - "Not Implemented", // 501 - NULL, - NULL, - NULL, - "HTTP Version Not Supported" // 505 - } -}; - -NSMutableURLRequest *parseRequest(NSMutableURLRequest *request, NSData *line) { - const uint8_t *buf = line.bytes; - NSUInteger size = line.length; - const uint8_t *p, *q = NULL; - - if (!request) { - // Parse request line - if ((p = memchr(buf, ' ', size)) != NULL) { - ++p; - q = memchr(p, ' ', size - (p - buf)); - } - if (!p || !q || memcmp(q + 1, HttpVersion, sizeof(HttpVersion) - 1)) - return nil; - for (int i = 0; i < sizeof(HttpRequestMethodToken); ++i) { - const char *token = HttpRequestMethodToken[i]; - if (!memcmp(buf, token, strlen(token) - 1)) { - NSString *path = [[NSString alloc] initWithBytes:p length:(q - p) encoding:NSASCIIStringEncoding]; - NSURL *url = [[NSURL alloc] initWithScheme:@"http" host:@"" path:path]; - request = [NSMutableURLRequest requestWithURL:url]; - request.HTTPMethod = [NSString stringWithUTF8String:token]; - break; - } - } - } else if ((p = memchr(buf, ':', size)) != NULL) { - // Parse header field - NSString *name, *value; - name = [[NSString alloc] initWithBytes:buf length:(p - buf) encoding:NSASCIIStringEncoding]; - while (isspace(*(++p))); - value = [[NSString alloc] initWithBytes:p length:(size - (p - buf)) encoding:NSASCIIStringEncoding]; - - if (!strncasecmp((const char *)buf, "Host", 4)) { - // Support origin-form only - request.URL = [[NSURL alloc] initWithScheme:@"http" host:value path:request.URL.path]; - } else { - if ([request valueForHTTPHeaderField:name]) - [request addValue:value forHTTPHeaderField:name]; - else - [request setValue:value forHTTPHeaderField:name]; - } - } else { - return nil; - } - return request; -} - -NSHTTPURLResponse *buildResponse(NSURLRequest *request, NSURL *documentRoot) { - // Date format, see section 7.1.1.1 of RFC7231 - NSDateFormatter *dateFormatter = [NSDateFormatter new]; - dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; - dateFormatter.dateFormat = @"E, dd MMM yyyy HH:mm:ss z"; - - NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithObject:[dateFormatter stringFromDate:NSDate.date] forKey:@"Date"]; - NSURL *fileURL = nil; - int statusCode = 500; - if (request == nil || request.URL == nil) { - statusCode = 400; // Bad request - } else if ([request.HTTPMethod compare:@"GET"] == NSOrderedSame || - [request.HTTPMethod compare:@"HEAD"] == NSOrderedSame) { - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDirectory = NO; - fileURL = [documentRoot URLByAppendingPathComponent:request.URL.path]; - if ([fileManager fileExistsAtPath:fileURL.path isDirectory:&isDirectory] && isDirectory) { - fileURL = [fileURL URLByAppendingPathComponent:@"/index.html"]; - } - if ([fileManager isReadableFileAtPath:fileURL.path]) { - statusCode = 200; - NSDictionary *attrs = [fileManager attributesOfItemAtPath:fileURL.path error:nil]; - headers[@"Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension); - headers[@"Content-Length"] = [NSString stringWithFormat:@"%llu", attrs.fileSize]; - headers[@"Last-Modified"] = [dateFormatter stringFromDate:attrs.fileModificationDate]; - } else { - statusCode = 404; // Not found - } - } else { - statusCode = 405; // Method Not Allowed - headers[@"Allow"] = @"GET HEAD"; - } - if (statusCode != 200) { - headers[@"Content-Length"] = @"0"; - } - return [[NSHTTPURLResponse alloc] initWithURL:fileURL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headers]; -} - -NSData *serializeResponse(const NSHTTPURLResponse *response) { - NSDictionary *headers = response.allHeaderFields; - NSEnumerator *enumerator; - NSString *name; - - int class = (int)response.statusCode / 100 - 1; - NSCAssert(class >= 0 && class < 5, @" status code must be in the range [100, 599]"); - - int code = (int)response.statusCode % 100; - if (code >= sizeof(HttpResponseReasonPhrase[class]) / sizeof(char *) || - HttpResponseReasonPhrase[class][code] == NULL) { - // Treat an unrecognized status code as being equivalent to the x00 status code of that class. - code = 0; - } - const char *reason = HttpResponseReasonPhrase[class][code]; - - // Calculate buffer size - size_t len = sizeof(HttpVersion) + 5 + strlen(reason) + 4; - enumerator = [headers keyEnumerator]; - while (name = [enumerator nextObject]) - len += name.length + [headers[name] length] + 4; - - NSMutableData *data = [[NSMutableData alloc] initWithLength:len]; - char *buf = data.mutableBytes; - buf += sprintf(buf, "%s %3zd %s\r\n", HttpVersion, response.statusCode, reason); - enumerator = [headers keyEnumerator]; - while (name = [enumerator nextObject]) - buf += sprintf(buf, "%s: %s\r\n", name.UTF8String, [headers[name] UTF8String]); - sprintf(buf, "\r\n"); - --data.length; - return data; -} - -NSString *getMIMETypeByExtension(NSString *extension) { - static NSMutableDictionary *mimeTypeCache = nil; - if (mimeTypeCache == nil) { - // Add all MIME types which are unknown to system here. - mimeTypeCache = [NSMutableDictionary dictionaryWithObjectsAndKeys: - @"text/css", @"css", - nil]; - } - - NSString *type = mimeTypeCache[extension]; - if (type == nil) { - // Get MIME type through system-declared uniform type identifier. - NSString *uti = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)(extension), NULL); - type = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)(uti), kUTTagClassMIMEType); - if (type == nil) - type = @"application/octet-stream"; // Fall back to binary stream. - } - mimeTypeCache[extension] = type; - - if ([type compare:@"text/" options:NSCaseInsensitiveSearch range:NSMakeRange(0,5)] == NSOrderedSame) - return [type stringByAppendingString:@"; charset=utf-8"]; // Assume text resource is UTF-8 encoding - return type; -} diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift new file mode 100644 index 0000000..b8870c3 --- /dev/null +++ b/XWebView/XWVHttpConnection.swift @@ -0,0 +1,280 @@ +/* + Copyright 2015 XWebView + + Licensed 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 Foundation + +protocol XWVHttpConnectionDelegate { + func handleRequest(request: NSURLRequest) -> NSHTTPURLResponse + func didOpenConnection(connection: XWVHttpConnection) + func didCloseConnection(connection: XWVHttpConnection) +} + +final class XWVHttpConnection : NSObject { + private let handle: CFSocketNativeHandle + private let delegate: XWVHttpConnectionDelegate + private var input: NSInputStream! + private var output: NSOutputStream! + private let bufferMaxSize = 64 * 1024 + + // input state + private var requestQueue = [NSURLRequest]() + private var inputBuffer: NSMutableData! + private var cursor: Int = 0 + + // output state + private var outputBuffer: NSData! + private var bytesRemained: Int = 0 + private var fileHandle: NSFileHandle! + private var fileSize: Int = 0 + + init(handle: CFSocketNativeHandle, delegate: XWVHttpConnectionDelegate) { + self.handle = handle + self.delegate = delegate + super.init() + } + + func open() -> Bool { + let ptr1 = UnsafeMutablePointer?>.alloc(1) + let ptr2 = UnsafeMutablePointer?>.alloc(1) + defer { + ptr1.dealloc(1) + ptr2.dealloc(1) + } + CFStreamCreatePairWithSocket(nil, handle, ptr1, ptr2) + if ptr1.memory == nil || ptr2.memory == nil { return false } + + input = ptr1.memory!.takeRetainedValue() + output = ptr2.memory!.takeRetainedValue() + CFReadStreamSetProperty(input, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) + CFWriteStreamSetProperty(output, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) + + input.delegate = self + output.delegate = self + input.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + output.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + input.open() + output.open() + delegate.didOpenConnection(self) + return true + } + + func close() { + input.close() + output.close() + input = nil + output = nil + delegate.didCloseConnection(self) + } +} + +extension XWVHttpConnection : NSStreamDelegate { + @objc func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { + switch eventCode { + case NSStreamEvent.OpenCompleted: + // Initialize input/output state. + if aStream === input { + inputBuffer = NSMutableData(length: 512) + cursor = 0; + } else { + outputBuffer = nil + fileHandle = nil + fileSize = 0 + } + + case NSStreamEvent.HasBytesAvailable: + let base = UnsafeMutablePointer(inputBuffer.mutableBytes) + let bytesReaded = input.read(base.advancedBy(cursor), maxLength: inputBuffer.length - cursor) + guard bytesReaded > 0 else { break } + + var bytesConsumed = 0 + var ptr = cursor > 3 ? base.advancedBy(cursor - 3): base + for _ in 0 ..< bytesReaded { + if UnsafePointer(ptr).memory == UInt32(bigEndian: 0x0d0a0d0a) { + // End of request header. + ptr += 3 + let data = inputBuffer.subdataWithRange(NSRange(bytesConsumed...base.distanceTo(ptr))) + if let request = NSMutableURLRequest(data: data) { + requestQueue.insert(request, atIndex: 0) + } else { + // Bad request + requestQueue.insert(NSURLRequest(), atIndex: 0) + } + bytesConsumed += data.length + } + ++ptr + } + if bytesConsumed > 0 { + // Move remained bytes to the begining. + inputBuffer.replaceBytesInRange(NSRange(0.. fileSize { + // Send response header + off = outputBuffer.length - (bytesRemained - fileSize) + } else { + // Send file content + off = (fileSize - bytesRemained) % bufferMaxSize + if off == 0 { + fileHandle.seekToFileOffset(UInt64(fileSize - bytesRemained)) + outputBuffer = fileHandle.readDataOfLength(bufferMaxSize) + } + } + let ptr = UnsafePointer(outputBuffer.bytes) + bytesSent = output.write(ptr.advancedBy(off), maxLength: outputBuffer.length - off) + bytesRemained -= bytesSent + } while bytesRemained > 0 && output.hasSpaceAvailable && bytesSent > 0 + if bytesRemained == 0 { + // Response has been sent completely. + fileHandle = nil + fileSize = 0 + outputBuffer = nil + } + if bytesSent < 0 { fallthrough } + + case NSStreamEvent.ErrorOccurred: + print(" ERROR: " + (aStream.streamError?.localizedDescription ?? "Unknown")) + fallthrough + + case NSStreamEvent.EndEncountered: + fileHandle = nil + inputBuffer = nil + outputBuffer = nil + close() + + default: + break + } + } +} + +private extension String { + mutating func trim(@noescape predicate: (Character) -> Bool) { + if !isEmpty { + var start = startIndex + var end = endIndex.predecessor() + for var s = start; s != endIndex && predicate(self[s]); start = ++s {} + if start == endIndex { + self = "" + } else { + for var e = end; predicate(self[e]); end = --e {} + self = self[start ... end] + } + } + } +} + +private extension NSMutableURLRequest { + private enum Version : String { + case v1_0 = "HTTP/1.0" + case v1_1 = "HTTP/1.1" + } + private enum Method : String { + case Get = "GET" + case Head = "HEAD" + case Post = "POST" + case Put = "PUT" + case Delete = "DELETE" + case Connect = "CONNECT" + case Options = "OPTIONS" + case Trace = "TRACE" + } + private var CRLF: NSData { + var CRLF: [UInt8] = [ 0x0d, 0x0a ] + return NSData(bytes: &CRLF, length: 2) + } + + convenience init?(data: NSData) { + self.init() + var cursor = 0 + repeat { + let range = NSRange(cursor.. 100 && statusCode < 600) + let reason = NSHTTPURLResponse.localizedStringForStatusCode(statusCode).capitalizedString + let statusLine = "HTTP/1.1 \(statusCode) \(reason)\r\n" + let data = allHeaderFields.reduce(NSMutableData(data: statusLine.dataUsingEncoding(NSASCIIStringEncoding)!)) { + $0.appendData(($1.0 as! NSString).dataUsingEncoding(NSASCIIStringEncoding)!) + $0.appendData(": ".dataUsingEncoding(NSASCIIStringEncoding)!) + $0.appendData(($1.1 as! NSString).dataUsingEncoding(NSASCIIStringEncoding)!) + $0.appendData("\r\n".dataUsingEncoding(NSASCIIStringEncoding)!) + return $0 + } + data.appendData("\r\n".dataUsingEncoding(NSASCIIStringEncoding)!) + return data + } +} diff --git a/XWebView/XWVHttpServer.h b/XWebView/XWVHttpServer.h deleted file mode 100644 index dbfa665..0000000 --- a/XWebView/XWVHttpServer.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed 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. - */ - -#ifndef XWebView_XWVHttpServer_h -#define XWebView_XWVHttpServer_h - -#import - -@interface XWVHttpServer : NSObject - -@property(nonatomic, readonly) in_port_t port; - -- (id)initWithDocumentRoot:(NSString *)root; -- (BOOL)start; -- (void)stop; - -@end - -#endif diff --git a/XWebView/XWVHttpServer.m b/XWebView/XWVHttpServer.m deleted file mode 100644 index 2d4cd2c..0000000 --- a/XWebView/XWVHttpServer.m +++ /dev/null @@ -1,127 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed 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. - */ - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#include -#include - -#import "XWVHttpServer.h" -#import "XWVHttpConnection.h" - -@interface XWVHttpServer () -@end - -@implementation XWVHttpServer { - CFSocketRef _socket; - NSMutableSet *_connections; - NSString *_documentRoot; -} - -- (in_port_t)port { - in_port_t port = 0; - if (_socket != NULL) { - NSData *addr = (__bridge_transfer NSData *)CFSocketCopyAddress(_socket); - port = ntohs(((const struct sockaddr_in *)[addr bytes])->sin_port); - } - return port; -} - -- (NSString *)documentRoot { - return _documentRoot; -} - -- (id)initWithDocumentRoot:(NSString *)root { - if (self = [super init]) { - BOOL isDirectory; - if (![[NSFileManager defaultManager] fileExistsAtPath:root isDirectory:&isDirectory] || !isDirectory) { - return nil; - } - _documentRoot = [root copy]; - } - return self; -} - -- (void)dealloc { - [self stop]; -} - -- (void)didCloseConnection:(NSNotification *)connection { - [_connections removeObject:connection]; -} - -static void ServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { - XWVHttpServer *server = (__bridge XWVHttpServer *)info; - CFSocketNativeHandle handle = *(CFSocketNativeHandle *)data; - assert(socket == server->_socket && type == kCFSocketAcceptCallBack); - - XWVHttpConnection * conn = [[XWVHttpConnection alloc] initWithNativeHandle:handle]; - [server->_connections addObject:conn]; - conn.delegate = server; - [conn open]; -} - -- (BOOL)start { - if (_socket != nil) return NO; - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_len = sizeof(addr); - addr.sin_family = AF_INET; - addr.sin_port = htons(0); - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)]; - CFSocketSignature signature = {PF_INET, SOCK_STREAM, IPPROTO_TCP, (__bridge CFDataRef)(address)}; - - CFSocketContext context = {0, (__bridge void *)self, NULL, NULL, NULL}; - _socket = CFSocketCreateWithSocketSignature(kCFAllocatorDefault, &signature, kCFSocketAcceptCallBack, &ServerAcceptCallBack, &context); - if (_socket == NULL) return NO; - - const int yes = 1; - setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - _connections = [[NSMutableSet alloc] init]; - [NSThread detachNewThreadSelector:@selector(serverLoop:) toTarget:self withObject:nil]; - return YES; -} - -- (void)stop { - // Close all connections. - for (XWVHttpConnection * conn in _connections) { - conn.delegate = nil; - [conn close]; - } - _connections = nil; - - // Close server socket. - if (_socket != NULL) { - CFSocketInvalidate(_socket); - CFRelease(_socket); - _socket = NULL; - } -} - -- (void)serverLoop:(id)unused { - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(NULL, _socket, 0); - CFRunLoopAddSource(runLoop, source, kCFRunLoopCommonModes); - CFRelease(source); - CFRunLoopRun(); -} - -@end diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift new file mode 100644 index 0000000..f83e981 --- /dev/null +++ b/XWebView/XWVHttpServer.swift @@ -0,0 +1,217 @@ +/* + Copyright 2015 XWebView + + Licensed 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 Foundation +#if os(iOS) +import UIKit +import MobileCoreServices +#else +import CoreServices +#endif + +class XWVHttpServer : NSObject { + private var socket: CFSocketRef! + private var connections = Set() + private var documentRoot: NSURL + private(set) var port: in_port_t = 0 + + init(documentRoot: NSURL) { + // documentRoot must be a directory + precondition(documentRoot.fileURL) + let fileManager = NSFileManager.defaultManager() + var isDirectory: ObjCBool = false + let result = fileManager.fileExistsAtPath(documentRoot.path!, isDirectory: &isDirectory) + precondition(result && isDirectory) + + self.documentRoot = documentRoot + super.init() + } + deinit { + stop() + } + + private func listenOnPort(port: in_port_t) -> Bool { + guard socket == nil else { return false } + + let info = UnsafeMutablePointer(unsafeAddressOf(self)) + var context = CFSocketContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) + let callbackType = CFSocketCallBackType.AcceptCallBack.rawValue + socket = CFSocketCreate(nil, PF_INET, SOCK_STREAM, 0, callbackType, ServerAcceptCallBack, &context) + guard socket != nil else { return false } + + var yes = UInt32(1) + setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, &yes, UInt32(sizeof(UInt32))) + + var sockaddr = sockaddr_in( + sin_len: UInt8(sizeof(sockaddr_in)), + sin_family: UInt8(AF_INET), + sin_port: port.bigEndian, + sin_addr: in_addr(s_addr: UInt32(0x7f000001).bigEndian), + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + let data = NSData(bytes: &sockaddr, length: sizeof(sockaddr_in)) + guard CFSocketSetAddress(socket, data) == CFSocketError.Success else { + print(" ERROR: \(String(UTF8String: strerror(errno))!)") + CFSocketInvalidate(socket) + return false + } + + NSThread.detachNewThreadSelector(Selector("serverLoop:"), toTarget: self, withObject: nil) + return true + } + + private func close() { + // Close all connections. + connections.forEach { $0.close() } + connections.removeAll() + + // Close server socket. + if socket != nil { + CFSocketInvalidate(socket) + socket = nil + } + } + + func start(port: in_port_t = 0) -> Bool { + if port == 0 { + // Try to find a random port in registered ports range + for _ in 0 ..< 100 { + let port = in_port_t(arc4random() % (49152 - 1024) + 1024) + if listenOnPort(port) { + print(" INFO: Listen on port: \(port)") + self.port = port + break + } + } + } else if listenOnPort(port) { + self.port = port + } + guard self.port != 0 else { return false } + + #if os(iOS) + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("suspend:"), name: UIApplicationDidEnterBackgroundNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("resume:"), name: UIApplicationWillEnterForegroundNotification, object: nil) + #endif + return true + } + + func stop() { + #if os(iOS) + NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) + NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil) + #endif + port = 0 + close() + } + + func suspend(_: NSNotification!) { + close() + } + func resume(_: NSNotification!) { + listenOnPort(port) + } + + func serverLoop(_: AnyObject) { + let runLoop = CFRunLoopGetCurrent() + let source = CFSocketCreateRunLoopSource(nil, socket, 0) + CFRunLoopAddSource(runLoop, source, kCFRunLoopCommonModes) + CFRunLoopRun() + } +} + +extension XWVHttpServer : XWVHttpConnectionDelegate { + func didOpenConnection(connection: XWVHttpConnection) { + connections.insert(connection) + } + func didCloseConnection(connection: XWVHttpConnection) { + connections.remove(connection) + } + + func handleRequest(request: NSURLRequest) -> NSHTTPURLResponse { + // Date format, see section 7.1.1.1 of RFC7231 + let dateFormatter = NSDateFormatter() + dateFormatter.locale = NSLocale(localeIdentifier: "en_US") + dateFormatter.timeZone = NSTimeZone(name: "GMT") + dateFormatter.dateFormat = "E, dd MMM yyyy HH:mm:ss z" + + var headers: [String: String] = ["Date": dateFormatter.stringFromDate(NSDate())] + var statusCode = 500 + var fileURL = NSURL() + if request.URL == nil { + // Bad request + statusCode = 400 + } else if request.HTTPMethod == "GET" || request.HTTPMethod == "HEAD" { + fileURL = documentRoot.URLByAppendingPathComponent(request.URL!.path!) + var isDirectory: ObjCBool = false + let fileManager = NSFileManager.defaultManager() + fileManager.fileExistsAtPath(fileURL.path!, isDirectory: &isDirectory) + if isDirectory { + fileURL = fileURL.URLByAppendingPathComponent("index.html") + } + if fileManager.isReadableFileAtPath(fileURL.path!) { + statusCode = 200 + let attrs = try! fileManager.attributesOfItemAtPath(fileURL.path!) + headers["Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension!) + headers["Content-Length"] = String(attrs[NSFileSize]!) + headers["Last-Modified"] = dateFormatter.stringFromDate(attrs[NSFileModificationDate] as! NSDate) + } else { + // Not found + statusCode = 404 + fileURL = NSURL() + } + } else { + // Method not allowed + statusCode = 405 + headers["Allow"] = "GET HEAD" + } + if statusCode != 200 { + headers["Content-Length"] = "0" + } + return NSHTTPURLResponse(URL: fileURL, statusCode: statusCode, HTTPVersion: "HTTP/1.1", headerFields: headers)! + } +} + +private func ServerAcceptCallBack(socket: CFSocket!, type: CFSocketCallBackType, address: CFData!, data:UnsafePointer, info: UnsafeMutablePointer) { + let server = unsafeBitCast(info, XWVHttpServer.self) + let handle = UnsafePointer(data).memory + assert(socket === server.socket && type == CFSocketCallBackType.AcceptCallBack) + + let connection = XWVHttpConnection(handle: handle, delegate: server) + connection.open() +} + +private var mimeTypeCache = [ + // MIME types which are unknown by system. + "css" : "text/css" +] +private func getMIMETypeByExtension(extensionName: String) -> String { + var type: String! = mimeTypeCache[extensionName] + if type == nil { + // Get MIME type through system-declared uniform type identifier. + if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionName, nil), + let mt = UTTypeCopyPreferredTagWithClass(uti.takeRetainedValue(), kUTTagClassMIMEType) { + type = mt.takeRetainedValue() as String + } else { + // Fall back to binary stream. + type = "application/octet-stream" + } + mimeTypeCache[extensionName] = type + } + if type.lowercaseString.hasPrefix("text/") { + // Assume text resource is UTF-8 encoding + return type + "; charset=utf-8" + } + return type +} diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index a154502..be97db7 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -24,8 +24,6 @@ FOUNDATION_EXPORT const unsigned char XWebViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import -#import "XWVHttpServer.h" - // The workaround for loading file URL on iOS 8.x. #import @interface WKWebView (XWebView) diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index d1f0d79..e3c317c 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -109,7 +109,7 @@ extension WKWebView { let method = class_getInstanceMethod(self, Selector("_loadFileURL:allowingReadAccessToURL:")) assert(method != nil) if class_addMethod(self, selector, method_getImplementation(method), method_getTypeEncoding(method)) { - print("iOS 8.x") + print(" INFO: Platform is iOS 8.x") method_exchangeImplementations( class_getInstanceMethod(self, Selector("loadHTMLString:baseURL:")), class_getInstanceMethod(self, Selector("_loadHTMLString:baseURL:")) @@ -120,21 +120,19 @@ extension WKWebView { @objc private func _loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation? { precondition(URL.fileURL && readAccessURL.fileURL) + + // readAccessURL must contain URL let fileManager = NSFileManager.defaultManager() var relationship: NSURLRelationship = NSURLRelationship.Other _ = try? fileManager.getRelationship(&relationship, ofDirectoryAtURL: readAccessURL, toItemAtURL: URL) + guard relationship != NSURLRelationship.Other else { return nil } - var isDirectory: ObjCBool = false - if fileManager.fileExistsAtPath(readAccessURL.path!, isDirectory: &isDirectory) && - isDirectory && relationship != NSURLRelationship.Other { - let port = startHttpd(documentRoot: readAccessURL.path!) - var path = URL.path![readAccessURL.path!.endIndex ..< URL.path!.endIndex] - if let query = URL.query { path += "?\(query)" } - if let fragment = URL.fragment { path += "#\(fragment)" } - let url = NSURL(string: path , relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) - return loadRequest(NSURLRequest(URL: url!)); - } - return nil + guard let port = startHttpd(documentRoot: readAccessURL) else { return nil } + var path = URL.path![readAccessURL.path!.endIndex ..< URL.path!.endIndex] + if let query = URL.query { path += "?\(query)" } + if let fragment = URL.fragment { path += "#\(fragment)" } + let url = NSURL(string: path , relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + return loadRequest(NSURLRequest(URL: url!)); } @objc private func _loadHTMLString(html: String, baseURL: NSURL) -> WKNavigation? { @@ -143,24 +141,20 @@ extension WKWebView { return _loadHTMLString(html, baseURL: baseURL) } - let fileManager = NSFileManager.defaultManager() - var isDirectory: ObjCBool = false - if fileManager.fileExistsAtPath(baseURL.path!, isDirectory: &isDirectory) && isDirectory { - let port = startHttpd(documentRoot: baseURL.path!) - let url = NSURL(string: "http://127.0.0.1:\(port)/") - return loadHTMLString(html, baseURL: url) - } - return nil + guard let port = startHttpd(documentRoot: baseURL) else { return nil } + let url = NSURL(string: "http://127.0.0.1:\(port)/") + return loadHTMLString(html, baseURL: url) } - private func startHttpd(documentRoot root: String) -> in_port_t { + private func startHttpd(documentRoot root: NSURL) -> in_port_t? { let key = unsafeAddressOf(XWVHttpServer) - var httpd = objc_getAssociatedObject(self, key) as? XWVHttpServer - if httpd == nil { - httpd = XWVHttpServer(documentRoot: root) - httpd!.start() - objc_setAssociatedObject(self, key, httpd!, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if let httpd = objc_getAssociatedObject(self, key) as? XWVHttpServer { + return httpd.port } - return httpd!.port + + let httpd = XWVHttpServer(documentRoot: root) + guard httpd.start() else { return nil } + objc_setAssociatedObject(self, key, httpd, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return httpd.port } } From bd32b39c665f62ff2457927cedb97f6c57c56cd1 Mon Sep 17 00:00:00 2001 From: David Kim Date: Wed, 14 Oct 2015 00:06:05 +0800 Subject: [PATCH 02/44] Bump version to 0.9.4 --- XWebView.podspec | 2 +- XWebView/Info.plist | 2 +- XWebViewTests/Info.plist | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/XWebView.podspec b/XWebView.podspec index 01692f9..d47cd2f 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.9.3" + s.version = "0.9.4" s.summary = "An extensible WebView (based on WKWebView) for iOS." s.description = <<-DESC diff --git a/XWebView/Info.plist b/XWebView/Info.plist index 600c65b..f8e7bb6 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.3 + 0.9.4 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index 0853990..76d6fb4 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.9.3 + 0.9.4 CFBundleSignature ???? CFBundleVersion From 04c5112b0772598d1b2e9a44831b0e218a25297d Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 20 Oct 2015 04:07:06 +0800 Subject: [PATCH 03/44] Add overlay support for loading file URL --- XWebView/XWVHttpServer.swift | 47 +++++++++++++++++++++---------- XWebView/XWebView.swift | 29 +++++++++++++++---- XWebViewTests/XWebViewTests.swift | 20 +++++++++++++ 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index f83e981..f2d0baf 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -25,20 +25,29 @@ import CoreServices class XWVHttpServer : NSObject { private var socket: CFSocketRef! private var connections = Set() - private var documentRoot: NSURL + private let overlays: [NSURL] private(set) var port: in_port_t = 0 - init(documentRoot: NSURL) { - // documentRoot must be a directory - precondition(documentRoot.fileURL) - let fileManager = NSFileManager.defaultManager() - var isDirectory: ObjCBool = false - let result = fileManager.fileExistsAtPath(documentRoot.path!, isDirectory: &isDirectory) - precondition(result && isDirectory) + var rootURL: NSURL { + return overlays.last! + } + var overlayURLs: [NSURL] { + return overlays.dropLast().reverse() + } - self.documentRoot = documentRoot + init(rootURL: NSURL, overlayURLs: [NSURL]?) { + precondition(rootURL.fileURL) + var overlays = [rootURL] + overlayURLs?.forEach { + precondition($0.fileURL) + overlays.append($0) + } + self.overlays = overlays.reverse() super.init() } + convenience init(rootURL: NSURL) { + self.init(rootURL: rootURL, overlayURLs: nil) + } deinit { stop() } @@ -153,14 +162,22 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { // Bad request statusCode = 400 } else if request.HTTPMethod == "GET" || request.HTTPMethod == "HEAD" { - fileURL = documentRoot.URLByAppendingPathComponent(request.URL!.path!) - var isDirectory: ObjCBool = false let fileManager = NSFileManager.defaultManager() - fileManager.fileExistsAtPath(fileURL.path!, isDirectory: &isDirectory) - if isDirectory { - fileURL = fileURL.URLByAppendingPathComponent("index.html") + let relativePath = String(request.URL!.path!.characters.dropFirst()) + for baseURL in overlays { + var isDirectory: ObjCBool = false + var url = NSURL(string: relativePath, relativeToURL: baseURL)! + if fileManager.fileExistsAtPath(url.path!, isDirectory: &isDirectory) { + if isDirectory { + url = url.URLByAppendingPathComponent("index.html") + } + if fileManager.isReadableFileAtPath(url.path!) { + fileURL = url + break + } + } } - if fileManager.isReadableFileAtPath(fileURL.path!) { + if fileURL.path != nil { statusCode = 200 let attrs = try! fileManager.attributesOfItemAtPath(fileURL.path!) headers["Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension!) diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index e3c317c..e37b0dd 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -92,6 +92,20 @@ extension WKWebView { } } +extension WKWebView { + // Overlay support for loading file URL + public func loadFileURL(URL: NSURL, overlayURLs: [NSURL]? = nil) -> WKNavigation? { + precondition(URL.fileURL && URL.baseURL != nil, "URL must be a relative file URL.") + guard overlayURLs?.count > 0 else { + return loadFileURL(URL, allowingReadAccessToURL: URL.baseURL!) + } + + guard let port = startHttpd(rootURL: URL.baseURL!, overlayURLs: overlayURLs) else { return nil } + let url = NSURL(string: URL.resourceSpecifier, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + return loadRequest(NSURLRequest(URL: url!)) + } +} + extension WKWebView { // WKWebView can't load file URL on iOS 8.x devices. // We have to start an embedded http server for proxy. @@ -127,12 +141,12 @@ extension WKWebView { _ = try? fileManager.getRelationship(&relationship, ofDirectoryAtURL: readAccessURL, toItemAtURL: URL) guard relationship != NSURLRelationship.Other else { return nil } - guard let port = startHttpd(documentRoot: readAccessURL) else { return nil } + guard let port = startHttpd(rootURL: readAccessURL) else { return nil } var path = URL.path![readAccessURL.path!.endIndex ..< URL.path!.endIndex] if let query = URL.query { path += "?\(query)" } if let fragment = URL.fragment { path += "#\(fragment)" } let url = NSURL(string: path , relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) - return loadRequest(NSURLRequest(URL: url!)); + return loadRequest(NSURLRequest(URL: url!)) } @objc private func _loadHTMLString(html: String, baseURL: NSURL) -> WKNavigation? { @@ -141,18 +155,21 @@ extension WKWebView { return _loadHTMLString(html, baseURL: baseURL) } - guard let port = startHttpd(documentRoot: baseURL) else { return nil } + guard let port = startHttpd(rootURL: baseURL) else { return nil } let url = NSURL(string: "http://127.0.0.1:\(port)/") return loadHTMLString(html, baseURL: url) } - private func startHttpd(documentRoot root: NSURL) -> in_port_t? { + private func startHttpd(rootURL rootURL: NSURL, overlayURLs: [NSURL]? = nil) -> in_port_t? { let key = unsafeAddressOf(XWVHttpServer) if let httpd = objc_getAssociatedObject(self, key) as? XWVHttpServer { - return httpd.port + if httpd.rootURL == rootURL && httpd.overlayURLs == overlayURLs ?? [] { + return httpd.port + } + httpd.stop() } - let httpd = XWVHttpServer(documentRoot: root) + let httpd = XWVHttpServer(rootURL: rootURL, overlayURLs: overlayURLs) guard httpd.start() else { return nil } objc_setAssociatedObject(self, key, httpd, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) return httpd.port diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 4c88b71..c1c12bb 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -39,6 +39,26 @@ class XWebViewTests: XWVTestCase { } } + func testLoadFileURLWithOverlay() { + _ = expectationWithDescription("loadFileURLWithOverlay") + let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") + if let root = bundle?.bundleURL.URLByAppendingPathComponent("www") { + // create overlay file in library directory + let library = try! NSFileManager.defaultManager().URLForDirectory( + NSSearchPathDirectory.LibraryDirectory, + inDomain: NSSearchPathDomainMask.UserDomainMask, + appropriateForURL: nil, + create: true) + var url = library.URLByAppendingPathComponent("webviewTest.html") + let content = "" + try! content.writeToURL(url, atomically: false, encoding: NSUTF8StringEncoding) + + url = NSURL(string: "webviewTest.html", relativeToURL: root)! + webview.loadFileURL(url, overlayURLs: [library]) + waitForExpectationsWithTimeout(2, handler: nil) + } + } + func testLoadHTMLStringWithBaseURL() { _ = expectationWithDescription("loadHTMLStringWithBaseURL") let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") From 9ce703c1690ee961d2418f037b4f6a33cd54e9ef Mon Sep 17 00:00:00 2001 From: David Kim Date: Sun, 15 Nov 2015 03:38:16 +0800 Subject: [PATCH 04/44] Adopt Swift 2 error handling --- XWebView/XWVBindingObject.swift | 25 ++++++++------- XWebView/XWVChannel.swift | 10 ++++-- XWebView/XWVObject.swift | 41 ++++++++++++------------ XWebView/XWVScriptObject.swift | 45 +++++++++++++++++---------- XWebView/XWebView.swift | 27 +++++++++++----- XWebViewTests/ConstructorPlugin.swift | 2 +- XWebViewTests/ObjectPlugin.swift | 6 ++-- 7 files changed, 92 insertions(+), 64 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index c8fb8dc..e1f1056 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -28,11 +28,12 @@ class XWVBindingObject : XWVScriptObject { startKVO() } - init(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { + init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { super.init(namespace: namespace, channel: channel, origin: nil) let member = channel.typeInfo[""] guard member != nil, case .Initializer(let selector, let arity) = member! else { - preconditionFailure("FATAL: Plugin is not a constructor") + print(" ERROR: Plugin is not a constructor") + return nil } var args = arguments?.map(wrapScriptObject) ?? [] @@ -49,7 +50,7 @@ class XWVBindingObject : XWVScriptObject { objc_setAssociatedObject(object, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) startKVO() syncProperties() - promise?.callMethod("resolve", withArguments: [self], resultHandler: nil) + promise?.callMethod("resolve", withArguments: [self], completionHandler: nil) } private func syncProperties() { var script = "" @@ -69,11 +70,11 @@ class XWVBindingObject : XWVScriptObject { } // Dispatch operation to plugin object - func invokeNativeMethod(name: String, withArguments arguments: [AnyObject]?) { + func invokeNativeMethod(name: String, withArguments arguments: [AnyObject]) { if let selector = channel.typeInfo[name]?.selector { - var args = arguments?.map(wrapScriptObject) + var args = arguments.map(wrapScriptObject) if object is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { - args = [args ?? []]; + args = [args]; } if channel.queue != nil { dispatch_async(channel.queue) { @@ -85,7 +86,7 @@ class XWVBindingObject : XWVScriptObject { } } } - func updateNativeProperty(name: String, withValue value: AnyObject!) { + func updateNativeProperty(name: String, withValue value: AnyObject) { if let setter = channel.typeInfo[name]?.setter { let val: AnyObject = wrapScriptObject(value) if channel.queue != nil { @@ -100,19 +101,19 @@ class XWVBindingObject : XWVScriptObject { } // override methods of XWVScriptObject - override func callMethod(name: String, withArguments arguments: [AnyObject]?, resultHandler: ((AnyObject!) -> Void)?) { + override func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { if let selector = channel.typeInfo[name]?.selector { let result: AnyObject! = XWVInvocation(target: object).call(selector, withObjects: arguments) - resultHandler?(result) + completionHandler?(result, nil) } else { - super.callMethod(name, withArguments: arguments, resultHandler: resultHandler) + super.callMethod(name, withArguments: arguments, completionHandler: completionHandler) } } - override func callMethod(name: String, withArguments arguments: [AnyObject]?) -> AnyObject! { + override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject! { if let selector = channel.typeInfo[name]?.selector { return XWVInvocation(target: object).call(selector, withObjects: arguments) } - return super.callMethod(name, withArguments: arguments) + return try super.callMethod(name, withArguments: arguments) } override func value(forProperty name: String) -> AnyObject? { if let getter = channel.typeInfo[name]?.getter { diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 8f4fe16..39c0782 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -80,6 +80,9 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } public func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { + // A workaround for crash when postMessage(undefined) + guard unsafeBitCast(message.body, COpaquePointer.self) != nil else { return } + if let body = message.body as? [String: AnyObject], let opcode = body["$opcode"] as? String { let target = (body["$target"] as? NSNumber)?.integerValue ?? 0 if let object = instances[target] { @@ -95,11 +98,12 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } } else if let member = typeInfo[opcode] where member.isProperty { // Update property - object.updateNativeProperty(opcode, withValue: body["$operand"]) + object.updateNativeProperty(opcode, withValue: body["$operand"] ?? NSNull()) } else if let member = typeInfo[opcode] where member.isMethod { // Invoke method - let args = body["$operand"] as? [AnyObject] - object.invokeNativeMethod(opcode, withArguments: args) + if let args = (body["$operand"] ?? []) as? [AnyObject] { + object.invokeNativeMethod(opcode, withArguments: args) + } // else malformatted operand } // else Unknown opcode } else if opcode == "+" { // Create instance diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index cea1a5d..6e3a9b6 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -40,41 +40,40 @@ public class XWVObject : NSObject { } deinit { - var script: String? + let script: String if reference == 0 { script = "delete \(namespace)" } else if origin != nil { script = "\(origin.namespace).$releaseObject(\(reference))" + } else { + assertionFailure() + return } - if let webView = webView, let script = script { - webView.evaluateJavaScript(script, completionHandler: nil) - } + webView?.evaluateJavaScript(script, completionHandler: nil) } // Evaluate JavaScript expression - public func evaluateExpression(exp: String, error: NSErrorPointer = nil) -> AnyObject? { - if let result: AnyObject = webView?.evaluateJavaScript(scriptForRetaining(exp), error: error) { - return wrapScriptObject(result) - } - return nil + public func evaluateExpression(expression: String) throws -> AnyObject? { + return wrapScriptObject(try webView?.evaluateJavaScript(scriptForRetaining(expression))) } - public func evaluateExpression(exp: String, onSuccess handler: ((AnyObject!)->Void)?) { - if handler == nil { - webView?.evaluateJavaScript(exp, completionHandler: nil) - } else { - webView?.evaluateJavaScript(scriptForRetaining(exp)) { - [weak self](result: AnyObject?, error: NSError?)->Void in - if self != nil && result != nil { - handler!(self!.wrapScriptObject(result!)) - } - } + public func evaluateExpression(expression: String, error: NSErrorPointer) -> AnyObject? { + return wrapScriptObject(webView?.evaluateJavaScript(expression, error: error)) + } + public func evaluateExpression(expression: String, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + guard let completionHandler = completionHandler else { + webView?.evaluateJavaScript(expression, completionHandler: nil) + return + } + webView?.evaluateJavaScript(scriptForRetaining(expression)) { + [weak self](result: AnyObject?, error: NSError?)->Void in + completionHandler(self?.wrapScriptObject(result) ?? result, error) } } private func scriptForRetaining(script: String) -> String { return origin != nil ? "\(origin.namespace).$retainObject(\(script))" : script } - func wrapScriptObject(object: AnyObject?) -> AnyObject { + func wrapScriptObject(object: AnyObject!) -> AnyObject! { if let dict = object as? [String: AnyObject] where dict["$sig"] as? NSNumber == 0x5857574F { if let num = dict["$ref"] as? NSNumber { return XWVScriptObject(reference: num.integerValue, channel: channel, origin: self) @@ -82,7 +81,7 @@ public class XWVObject : NSObject { return XWVScriptObject(namespace: namespace, channel: channel, origin: self) } } - return object ?? NSNull() + return object } func serialize(object: AnyObject?) -> String { diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index edb39a8..fa70eeb 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -18,50 +18,61 @@ import Foundation public class XWVScriptObject : XWVObject { // JavaScript object operations - public func construct(arguments arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { + public func construct(arguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { let exp = "new " + scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + evaluateExpression(exp, completionHandler: completionHandler) } - public func call(arguments arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { + public func call(arguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { let exp = scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + evaluateExpression(exp, completionHandler: completionHandler) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { + public func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { let exp = scriptForCallingMethod(name, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + evaluateExpression(exp, completionHandler: completionHandler) } - public func construct(arguments arguments: [AnyObject]?) -> AnyObject! { - return evaluateExpression("new \(scriptForCallingMethod(nil, arguments: arguments))") + public func construct(arguments arguments: [AnyObject]?) throws -> AnyObject { + let exp = "new \(scriptForCallingMethod(nil, arguments: arguments))" + guard let result = try evaluateExpression(exp) else { + let code = WKErrorCode.JavaScriptExceptionOccurred.rawValue + throw NSError(domain: WKErrorDomain, code: code, userInfo: nil) + } + return result + } + public func call(arguments arguments: [AnyObject]?) throws -> AnyObject! { + return try evaluateExpression(scriptForCallingMethod(nil, arguments: arguments)) + } + public func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject! { + return try evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) } - public func call(arguments arguments: [AnyObject]?) -> AnyObject! { - return evaluateExpression(scriptForCallingMethod(nil, arguments: arguments)) + public func call(arguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject! { + return evaluateExpression(scriptForCallingMethod(nil, arguments: arguments), error: error) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?) -> AnyObject! { - return evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) + public func callMethod(name: String, withArguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject! { + return evaluateExpression(scriptForCallingMethod(name, arguments: arguments), error: error) } public func defineProperty(name: String, descriptor: [String:AnyObject]) -> AnyObject? { let exp = "Object.defineProperty(\(namespace), \(name), \(serialize(descriptor)))" - return evaluateExpression(exp) + return try! evaluateExpression(exp) } public func deleteProperty(name: String) -> Bool { - let result: AnyObject? = evaluateExpression("delete \(scriptForFetchingProperty(name))") + let result: AnyObject? = try! evaluateExpression("delete \(scriptForFetchingProperty(name))") return (result as? NSNumber)?.boolValue ?? false } public func hasProperty(name: String) -> Bool { - let result: AnyObject? = evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") + let result: AnyObject? = try! evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") return (result as? NSNumber)?.boolValue ?? false } public func value(forProperty name: String) -> AnyObject? { - return evaluateExpression(scriptForFetchingProperty(name)) + return try! evaluateExpression(scriptForFetchingProperty(name)) } public func setValue(value: AnyObject?, forProperty name:String) { webView?.evaluateJavaScript(scriptForUpdatingProperty(name, value: value), completionHandler: nil) } public func value(atIndex index: UInt) -> AnyObject? { - return evaluateExpression("\(namespace)[\(index)]") + return try! evaluateExpression("\(namespace)[\(index)]") } public func setValue(value: AnyObject?, atIndex index: UInt) { webView?.evaluateJavaScript("\(namespace)[\(index)] = \(serialize(value))", completionHandler: nil) diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index e37b0dd..123ccd8 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -42,17 +42,18 @@ extension WKWebView { extension WKWebView { // Synchronized evaluateJavaScript - public func evaluateJavaScript(script: String, error: NSErrorPointer = nil) -> AnyObject? { + // It returns nil if script is a statement or its result is undefined. + // So, Swift cannot map the throwing method to Objective-C method. + public func evaluateJavaScript(script: String) throws -> AnyObject? { var result: AnyObject? + var error: NSError? var done = false let timeout = 3.0 if NSThread.isMainThread() { evaluateJavaScript(script) { (obj: AnyObject?, err: NSError?)->Void in result = obj - if error != nil { - error.memory = err - } + error = err done = true } while !done { @@ -69,9 +70,7 @@ extension WKWebView { (obj: AnyObject?, err: NSError?)->Void in condition.lock() result = obj - if error != nil { - error.memory = err - } + error = err done = true condition.signal() condition.unlock() @@ -85,11 +84,25 @@ extension WKWebView { } condition.unlock() } + if error != nil { throw error! } if !done { print(" ERROR: Timeout to evaluate script.") } return result } + + // Wrapper method of synchronized evaluateJavaScript for Objective-C + public func evaluateJavaScript(script: String, error: NSErrorPointer) -> AnyObject? { + var result: AnyObject? + var err: NSError? + do { + result = try evaluateJavaScript(script) + } catch let e as NSError { + err = e + } + if error != nil { error.memory = err } + return result + } } extension WKWebView { diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index aa89232..b5b29cd 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -40,7 +40,7 @@ class ConstructorPlugin : XWVTestCase { class Plugin2 : NSObject, XWVScripting { override init() {} init(expectation: AnyObject?) { - (expectation as? XWVScriptObject)?.callMethod("fulfill", withArguments: nil, resultHandler: nil) + (expectation as? XWVScriptObject)?.callMethod("fulfill", withArguments: nil, completionHandler: nil) } class func scriptNameForSelector(selector: Selector) -> String? { return selector == Selector("initWithExpectation:") ? "" : nil diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index 062c558..5924d83 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -36,10 +36,10 @@ class ObjectPlugin : XWVTestCase { } } func method(callback callback: XWVScriptObject) { - callback.call(arguments: nil, resultHandler: nil) + callback.call(arguments: nil, completionHandler: nil) } func method(promiseObject promiseObject: XWVScriptObject) { - promiseObject.callMethod("resolve", withArguments: nil, resultHandler: nil) + promiseObject.callMethod("resolve", withArguments: nil, completionHandler: nil) } func windowObject() { if let test: AnyObject = scriptObject?.windowObject["test"] { @@ -125,7 +125,7 @@ class ObjectPlugin : XWVTestCase { let plugin = Plugin(expectation: expectation) loadPlugin(plugin as NSObject, namespace: namespace, script: "") { (webView)->Void in - plugin.scriptObject?.callMethod("method", withArguments: nil) + try! plugin.scriptObject?.callMethod("method", withArguments: nil) return } waitForExpectationsWithTimeout(2, handler: nil) From 516994699dff6523f132ec9a703daa4a341a1293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 09:24:42 -0300 Subject: [PATCH 05/44] Add 'self' to reference instances outside blocks --- XWebView/XWVBindingObject.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index e1f1056..e1a6658 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -78,7 +78,7 @@ class XWVBindingObject : XWVScriptObject { } if channel.queue != nil { dispatch_async(channel.queue) { - XWVInvocation(target: object).call(selector, withObjects: args) + XWVInvocation(target: self.object).call(selector, withObjects: args) } } else { // FIXME: Add NSThread support back while migrate to Swift 2.0 @@ -91,7 +91,7 @@ class XWVBindingObject : XWVScriptObject { let val: AnyObject = wrapScriptObject(value) if channel.queue != nil { dispatch_async(channel.queue) { - XWVInvocation(target: object).call(setter, withObjects: [val]) + XWVInvocation(target: self.object).call(setter, withObjects: [val]) } } else { // FIXME: Add NSThread support back while migrate to Swift 2.0 From c0fde5395068f3540fa3635cb982dd2251d7ef75 Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 21 Nov 2015 02:32:26 +0800 Subject: [PATCH 06/44] Improve XWVInvocation class Asynchronized calling support Recognize selector family, avoid potential leaks Unit test on 32-bit OS --- XWebView/XWVBindingObject.swift | 121 +++++---- XWebView/XWVChannel.swift | 2 +- XWebView/XWVInvocation.swift | 376 ++++++++++++++++---------- XWebViewTests/XWVInvocationTest.swift | 48 +++- 4 files changed, 335 insertions(+), 212 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index e1a6658..d45ca6d 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -18,14 +18,13 @@ import Foundation import ObjectiveC class XWVBindingObject : XWVScriptObject { - let key = unsafeAddressOf(XWVScriptObject) - var object: AnyObject! + private let key = unsafeAddressOf(XWVScriptObject) + private var proxy: XWVInvocation! + final var plugin: AnyObject { return proxy.target } init(namespace: String, channel: XWVChannel, object: AnyObject) { super.init(namespace: namespace, channel: channel, origin: nil) - self.object = object - objc_setAssociatedObject(object, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - startKVO() + proxy = bindObject(object) } init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { @@ -36,74 +35,96 @@ class XWVBindingObject : XWVScriptObject { return nil } - var args = arguments?.map(wrapScriptObject) ?? [] + var arguments = arguments?.map(wrapScriptObject) ?? [] var promise: XWVScriptObject? - if arity == Int32(args.count) - 1 || arity < 0 { - promise = args.last as? XWVScriptObject - args.removeLast() + if arity == Int32(arguments.count) - 1 || arity < 0 { + promise = arguments.last as? XWVScriptObject + arguments.removeLast() } if selector == "initByScriptWithArguments:" { - args = [args] + arguments = [arguments] } - object = XWVInvocation(target: channel.typeInfo.plugin).call(Selector("alloc")) as? AnyObject - object = XWVInvocation(target: object).call(selector, withObjects: args) - objc_setAssociatedObject(object, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - startKVO() + + let cls: AnyClass = channel.typeInfo.plugin + let args: [Any!] = arguments.map{ $0 !== NSNull() ? ($0 as Any) : nil } + guard let instance = XWVInvocation.construct(cls, initializer: selector, withArguments: args) else { + print(" ERROR: Create plugin instance failed") + return nil + } + + proxy = bindObject(instance) syncProperties() promise?.callMethod("resolve", withArguments: [self], completionHandler: nil) } + + deinit { + (plugin as? XWVScripting)?.finalizeForScript?() + unbindObject(plugin) + } + + private func bindObject(object: AnyObject) -> XWVInvocation { + let option: XWVInvocation.Option + if channel.queue != nil { + option = .Queue(queue: channel.queue) + } else if channel.thread != nil { + option = .Thread(thread: channel.thread) + } else { + option = .None + } + let proxy = XWVInvocation(target: object, option: option) + + objc_setAssociatedObject(object, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + + // Start KVO + if object is NSObject { + for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { + let key = member.getter!.description + object.addObserver(self, forKeyPath: key, options: NSKeyValueObservingOptions.New, context: nil) + } + } + return proxy + } + private func unbindObject(object: AnyObject) { + objc_setAssociatedObject(object, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + + // Stop KVO + if object is NSObject { + for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { + let key = member.getter!.description + object.removeObserver(self, forKeyPath: key, context: nil) + } + } + } private func syncProperties() { var script = "" for (name, member) in channel.typeInfo.filter({ $1.isProperty }) { - let val: AnyObject! = XWVInvocation(target: object).call(member.getter!, withObjects: nil) + let val: AnyObject! = proxy.call(member.getter!, withObjects: nil) script += "\(namespace).$properties['\(name)'] = \(serialize(val));\n" } webView?.evaluateJavaScript(script, completionHandler: nil) } - deinit { - if (object as? XWVScripting)?.finalizeForScript != nil { - XWVInvocation(target: object)[Selector("finalizeForScript")]() - } - objc_setAssociatedObject(object, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - stopKVO() - } - // Dispatch operation to plugin object func invokeNativeMethod(name: String, withArguments arguments: [AnyObject]) { if let selector = channel.typeInfo[name]?.selector { var args = arguments.map(wrapScriptObject) - if object is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { + if plugin is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { args = [args]; } - if channel.queue != nil { - dispatch_async(channel.queue) { - XWVInvocation(target: self.object).call(selector, withObjects: args) - } - } else { - // FIXME: Add NSThread support back while migrate to Swift 2.0 - XWVInvocation(target: object).call(selector, withObjects: args) - } + proxy.asyncCall(selector, withObjects: args) } } func updateNativeProperty(name: String, withValue value: AnyObject) { if let setter = channel.typeInfo[name]?.setter { let val: AnyObject = wrapScriptObject(value) - if channel.queue != nil { - dispatch_async(channel.queue) { - XWVInvocation(target: self.object).call(setter, withObjects: [val]) - } - } else { - // FIXME: Add NSThread support back while migrate to Swift 2.0 - XWVInvocation(target: self.object)[name] = val - } + proxy.asyncCall(setter, withObjects: [val]) } } // override methods of XWVScriptObject override func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { if let selector = channel.typeInfo[name]?.selector { - let result: AnyObject! = XWVInvocation(target: object).call(selector, withObjects: arguments) + let result: AnyObject! = proxy.call(selector, withObjects: arguments) completionHandler?(result, nil) } else { super.callMethod(name, withArguments: arguments, completionHandler: completionHandler) @@ -111,19 +132,19 @@ class XWVBindingObject : XWVScriptObject { } override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject! { if let selector = channel.typeInfo[name]?.selector { - return XWVInvocation(target: object).call(selector, withObjects: arguments) + return proxy.call(selector, withObjects: arguments) } return try super.callMethod(name, withArguments: arguments) } override func value(forProperty name: String) -> AnyObject? { if let getter = channel.typeInfo[name]?.getter { - return XWVInvocation(target: object).call(getter, withObjects: nil) + return proxy.call(getter, withObjects: nil) } return super.value(forProperty: name) } override func setValue(value: AnyObject?, forProperty name: String) { if channel.typeInfo[name]?.setter != nil { - XWVInvocation(target: object)[name] = value + proxy[name] = value } else { assert(channel.typeInfo[name] == nil, "Property '\(name)' is readonly") super.setValue(value, forProperty: name) @@ -142,18 +163,6 @@ class XWVBindingObject : XWVScriptObject { let script = "\(namespace).$properties['\(prop)'] = \(serialize(change?[NSKeyValueChangeNewKey]))" webView.evaluateJavaScript(script, completionHandler: nil) } - private func startKVO() { - guard object is NSObject else { return } - for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { - object.addObserver(self, forKeyPath: member.getter!.description, options: NSKeyValueObservingOptions.New, context: nil) - } - } - private func stopKVO() { - guard object is NSObject else { return } - for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { - object.removeObserver(self, forKeyPath: member.getter!.description, context: nil) - } - } } public extension NSObject { diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 39c0782..6a5082c 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -111,7 +111,7 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { let namespace = "\(instances[0]!.namespace)[\(target)]" instances[target] = XWVBindingObject(namespace: namespace, channel: self, arguments: args) } // else Unknown opcode - } else if let obj = instances[0]!.object as? WKScriptMessageHandler { + } else if let obj = instances[0]!.plugin as? WKScriptMessageHandler { // Plugin claims for raw messages obj.userContentController(userContentController, didReceiveScriptMessage: message) } else { diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index 291936a..abfd983 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -17,91 +17,66 @@ import Foundation import ObjectiveC -private let _NSInvocation: AnyClass = NSClassFromString("NSInvocation")! -private let _NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! - public class XWVInvocation { public final let target: AnyObject + private let queue: dispatch_queue_t? + private let thread: NSThread? - public init(target: AnyObject) { - self.target = target + public enum Option { + case None + case Queue(queue: dispatch_queue_t) + case Thread(thread: NSThread) } - public func call(selector: Selector, withArguments arguments: [Any!]) -> Any! { - let method = class_getInstanceMethod(target.dynamicType, selector) - if method == nil { - // TODO: supports forwordingTargetForSelector: of NSObject? - (target as? NSObject)?.doesNotRecognizeSelector(selector) - // Not an NSObject, mimic the behavior of NSObject - let reason = "-[\(target.dynamicType) \(selector)]: unrecognized selector sent to instance \(unsafeAddressOf(target))" - withVaList([reason]) { NSLogv("%@", $0) } - NSException(name: NSInvalidArgumentException, reason: reason, userInfo: nil).raise() - } - - let sig = _NSMethodSignature.signatureWithObjCTypes(method_getTypeEncoding(method))! - let inv = _NSInvocation.invocationWithMethodSignature(sig) - - // Setup arguments - assert(arguments.count + 2 <= Int(sig.numberOfArguments), "Too many arguments for calling -[\(target.dynamicType) \(selector)]") - var args = [[UInt]](count: arguments.count, repeatedValue: []) - for var i = 0; i < arguments.count; ++i { - let type = sig.getArgumentTypeAtIndex(i + 2) - let typeChar = Character(UnicodeScalar(UInt8(type[0]))) - - // Convert argument type to adapte requirement of method. - // Firstly, convert argument to appropriate object type. - var argument: Any! = self.dynamicType.convertToObjectFromAnyValue(arguments[i]) - assert(argument != nil || arguments[i] == nil, "Can't convert '\(arguments[i].dynamicType)' to object type") - if typeChar != "@", let obj: AnyObject = argument as? AnyObject { - // Convert back to scalar type as method requires. - argument = self.dynamicType.convertFromObject(obj, toObjCType: type) - } - - if typeChar == "f", let float = argument as? Float { - // Float type shouldn't be promoted to double if it is not variadic. - args[i] = [ UInt(unsafeBitCast(float, UInt32.self)) ] - } else if let val = argument as? CVarArgType { - // Scalar(except float), pointer and Objective-C object types - args[i] = val._cVarArgEncoding.map{ UInt(bitPattern: $0) } - } else if let obj: AnyObject = argument as? AnyObject { - // Pure swift object type - args[i] = [ unsafeBitCast(unsafeAddressOf(obj), UInt.self) ] - } else { - // Nil or unsupported type - assert(argument == nil, "Unsupported argument type '\(String(UTF8String: type))'") - var align: Int = 0 - NSGetSizeAndAlignment(sig.getArgumentTypeAtIndex(i), nil, &align) - args[i] = [UInt](count: align / sizeof(UInt), repeatedValue: 0) - } - args[i].withUnsafeBufferPointer { - inv.setArgument(UnsafeMutablePointer($0.baseAddress), atIndex: i + 2) - } + public init(target: AnyObject, option: Option = .None) { + self.target = target + switch option { + case .None: + self.queue = nil + self.thread = nil + case .Queue(let queue): + self.queue = queue + self.thread = nil + case .Thread(let thread): + self.thread = thread + self.queue = nil } + } - inv.selector = selector - inv.invokeWithTarget(target) - if sig.methodReturnLength == 0 { return Void() } - - // Fetch the return value - // TODO: Methods with 'ns_returns_retained' attribute cause leak of returned object. - let buffer = UnsafeMutablePointer.alloc(sig.methodReturnLength) - inv.getReturnValue(buffer) - defer { - buffer.destroy() - buffer.dealloc(sig.methodReturnLength) + public class func construct(`class`: AnyClass, initializer: Selector = Selector("init"), withArguments arguments: [Any!] = []) -> AnyObject? { + let alloc = Selector("alloc") + guard let obj = invoke(`class`, selector: alloc, withArguments: []) as? AnyObject else { + return nil } - return bitCast(buffer, toObjCType: sig.methodReturnType) + return invoke(obj, selector: initializer, withArguments: arguments) as? AnyObject } - public func call(selector: Selector, withArguments arguments: Any!...) -> Any! { - return call(selector, withArguments: arguments) + public func call(selector: Selector, withArguments arguments: [Any!] = []) -> Any! { + return invoke(target, selector: selector, withArguments: arguments, onThread: thread) + } + // No callback support, so return value is expected to lose. + public func asyncCall(selector: Selector, withArguments arguments: [Any!] = []) { + if queue == nil { + invoke(target, selector: selector, withArguments: arguments, onThread: thread, waitUntilDone: false) + } else { + dispatch_async(queue!) { + invoke(self.target, selector: selector, withArguments: arguments) + } + } } - // Helper for Objective-C, accept ObjC 'id' instead of Swift 'Any' type for in/out parameters . + // Objective-C interface + // These methods accept parameters in ObjC 'id' instead of Swift 'Any' type. + // Meanwhile, arguments which are NSNull will be converted to nil before calling. + // Return value in scalar type will be converted to object type if feasible. @objc public func call(selector: Selector, withObjects objects: [AnyObject]?) -> AnyObject! { let args: [Any!] = objects?.map{ $0 !== NSNull() ? ($0 as Any) : nil } ?? [] let result = call(selector, withArguments: args) - return self.dynamicType.convertToObjectFromAnyValue(result) + return castToObjectFromAny(result) + } + @objc public func asyncCall(selector: Selector, withObjects objects: [AnyObject]?) { + let args: [Any!] = objects?.map{ $0 !== NSNull() ? ($0 as Any) : nil } ?? [] + call(selector, withArguments: args) } // Syntactic sugar for calling method @@ -126,7 +101,7 @@ extension XWVInvocation { (getterOfName(name) == nil ? "does not exist" : "is readonly")) assert(!(value is Void)) if setter != Selector() { - call(setter, withArguments: value) + call(setter, withArguments: [value]) } } @@ -169,86 +144,169 @@ extension XWVInvocation { } } -extension XWVInvocation { - // Type casting and conversion, reference: - // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html - // Cast bits to specified Objective-C type - private func bitCast(buffer: UnsafePointer, toObjCType type: UnsafePointer) -> Any? { - switch Character(UnicodeScalar(UInt8(type[0]))) { - case "c": return UnsafePointer(buffer).memory - case "i": return UnsafePointer(buffer).memory - case "s": return UnsafePointer(buffer).memory - case "l": return UnsafePointer(buffer).memory - case "q": return UnsafePointer(buffer).memory - case "C": return UnsafePointer(buffer).memory - case "I": return UnsafePointer(buffer).memory - case "S": return UnsafePointer(buffer).memory - case "L": return UnsafePointer(buffer).memory - case "Q": return UnsafePointer(buffer).memory - case "f": return UnsafePointer(buffer).memory - case "d": return UnsafePointer(buffer).memory - case "B": return UnsafePointer(buffer).memory - case "v": assertionFailure("Why cast to Void type?") - case "*": return UnsafePointer(buffer) - case "@": return UnsafePointer(buffer).memory - case "#": return UnsafePointer(buffer).memory - case ":": return UnsafePointer(buffer).memory - case "^", "?": return COpaquePointer(buffer) - default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") - } - return Void() +// Notice: The target method must strictly obey the Cocoa convention. +// Do NOT call method with explicit family control or parameter attribute of ARC. +// See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html +private let _NSInvocation: AnyClass = NSClassFromString("NSInvocation")! +private let _NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! +public func invoke(target: AnyObject, selector: Selector, withArguments arguments: [Any!], onThread thread: NSThread? = nil, waitUntilDone wait: Bool = true) -> Any! { + let method = class_getInstanceMethod(target.dynamicType, selector) + if method == nil { + // TODO: supports forwordingTargetForSelector: of NSObject? + (target as? NSObject)?.doesNotRecognizeSelector(selector) + // Not an NSObject, mimic the behavior of NSObject + let reason = "-[\(target.dynamicType) \(selector)]: unrecognized selector sent to instance \(unsafeAddressOf(target))" + withVaList([reason]) { NSLogv("%@", $0) } + NSException(name: NSInvalidArgumentException, reason: reason, userInfo: nil).raise() } - // Convert AnyObject to appropriate Objective-C type - private class func convertFromObject(object: AnyObject, toObjCType type: UnsafePointer) -> Any! { - let num = object as? NSNumber - switch Character(UnicodeScalar(UInt8(type[0]))) { - case "c": return num?.charValue - case "i": return num?.intValue - case "s": return num?.shortValue - case "l": return num?.intValue - case "q": return num?.longLongValue - case "C": return num?.unsignedCharValue - case "I": return num?.unsignedIntValue - case "S": return num?.unsignedShortValue - case "L": return num?.unsignedIntValue - case "Q": return num?.unsignedLongLongValue - case "f": return num?.floatValue - case "d": return num?.doubleValue - case "B": return num?.boolValue - case "v": return Void() - case "*": return (object as? String)?.nulTerminatedUTF8.withUnsafeBufferPointer{ COpaquePointer($0.baseAddress) } - case ":": return object is String ? Selector(object as! String) : Selector() - case "@": return object - case "#": return object as? AnyClass - case "^", "?": return (object as? NSValue)?.pointerValue - default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") + let sig = _NSMethodSignature.signatureWithObjCTypes(method_getTypeEncoding(method))! + let inv = _NSInvocation.invocationWithMethodSignature(sig) + + // Setup arguments + assert(arguments.count + 2 <= Int(sig.numberOfArguments), "Too many arguments for calling -[\(target.dynamicType) \(selector)]") + var args = [[Int]](count: arguments.count, repeatedValue: []) + for var i = 0; i < arguments.count; ++i { + let type = sig.getArgumentTypeAtIndex(i + 2) + let typeChar = Character(UnicodeScalar(UInt8(type[0]))) + + // Convert argument type to adapte requirement of method. + // Firstly, convert argument to appropriate object type. + var argument: Any! = castToObjectFromAny(arguments[i]) + assert(argument != nil || arguments[i] == nil, "Can't convert '\(arguments[i].dynamicType)' to object type") + if typeChar != "@", let obj: AnyObject = argument as? AnyObject { + // Convert back to scalar type as method requires. + argument = castToAnyFromObject(obj, withObjCType: type) + } + + if typeChar == "f", let float = argument as? Float { + // Float type shouldn't be promoted to double if it is not variadic. + args[i] = [ Int(unsafeBitCast(float, Int32.self)) ] + } else if let val = argument as? CVarArgType { + // Scalar(except float), pointer and Objective-C object types + args[i] = val._cVarArgEncoding + } else if let obj: AnyObject = argument as? AnyObject { + // Pure swift object type + args[i] = [ unsafeBitCast(unsafeAddressOf(obj), Int.self) ] + } else { + // Nil or unsupported type + assert(argument == nil, "Unsupported argument type '\(String(UTF8String: type))'") + var align: Int = 0 + NSGetSizeAndAlignment(sig.getArgumentTypeAtIndex(i), nil, &align) + args[i] = [Int](count: align / sizeof(Int), repeatedValue: 0) + } + args[i].withUnsafeBufferPointer { + inv.setArgument(UnsafeMutablePointer($0.baseAddress), atIndex: i + 2) } - return nil } - // Convert Any value to appropriate Objective-C object - public class func convertToObjectFromAnyValue(value: Any!) -> AnyObject! { - if value == nil || value is AnyObject { - // Some scalar types (Int, UInt, Bool, Float and Double) can be converted automatically by runtime. - return value as? AnyObject + if selector.family == .init_ { + // Self should be consumed for method belongs to init famlily + _ = Unmanaged.passRetained(target) + } + inv.selector = selector + + if thread == nil || (thread == NSThread.currentThread() && wait) { + inv.invokeWithTarget(target) + } else { + let selector = Selector("invokeWithTarget:") + if !wait { inv.retainArguments() } + inv.performSelector(selector, onThread: thread!, withObject: target, waitUntilDone: wait) + guard wait else { return Void() } + } + if sig.methodReturnLength == 0 { return Void() } + + // Fetch the return value + let buffer = UnsafeMutablePointer.alloc(sig.methodReturnLength) + inv.getReturnValue(buffer) + defer { + if sig.methodReturnType[0] == 0x40 && selector.returnsRetained { + // To balance the retained return value + Unmanaged.passUnretained(UnsafePointer(buffer).memory).release() } + buffer.dealloc(sig.methodReturnLength) + } + return castToAnyFromBytes(buffer, withObjCType: sig.methodReturnType) +} + + +// Convert byte array to specified Objective-C type +// See: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html +private func castToAnyFromBytes(bytes: UnsafePointer, withObjCType type: UnsafePointer) -> Any! { + switch Character(UnicodeScalar(UInt8(type[0]))) { + case "c": return UnsafePointer(bytes).memory + case "i": return UnsafePointer(bytes).memory + case "s": return UnsafePointer(bytes).memory + case "l": return UnsafePointer(bytes).memory + case "q": return UnsafePointer(bytes).memory + case "C": return UnsafePointer(bytes).memory + case "I": return UnsafePointer(bytes).memory + case "S": return UnsafePointer(bytes).memory + case "L": return UnsafePointer(bytes).memory + case "Q": return UnsafePointer(bytes).memory + case "f": return UnsafePointer(bytes).memory + case "d": return UnsafePointer(bytes).memory + case "B": return UnsafePointer(bytes).memory + case "v": assertionFailure("Why cast to Void type?") + case "*": return UnsafePointer(bytes) + case "@": return UnsafePointer(bytes).memory + case "#": return UnsafePointer(bytes).memory + case ":": return UnsafePointer(bytes).memory + case "^": return UnsafePointer(bytes).memory + default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") + } + return Void() +} - if let i8 = value as? Int8 { return NSNumber(char: i8) } else - if let i16 = value as? Int16 { return NSNumber(short: i16) } else - if let i32 = value as? Int32 { return NSNumber(int: i32) } else - if let i64 = value as? Int64 { return NSNumber(longLong: i64) } else - if let u8 = value as? UInt8 { return NSNumber(unsignedChar: u8) } else - if let u16 = value as? UInt16 { return NSNumber(unsignedShort: u16) } else - if let u32 = value as? UInt32 { return NSNumber(unsignedInt: u32) } else - if let u64 = value as? UInt64 { return NSNumber(unsignedLongLong: u64) } else - if let us = value as? UnicodeScalar { return NSNumber(unsignedInt: us.value) } else - if let sel = value as? Selector { return sel.description } else - if let ptr = value as? COpaquePointer { return NSValue(pointer: UnsafePointer(ptr)) } - //assertionFailure("Can't convert '\(value.dynamicType)' to AnyObject") - return nil +// Convert AnyObject to specified Objective-C type +private func castToAnyFromObject(object: AnyObject, withObjCType type: UnsafePointer) -> Any! { + let num = object as? NSNumber + switch Character(UnicodeScalar(UInt8(type[0]))) { + case "c": return num?.charValue + case "i": return num?.intValue + case "s": return num?.shortValue + case "l": return num?.intValue + case "q": return num?.longLongValue + case "C": return num?.unsignedCharValue + case "I": return num?.unsignedIntValue + case "S": return num?.unsignedShortValue + case "L": return num?.unsignedIntValue + case "Q": return num?.unsignedLongLongValue + case "f": return num?.floatValue + case "d": return num?.doubleValue + case "B": return num?.boolValue + case "v": return Void() + case "*": return (object as? String)?.nulTerminatedUTF8.withUnsafeBufferPointer{ COpaquePointer($0.baseAddress) } + case ":": return object is String ? Selector(object as! String) : Selector() + case "@": return object + case "#": return object as? AnyClass + case "^": return (object as? NSValue)?.pointerValue + default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") } + return nil +} + +// Convert Any value to appropriate Objective-C object +public func castToObjectFromAny(value: Any!) -> AnyObject! { + if value == nil || value is AnyObject { + // Some scalar types (Int, UInt, Bool, Float and Double) can be converted automatically by runtime. + return value as? AnyObject + } + + if let v = value as? Int8 { return NSNumber(char: v) } else + if let v = value as? Int16 { return NSNumber(short: v) } else + if let v = value as? Int32 { return NSNumber(int: v) } else + if let v = value as? Int64 { return NSNumber(longLong: v) } else + if let v = value as? UInt8 { return NSNumber(unsignedChar: v) } else + if let v = value as? UInt16 { return NSNumber(unsignedShort: v) } else + if let v = value as? UInt32 { return NSNumber(unsignedInt: v) } else + if let v = value as? UInt64 { return NSNumber(unsignedLongLong: v) } else + if let v = value as? UnicodeScalar { return NSNumber(unsignedInt: v.value) } else + if let s = value as? Selector { return s.description } else + if let p = value as? COpaquePointer { return NSValue(pointer: UnsafePointer(p)) } + //assertionFailure("Can't convert '\(value.dynamicType)' to AnyObject") + return nil } // Additional Swift types which can be represented in C type. @@ -267,3 +325,37 @@ extension Selector: CVarArgType { return [ unsafeBitCast(self, Int.self) ] } } + +private extension Selector { + enum Family : Int8 { + case none = 0 + case alloc = 97 + case copy = 99 + case mutableCopy = 109 + case init_ = 105 + case new = 110 + } + static var prefixes : [[CChar]] = [ + /* alloc */ [97, 108, 108, 111, 99], + /* copy */ [99, 111, 112, 121], + /* mutableCopy */ [109, 117, 116, 97, 98, 108, 101, 67, 111, 112, 121], + /* init */ [105, 110, 105, 116], + /* new */ [110, 101, 119] + ] + var family: Family { + // See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#id34 + var s = unsafeBitCast(self, UnsafePointer.self) + while s.memory == 0x5f { ++s } // skip underscore + for p in Selector.prefixes { + let lowercase: Range = 97...122 + let l = p.count + if strncmp(s, p, l) == 0 && !lowercase.contains(s.advancedBy(l).memory) { + return Family(rawValue: s.memory)! + } + } + return .none + } + var returnsRetained: Bool { + return family != .none + } +} diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index 3271d46..d2f046e 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -18,9 +18,9 @@ import XCTest import XWebView class InvocationTarget: NSObject { - class ObjectForLeakTest { + class LeakTest { let expectation: XCTestExpectation - init(expectation: XCTestExpectation) { + @objc init(expectation: XCTestExpectation) { self.expectation = expectation } deinit { @@ -53,8 +53,8 @@ class InvocationTarget: NSObject { func concat(a: String, _ b: String) -> String { return a + b } func convert(num: NSNumber) -> Int { return num.integerValue } - func leak(expectation: XCTestExpectation) -> AnyObject { - return ObjectForLeakTest(expectation: expectation) + func _new(expectation: XCTestExpectation) -> AnyObject { + return LeakTest(expectation: expectation) } } @@ -70,15 +70,28 @@ class InvocationTests : XCTestCase { inv = nil } + #if arch(x86_64) || arch(arm64) + typealias XInt = Int64 + typealias XUInt = UInt64 + #else + typealias XInt = Int32 + typealias XUInt = UInt32 + #endif + func testMethods() { XCTAssertTrue(inv[Selector("dummy")]() is Void) + #if arch(x86_64) || arch(arm64) XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Bool == true) - XCTAssertTrue(inv[Selector("echoWithInt:")](Int(-11)) as? Int64 == -11) + #else + // http://stackoverflow.com/questions/26459754/bool-encoding-wrong-from-nsmethodsignature + XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Int8 == 1) + #endif + XCTAssertTrue(inv[Selector("echoWithInt:")](Int(-11)) as? XInt == -11) XCTAssertTrue(inv[Selector("echoWithInt8:")](Int8(-22)) as? Int8 == -22) XCTAssertTrue(inv[Selector("echoWithInt16:")](Int16(-33)) as? Int16 == -33) XCTAssertTrue(inv[Selector("echoWithInt32:")](Int32(-44)) as? Int32 == -44) XCTAssertTrue(inv[Selector("echoWithInt64:")](Int64(-55)) as? Int64 == -55) - XCTAssertTrue(inv[Selector("echoWithUint:")](UInt(11)) as? UInt64 == 11) + XCTAssertTrue(inv[Selector("echoWithUint:")](UInt(11)) as? XUInt == 11) XCTAssertTrue(inv[Selector("echoWithUint8:")](UInt8(22)) as? UInt8 == 22) XCTAssertTrue(inv[Selector("echoWithUint16:")](UInt16(33)) as? UInt16 == 33) XCTAssertTrue(inv[Selector("echoWithUint32:")](UInt32(44)) as? UInt32 == 44) @@ -92,22 +105,31 @@ class InvocationTests : XCTestCase { let cls = self.dynamicType XCTAssertTrue(inv[Selector("echoWithClass:")](cls) as? AnyClass === cls) - XCTAssertTrue(inv[Selector("convert:")](UInt8(12)) as? Int64 == 12) - XCTAssertTrue(inv[Selector("add::")](2, 3) as? Int64 == 5) + XCTAssertTrue(inv[Selector("convert:")](UInt8(12)) as? XInt == 12) + XCTAssertTrue(inv[Selector("add::")](2, 3) as? XInt == 5) XCTAssertTrue(inv[Selector("concat::")]("ab", "cd") as? String == "abcd") } func testProperty() { - XCTAssertTrue(inv["integer"] as? Int64 == 123) + XCTAssertTrue(inv["integer"] as? XInt == 123) inv["integer"] = 321 - XCTAssertTrue(inv["integer"] as? Int64 == 321) + XCTAssertTrue(inv["integer"] as? XInt == 321) + } + + func testLeak1() { + autoreleasepool { + let expectation = expectationWithDescription("leak") + let obj = inv[Selector("_new:")](expectation) as? InvocationTarget.LeakTest + XCTAssertEqual(expectation, obj!.expectation) + } + waitForExpectationsWithTimeout(2, handler: nil) } - func testLeak() { + func testLeak2() { autoreleasepool { let expectation = expectationWithDescription("leak") - let obj = inv.call(Selector("leak:"), withArguments: expectation) as! InvocationTarget.ObjectForLeakTest - XCTAssertEqual(expectation, obj.expectation) + let obj = XWVInvocation.construct(InvocationTarget.LeakTest.self, initializer: Selector("initWithExpectation:"), withArguments: [expectation]) as? InvocationTarget.LeakTest + XCTAssertEqual(expectation, obj!.expectation) } waitForExpectationsWithTimeout(2, handler: nil) } From be8efa2ffc3e7667bc7f5056e627107350f80bee Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 28 Nov 2015 16:42:14 +0800 Subject: [PATCH 07/44] Fixed a crash bug of sync call on specific thread --- XWebView/XWVInvocation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index abfd983..63ad013 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -211,7 +211,7 @@ public func invoke(target: AnyObject, selector: Selector, withArguments argument inv.invokeWithTarget(target) } else { let selector = Selector("invokeWithTarget:") - if !wait { inv.retainArguments() } + inv.retainArguments() inv.performSelector(selector, onThread: thread!, withObject: target, waitUntilDone: wait) guard wait else { return Void() } } From f603710db1d349392edf74df669fd4fb2d3f7839 Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 28 Nov 2015 16:54:44 +0800 Subject: [PATCH 08/44] Add logging facility base on ASL Replace all print() with log() Improve error handling and message --- XWebView.xcodeproj/project.pbxproj | 6 ++ XWebView/XWVBindingObject.swift | 6 +- XWebView/XWVChannel.swift | 43 +++++---- XWebView/XWVHttpConnection.swift | 3 +- XWebView/XWVHttpServer.swift | 16 +++- XWebView/XWVLogging.swift | 135 +++++++++++++++++++++++++++++ XWebView/XWVMetaObject.swift | 2 +- XWebView/XWVUserScript.swift | 2 +- XWebView/XWebView.swift | 22 +++-- 9 files changed, 203 insertions(+), 32 deletions(-) create mode 100644 XWebView/XWVLogging.swift diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 3450aab..f4bf3c9 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3379381AE2E298009124A4 /* XWVTestCase.swift */; }; EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; + EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; settings = {ASSET_TAGS = (); }; }; EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; settings = {ASSET_TAGS = (); }; }; @@ -71,6 +72,7 @@ EE3379381AE2E298009124A4 /* XWVTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVTestCase.swift; sourceTree = ""; }; EE33793D1AE56875009124A4 /* XWVScriptObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScriptObject.swift; path = XWebView/XWVScriptObject.swift; sourceTree = ""; }; EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; + EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVLogging.swift; path = XWebView/XWVLogging.swift; sourceTree = ""; }; EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; EE62683519FA323900EFC3F8 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE62691319FA52D100EFC3F8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = XWebView/Info.plist; sourceTree = ""; }; @@ -154,6 +156,7 @@ EE62683719FA323900EFC3F8 /* XWebView */ = { isa = PBXGroup; children = ( + EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */, EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */, EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */, EEDF305F1B6555B900A21659 /* XWVInvocation.swift */, @@ -319,6 +322,7 @@ EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */, EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */, EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */, + EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -458,6 +462,7 @@ INFOPLIST_FILE = XWebView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -474,6 +479,7 @@ INFOPLIST_FILE = XWebView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index d45ca6d..4fa0160 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -29,9 +29,10 @@ class XWVBindingObject : XWVScriptObject { init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { super.init(namespace: namespace, channel: channel, origin: nil) + let cls: AnyClass = channel.typeInfo.plugin let member = channel.typeInfo[""] guard member != nil, case .Initializer(let selector, let arity) = member! else { - print(" ERROR: Plugin is not a constructor") + log("!Plugin class \(cls) is not a constructor") return nil } @@ -45,10 +46,9 @@ class XWVBindingObject : XWVScriptObject { arguments = [arguments] } - let cls: AnyClass = channel.typeInfo.plugin let args: [Any!] = arguments.map{ $0 !== NSNull() ? ($0 as Any) : nil } guard let instance = XWVInvocation.construct(cls, initializer: selector, withArguments: args) else { - print(" ERROR: Create plugin instance failed") + log("!Failed to create instance for plugin class \(cls)") return nil } diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 6a5082c..2c43ed4 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -26,6 +26,10 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { private var instances = [Int: XWVBindingObject]() private var userScript: XWVUserScript? + private(set) var principal: XWVBindingObject { + get { return instances[0]! } + set { instances[0] = newValue } + } private class var sequenceNumber: UInt { struct sequence{ @@ -38,7 +42,7 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL) self.init(name: name, webView:webView, queue: queue) } - + public init(name: String?, webView: WKWebView, queue: dispatch_queue_t) { self.name = name ?? "\(XWVChannel.sequenceNumber)" self.webView = webView @@ -46,7 +50,7 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { thread = nil webView.prepareForPlugin() } - + public init(name: String?, webView: WKWebView, thread: NSThread) { self.name = name ?? "\(XWVChannel.sequenceNumber)" self.webView = webView @@ -56,9 +60,9 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } public func bindPlugin(object: AnyObject, toNamespace namespace: String) -> XWVScriptObject? { - assert(typeInfo == nil, " This channel already has a bound object") - guard let webView = webView else { return nil } - + assert(typeInfo == nil, "Channel \(name) is occupied by plugin object \(principal.plugin)") + guard typeInfo == nil, let webView = webView else { return nil } + webView.configuration.userContentController.addScriptMessageHandler(self, name: name) typeInfo = XWVMetaObject(plugin: object.dynamicType) let plugin = XWVBindingObject(namespace: namespace, channel: self, object: object) @@ -69,14 +73,20 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { forMainFrameOnly: true) userScript = XWVUserScript(webView: webView, script: script) - instances[0] = plugin + principal = plugin + log("+Plugin object \(object) is bound to \(namespace) with channel \(name)") return plugin as XWVScriptObject } public func unbind() { - assert(typeInfo != nil, " Error: can't unbind inexistent plugin.") + guard typeInfo != nil else { return } + let namespace = principal.namespace + let plugin = principal.plugin instances.removeAll(keepCapacity: false) webView?.configuration.userContentController.removeScriptMessageHandlerForName(name) + userScript = nil + //typeInfo = nil // FIXME: crash while instance deinit + log("+Plugin object \(plugin) is unbound from \(namespace)") } public func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { @@ -90,11 +100,11 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { if target == 0 { // Dispose plugin unbind() - print(" Plugin was disposed") - } else { + } else if let instance = instances.removeValueForKey(target) { // Dispose instance - let object = instances.removeValueForKey(target) - assert(object != nil, " Warning: bad instance id was received") + log("+Instance \(target) is unbound from \(instance.namespace)") + } else { + log("?Invalid instance id: \(target)") } } else if let member = typeInfo[opcode] where member.isProperty { // Update property @@ -104,19 +114,22 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { if let args = (body["$operand"] ?? []) as? [AnyObject] { object.invokeNativeMethod(opcode, withArguments: args) } // else malformatted operand - } // else Unknown opcode + } else { + log("?Invalid member name: \(opcode)") + } } else if opcode == "+" { // Create instance let args = body["$operand"] as? [AnyObject] - let namespace = "\(instances[0]!.namespace)[\(target)]" + let namespace = "\(principal.namespace)[\(target)]" instances[target] = XWVBindingObject(namespace: namespace, channel: self, arguments: args) + log("+Instance \(target) is bound to \(namespace)") } // else Unknown opcode - } else if let obj = instances[0]!.plugin as? WKScriptMessageHandler { + } else if let obj = principal.plugin as? WKScriptMessageHandler { // Plugin claims for raw messages obj.userContentController(userContentController, didReceiveScriptMessage: message) } else { // discard unknown message - print(" WARNING: Unknown message: \(message.body)") + log("-Unknown message: \(message.body)") } } diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift index b8870c3..be9009c 100644 --- a/XWebView/XWVHttpConnection.swift +++ b/XWebView/XWVHttpConnection.swift @@ -169,7 +169,8 @@ extension XWVHttpConnection : NSStreamDelegate { if bytesSent < 0 { fallthrough } case NSStreamEvent.ErrorOccurred: - print(" ERROR: " + (aStream.streamError?.localizedDescription ?? "Unknown")) + let error = aStream.streamError?.localizedDescription ?? "Unknown" + log("!HTTP connection error: \(error)") fallthrough case NSStreamEvent.EndEncountered: diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index f2d0baf..0e78cb7 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -59,7 +59,10 @@ class XWVHttpServer : NSObject { var context = CFSocketContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) let callbackType = CFSocketCallBackType.AcceptCallBack.rawValue socket = CFSocketCreate(nil, PF_INET, SOCK_STREAM, 0, callbackType, ServerAcceptCallBack, &context) - guard socket != nil else { return false } + guard socket != nil else { + log("!Failed to create socket") + return false + } var yes = UInt32(1) setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, &yes, UInt32(sizeof(UInt32))) @@ -72,7 +75,7 @@ class XWVHttpServer : NSObject { sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) let data = NSData(bytes: &sockaddr, length: sizeof(sockaddr_in)) guard CFSocketSetAddress(socket, data) == CFSocketError.Success else { - print(" ERROR: \(String(UTF8String: strerror(errno))!)") + log("!Failed to listen on port \(port) \(String(UTF8String: strerror(errno))!)") CFSocketInvalidate(socket) return false } @@ -99,7 +102,6 @@ class XWVHttpServer : NSObject { for _ in 0 ..< 100 { let port = in_port_t(arc4random() % (49152 - 1024) + 1024) if listenOnPort(port) { - print(" INFO: Listen on port: \(port)") self.port = port break } @@ -127,9 +129,12 @@ class XWVHttpServer : NSObject { func suspend(_: NSNotification!) { close() + log("+HTTP server is suspended") } func resume(_: NSNotification!) { - listenOnPort(port) + if listenOnPort(port) { + log("+HTTP server is resumed") + } } func serverLoop(_: AnyObject) { @@ -161,6 +166,7 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { if request.URL == nil { // Bad request statusCode = 400 + log("?Bad request") } else if request.HTTPMethod == "GET" || request.HTTPMethod == "HEAD" { let fileManager = NSFileManager.defaultManager() let relativePath = String(request.URL!.path!.characters.dropFirst()) @@ -183,10 +189,12 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { headers["Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension!) headers["Content-Length"] = String(attrs[NSFileSize]!) headers["Last-Modified"] = dateFormatter.stringFromDate(attrs[NSFileModificationDate] as! NSDate) + log("+\(request.HTTPMethod) fileURL.path") } else { // Not found statusCode = 404 fileURL = NSURL() + log("-File NOT found for URL \(request.URL!)") } } else { // Method not allowed diff --git a/XWebView/XWVLogging.swift b/XWebView/XWVLogging.swift new file mode 100644 index 0000000..e09727a --- /dev/null +++ b/XWebView/XWVLogging.swift @@ -0,0 +1,135 @@ +/* + Copyright 2015 XWebView + + Licensed 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 Darwin + +public typealias asl_object_t = COpaquePointer + +@asmname("asl_open") func asl_open(ident: UnsafePointer, _ facility: UnsafePointer, _ opts: UInt32) -> asl_object_t; +@asmname("asl_close") func asl_close(obj: asl_object_t); +@asmname("asl_vlog") func asl_vlog(obj: asl_object_t, _ msg: asl_object_t, _ level: Int32, _ format: UnsafePointer, _ ap: CVaListPointer) -> Int32; +@asmname("asl_add_output_file") func asl_add_output_file(client: asl_object_t, _ descriptor: Int32, _ msg_fmt: UnsafePointer, _ time_fmt: UnsafePointer, _ filter: Int32, _ text_encoding: Int32) -> Int32; +@asmname("asl_set_output_file_filter") func asl_set_output_file_filter(asl: asl_object_t, _ descriptor: Int32, _ filter: Int32) -> Int32; + +public class XWVLogging : XWVScripting { + public enum Level : Int32 { + case Emergency = 0 + case Alert = 1 + case Critical = 2 + case Error = 3 + case Warning = 4 + case Notice = 5 + case Info = 6 + case Debug = 7 + + private static let symbols : [Character] = [ + "\0", "\0", "$", "!", "?", "-", "+", " " + ] + private init?(symbol: Character) { + guard symbol != "\0", let value = Level.symbols.indexOf(symbol) else { + return nil + } + self = Level(rawValue: Int32(value))! + } + } + + public struct Filter : OptionSetType { + private var value: Int32 + public var rawValue: Int32 { + return value + } + + public init(rawValue: Int32) { + self.value = rawValue + } + public init(mask: Level) { + self.init(rawValue: 1 << mask.rawValue) + } + public init(upto: Level) { + self.init(rawValue: 1 << (upto.rawValue + 1) - 1) + } + public init(filter: Level...) { + self.init(rawValue: filter.reduce(0) { $0 | $1.rawValue }) + } + } + + public var filter: Filter { + didSet { + asl_set_output_file_filter(client, STDERR_FILENO, filter.rawValue) + } + } + + private let client: asl_object_t + private var lock: pthread_mutex_t = pthread_mutex_t() + public init(facility: String, format: String? = nil) { + client = asl_open(nil, facility, 0) + pthread_mutex_init(&lock, nil) + + #if DEBUG + filter = Filter(upto: .Debug) + #else + filter = Filter(upto: .Notice) + #endif + + let format = format ?? "$((Time)(lcl)) $(Facility) <$((Level)(char))>: $(Message)" + asl_add_output_file(client, STDERR_FILENO, format, "sec", filter.rawValue, 1) + } + deinit { + asl_close(client) + pthread_mutex_destroy(&lock) + } + + public func log(message: String, level: Level) { + pthread_mutex_lock(&lock) + asl_vlog(client, nil, level.rawValue, message, getVaList([])) + pthread_mutex_unlock(&lock) + } + + public func log(message: String, level: Level? = nil) { + var msg = message + var lvl = level ?? .Debug + if level == nil, let ch = msg.characters.first, l = Level(symbol: ch) { + msg = msg[msg.startIndex.successor() ..< msg.endIndex] + lvl = l + } + log(msg, level: lvl) + } + + @objc public func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! { + guard args.count > 0 else { return nil } + let message = args[0] as? String ?? "\(args[0])" + var level: Level? = nil + if args.count > 1, let num = args[1] as? Int { + if 3 <= num && num <= 7 { + level = Level(rawValue: Int32(num)) + } else { + level = .Debug + } + } + log(message, level: level) + return nil + } +} + +private let logger = XWVLogging(facility: "org.xwebview.xwebview") +func log(message: String, level: XWVLogging.Level? = nil) { + logger.log(message, level: level) +} + +@noreturn func die(@autoclosure message: ()->String, file: StaticString = __FILE__, line: UInt = __LINE__) { + logger.log(message(), level: .Alert) + fatalError(message, file: file, line: line) +} diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index b7a274d..d372650 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -142,7 +142,7 @@ class XWVMetaObject: CollectionType { return true } } - assert(members.indexForKey(name) == nil, "Script name '\(name)' has conflict") + assert(members.indexForKey(name) == nil, "Plugin class \(plugin) has a conflict in member name '\(name)'") members[name] = member return true } diff --git a/XWebView/XWVUserScript.swift b/XWebView/XWVUserScript.swift index e89f626..ef50abe 100644 --- a/XWebView/XWVUserScript.swift +++ b/XWebView/XWVUserScript.swift @@ -45,7 +45,7 @@ class XWVUserScript { if webView.URL != nil { webView.evaluateJavaScript(script.source) { if let error = $1 { - print(" ERROR: Inject user script in context.\n\(error)") + log("!Failed to inject script. \(error)") } } } diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 123ccd8..7e0747e 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -31,12 +31,13 @@ extension WKWebView { let bundle = NSBundle(forClass: XWVChannel.self) guard let path = bundle.pathForResource("xwebview", ofType: "js"), let source = try? NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) else { - preconditionFailure("FATAL: Internal error") + die("Failed to read provision script: xwebview.js") } let time = WKUserScriptInjectionTime.AtDocumentStart let script = WKUserScript(source: source as String, injectionTime: time, forMainFrameOnly: true) let xwvplugin = XWVUserScript(webView: self, script: script, namespace: "XWVPlugin") objc_setAssociatedObject(self, key, xwvplugin, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + log("+WKWebView(\(unsafeAddressOf(self))) is ready for loading plugins") } } @@ -86,7 +87,7 @@ extension WKWebView { } if error != nil { throw error! } if !done { - print(" ERROR: Timeout to evaluate script.") + log("!Timeout to evaluate script: \(script)") } return result } @@ -108,11 +109,15 @@ extension WKWebView { extension WKWebView { // Overlay support for loading file URL public func loadFileURL(URL: NSURL, overlayURLs: [NSURL]? = nil) -> WKNavigation? { - precondition(URL.fileURL && URL.baseURL != nil, "URL must be a relative file URL.") guard overlayURLs?.count > 0 else { return loadFileURL(URL, allowingReadAccessToURL: URL.baseURL!) } + guard URL.fileURL && URL.baseURL != nil else { + assertionFailure("URL must be a relative file URL.") + return nil + } + guard let port = startHttpd(rootURL: URL.baseURL!, overlayURLs: overlayURLs) else { return nil } let url = NSURL(string: URL.resourceSpecifier, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) return loadRequest(NSURLRequest(URL: url!)) @@ -136,7 +141,7 @@ extension WKWebView { let method = class_getInstanceMethod(self, Selector("_loadFileURL:allowingReadAccessToURL:")) assert(method != nil) if class_addMethod(self, selector, method_getImplementation(method), method_getTypeEncoding(method)) { - print(" INFO: Platform is iOS 8.x") + log("+Running on iOS 8.x") method_exchangeImplementations( class_getInstanceMethod(self, Selector("loadHTMLString:baseURL:")), class_getInstanceMethod(self, Selector("_loadHTMLString:baseURL:")) @@ -146,13 +151,15 @@ extension WKWebView { } @objc private func _loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation? { - precondition(URL.fileURL && readAccessURL.fileURL) - // readAccessURL must contain URL let fileManager = NSFileManager.defaultManager() var relationship: NSURLRelationship = NSURLRelationship.Other _ = try? fileManager.getRelationship(&relationship, ofDirectoryAtURL: readAccessURL, toItemAtURL: URL) - guard relationship != NSURLRelationship.Other else { return nil } + guard URL.fileURL && readAccessURL.fileURL && relationship != NSURLRelationship.Other else { + assert(relationship != NSURLRelationship.Other, "readAccessURL must contain URL") + assert(URL.fileURL && readAccessURL.fileURL, "URL and readAccessURL must be file URLs") + return nil + } guard let port = startHttpd(rootURL: readAccessURL) else { return nil } var path = URL.path![readAccessURL.path!.endIndex ..< URL.path!.endIndex] @@ -185,6 +192,7 @@ extension WKWebView { let httpd = XWVHttpServer(rootURL: rootURL, overlayURLs: overlayURLs) guard httpd.start() else { return nil } objc_setAssociatedObject(self, key, httpd, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + log("+HTTP server is started on port: \(httpd.port)") return httpd.port } } From 9d05241dae581e5f0b3822dc7b291097cf7bbc78 Mon Sep 17 00:00:00 2001 From: David Kim Date: Thu, 17 Dec 2015 03:15:55 +0800 Subject: [PATCH 09/44] Improve JavaScript stubs generation Plugin can partially rewrite auto-generated stubs. --- XWebView/XWVChannel.swift | 60 +++++++++++++++++----------- XWebView/XWVScripting.swift | 2 +- XWebViewTests/XWVScriptingTest.swift | 12 ++++-- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 2c43ed4..08fb397 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -65,17 +65,15 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { webView.configuration.userContentController.addScriptMessageHandler(self, name: name) typeInfo = XWVMetaObject(plugin: object.dynamicType) - let plugin = XWVBindingObject(namespace: namespace, channel: self, object: object) + principal = XWVBindingObject(namespace: namespace, channel: self, object: object) - let stub = generateStub(plugin) - let script = WKUserScript(source: (object as? XWVScripting)?.javascriptStub?(stub) ?? stub, + let script = WKUserScript(source: generateStubs(), injectionTime: WKUserScriptInjectionTime.AtDocumentStart, forMainFrameOnly: true) userScript = XWVUserScript(webView: webView, script: script) - principal = plugin log("+Plugin object \(object) is bound to \(namespace) with channel \(name)") - return plugin as XWVScriptObject + return principal as XWVScriptObject } public func unbind() { @@ -133,34 +131,48 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } } - private func generateStub(object: XWVBindingObject) -> String { - func generateMethod(this: String, name: String, prebind: Bool) -> String { - let stub = "XWVPlugin.invokeNative.bind(\(this), '\(name)')" + private func generateStubs() -> String { + func generateMethod(key: String, this: String, prebind: Bool) -> String { + let stub = "XWVPlugin.invokeNative.bind(\(this), '\(key)')" return prebind ? "\(stub);" : "function(){return \(stub).apply(null, arguments);}" } + func rewriteStub(stub: String, forKey key: String) -> String { + return (principal.plugin as? XWVScripting)?.rewriteGeneratedStub?(stub, forKey: key) ?? stub + } + + let prebind = !(typeInfo[""]?.isInitializer ?? false) + let stubs = typeInfo.reduce("") { + let key = $1.0 + let member = $1.1 + let stub: String + if member.isMethod && !key.isEmpty { + let method = generateMethod("\(key)\(member.type)", this: prebind ? "exports" : "this", prebind: prebind) + stub = "exports.\(key) = \(method)" + } else if member.isProperty { + let value = principal.serialize(principal[key]) + stub = "XWVPlugin.defineProperty(exports, '\(key)', \(value), \(member.setter != nil));" + } else { + return $0 + } + return $0 + rewriteStub(stub, forKey: key) + "\n" + } - var base = "null" - var prebind = true + let base: String if let member = typeInfo[""] { if member.isInitializer { base = "'\(member.type)'" - prebind = false } else { - base = generateMethod("arguments.callee", name: "\(member.type)", prebind: false) + base = generateMethod("\(member.type)", this: "arguments.callee", prebind: false) } + } else { + base = rewriteStub("null", forKey: ".base") } - var stub = "(function(exports) {\n" - for (name, member) in typeInfo { - if member.isMethod && !name.isEmpty { - let method = generateMethod(prebind ? "exports" : "this", name: "\(name)\(member.type)", prebind: prebind) - stub += "exports.\(name) = \(method)\n" - } else if member.isProperty { - let value = object.serialize(object[name]) - stub += "XWVPlugin.defineProperty(exports, '\(name)', \(value), \(member.setter != nil));\n" - } - } - stub += "})(XWVPlugin.createPlugin('\(name)', '\(object.namespace)', \(base)));\n\n" - return stub + return rewriteStub( + "(function(exports) {\n" + + rewriteStub(stubs, forKey: ".local") + + "})(XWVPlugin.createPlugin('\(name)', '\(principal.namespace)', \(base)));\n", + forKey: ".global" + ) } } diff --git a/XWebView/XWVScripting.swift b/XWebView/XWVScripting.swift index b14fe9f..e4eb0c3 100644 --- a/XWebView/XWVScripting.swift +++ b/XWebView/XWVScripting.swift @@ -17,7 +17,7 @@ import Foundation @objc public protocol XWVScripting : class { - optional func javascriptStub(stub: String) -> String + optional func rewriteGeneratedStub(stub: String, forKey: String) -> String optional func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! optional func finalizeForScript() diff --git a/XWebViewTests/XWVScriptingTest.swift b/XWebViewTests/XWVScriptingTest.swift index 050c865..5b20827 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -24,8 +24,12 @@ class XWVScriptingTest : XWVTestCase { init(expectation: XCTestExpectation?) { self.expectation = expectation } - func javascriptStub(stub: String) -> String { - return stub + "\nwindow.stub = true;\n" + func rewriteGeneratedStub(stub: String, forKey key: String) -> String { + switch key { + case ".global": return stub + "window.stub = true;\n" + case ".local": return stub + "exports.abc = true;\n" + default: return stub + } } func finalizeForScript() { expectation?.fulfill() @@ -40,9 +44,9 @@ class XWVScriptingTest : XWVTestCase { let namespace = "xwvtest" - func testJavascriptStub() { + func testRewriteGeneratedStub() { let desc = "javascriptStub" - let script = "if (window.stub) fulfill('\(desc)');" + let script = "if (window.stub && \(namespace).abc) fulfill('\(desc)');" _ = expectationWithDescription(desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) waitForExpectationsWithTimeout(2, handler: nil) From 600e9207c5da8c6e234760842bc9c4dfe6c076ff Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 22 Dec 2015 01:32:49 +0800 Subject: [PATCH 10/44] Fixed a typo --- XWebView/XWVInvocation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index 63ad013..de41d09 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -76,7 +76,7 @@ public class XWVInvocation { } @objc public func asyncCall(selector: Selector, withObjects objects: [AnyObject]?) { let args: [Any!] = objects?.map{ $0 !== NSNull() ? ($0 as Any) : nil } ?? [] - call(selector, withArguments: args) + asyncCall(selector, withArguments: args) } // Syntactic sugar for calling method From fbd96cde5b8551f4e1aa370943e719e9960f16bb Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 22 Dec 2015 02:18:25 +0800 Subject: [PATCH 11/44] All channels share a global serial queue by default. --- XWebView/XWVBindingObject.swift | 8 ++++---- XWebView/XWVChannel.swift | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 4fa0160..70190aa 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -64,10 +64,10 @@ class XWVBindingObject : XWVScriptObject { private func bindObject(object: AnyObject) -> XWVInvocation { let option: XWVInvocation.Option - if channel.queue != nil { - option = .Queue(queue: channel.queue) - } else if channel.thread != nil { - option = .Thread(thread: channel.thread) + if let queue = channel.queue { + option = .Queue(queue: queue) + } else if let thread = channel.thread { + option = .Thread(thread: thread) } else { option = .None } diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 08fb397..fba7e86 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -19,8 +19,8 @@ import WebKit public class XWVChannel : NSObject, WKScriptMessageHandler { public let name: String - public let thread: NSThread! - public let queue: dispatch_queue_t! + public let thread: NSThread? + public let queue: dispatch_queue_t? private(set) public weak var webView: WKWebView? var typeInfo: XWVMetaObject! @@ -38,9 +38,13 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { return ++sequence.number } + private static var defaultQueue: dispatch_queue_t = { + let label = "org.xwebview.default-queue" + return dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL) + }() + public convenience init(name: String?, webView: WKWebView) { - let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL) - self.init(name: name, webView:webView, queue: queue) + self.init(name: name, webView:webView, queue: XWVChannel.defaultQueue) } public init(name: String?, webView: WKWebView, queue: dispatch_queue_t) { From 4fe437f5f1b7ac17ada966b24096a78e922ae503 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 22 Dec 2015 02:50:18 +0800 Subject: [PATCH 12/44] Channel identifier can be specified by plugin instead of embedder. Channel identifier (previously called channel name) should be commonly auto-generated to avoid collision. In some cases (e.g. cordova bridge), plugin can require a specific identifier for its associated channel. Embedders should never care about identifiers. Removing this parameter from channel initializer makes the interface more simple and clear. --- XWebView/XWVChannel.swift | 29 ++++++++++++++--------------- XWebView/XWVScripting.swift | 1 + XWebView/XWebView.swift | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index fba7e86..0f15431 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -18,7 +18,7 @@ import Foundation import WebKit public class XWVChannel : NSObject, WKScriptMessageHandler { - public let name: String + private(set) public var identifier: String? public let thread: NSThread? public let queue: dispatch_queue_t? private(set) public weak var webView: WKWebView? @@ -43,20 +43,18 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { return dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL) }() - public convenience init(name: String?, webView: WKWebView) { - self.init(name: name, webView:webView, queue: XWVChannel.defaultQueue) + public convenience init(webView: WKWebView) { + self.init(webView: webView, queue: XWVChannel.defaultQueue) } - public init(name: String?, webView: WKWebView, queue: dispatch_queue_t) { - self.name = name ?? "\(XWVChannel.sequenceNumber)" + public init(webView: WKWebView, queue: dispatch_queue_t) { self.webView = webView self.queue = queue thread = nil webView.prepareForPlugin() } - public init(name: String?, webView: WKWebView, thread: NSThread) { - self.name = name ?? "\(XWVChannel.sequenceNumber)" + public init(webView: WKWebView, thread: NSThread) { self.webView = webView self.thread = thread queue = nil @@ -64,10 +62,11 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } public func bindPlugin(object: AnyObject, toNamespace namespace: String) -> XWVScriptObject? { - assert(typeInfo == nil, "Channel \(name) is occupied by plugin object \(principal.plugin)") - guard typeInfo == nil, let webView = webView else { return nil } + guard identifier == nil, let webView = webView else { return nil } - webView.configuration.userContentController.addScriptMessageHandler(self, name: name) + let id = (object as? XWVScripting)?.channelIdentifier ?? String(XWVChannel.sequenceNumber) + identifier = id + webView.configuration.userContentController.addScriptMessageHandler(self, name: id) typeInfo = XWVMetaObject(plugin: object.dynamicType) principal = XWVBindingObject(namespace: namespace, channel: self, object: object) @@ -76,18 +75,18 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { forMainFrameOnly: true) userScript = XWVUserScript(webView: webView, script: script) - log("+Plugin object \(object) is bound to \(namespace) with channel \(name)") + log("+Plugin object \(object) is bound to \(namespace) with channel \(id)") return principal as XWVScriptObject } public func unbind() { - guard typeInfo != nil else { return } + guard let id = identifier else { return } let namespace = principal.namespace let plugin = principal.plugin instances.removeAll(keepCapacity: false) - webView?.configuration.userContentController.removeScriptMessageHandlerForName(name) + webView?.configuration.userContentController.removeScriptMessageHandlerForName(id) userScript = nil - //typeInfo = nil // FIXME: crash while instance deinit + identifier = nil log("+Plugin object \(plugin) is unbound from \(namespace)") } @@ -175,7 +174,7 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { return rewriteStub( "(function(exports) {\n" + rewriteStub(stubs, forKey: ".local") + - "})(XWVPlugin.createPlugin('\(name)', '\(principal.namespace)', \(base)));\n", + "})(XWVPlugin.createPlugin('\(identifier!)', '\(principal.namespace)', \(base)));\n", forKey: ".global" ) } diff --git a/XWebView/XWVScripting.swift b/XWebView/XWVScripting.swift index e4eb0c3..e329266 100644 --- a/XWebView/XWVScripting.swift +++ b/XWebView/XWVScripting.swift @@ -17,6 +17,7 @@ import Foundation @objc public protocol XWVScripting : class { + optional var channelIdentifier: String { get } optional func rewriteGeneratedStub(stub: String, forKey: String) -> String optional func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! optional func finalizeForScript() diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 7e0747e..7177507 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -20,7 +20,7 @@ import WebKit extension WKWebView { public func loadPlugin(object: AnyObject, namespace: String) -> XWVScriptObject? { - let channel = XWVChannel(name: nil, webView: self) + let channel = XWVChannel(webView: self) return channel.bindPlugin(object, toNamespace: namespace) } From e44c4cd4b2d14a89d25d8418e968779cd06ae671 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 01:34:44 +0800 Subject: [PATCH 13/44] typo --- XWebView/XWVHttpServer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index 0e78cb7..045372a 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -189,7 +189,7 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { headers["Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension!) headers["Content-Length"] = String(attrs[NSFileSize]!) headers["Last-Modified"] = dateFormatter.stringFromDate(attrs[NSFileModificationDate] as! NSDate) - log("+\(request.HTTPMethod) fileURL.path") + log("+\(request.HTTPMethod!) \(fileURL.path!)") } else { // Not found statusCode = 404 From 4f76abf5355970947016400669181eb94971fe01 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 01:37:18 +0800 Subject: [PATCH 14/44] call and callMethod return optional value Nil means undefined in JavaScript. --- XWebView/XWVBindingObject.swift | 2 +- XWebView/XWVScriptObject.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 70190aa..90989eb 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -130,7 +130,7 @@ class XWVBindingObject : XWVScriptObject { super.callMethod(name, withArguments: arguments, completionHandler: completionHandler) } } - override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject! { + override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject? { if let selector = channel.typeInfo[name]?.selector { return proxy.call(selector, withObjects: arguments) } diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index fa70eeb..3085c0a 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -39,16 +39,16 @@ public class XWVScriptObject : XWVObject { } return result } - public func call(arguments arguments: [AnyObject]?) throws -> AnyObject! { + public func call(arguments arguments: [AnyObject]?) throws -> AnyObject? { return try evaluateExpression(scriptForCallingMethod(nil, arguments: arguments)) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject! { + public func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject? { return try evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) } - public func call(arguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject! { + public func call(arguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject? { return evaluateExpression(scriptForCallingMethod(nil, arguments: arguments), error: error) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject! { + public func callMethod(name: String, withArguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject? { return evaluateExpression(scriptForCallingMethod(name, arguments: arguments), error: error) } From c43fd671db4a3f56e497d937030e01996eca6e57 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 01:46:46 +0800 Subject: [PATCH 15/44] Throw an exception or return an error when WebView has gone --- XWebView/XWVObject.swift | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index 6e3a9b6..c2d1569 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -17,6 +17,9 @@ import Foundation import WebKit +private let webViewInvalidated = + NSError(domain: WKErrorDomain, code: WKErrorCode.WebViewInvalidated.rawValue, userInfo: nil) + public class XWVObject : NSObject { public let namespace: String public unowned let channel: XWVChannel @@ -40,6 +43,7 @@ public class XWVObject : NSObject { } deinit { + guard let webView = webView else { return } let script: String if reference == 0 { script = "delete \(namespace)" @@ -49,22 +53,33 @@ public class XWVObject : NSObject { assertionFailure() return } - webView?.evaluateJavaScript(script, completionHandler: nil) + webView.evaluateJavaScript(script, completionHandler: nil) } // Evaluate JavaScript expression public func evaluateExpression(expression: String) throws -> AnyObject? { - return wrapScriptObject(try webView?.evaluateJavaScript(scriptForRetaining(expression))) + guard let webView = webView else { + throw webViewInvalidated + } + return wrapScriptObject(try webView.evaluateJavaScript(scriptForRetaining(expression))) } public func evaluateExpression(expression: String, error: NSErrorPointer) -> AnyObject? { - return wrapScriptObject(webView?.evaluateJavaScript(expression, error: error)) + guard let webView = webView else { + if error != nil { error.memory = webViewInvalidated } + return nil + } + return wrapScriptObject(webView.evaluateJavaScript(scriptForRetaining(expression), error: error)) } public func evaluateExpression(expression: String, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + guard let webView = webView else { + completionHandler?(nil, webViewInvalidated) + return + } guard let completionHandler = completionHandler else { - webView?.evaluateJavaScript(expression, completionHandler: nil) + webView.evaluateJavaScript(expression, completionHandler: nil) return } - webView?.evaluateJavaScript(scriptForRetaining(expression)) { + webView.evaluateJavaScript(scriptForRetaining(expression)) { [weak self](result: AnyObject?, error: NSError?)->Void in completionHandler(self?.wrapScriptObject(result) ?? result, error) } From 3a0f597ec782e3e2d4c70d17d68ba8a8b2533016 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 02:08:20 +0800 Subject: [PATCH 16/44] Lazily dispose plugin object on JavaScript side So native side can access plugin object during finalizeForScript(). --- XWebView/XWVBindingObject.swift | 1 + XWebView/xwebview.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 90989eb..06a3520 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -59,6 +59,7 @@ class XWVBindingObject : XWVScriptObject { deinit { (plugin as? XWVScripting)?.finalizeForScript?() + super.callMethod("dispose", withArguments: [true], completionHandler: nil) unbindObject(plugin) } diff --git a/XWebView/xwebview.js b/XWebView/xwebview.js index 3312dae..89bf1c5 100644 --- a/XWebView/xwebview.js +++ b/XWebView/xwebview.js @@ -187,8 +187,11 @@ XWVPlugin.prototype = { delete this.$references[refid]; this.$lastRefID = refid; }, - dispose: function() { - this.$channel.postMessage({'$opcode': '-', '$target': this.$instanceID}); + dispose: function(immediate) { + if (!immediate) { + this.$channel.postMessage({'$opcode': '-', '$target': this.$instanceID}); + return; + } delete this.$channel; delete this.$properties; From dce3f15c997ce4d7df901808837417ca599e571a Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 02:22:21 +0800 Subject: [PATCH 17/44] Decouple XWVObject from XWVChannel --- XWebView/XWVBindingObject.swift | 7 +++-- XWebView/XWVObject.swift | 50 ++++++++++++++++++++------------- XWebView/XWVScriptObject.swift | 4 +-- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 06a3520..8f073d8 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -19,16 +19,19 @@ import ObjectiveC class XWVBindingObject : XWVScriptObject { private let key = unsafeAddressOf(XWVScriptObject) + unowned let channel: XWVChannel private var proxy: XWVInvocation! final var plugin: AnyObject { return proxy.target } init(namespace: String, channel: XWVChannel, object: AnyObject) { - super.init(namespace: namespace, channel: channel, origin: nil) + self.channel = channel + super.init(namespace: namespace, webView: channel.webView!) proxy = bindObject(object) } init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { - super.init(namespace: namespace, channel: channel, origin: nil) + self.channel = channel + super.init(namespace: namespace, webView: channel.webView!) let cls: AnyClass = channel.typeInfo.plugin let member = channel.typeInfo[""] guard member != nil, case .Initializer(let selector, let arity) = member! else { diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index c2d1569..930db7c 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -22,35 +22,45 @@ private let webViewInvalidated = public class XWVObject : NSObject { public let namespace: String - public unowned let channel: XWVChannel - public var webView: WKWebView? { return channel.webView } - weak var origin: XWVObject! + private(set) public weak var webView: WKWebView? + weak var origin: XWVObject? + private let reference: Int - // This object is a plugin object. - init(namespace: String, channel: XWVChannel, origin: XWVObject?) { + // initializer for plugin object. + init(namespace: String, webView: WKWebView) { self.namespace = namespace - self.channel = channel + self.webView = webView + reference = 0 super.init() - self.origin = origin ?? self + origin = self } - // The object is a stub for a JavaScript object which was retained as an argument. - private var reference = 0 - convenience init(reference: Int, channel: XWVChannel, origin: XWVObject) { - let namespace = "\(origin.namespace).$references[\(reference)]" - self.init(namespace: namespace, channel: channel, origin: origin) + // initializer for script object with global namespace. + init(namespace: String, origin: XWVObject) { + self.namespace = namespace + self.origin = origin + webView = origin.webView + reference = 0 + super.init() + } + + // initializer for script object which is retained on script side. + init(reference: Int, origin: XWVObject) { self.reference = reference + self.origin = origin + webView = origin.webView + namespace = "\(origin.namespace).$references[\(reference)]" + super.init() } deinit { guard let webView = webView else { return } let script: String - if reference == 0 { + if origin === self { script = "delete \(namespace)" - } else if origin != nil { + } else if reference != 0, let origin = origin { script = "\(origin.namespace).$releaseObject(\(reference))" } else { - assertionFailure() return } webView.evaluateJavaScript(script, completionHandler: nil) @@ -85,15 +95,17 @@ public class XWVObject : NSObject { } } private func scriptForRetaining(script: String) -> String { - return origin != nil ? "\(origin.namespace).$retainObject(\(script))" : script + guard let origin = origin else { return script } + return "\(origin.namespace).$retainObject(\(script))" } func wrapScriptObject(object: AnyObject!) -> AnyObject! { + guard let origin = origin else { return object } if let dict = object as? [String: AnyObject] where dict["$sig"] as? NSNumber == 0x5857574F { - if let num = dict["$ref"] as? NSNumber { - return XWVScriptObject(reference: num.integerValue, channel: channel, origin: self) + if let num = dict["$ref"] as? NSNumber where num != 0 { + return XWVScriptObject(reference: num.integerValue, origin: origin) } else if let namespace = dict["$ns"] as? String { - return XWVScriptObject(namespace: namespace, channel: channel, origin: self) + return XWVScriptObject(namespace: namespace, origin: origin) } } return object diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index 3085c0a..14fd894 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -121,9 +121,9 @@ extension XWVScriptObject { extension XWVScriptObject { // DOM objects public var windowObject: XWVScriptObject { - return XWVScriptObject(namespace: "window", channel: self.channel, origin: self.origin) + return XWVScriptObject(namespace: "window", origin: origin!) } public var documentObject: XWVScriptObject { - return XWVScriptObject(namespace: "document", channel: self.channel, origin: self.origin) + return XWVScriptObject(namespace: "document", origin: origin!) } } From d224ffc954bbd0f238148fc2378068530c675136 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 16 Feb 2016 02:24:47 +0800 Subject: [PATCH 18/44] Move windowObject property to WebView class --- XWebView/XWVObject.swift | 2 +- XWebView/XWVScriptObject.swift | 12 +++++------- XWebView/XWebView.swift | 4 ++++ XWebView/xwebview.js | 5 ++++- XWebViewTests/ObjectPlugin.swift | 14 -------------- XWebViewTests/XWebViewTests.swift | 14 ++++++++++++++ 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index 930db7c..dce7c6f 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -23,7 +23,7 @@ private let webViewInvalidated = public class XWVObject : NSObject { public let namespace: String private(set) public weak var webView: WKWebView? - weak var origin: XWVObject? + private weak var origin: XWVObject? private let reference: Int // initializer for plugin object. diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index 14fd894..016f297 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -118,12 +118,10 @@ extension XWVScriptObject { } } -extension XWVScriptObject { - // DOM objects - public var windowObject: XWVScriptObject { - return XWVScriptObject(namespace: "window", origin: origin!) - } - public var documentObject: XWVScriptObject { - return XWVScriptObject(namespace: "document", origin: origin!) +class XWVWindowObject: XWVScriptObject { + private let origin: XWVObject + init(webView: WKWebView) { + origin = XWVObject(namespace: "XWVPlugin.context", webView: webView) + super.init(namespace: "window", origin: origin) } } diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 7177507..01a1c0d 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -19,6 +19,10 @@ import ObjectiveC import WebKit extension WKWebView { + public var windowObject: XWVScriptObject { + return XWVWindowObject(webView: self) + } + public func loadPlugin(object: AnyObject, namespace: String) -> XWVScriptObject? { let channel = XWVChannel(webView: self) return channel.bindPlugin(object, toNamespace: namespace) diff --git a/XWebView/xwebview.js b/XWebView/xwebview.js index 89bf1c5..15ee1b3 100644 --- a/XWebView/xwebview.js +++ b/XWebView/xwebview.js @@ -16,7 +16,8 @@ XWVPlugin = function(channelName) { var channel = webkit.messageHandlers[channelName]; - if (!channel) throw 'channel has not established'; + if (channelName && !channel) + throw 'channel has not established'; Object.defineProperty(this, '$channel', {'configurable': true, 'value': channel}); Object.defineProperty(this, '$references', {'configurable': true, 'value': []}); @@ -207,3 +208,5 @@ XWVPlugin.prototype = { this.__proto__ = Object.getPrototypeOf(this.__proto__); } } + +XWVPlugin.context = new XWVPlugin(0); diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index 5924d83..dc1f9c6 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -41,13 +41,6 @@ class ObjectPlugin : XWVTestCase { func method(promiseObject promiseObject: XWVScriptObject) { promiseObject.callMethod("resolve", withArguments: nil, completionHandler: nil) } - func windowObject() { - if let test: AnyObject = scriptObject?.windowObject["test"] { - if (test as? NSNumber)?.integerValue == 234 { - expectation?.fulfill() - } - } - } init(expectation: XCTestExpectation?) { self.expectation = expectation } @@ -130,11 +123,4 @@ class ObjectPlugin : XWVTestCase { } waitForExpectationsWithTimeout(2, handler: nil) } - func testWindowObject() { - let desc = "windowObject" - let expectation = expectationWithDescription(desc) - let plugin = Plugin(expectation: expectation) - loadPlugin(plugin as NSObject, namespace: namespace, script: "window.test=234;\(namespace).windowObject()") - waitForExpectationsWithTimeout(2, handler: nil) - } } diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index c1c12bb..4e0aba6 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -22,6 +22,20 @@ class XWebViewTests: XWVTestCase { class Plugin : NSObject { } + func testWindowObject() { + let expectation = expectationWithDescription("testWindowObject") + loadPlugin(Plugin(), namespace: "xwvtest", script: "") { + if let math = $0.windowObject["Math"] as? XWVScriptObject, + num = try? math.callMethod("sqrt", withArguments: [9]), + result = (num as? NSNumber)?.integerValue where result == 3 { + expectation.fulfill() + } else { + XCTFail("testWindowObject Failed") + } + } + waitForExpectationsWithTimeout(2, handler: nil) + } + func testLoadPlugin() { if webview.loadPlugin(Plugin(), namespace: "xwvtest") == nil { XCTFail("testLoadPlugin Failed") From f8f45e3ff2b6e16cd56077d4b0d6069792fcb036 Mon Sep 17 00:00:00 2001 From: David Kim Date: Fri, 11 Mar 2016 20:10:37 +0800 Subject: [PATCH 19/44] Refactory threading support --- XWebView/XWVBindingObject.swift | 129 +++++++++++++++++++------------- XWebView/XWVChannel.swift | 13 +++- XWebView/XWVInvocation.swift | 45 +---------- 3 files changed, 88 insertions(+), 99 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 8f073d8..27ab095 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -17,16 +17,16 @@ import Foundation import ObjectiveC -class XWVBindingObject : XWVScriptObject { +final class XWVBindingObject : XWVScriptObject { private let key = unsafeAddressOf(XWVScriptObject) unowned let channel: XWVChannel - private var proxy: XWVInvocation! - final var plugin: AnyObject { return proxy.target } + var plugin: AnyObject! init(namespace: String, channel: XWVChannel, object: AnyObject) { self.channel = channel + self.plugin = object super.init(namespace: namespace, webView: channel.webView!) - proxy = bindObject(object) + bind() } init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { @@ -49,13 +49,14 @@ class XWVBindingObject : XWVScriptObject { arguments = [arguments] } - let args: [Any!] = arguments.map{ $0 !== NSNull() ? ($0 as Any) : nil } - guard let instance = XWVInvocation.construct(cls, initializer: selector, withArguments: args) else { + let args: [Any!] = arguments.map{ $0 is NSNull ? nil : ($0 as Any) } + plugin = XWVInvocation.construct(cls, initializer: selector, withArguments: args) + guard plugin != nil else { log("!Failed to create instance for plugin class \(cls)") return nil } - proxy = bindObject(instance) + bind() syncProperties() promise?.callMethod("resolve", withArguments: [self], completionHandler: nil) } @@ -63,72 +64,56 @@ class XWVBindingObject : XWVScriptObject { deinit { (plugin as? XWVScripting)?.finalizeForScript?() super.callMethod("dispose", withArguments: [true], completionHandler: nil) - unbindObject(plugin) + unbind() } - private func bindObject(object: AnyObject) -> XWVInvocation { - let option: XWVInvocation.Option - if let queue = channel.queue { - option = .Queue(queue: queue) - } else if let thread = channel.thread { - option = .Thread(thread: thread) - } else { - option = .None - } - let proxy = XWVInvocation(target: object, option: option) - - objc_setAssociatedObject(object, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + private func bind() { + objc_setAssociatedObject(plugin, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) // Start KVO - if object is NSObject { - for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { - let key = member.getter!.description - object.addObserver(self, forKeyPath: key, options: NSKeyValueObservingOptions.New, context: nil) - } + guard let plugin = plugin as? NSObject else { return } + channel.typeInfo.filter{ $1.isProperty }.forEach { + plugin.addObserver(self, forKeyPath: String($1.getter!), options: NSKeyValueObservingOptions.New, context: nil) } - return proxy } - private func unbindObject(object: AnyObject) { - objc_setAssociatedObject(object, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + private func unbind() { + objc_setAssociatedObject(plugin, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) // Stop KVO - if object is NSObject { - for (_, member) in channel.typeInfo.filter({ $1.isProperty }) { - let key = member.getter!.description - object.removeObserver(self, forKeyPath: key, context: nil) - } + guard plugin is NSObject else { return } + channel.typeInfo.filter{ $1.isProperty }.forEach { + plugin.removeObserver(self, forKeyPath: String($1.getter!), context: nil) } } private func syncProperties() { - var script = "" - for (name, member) in channel.typeInfo.filter({ $1.isProperty }) { - let val: AnyObject! = proxy.call(member.getter!, withObjects: nil) - script += "\(namespace).$properties['\(name)'] = \(serialize(val));\n" + let script = channel.typeInfo.filter{ $1.isProperty }.reduce("") { + let val: AnyObject! = performSelector($1.1.getter!, withObjects: nil) + return "\($0)\(namespace).$properties['\($1.0)'] = \(serialize(val));\n" } webView?.evaluateJavaScript(script, completionHandler: nil) } // Dispatch operation to plugin object func invokeNativeMethod(name: String, withArguments arguments: [AnyObject]) { - if let selector = channel.typeInfo[name]?.selector { - var args = arguments.map(wrapScriptObject) - if plugin is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { - args = [args]; - } - proxy.asyncCall(selector, withObjects: args) + guard let selector = channel.typeInfo[name]?.selector else { return } + + var args = arguments.map(wrapScriptObject) + if plugin is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { + args = [args]; } + performSelector(selector, withObjects: args, waitUntilDone: false) } func updateNativeProperty(name: String, withValue value: AnyObject) { - if let setter = channel.typeInfo[name]?.setter { - let val: AnyObject = wrapScriptObject(value) - proxy.asyncCall(setter, withObjects: [val]) - } + guard let setter = channel.typeInfo[name]?.setter else { return } + + let val: AnyObject = wrapScriptObject(value) + performSelector(setter, withObjects: [val], waitUntilDone: false) } // override methods of XWVScriptObject override func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { if let selector = channel.typeInfo[name]?.selector { - let result: AnyObject! = proxy.call(selector, withObjects: arguments) + let result: AnyObject! = performSelector(selector, withObjects: arguments) completionHandler?(result, nil) } else { super.callMethod(name, withArguments: arguments, completionHandler: completionHandler) @@ -136,22 +121,23 @@ class XWVBindingObject : XWVScriptObject { } override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject? { if let selector = channel.typeInfo[name]?.selector { - return proxy.call(selector, withObjects: arguments) + return performSelector(selector, withObjects: arguments) } return try super.callMethod(name, withArguments: arguments) } override func value(forProperty name: String) -> AnyObject? { if let getter = channel.typeInfo[name]?.getter { - return proxy.call(getter, withObjects: nil) + return performSelector(getter, withObjects: nil) } return super.value(forProperty: name) } override func setValue(value: AnyObject?, forProperty name: String) { - if channel.typeInfo[name]?.setter != nil { - proxy[name] = value - } else { - assert(channel.typeInfo[name] == nil, "Property '\(name)' is readonly") + if let setter = channel.typeInfo[name]?.setter { + performSelector(setter, withObjects: [value ?? NSNull()]) + } else if channel.typeInfo[name] == nil { super.setValue(value, forProperty: name) + } else { + assertionFailure("Property '\(name)' is readonly") } } @@ -169,6 +155,41 @@ class XWVBindingObject : XWVScriptObject { } } +extension XWVBindingObject { + private func performSelector(selector: Selector, withObjects arguments: [AnyObject]?, waitUntilDone wait: Bool = true) -> AnyObject! { + var result: Any! = () + let trampoline: dispatch_block_t = { + [weak self] in + guard let plugin = self?.plugin else { return } + let args: [Any!] = arguments?.map{ $0 is NSNull ? nil : ($0 as Any) } ?? [] + result = castToObjectFromAny(invoke(plugin, selector: selector, withArguments: args)) + } + if let queue = channel.queue { + if !wait { + dispatch_async(queue, trampoline) + } else if dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) != dispatch_queue_get_label(queue) { + dispatch_sync(queue, trampoline) + } else { + trampoline() + } + } else if let runLoop = channel.runLoop?.getCFRunLoop() { + if wait && CFRunLoopGetCurrent() === runLoop { + trampoline() + } else { + CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, trampoline) + CFRunLoopWakeUp(runLoop) + while wait && result is Void { + let reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true) + if reason != CFRunLoopRunResult.HandledSource { + break + } + } + } + } + return result as? AnyObject + } +} + public extension NSObject { var scriptObject: XWVScriptObject? { return objc_getAssociatedObject(self, unsafeAddressOf(XWVScriptObject)) as? XWVScriptObject diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 0f15431..066a809 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -19,7 +19,7 @@ import WebKit public class XWVChannel : NSObject, WKScriptMessageHandler { private(set) public var identifier: String? - public let thread: NSThread? + public let runLoop: NSRunLoop? public let queue: dispatch_queue_t? private(set) public weak var webView: WKWebView? var typeInfo: XWVMetaObject! @@ -46,17 +46,22 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { public convenience init(webView: WKWebView) { self.init(webView: webView, queue: XWVChannel.defaultQueue) } + public convenience init(webView: WKWebView, thread: NSThread) { + let runLoop = invoke(NSRunLoop.self, selector: "currentRunLoop", withArguments: [], onThread: thread) as! NSRunLoop + self.init(webView: webView, runLoop: runLoop) + } public init(webView: WKWebView, queue: dispatch_queue_t) { + assert(dispatch_queue_get_label(queue).memory != 0, "Queue must be labeled") self.webView = webView self.queue = queue - thread = nil + runLoop = nil webView.prepareForPlugin() } - public init(webView: WKWebView, thread: NSThread) { + public init(webView: WKWebView, runLoop: NSRunLoop) { self.webView = webView - self.thread = thread + self.runLoop = runLoop queue = nil webView.prepareForPlugin() } diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index de41d09..af7e555 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -19,28 +19,11 @@ import ObjectiveC public class XWVInvocation { public final let target: AnyObject - private let queue: dispatch_queue_t? private let thread: NSThread? - public enum Option { - case None - case Queue(queue: dispatch_queue_t) - case Thread(thread: NSThread) - } - - public init(target: AnyObject, option: Option = .None) { + public init(target: AnyObject, thread: NSThread? = nil) { self.target = target - switch option { - case .None: - self.queue = nil - self.thread = nil - case .Queue(let queue): - self.queue = queue - self.thread = nil - case .Thread(let thread): - self.thread = thread - self.queue = nil - } + self.thread = thread } public class func construct(`class`: AnyClass, initializer: Selector = Selector("init"), withArguments arguments: [Any!] = []) -> AnyObject? { @@ -56,27 +39,7 @@ public class XWVInvocation { } // No callback support, so return value is expected to lose. public func asyncCall(selector: Selector, withArguments arguments: [Any!] = []) { - if queue == nil { - invoke(target, selector: selector, withArguments: arguments, onThread: thread, waitUntilDone: false) - } else { - dispatch_async(queue!) { - invoke(self.target, selector: selector, withArguments: arguments) - } - } - } - - // Objective-C interface - // These methods accept parameters in ObjC 'id' instead of Swift 'Any' type. - // Meanwhile, arguments which are NSNull will be converted to nil before calling. - // Return value in scalar type will be converted to object type if feasible. - @objc public func call(selector: Selector, withObjects objects: [AnyObject]?) -> AnyObject! { - let args: [Any!] = objects?.map{ $0 !== NSNull() ? ($0 as Any) : nil } ?? [] - let result = call(selector, withArguments: args) - return castToObjectFromAny(result) - } - @objc public func asyncCall(selector: Selector, withObjects objects: [AnyObject]?) { - let args: [Any!] = objects?.map{ $0 !== NSNull() ? ($0 as Any) : nil } ?? [] - asyncCall(selector, withArguments: args) + invoke(target, selector: selector, withArguments: arguments, onThread: thread, waitUntilDone: false) } // Syntactic sugar for calling method @@ -305,7 +268,7 @@ public func castToObjectFromAny(value: Any!) -> AnyObject! { if let v = value as? UnicodeScalar { return NSNumber(unsignedInt: v.value) } else if let s = value as? Selector { return s.description } else if let p = value as? COpaquePointer { return NSValue(pointer: UnsafePointer(p)) } - //assertionFailure("Can't convert '\(value.dynamicType)' to AnyObject") + assert(value is Void, "Can't convert '\(value.dynamicType)' to AnyObject") return nil } From ad5751b742e481c43a35eda99c3d2d3161feda96 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 15 Mar 2016 02:48:15 +0800 Subject: [PATCH 20/44] Plugin can be loaded into multiple WebViews Get current binding object through TLS instead of associated object. --- XWebView/XWVBindingObject.swift | 31 ++++++++++++------ XWebViewTests/ConstructorPlugin.swift | 47 +++++++++++++-------------- XWebViewTests/ObjectPlugin.swift | 13 +++++--- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 27ab095..4b41e7b 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -18,7 +18,6 @@ import Foundation import ObjectiveC final class XWVBindingObject : XWVScriptObject { - private let key = unsafeAddressOf(XWVScriptObject) unowned let channel: XWVChannel var plugin: AnyObject! @@ -49,8 +48,10 @@ final class XWVBindingObject : XWVScriptObject { arguments = [arguments] } - let args: [Any!] = arguments.map{ $0 is NSNull ? nil : ($0 as Any) } - plugin = XWVInvocation.construct(cls, initializer: selector, withArguments: args) + plugin = invoke(cls, selector: "alloc", withArguments: []) as? AnyObject + if plugin != nil { + plugin = performSelector(selector, withObjects: arguments) + } guard plugin != nil else { log("!Failed to create instance for plugin class \(cls)") return nil @@ -68,8 +69,6 @@ final class XWVBindingObject : XWVScriptObject { } private func bind() { - objc_setAssociatedObject(plugin, key, self, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - // Start KVO guard let plugin = plugin as? NSObject else { return } channel.typeInfo.filter{ $1.isProperty }.forEach { @@ -77,8 +76,6 @@ final class XWVBindingObject : XWVScriptObject { } } private func unbind() { - objc_setAssociatedObject(plugin, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - // Stop KVO guard plugin is NSObject else { return } channel.typeInfo.filter{ $1.isProperty }.forEach { @@ -156,13 +153,27 @@ final class XWVBindingObject : XWVScriptObject { } extension XWVBindingObject { + private static var key: pthread_key_t = { + var key = pthread_key_t() + pthread_key_create(&key, nil) + return key + }() + + private static var currentBindingObject: XWVBindingObject? { + let ptr = pthread_getspecific(XWVBindingObject.key) + guard ptr != nil else { return nil } + return unsafeBitCast(ptr, XWVBindingObject.self) + } private func performSelector(selector: Selector, withObjects arguments: [AnyObject]?, waitUntilDone wait: Bool = true) -> AnyObject! { var result: Any! = () let trampoline: dispatch_block_t = { [weak self] in guard let plugin = self?.plugin else { return } let args: [Any!] = arguments?.map{ $0 is NSNull ? nil : ($0 as Any) } ?? [] + let save = pthread_getspecific(XWVBindingObject.key) + pthread_setspecific(XWVBindingObject.key, unsafeAddressOf(self!)) result = castToObjectFromAny(invoke(plugin, selector: selector, withArguments: args)) + pthread_setspecific(XWVBindingObject.key, save) } if let queue = channel.queue { if !wait { @@ -190,8 +201,8 @@ extension XWVBindingObject { } } -public extension NSObject { - var scriptObject: XWVScriptObject? { - return objc_getAssociatedObject(self, unsafeAddressOf(XWVScriptObject)) as? XWVScriptObject +public extension XWVScriptObject { + static var bindingObject: XWVScriptObject? { + return XWVBindingObject.currentBindingObject } } diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index b5b29cd..890654c 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -19,28 +19,32 @@ import XCTest import XWebView class ConstructorPlugin : XWVTestCase { - class Plugin : NSObject, XWVScripting { - dynamic var property = 123 - private var expectation: XCTestExpectation?; - init(expectation: XCTestExpectation?) { - self.expectation = expectation + class Plugin0 : NSObject, XWVScripting { + init(expectation: AnyObject?) { + if let e = expectation as? XWVScriptObject { + e.callMethod("fulfill", withArguments: nil, completionHandler: nil) + } + } + class func scriptNameForSelector(selector: Selector) -> String? { + return selector == Selector("initWithExpectation:") ? "" : nil } + } + class Plugin1 : NSObject, XWVScripting { + dynamic let property: Int init(value: Int) { property = value } - func finalizeForScript() { - if property == 456 { - scriptObject?.webView?.evaluateJavaScript("fulfill('finalizeForScript')", completionHandler: nil) - } - } class func scriptNameForSelector(selector: Selector) -> String? { return selector == Selector("initWithValue:") ? "" : nil } } class Plugin2 : NSObject, XWVScripting { - override init() {} + private let expectation: XWVScriptObject? init(expectation: AnyObject?) { - (expectation as? XWVScriptObject)?.callMethod("fulfill", withArguments: nil, completionHandler: nil) + self.expectation = expectation as? XWVScriptObject + } + func finalizeForScript() { + expectation?.callMethod("fulfill", withArguments: nil, completionHandler: nil) } class func scriptNameForSelector(selector: Selector) -> String? { return selector == Selector("initWithExpectation:") ? "" : nil @@ -53,35 +57,28 @@ class ConstructorPlugin : XWVTestCase { let desc = "constructor" let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) + loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) waitForExpectationsWithTimeout(2, handler: nil) } func testConstruction() { let desc = "construction" - let script = "if (new \(namespace)(456) instanceof Promise) fulfill('\(desc)')" - _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) - } -/* func testConstruction2() { - let desc = "construction2" let script = "new \(namespace)(expectation('\(desc)'))" _ = expectationWithDescription(desc) - loadPlugin(Plugin2(), namespace: namespace, script: script) + loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) waitForExpectationsWithTimeout(2, handler: nil) - }*/ + } func testSyncProperties() { let desc = "syncProperties" let script = "(new \(namespace)(456)).then(function(o){if (o.property==456) fulfill('\(desc)');})" _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) + loadPlugin(Plugin1(value: 123), namespace: namespace, script: script) waitForExpectationsWithTimeout(2, handler: nil) } func testFinalizeForScript() { let desc = "finalizeForScript" - let script = "(new \(namespace)(456)).then(function(o){o.dispose();})" + let script = "(new \(namespace)(expectation('\(desc)'))).then(function(o){o.dispose();})" _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) + loadPlugin(Plugin2(expectation: nil), namespace: namespace, script: script) waitForExpectationsWithTimeout(2, handler: nil) } } diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index dc1f9c6..814e5ae 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -41,6 +41,13 @@ class ObjectPlugin : XWVTestCase { func method(promiseObject promiseObject: XWVScriptObject) { promiseObject.callMethod("resolve", withArguments: nil, completionHandler: nil) } + func method1() { + guard let bindingObject = XWVScriptObject.bindingObject else { return } + property = 456 + if (bindingObject["property"] as? NSNumber)?.integerValue == 456 { + expectation?.fulfill() + } + } init(expectation: XCTestExpectation?) { self.expectation = expectation } @@ -116,11 +123,7 @@ class ObjectPlugin : XWVTestCase { let desc = "scriptObject" let expectation = expectationWithDescription(desc) let plugin = Plugin(expectation: expectation) - loadPlugin(plugin as NSObject, namespace: namespace, script: "") { - (webView)->Void in - try! plugin.scriptObject?.callMethod("method", withArguments: nil) - return - } + loadPlugin(plugin, namespace: namespace, script: "\(namespace).method1();") waitForExpectationsWithTimeout(2, handler: nil) } } From faffb2bfc0d95f0224a32f743f5b0b81b261730f Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 26 Mar 2016 04:40:10 +0800 Subject: [PATCH 21/44] Migrate to Swift 2.2 --- .travis.yml | 2 +- README.md | 2 +- XWebView/XWVBindingObject.swift | 6 ++-- XWebView/XWVChannel.swift | 6 ++-- XWebView/XWVHttpConnection.swift | 18 ++++++---- XWebView/XWVHttpServer.swift | 13 +++++-- XWebView/XWVInvocation.swift | 46 +++++++++++++----------- XWebView/XWVLogging.swift | 12 +++---- XWebView/XWVMetaObject.swift | 51 ++++++++++++++------------- XWebView/XWebView.h | 26 ++++++++++++++ XWebView/XWebView.swift | 8 ++--- XWebViewTests/ConstructorPlugin.swift | 6 ++-- XWebViewTests/FunctionPlugin.swift | 2 +- XWebViewTests/XWVInvocationTest.swift | 46 ++++++++++++------------ XWebViewTests/XWVMetaObjectTest.swift | 22 ++++++------ XWebViewTests/XWVScriptingTest.swift | 2 +- XWebViewTests/XWVTestCase.swift | 3 +- 17 files changed, 159 insertions(+), 112 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f7e16a..a24be87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode7 +osx_image: xcode7.3 #xcode_sdk: iphonesimulator9.0 #xcode_project: XWebView.xcodeproj #xcode_scheme: XWebViewTests diff --git a/README.md b/README.md index 33b2629..b4bd5e4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ For more documents, please go to the project [Wiki](../../wiki). ## Minimum Requirements: -* Development: Xcode 7 +* Development: Xcode 7.3 * Deployment: iOS 8.0 ## License diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 4b41e7b..e2f6e76 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -44,11 +44,11 @@ final class XWVBindingObject : XWVScriptObject { promise = arguments.last as? XWVScriptObject arguments.removeLast() } - if selector == "initByScriptWithArguments:" { + if selector == Selector("initByScriptWithArguments:") { arguments = [arguments] } - plugin = invoke(cls, selector: "alloc", withArguments: []) as? AnyObject + plugin = invoke(cls, selector: #selector(_SpecialSelectors.alloc), withArguments: []) as? AnyObject if plugin != nil { plugin = performSelector(selector, withObjects: arguments) } @@ -95,7 +95,7 @@ final class XWVBindingObject : XWVScriptObject { guard let selector = channel.typeInfo[name]?.selector else { return } var args = arguments.map(wrapScriptObject) - if plugin is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { + if plugin is XWVScripting && name.isEmpty && selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:)) { args = [args]; } performSelector(selector, withObjects: args, waitUntilDone: false) diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 066a809..5812ba3 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -35,7 +35,8 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { struct sequence{ static var number: UInt = 0 } - return ++sequence.number + sequence.number += 1 + return sequence.number } private static var defaultQueue: dispatch_queue_t = { @@ -47,7 +48,8 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { self.init(webView: webView, queue: XWVChannel.defaultQueue) } public convenience init(webView: WKWebView, thread: NSThread) { - let runLoop = invoke(NSRunLoop.self, selector: "currentRunLoop", withArguments: [], onThread: thread) as! NSRunLoop + let selector = #selector(NSRunLoop.currentRunLoop) + let runLoop = invoke(NSRunLoop.self, selector: selector, withArguments: [], onThread: thread) as! NSRunLoop self.init(webView: webView, runLoop: runLoop) } diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift index be9009c..865e80e 100644 --- a/XWebView/XWVHttpConnection.swift +++ b/XWebView/XWVHttpConnection.swift @@ -114,7 +114,7 @@ extension XWVHttpConnection : NSStreamDelegate { } bytesConsumed += data.length } - ++ptr + ptr = ptr.successor() } if bytesConsumed > 0 { // Move remained bytes to the begining. @@ -189,13 +189,17 @@ private extension String { mutating func trim(@noescape predicate: (Character) -> Bool) { if !isEmpty { var start = startIndex - var end = endIndex.predecessor() - for var s = start; s != endIndex && predicate(self[s]); start = ++s {} - if start == endIndex { - self = "" - } else { - for var e = end; predicate(self[e]); end = --e {} + while start != endIndex && predicate(self[start]) { + start = start.successor() + } + if start < endIndex { + var end = endIndex + repeat { + end = end.predecessor() + } while predicate(self[end]) self = self[start ... end] + } else { + self = "" } } } diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index 045372a..6a4741a 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -80,7 +80,8 @@ class XWVHttpServer : NSObject { return false } - NSThread.detachNewThreadSelector(Selector("serverLoop:"), toTarget: self, withObject: nil) + let serverLoop = #selector(XWVHttpServer.serverLoop(_:)) + NSThread.detachNewThreadSelector(serverLoop, toTarget: self, withObject: nil) return true } @@ -112,8 +113,14 @@ class XWVHttpServer : NSObject { guard self.port != 0 else { return false } #if os(iOS) - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("suspend:"), name: UIApplicationDidEnterBackgroundNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("resume:"), name: UIApplicationWillEnterForegroundNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, + selector: #selector(XWVHttpServer.suspend(_:)), + name: UIApplicationDidEnterBackgroundNotification, + object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, + selector: #selector(XWVHttpServer.resume(_:)), + name: UIApplicationWillEnterForegroundNotification, + object: nil) #endif return true } diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index af7e555..ab3ef8a 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -26,8 +26,8 @@ public class XWVInvocation { self.thread = thread } - public class func construct(`class`: AnyClass, initializer: Selector = Selector("init"), withArguments arguments: [Any!] = []) -> AnyObject? { - let alloc = Selector("alloc") + public class func construct(`class`: AnyClass, initializer: Selector = #selector(NSObject.init), withArguments arguments: [Any!] = []) -> AnyObject? { + let alloc = #selector(_SpecialSelectors.alloc) guard let obj = invoke(`class`, selector: alloc, withArguments: []) as? AnyObject else { return nil } @@ -124,13 +124,14 @@ public func invoke(target: AnyObject, selector: Selector, withArguments argument NSException(name: NSInvalidArgumentException, reason: reason, userInfo: nil).raise() } - let sig = _NSMethodSignature.signatureWithObjCTypes(method_getTypeEncoding(method))! - let inv = _NSInvocation.invocationWithMethodSignature(sig) + let sig = (_NSMethodSignature as! _NSMethodSignatureFactory).signatureWithObjCTypes(method_getTypeEncoding(method)) + let inv = (_NSInvocation as! _NSInvocationFactory).invocationWithMethodSignature(sig) // Setup arguments - assert(arguments.count + 2 <= Int(sig.numberOfArguments), "Too many arguments for calling -[\(target.dynamicType) \(selector)]") + precondition(arguments.count + 2 <= Int(method_getNumberOfArguments(method)), + "Too many arguments for calling -[\(target.dynamicType) \(selector)]") var args = [[Int]](count: arguments.count, repeatedValue: []) - for var i = 0; i < arguments.count; ++i { + for i in 0 ..< arguments.count { let type = sig.getArgumentTypeAtIndex(i + 2) let typeChar = Character(UnicodeScalar(UInt8(type[0]))) @@ -156,7 +157,7 @@ public func invoke(target: AnyObject, selector: Selector, withArguments argument // Nil or unsupported type assert(argument == nil, "Unsupported argument type '\(String(UTF8String: type))'") var align: Int = 0 - NSGetSizeAndAlignment(sig.getArgumentTypeAtIndex(i), nil, &align) + NSGetSizeAndAlignment(type, nil, &align) args[i] = [Int](count: align / sizeof(Int), repeatedValue: 0) } args[i].withUnsafeBufferPointer { @@ -173,7 +174,7 @@ public func invoke(target: AnyObject, selector: Selector, withArguments argument if thread == nil || (thread == NSThread.currentThread() && wait) { inv.invokeWithTarget(target) } else { - let selector = Selector("invokeWithTarget:") + let selector = #selector(_SpecialSelectors.invokeWithTarget(_:)) inv.retainArguments() inv.performSelector(selector, onThread: thread!, withObject: target, waitUntilDone: wait) guard wait else { return Void() } @@ -257,18 +258,21 @@ public func castToObjectFromAny(value: Any!) -> AnyObject! { return value as? AnyObject } - if let v = value as? Int8 { return NSNumber(char: v) } else - if let v = value as? Int16 { return NSNumber(short: v) } else - if let v = value as? Int32 { return NSNumber(int: v) } else - if let v = value as? Int64 { return NSNumber(longLong: v) } else - if let v = value as? UInt8 { return NSNumber(unsignedChar: v) } else - if let v = value as? UInt16 { return NSNumber(unsignedShort: v) } else - if let v = value as? UInt32 { return NSNumber(unsignedInt: v) } else - if let v = value as? UInt64 { return NSNumber(unsignedLongLong: v) } else - if let v = value as? UnicodeScalar { return NSNumber(unsignedInt: v.value) } else - if let s = value as? Selector { return s.description } else - if let p = value as? COpaquePointer { return NSValue(pointer: UnsafePointer(p)) } - assert(value is Void, "Can't convert '\(value.dynamicType)' to AnyObject") + switch value { + case let v as Int8: return NSNumber(char: v) + case let v as Int16: return NSNumber(short: v) + case let v as Int32: return NSNumber(int: v) + case let v as Int64: return NSNumber(longLong: v) + case let v as UInt8: return NSNumber(unsignedChar: v) + case let v as UInt16: return NSNumber(unsignedShort: v) + case let v as UInt32: return NSNumber(unsignedInt: v) + case let v as UInt64: return NSNumber(unsignedLongLong: v) + case let v as UnicodeScalar: return NSNumber(unsignedInt: v.value) + case let s as Selector: return String(s) + case let p as COpaquePointer: return NSValue(pointer: UnsafePointer(p)) + default: + assert(value is Void, "Can't convert '\(value.dynamicType)' to AnyObject") + } return nil } @@ -308,7 +312,7 @@ private extension Selector { var family: Family { // See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#id34 var s = unsafeBitCast(self, UnsafePointer.self) - while s.memory == 0x5f { ++s } // skip underscore + while s.memory == 0x5f { s += 1 } // skip underscore for p in Selector.prefixes { let lowercase: Range = 97...122 let l = p.count diff --git a/XWebView/XWVLogging.swift b/XWebView/XWVLogging.swift index e09727a..945dde9 100644 --- a/XWebView/XWVLogging.swift +++ b/XWebView/XWVLogging.swift @@ -18,11 +18,11 @@ import Darwin public typealias asl_object_t = COpaquePointer -@asmname("asl_open") func asl_open(ident: UnsafePointer, _ facility: UnsafePointer, _ opts: UInt32) -> asl_object_t; -@asmname("asl_close") func asl_close(obj: asl_object_t); -@asmname("asl_vlog") func asl_vlog(obj: asl_object_t, _ msg: asl_object_t, _ level: Int32, _ format: UnsafePointer, _ ap: CVaListPointer) -> Int32; -@asmname("asl_add_output_file") func asl_add_output_file(client: asl_object_t, _ descriptor: Int32, _ msg_fmt: UnsafePointer, _ time_fmt: UnsafePointer, _ filter: Int32, _ text_encoding: Int32) -> Int32; -@asmname("asl_set_output_file_filter") func asl_set_output_file_filter(asl: asl_object_t, _ descriptor: Int32, _ filter: Int32) -> Int32; +@_silgen_name("asl_open") func asl_open(ident: UnsafePointer, _ facility: UnsafePointer, _ opts: UInt32) -> asl_object_t +@_silgen_name("asl_close") func asl_close(obj: asl_object_t) +@_silgen_name("asl_vlog") func asl_vlog(obj: asl_object_t, _ msg: asl_object_t, _ level: Int32, _ format: UnsafePointer, _ ap: CVaListPointer) -> Int32 +@_silgen_name("asl_add_output_file") func asl_add_output_file(client: asl_object_t, _ descriptor: Int32, _ msg_fmt: UnsafePointer, _ time_fmt: UnsafePointer, _ filter: Int32, _ text_encoding: Int32) -> Int32 +@_silgen_name("asl_set_output_file_filter") func asl_set_output_file_filter(asl: asl_object_t, _ descriptor: Int32, _ filter: Int32) -> Int32 public class XWVLogging : XWVScripting { public enum Level : Int32 { @@ -129,7 +129,7 @@ func log(message: String, level: XWVLogging.Level? = nil) { logger.log(message, level: level) } -@noreturn func die(@autoclosure message: ()->String, file: StaticString = __FILE__, line: UInt = __LINE__) { +@noreturn func die(@autoclosure message: ()->String, file: StaticString = #file, line: UInt = #line) { logger.log(message(), level: .Alert) fatalError(message, file: file, line: line) } diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index d372650..7ac713a 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -87,29 +87,26 @@ class XWVMetaObject: CollectionType { private var members = [String: Member]() private static let exclusion: Set = { var methods = instanceMethods(forProtocol: XWVScripting.self) - methods.remove(Selector("invokeDefaultMethodWithArguments:")) + methods.remove(#selector(XWVScripting.invokeDefaultMethodWithArguments(_:))) return methods.union([ - Selector(".cxx_construct"), - Selector(".cxx_destruct"), - Selector("dealloc"), - Selector("copy") + #selector(_SpecialSelectors.dealloc), + #selector(NSObject.copy as ()->AnyObject) ]) }() init(plugin: AnyClass) { self.plugin = plugin enumerateExcluding(self.dynamicType.exclusion) { - (var name, var member) -> Bool in + (name, member) -> Bool in + var name = name + var member = member switch member { case let .Method(selector, _): - if let end = name.characters.indexOf(":") { - name = name[name.startIndex ..< end] - } if let cls = plugin as? XWVScripting.Type { if cls.isSelectorExcludedFromScript?(selector) ?? false { return true } - if selector == Selector("invokeDefaultMethodWithArguments:") { + if selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:)) { member = .Method(selector: selector, arity: -1) name = "" } else { @@ -152,15 +149,17 @@ class XWVMetaObject: CollectionType { var known = selectors // enumerate properties - let properties = class_copyPropertyList(plugin, nil) - if properties != nil { - for var prop = properties; prop.memory != nil; prop = prop.successor() { + let propertyList = class_copyPropertyList(plugin, nil) + if propertyList != nil, var prop = Optional(propertyList) { + defer { free(propertyList) } + while prop.memory != nil { let name = String(UTF8String: property_getName(prop.memory))! // get getter var attr = property_copyAttributeValue(prop.memory, "G") let getter = Selector(attr == nil ? name : String(UTF8String: attr)!) free(attr) if known.contains(getter) { + prop = prop.successor() continue } known.insert(getter) @@ -185,19 +184,19 @@ class XWVMetaObject: CollectionType { let info = Member.Property(getter: getter, setter: setter) if !callback(name, info) { - free(properties) return false } + prop = prop.successor() } - free(properties) } // enumerate methods - let methods = class_copyMethodList(plugin, nil) - if methods != nil { - for var method = methods; method.memory != nil; method = method.successor() { + let methodList = class_copyMethodList(plugin, nil) + if methodList != nil, var method = Optional(methodList) { + defer { free(methodList) } + while method.memory != nil { let sel = method_getName(method.memory) - if !known.contains(sel) { + if !known.contains(sel) && !sel.description.hasPrefix(".") { let arity = Int32(method_getNumberOfArguments(method.memory) - 2) let member: Member if sel.description.hasPrefix("init") { @@ -205,13 +204,16 @@ class XWVMetaObject: CollectionType { } else { member = Member.Method(selector: sel, arity: arity) } - if !callback(sel.description, member) { - free(methods) + var name = sel.description + if let end = name.characters.indexOf(":") { + name = name[name.startIndex ..< end] + } + if !callback(name, member) { return false } } + method = method.successor() } - free(methods) } return true } @@ -244,9 +246,10 @@ private func instanceMethods(forProtocol aProtocol: Protocol) -> Set { var selectors = Set() for (req, inst) in [(true, true), (false, true)] { let methodList = protocol_copyMethodDescriptionList(aProtocol.self, req, inst, nil) - if methodList != nil { - for var desc = methodList; desc.memory.name != nil; desc = desc.successor() { + if methodList != nil, var desc = Optional(methodList) { + while desc.memory.name != nil { selectors.insert(desc.memory.name) + desc = desc.successor() } free(methodList) } diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index be97db7..95899c0 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -29,3 +29,29 @@ FOUNDATION_EXPORT const unsigned char XWebViewVersionString[]; @interface WKWebView (XWebView) - (nullable WKNavigation *)loadFileURL:(nonnull NSURL *)URL allowingReadAccessToURL:(nonnull NSURL *)readAccessURL; @end + +NS_ASSUME_NONNULL_BEGIN + +// The workaround for using NSInvocation and NSMethodSignature in Swift. +@protocol _NSMethodSignatureFactory +- (NSMethodSignature *)signatureWithObjCTypes:(const char *)types; +@end +@interface NSMethodSignature (Swift) <_NSMethodSignatureFactory> +@end + +@protocol _NSInvocationFactory +- (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; +@end +@interface NSInvocation (Swift) <_NSInvocationFactory> +@end + +// Special selectors which can't be referenced directly in Swift. +@protocol _SpecialSelectors +// NSObject +- (instancetype)alloc; +- (void)dealloc; +// NSInvocation +- (void)invokeWithTarget:(id)target; +@end + +NS_ASSUME_NONNULL_END diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 01a1c0d..a08af31 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -141,14 +141,14 @@ extension WKWebView { //if #available(iOS 9, *) { return } guard self == WKWebView.self else { return } dispatch_once(&initialized) { - let selector = Selector("loadFileURL:allowingReadAccessToURL:") - let method = class_getInstanceMethod(self, Selector("_loadFileURL:allowingReadAccessToURL:")) + let selector = #selector(WKWebView.loadFileURL(_:allowingReadAccessToURL:)) + let method = class_getInstanceMethod(self, #selector(WKWebView._loadFileURL(_:allowingReadAccessToURL:))) assert(method != nil) if class_addMethod(self, selector, method_getImplementation(method), method_getTypeEncoding(method)) { log("+Running on iOS 8.x") method_exchangeImplementations( - class_getInstanceMethod(self, Selector("loadHTMLString:baseURL:")), - class_getInstanceMethod(self, Selector("_loadHTMLString:baseURL:")) + class_getInstanceMethod(self, #selector(WKWebView.loadHTMLString(_:baseURL:))), + class_getInstanceMethod(self, #selector(WKWebView._loadHTMLString(_:baseURL:))) ) } } diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index 890654c..58d036d 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -26,7 +26,7 @@ class ConstructorPlugin : XWVTestCase { } } class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithExpectation:") ? "" : nil + return selector == #selector(Plugin0.init(expectation:)) ? "" : nil } } class Plugin1 : NSObject, XWVScripting { @@ -35,7 +35,7 @@ class ConstructorPlugin : XWVTestCase { property = value } class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithValue:") ? "" : nil + return selector == #selector(Plugin1.init(value:)) ? "" : nil } } class Plugin2 : NSObject, XWVScripting { @@ -47,7 +47,7 @@ class ConstructorPlugin : XWVTestCase { expectation?.callMethod("fulfill", withArguments: nil, completionHandler: nil) } class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithExpectation:") ? "" : nil + return selector == #selector(Plugin2.init(expectation:)) ? "" : nil } } diff --git a/XWebViewTests/FunctionPlugin.swift b/XWebViewTests/FunctionPlugin.swift index b1a550f..5cda244 100644 --- a/XWebViewTests/FunctionPlugin.swift +++ b/XWebViewTests/FunctionPlugin.swift @@ -29,7 +29,7 @@ class FunctionPlugin : XWVTestCase { expectation?.fulfill() } class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("defaultMethod") ? "" : nil + return selector == #selector(Plugin.defaultMethod) ? "" : nil } } diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index d2f046e..db022ae 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -79,35 +79,35 @@ class InvocationTests : XCTestCase { #endif func testMethods() { - XCTAssertTrue(inv[Selector("dummy")]() is Void) + XCTAssertTrue(inv[ #selector(InvocationTarget.dummy)]() is Void) #if arch(x86_64) || arch(arm64) - XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Bool == true) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(bool:))](Bool(true)) as? Bool == true) #else // http://stackoverflow.com/questions/26459754/bool-encoding-wrong-from-nsmethodsignature XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Int8 == 1) #endif - XCTAssertTrue(inv[Selector("echoWithInt:")](Int(-11)) as? XInt == -11) - XCTAssertTrue(inv[Selector("echoWithInt8:")](Int8(-22)) as? Int8 == -22) - XCTAssertTrue(inv[Selector("echoWithInt16:")](Int16(-33)) as? Int16 == -33) - XCTAssertTrue(inv[Selector("echoWithInt32:")](Int32(-44)) as? Int32 == -44) - XCTAssertTrue(inv[Selector("echoWithInt64:")](Int64(-55)) as? Int64 == -55) - XCTAssertTrue(inv[Selector("echoWithUint:")](UInt(11)) as? XUInt == 11) - XCTAssertTrue(inv[Selector("echoWithUint8:")](UInt8(22)) as? UInt8 == 22) - XCTAssertTrue(inv[Selector("echoWithUint16:")](UInt16(33)) as? UInt16 == 33) - XCTAssertTrue(inv[Selector("echoWithUint32:")](UInt32(44)) as? UInt32 == 44) - XCTAssertTrue(inv[Selector("echoWithUint64:")](UInt64(55)) as? UInt64 == 55) - XCTAssertTrue(inv[Selector("echoWithFloat:")](Float(12.34)) as? Float == 12.34) - XCTAssertTrue(inv[Selector("echoWithDouble:")](Double(-56.78)) as? Double == -56.78) - XCTAssertTrue(inv[Selector("echoWithUnicode:")](UnicodeScalar(78)) as? Int32 == 78) - XCTAssertTrue(inv[Selector("echoWithString:")]("abc") as? String == "abc") - let selector = Selector("echoWithSelector:") + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int:))](Int(-11)) as? XInt == -11) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int8:))](Int8(-22)) as? Int8 == -22) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int16:))](Int16(-33)) as? Int16 == -33) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int32:))](Int32(-44)) as? Int32 == -44) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int64:))](Int64(-55)) as? Int64 == -55) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint:))](UInt(11)) as? XUInt == 11) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint8:))](UInt8(22)) as? UInt8 == 22) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint16:))](UInt16(33)) as? UInt16 == 33) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint32:))](UInt32(44)) as? UInt32 == 44) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint64:))](UInt64(55)) as? UInt64 == 55) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(float:))](Float(12.34)) as? Float == 12.34) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(double:))](Double(-56.78)) as? Double == -56.78) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(unicode:))](UnicodeScalar(78)) as? Int32 == 78) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(string:))]("abc") as? String == "abc") + let selector = #selector(InvocationTarget.echo(selector:)) XCTAssertTrue(inv[selector](selector) as? Selector == selector) let cls = self.dynamicType - XCTAssertTrue(inv[Selector("echoWithClass:")](cls) as? AnyClass === cls) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(class:))](cls) as? AnyClass === cls) - XCTAssertTrue(inv[Selector("convert:")](UInt8(12)) as? XInt == 12) - XCTAssertTrue(inv[Selector("add::")](2, 3) as? XInt == 5) - XCTAssertTrue(inv[Selector("concat::")]("ab", "cd") as? String == "abcd") + XCTAssertTrue(inv[ #selector(InvocationTarget.convert(_:))](UInt8(12)) as? XInt == 12) + XCTAssertTrue(inv[ #selector(InvocationTarget.add(_:_:))](2, 3) as? XInt == 5) + XCTAssertTrue(inv[ #selector(InvocationTarget.concat(_:_:))]("ab", "cd") as? String == "abcd") } func testProperty() { @@ -119,7 +119,7 @@ class InvocationTests : XCTestCase { func testLeak1() { autoreleasepool { let expectation = expectationWithDescription("leak") - let obj = inv[Selector("_new:")](expectation) as? InvocationTarget.LeakTest + let obj = inv[ #selector(InvocationTarget._new(_:))](expectation) as? InvocationTarget.LeakTest XCTAssertEqual(expectation, obj!.expectation) } waitForExpectationsWithTimeout(2, handler: nil) @@ -128,7 +128,7 @@ class InvocationTests : XCTestCase { func testLeak2() { autoreleasepool { let expectation = expectationWithDescription("leak") - let obj = XWVInvocation.construct(InvocationTarget.LeakTest.self, initializer: Selector("initWithExpectation:"), withArguments: [expectation]) as? InvocationTarget.LeakTest + let obj = XWVInvocation.construct(InvocationTarget.LeakTest.self, initializer: #selector(InvocationTarget.LeakTest.init(expectation:)), withArguments: [expectation]) as? InvocationTarget.LeakTest XCTAssertEqual(expectation, obj!.expectation) } waitForExpectationsWithTimeout(2, handler: nil) diff --git a/XWebViewTests/XWVMetaObjectTest.swift b/XWebViewTests/XWVMetaObjectTest.swift index 98d7753..bd07996 100644 --- a/XWebViewTests/XWVMetaObjectTest.swift +++ b/XWebViewTests/XWVMetaObjectTest.swift @@ -28,14 +28,14 @@ class XWVMetaObjectTest: XCTestCase { let meta = XWVMetaObject(plugin: TestForMethod.self) if let member = meta["method"] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("method")) + XCTAssertTrue(member.selector == #selector(TestForMethod.method as (TestForMethod) -> () -> ())) XCTAssertTrue(member.type == "#0a") } else { XCTFail() } if let member = meta["methodWithArgument"] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("methodWithArgument:")) + XCTAssertTrue(member.selector == #selector(TestForMethod.method(argument:))) XCTAssertTrue(member.type == "#1a") } else { XCTFail() @@ -76,14 +76,14 @@ class XWVMetaObjectTest: XCTestCase { let meta = XWVMetaObject(plugin: TestForPromise.self) if let member = meta["methodWithPromiseObject"] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("methodWithPromiseObject:")) + XCTAssertTrue(member.selector == #selector(TestForPromise.method(promiseObject:))) XCTAssertTrue(member.type == "#1p") } else { XCTFail() } if let member = meta["methodWithArgument"] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("methodWithArgument:promiseObject:")) + XCTAssertTrue(member.selector == #selector(TestForPromise.method(argument:promiseObject:))) XCTAssertTrue(member.type == "#2p") } else { XCTFail() @@ -94,7 +94,7 @@ class XWVMetaObjectTest: XCTestCase { @objc let property = 0 @objc func method() {} @objc class func isSelectorExcludedFromScript(selector: Selector) -> Bool { - return selector == Selector("method") + return selector == #selector(TestForExclusion.method) } @objc class func isKeyExcludedFromScript(name: UnsafePointer) -> Bool { return String(UTF8String: name) == "property" @@ -109,13 +109,13 @@ class XWVMetaObjectTest: XCTestCase { class TestForFunction : XWVScripting { @objc func defaultMethod() {} @objc class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("defaultMethod") ? "" : nil + return selector == #selector(TestForFunction.defaultMethod) ? "" : nil } } let meta = XWVMetaObject(plugin: TestForFunction.self) if let member = meta[""] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("defaultMethod")) + XCTAssertTrue(member.selector == #selector(TestForFunction.defaultMethod)) XCTAssertTrue(member.type == "#0a") } else { XCTFail() @@ -131,7 +131,7 @@ class XWVMetaObjectTest: XCTestCase { let meta = XWVMetaObject(plugin: TestForFunction.self) if let member = meta[""] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == Selector("invokeDefaultMethodWithArguments:")) + XCTAssertTrue(member.selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:))) XCTAssertTrue(member.type == "") } else { XCTFail() @@ -142,13 +142,13 @@ class XWVMetaObjectTest: XCTestCase { class TestForConstructor : XWVScripting { @objc init(argument: AnyObject?) {} @objc class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithArgument:") ? "" : nil + return selector == #selector(TestForConstructor.init(argument:)) ? "" : nil } } let meta = XWVMetaObject(plugin: TestForConstructor.self) if let member = meta[""] { XCTAssertTrue(member.isInitializer) - XCTAssertTrue(member.selector == Selector("initWithArgument:")) + XCTAssertTrue(member.selector == #selector(TestForConstructor.init(argument:))) XCTAssertTrue(member.type == "#2p") } else { XCTFail() @@ -162,7 +162,7 @@ class XWVMetaObjectTest: XCTestCase { let meta = XWVMetaObject(plugin: TestForConstructor.self) if let member = meta[""] { XCTAssertTrue(member.isInitializer) - XCTAssertTrue(member.selector == Selector("initByScriptWithArguments:")) + XCTAssertTrue(member.selector == #selector(TestForConstructor.init(byScriptWithArguments:))) XCTAssertTrue(member.type == "#p") } else { XCTFail() diff --git a/XWebViewTests/XWVScriptingTest.swift b/XWebViewTests/XWVScriptingTest.swift index 5b20827..e1e6b87 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -35,7 +35,7 @@ class XWVScriptingTest : XWVTestCase { expectation?.fulfill() } class func isSelectorExcludedFromScript(selector: Selector) -> Bool { - return selector == Selector("initWithExpectation:") + return selector == #selector(Plugin.init(expectation:)) } class func isKeyExcludedFromScript(name: UnsafePointer) -> Bool { return String(UTF8String: name) == "expectation" diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index 383c526..96ce861 100644 --- a/XWebViewTests/XWVTestCase.swift +++ b/XWebViewTests/XWVTestCase.swift @@ -21,7 +21,8 @@ import XWebView extension XCTestExpectation : XWVScripting { public class func isSelectorExcludedFromScript(selector: Selector) -> Bool { - return selector != Selector("fulfill") && selector != Selector("description") + return selector != #selector(XCTestExpectation.fulfill) && + selector != #selector(NSObject.description as () -> String) } public class func isKeyExcludedFromScript(name: UnsafePointer) -> Bool { return true From 57184daf9a0723afd75ff2496cd45675441bf2c2 Mon Sep 17 00:00:00 2001 From: James Hartt Date: Tue, 29 Mar 2016 20:13:21 +0100 Subject: [PATCH 22/44] Add _InitSelector trick with va_list to be used in Swift To remove the final warning in Swift2.2 --- XWebView/XWVBindingObject.swift | 2 +- XWebView/XWVMetaObject.swift | 2 +- XWebView/XWebView.h | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index e2f6e76..9c51e15 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -44,7 +44,7 @@ final class XWVBindingObject : XWVScriptObject { promise = arguments.last as? XWVScriptObject arguments.removeLast() } - if selector == Selector("initByScriptWithArguments:") { + if selector == #selector(_InitSelector.init(byScriptWithArguments:)) { arguments = [arguments] } diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index 7ac713a..630da60 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -129,7 +129,7 @@ class XWVMetaObject: CollectionType { } case let .Initializer(selector, _): - if selector == Selector("initByScriptWithArguments:") { + if selector == #selector(_InitSelector.init(byScriptWithArguments:)) { member = .Initializer(selector: selector, arity: -1) name = "" } else if let cls = plugin as? XWVScripting.Type { diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index 95899c0..caa882c 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -54,4 +54,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)invokeWithTarget:(id)target; @end +// Special init which can't be reference directly in Swift, but cannot be a protocol either. +@interface _InitSelector: NSObject +// Init with script +- (id)initByScriptWithArguments:(va_list)args; +@end + NS_ASSUME_NONNULL_END From 0ec9bb98fc6560a30166e64c54692924868ada97 Mon Sep 17 00:00:00 2001 From: James Hartt Date: Fri, 1 Apr 2016 11:32:36 +0100 Subject: [PATCH 23/44] Replace va_list with NSArray * --- XWebView/XWebView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index caa882c..7d5dcf6 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -57,7 +57,7 @@ NS_ASSUME_NONNULL_BEGIN // Special init which can't be reference directly in Swift, but cannot be a protocol either. @interface _InitSelector: NSObject // Init with script -- (id)initByScriptWithArguments:(va_list)args; +- (id)initByScriptWithArguments:(NSArray *)args; @end NS_ASSUME_NONNULL_END From 4d00404d809773aa50e996defc77dac4d9e03f8a Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 5 Apr 2016 01:18:30 +0800 Subject: [PATCH 24/44] Bump to version 0.9.5 and update the podspec Subspecs are removed. --- XWebView.podspec | 15 ++++----------- XWebView/Info.plist | 2 +- XWebView/XWVScriptObject.swift | 1 + XWebViewTests/Info.plist | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/XWebView.podspec b/XWebView.podspec index d47cd2f..ad047e6 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.9.4" + s.version = "0.9.5" s.summary = "An extensible WebView (based on WKWebView) for iOS." s.description = <<-DESC @@ -89,8 +89,8 @@ Pod::Spec.new do |s| # Not including the public_header_files will make all headers public. # - s.source_files = "XWebView/*.swift" - s.exclude_files = "XWebView/XWVInvocation.swift", "XWebView/XWVHttp*.swift" + s.source_files = "XWebView/*.swift", "XWebView/XWebView.h" + # s.exclude_files = "Classes/Exclude" # s.public_header_files = "Classes/**/*.h" @@ -116,6 +116,7 @@ Pod::Spec.new do |s| # s.framework = "WebKit" + s.ios.framework = "MobileCoreServices" # s.frameworks = "SomeFramework", "AnotherFramework" # s.library = "iconv" @@ -133,12 +134,4 @@ Pod::Spec.new do |s| # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } # s.dependency "JSONKit", "~> 1.4" - s.subspec "Invocation" do |sp| - sp.source_files = "XWebView/XWVInvocation.swift" - end - - s.subspec "HttpServer" do |sp| - sp.source_files = "XWebView/XWVHttp*.swift" - sp.framework = "MobileCoreServices" - end end diff --git a/XWebView/Info.plist b/XWebView/Info.plist index f8e7bb6..39049e5 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.4 + 0.9.5 CFBundleSignature ???? CFBundleVersion diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index 016f297..a276d35 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -15,6 +15,7 @@ */ import Foundation +import WebKit public class XWVScriptObject : XWVObject { // JavaScript object operations diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index 76d6fb4..e4789f0 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.9.4 + 0.9.5 CFBundleSignature ???? CFBundleVersion From f93ac7c8cec4f5ee182ca3e9dfc0c93f49ed049a Mon Sep 17 00:00:00 2001 From: yeti23 Date: Wed, 20 Apr 2016 18:26:29 +0200 Subject: [PATCH 25/44] Port to OS X Fixed #32 --- XWebView.xcodeproj/project.pbxproj | 158 ++++++++++++++++++++++++++++- XWebViewX/Info.plist | 28 +++++ XWebViewX/XWebViewX.h | 65 ++++++++++++ 3 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 XWebViewX/Info.plist create mode 100644 XWebViewX/XWebViewX.h diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index f4bf3c9..9204548 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -7,6 +7,20 @@ objects = { /* Begin PBXBuildFile section */ + 663C48C51CC034270031251A /* XWebViewX.h in Headers */ = {isa = PBXBuildFile; fileRef = 663C48C41CC034270031251A /* XWebViewX.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 663C48CA1CC034F10031251A /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; }; + 663C48CB1CC034F50031251A /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; }; + 663C48CC1CC034F80031251A /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; + 663C48CD1CC034FC0031251A /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; + 663C48CE1CC035090031251A /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE131CA61B5F900400A9E790 /* XWVUserScript.swift */; }; + 663C48CF1CC0350C0031251A /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; + 663C48D01CC0350F0031251A /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */; }; + 663C48D11CC035130031251A /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; + 663C48D21CC035160031251A /* xwebview.js in Resources */ = {isa = PBXBuildFile; fileRef = EE174E771A0361CB00168D96 /* xwebview.js */; }; + 663C48D31CC035190031251A /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */; }; + 663C48D41CC0351D0031251A /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */; }; + 663C48D51CC035200031251A /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; + 663C48D61CC035230031251A /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */; }; AB023EA51A8C506600580A2A /* XWebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB023EA41A8C506600580A2A /* XWebViewTests.swift */; }; AB023EA61A8C506600580A2A /* XWebView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE62683519FA323900EFC3F8 /* XWebView.framework */; }; AB023EBE1A8C8BC700580A2A /* XWebView.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EE62683519FA323900EFC3F8 /* XWebView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -21,11 +35,11 @@ EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3379381AE2E298009124A4 /* XWVTestCase.swift */; }; EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; - EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; settings = {ASSET_TAGS = (); }; }; + EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; }; EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; - EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; settings = {ASSET_TAGS = (); }; }; - EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; settings = {ASSET_TAGS = (); }; }; + EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; }; + EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */; }; EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; @@ -59,6 +73,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 663C48C21CC034270031251A /* XWebViewX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebViewX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 663C48C41CC034270031251A /* XWebViewX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XWebViewX.h; sourceTree = ""; }; + 663C48C61CC034270031251A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB023EA01A8C506600580A2A /* XWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AB023EA31A8C506600580A2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB023EA41A8C506600580A2A /* XWebViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XWebViewTests.swift; sourceTree = ""; }; @@ -90,6 +107,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 663C48BE1CC034270031251A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AB023E9D1A8C506600580A2A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -109,6 +133,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 663C48C31CC034270031251A /* XWebViewX */ = { + isa = PBXGroup; + children = ( + 663C48C41CC034270031251A /* XWebViewX.h */, + 663C48C61CC034270031251A /* Info.plist */, + ); + path = XWebViewX; + sourceTree = ""; + }; AB023EA11A8C506600580A2A /* XWebViewTests */ = { isa = PBXGroup; children = ( @@ -140,6 +173,7 @@ EEF27EB61AFA1D89004740CF /* WebKit.framework */, EE62683719FA323900EFC3F8 /* XWebView */, AB023EA11A8C506600580A2A /* XWebViewTests */, + 663C48C31CC034270031251A /* XWebViewX */, EE62683619FA323900EFC3F8 /* Products */, ); sourceTree = ""; @@ -149,6 +183,7 @@ children = ( EE62683519FA323900EFC3F8 /* XWebView.framework */, AB023EA01A8C506600580A2A /* XWebViewTests.xctest */, + 663C48C21CC034270031251A /* XWebViewX.framework */, ); name = Products; sourceTree = ""; @@ -186,6 +221,14 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 663C48BF1CC034270031251A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 663C48C51CC034270031251A /* XWebViewX.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE62683219FA323900EFC3F8 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -197,6 +240,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 663C48C11CC034270031251A /* XWebViewX */ = { + isa = PBXNativeTarget; + buildConfigurationList = 663C48C71CC034270031251A /* Build configuration list for PBXNativeTarget "XWebViewX" */; + buildPhases = ( + 663C48BD1CC034270031251A /* Sources */, + 663C48BE1CC034270031251A /* Frameworks */, + 663C48BF1CC034270031251A /* Headers */, + 663C48C01CC034270031251A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XWebViewX; + productName = XWebViewX; + productReference = 663C48C21CC034270031251A /* XWebViewX.framework */; + productType = "com.apple.product-type.framework"; + }; AB023E9F1A8C506600580A2A /* XWebViewTests */ = { isa = PBXNativeTarget; buildConfigurationList = AB023EAB1A8C506600580A2A /* Build configuration list for PBXNativeTarget "XWebViewTests" */; @@ -241,10 +302,13 @@ isa = PBXProject; attributes = { LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0700; + LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0700; ORGANIZATIONNAME = XWebView; TargetAttributes = { + 663C48C11CC034270031251A = { + CreatedOnToolsVersion = 7.3; + }; AB023E9F1A8C506600580A2A = { CreatedOnToolsVersion = 6.1; }; @@ -268,11 +332,20 @@ targets = ( EE62683419FA323900EFC3F8 /* XWebView */, AB023E9F1A8C506600580A2A /* XWebViewTests */, + 663C48C11CC034270031251A /* XWebViewX */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 663C48C01CC034270031251A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 663C48D21CC035160031251A /* xwebview.js in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AB023E9E1A8C506600580A2A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -292,6 +365,25 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 663C48BD1CC034270031251A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 663C48CA1CC034F10031251A /* XWVLogging.swift in Sources */, + 663C48D01CC0350F0031251A /* XWVChannel.swift in Sources */, + 663C48D11CC035130031251A /* XWebView.swift in Sources */, + 663C48CD1CC034FC0031251A /* XWVInvocation.swift in Sources */, + 663C48CB1CC034F50031251A /* XWVHttpConnection.swift in Sources */, + 663C48CF1CC0350C0031251A /* XWVMetaObject.swift in Sources */, + 663C48D41CC0351D0031251A /* XWVObject.swift in Sources */, + 663C48CC1CC034F80031251A /* XWVHttpServer.swift in Sources */, + 663C48D61CC035230031251A /* XWVBindingObject.swift in Sources */, + 663C48D51CC035200031251A /* XWVScriptObject.swift in Sources */, + 663C48D31CC035190031251A /* XWVScripting.swift in Sources */, + 663C48CE1CC035090031251A /* XWVUserScript.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AB023E9C1A8C506600580A2A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -337,6 +429,55 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 663C48C81CC034270031251A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = XWebViewX/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewX; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 663C48C91CC034270031251A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = XWebViewX/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewX; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; AB023EA91A8C506600580A2A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -489,6 +630,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 663C48C71CC034270031251A /* Build configuration list for PBXNativeTarget "XWebViewX" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 663C48C81CC034270031251A /* Debug */, + 663C48C91CC034270031251A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; AB023EAB1A8C506600580A2A /* Build configuration list for PBXNativeTarget "XWebViewTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/XWebViewX/Info.plist b/XWebViewX/Info.plist new file mode 100644 index 0000000..6344153 --- /dev/null +++ b/XWebViewX/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 0.9.5 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 XWebView. All rights reserved. + NSPrincipalClass + + + diff --git a/XWebViewX/XWebViewX.h b/XWebViewX/XWebViewX.h new file mode 100644 index 0000000..8006ef1 --- /dev/null +++ b/XWebViewX/XWebViewX.h @@ -0,0 +1,65 @@ +/* + Copyright 2015 XWebView + + Licensed 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 + +//! Project version number for XWebViewX. +FOUNDATION_EXPORT double XWebViewXVersionNumber; + +//! Project version string for XWebViewX. +FOUNDATION_EXPORT const unsigned char XWebViewXVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +// The workaround for loading file URL on OS X 10.10.x. +#import +@interface WKWebView (XWebView) +- (nullable WKNavigation *)loadFileURL:(nonnull NSURL *)URL allowingReadAccessToURL:(nonnull NSURL *)readAccessURL; +@end + + +NS_ASSUME_NONNULL_BEGIN + +// The workaround for using NSInvocation and NSMethodSignature in Swift. +@protocol _NSMethodSignatureFactory +- (NSMethodSignature *)signatureWithObjCTypes:(const char *)types; +@end +@interface NSMethodSignature (Swift) <_NSMethodSignatureFactory> +@end + +@protocol _NSInvocationFactory +- (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; +@end +@interface NSInvocation (Swift) <_NSInvocationFactory> +@end + +// Special selectors which can't be referenced directly in Swift. +@protocol _SpecialSelectors +// NSObject +- (instancetype)alloc; +- (void)dealloc; +// NSInvocation +- (void)invokeWithTarget:(id)target; +@end + +// Special init which can't be reference directly in Swift, but cannot be a protocol either. +@interface _InitSelector: NSObject +// Init with script +- (id)initByScriptWithArguments:(NSArray *)args; +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file From f5dfb9df300e1bea5bd19c4f9f32a5bf1c7efa5f Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 25 Apr 2016 11:26:37 -0700 Subject: [PATCH 26/44] Add link to Sample project to README. (#48) Most folks will land on this project directly and may not take the time to explore the other repositories in the XWebView "Organization". This helps nudge people who are looking to write code as quickly as possible to a working sample. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b4bd5e4..52bcb6e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ XWebView is an extensible WebView which is built on top of [WKWebView](https://d Plugins written in Objective-C or Swift programming language can be automatically exposed in JavaScript context. With capabilities offered by plugins, Web apps can look and behave exactly like native apps. They will be no longer a second-class citizen on iOS platform. +## Sample Project + +For a complete example on how to use XWebView including both Swift and JavaScript code, see the [Sample Project](https://github.com/XWebView/Sample). + ## Features Basically, plugins are native classes which can export their interfaces to a JavaScript environment. Calling methods and accessing properties of a plugin object in JavaScript result in same operations to the native plugin object. If you know the [Apache Cordova](https://cordova.apache.org/), you may have the concept of plugins. Well, XWebView does more in simpler manner. From 73c40f852db8935d739a77fb81ca971dd21dcf4c Mon Sep 17 00:00:00 2001 From: Andrey Slyusar Date: Sat, 17 Sep 2016 20:35:16 +0200 Subject: [PATCH 27/44] Swift 2.3 support --- README.md | 9 +++++++++ XWebView.podspec | 2 +- XWebView.xcodeproj/project.pbxproj | 6 ++++++ XWebView/XWVHttpServer.swift | 6 +++++- XWebView/XWebView.swift | 8 +++++++- XWebViewTests/XWebViewTests.swift | 16 ++++++++++++++-- 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 52bcb6e..6c48be8 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,15 @@ For more documents, please go to the project [Wiki](../../wiki). * Development: Xcode 7.3 * Deployment: iOS 8.0 +## XWebView vs. Swift + +| Swift | XWebView | +| ----- | ---------- | +| 3 | ? | +| 2.3 | 0.10.0 | +| 2.2 | 0.10.0 | + + ## License XWebView is distributed under the [Apache License 2.0](LICENSE). diff --git a/XWebView.podspec b/XWebView.podspec index ad047e6..bf15e10 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.9.5" + s.version = "0.10.0" s.summary = "An extensible WebView (based on WKWebView) for iOS." s.description = <<-DESC diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 9204548..beedc4e 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -311,9 +311,11 @@ }; AB023E9F1A8C506600580A2A = { CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; }; EE62683419FA323900EFC3F8 = { CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; }; }; }; @@ -492,6 +494,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -504,6 +507,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -607,6 +611,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -624,6 +629,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 2.3; }; name = Release; }; diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index 6a4741a..321dce8 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -182,7 +182,11 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { var url = NSURL(string: relativePath, relativeToURL: baseURL)! if fileManager.fileExistsAtPath(url.path!, isDirectory: &isDirectory) { if isDirectory { - url = url.URLByAppendingPathComponent("index.html") + #if swift(>=2.3) + url = url.URLByAppendingPathComponent("index.html")! + #else + url = url.URLByAppendingPathComponent("index.html") + #endif } if fileManager.isReadableFileAtPath(url.path!) { fileURL = url diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index a08af31..11a7805 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -123,7 +123,13 @@ extension WKWebView { } guard let port = startHttpd(rootURL: URL.baseURL!, overlayURLs: overlayURLs) else { return nil } - let url = NSURL(string: URL.resourceSpecifier, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + + #if swift(>=2.3) + let url = NSURL(string: URL.resourceSpecifier!, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + #else + let url = NSURL(string: URL.resourceSpecifier, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + #endif + return loadRequest(NSURLRequest(URL: url!)) } } diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 4e0aba6..89c7e36 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -45,8 +45,14 @@ class XWebViewTests: XWVTestCase { func testLoadFileURL() { _ = expectationWithDescription("loadFileURL") let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") + if let root = bundle?.bundleURL.URLByAppendingPathComponent("www") { - let url = root.URLByAppendingPathComponent("webviewTest.html") + #if swift(>=2.3) + let url = root.URLByAppendingPathComponent("webviewTest.html")! + #else + let url = root.URLByAppendingPathComponent("webviewTest.html") + #endif + XCTAssert(url.checkResourceIsReachableAndReturnError(nil), "HTML file not found") webview.loadFileURL(url, allowingReadAccessToURL: root) waitForExpectationsWithTimeout(2, handler: nil) @@ -63,7 +69,13 @@ class XWebViewTests: XWVTestCase { inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: true) - var url = library.URLByAppendingPathComponent("webviewTest.html") + + #if swift(>=2.3) + var url = library.URLByAppendingPathComponent("webviewTest.html")! + #else + var url = library.URLByAppendingPathComponent("webviewTest.html") + #endif + let content = "" try! content.writeToURL(url, atomically: false, encoding: NSUTF8StringEncoding) From f73636eefcbedeb08559b4c876fb0a231bc8d71e Mon Sep 17 00:00:00 2001 From: David Kim Date: Wed, 21 Sep 2016 23:56:39 +0800 Subject: [PATCH 28/44] Fixed #58 promises not working in iOS 10 --- XWebView/xwebview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/xwebview.js b/XWebView/xwebview.js index 15ee1b3..2bf8677 100644 --- a/XWebView/xwebview.js +++ b/XWebView/xwebview.js @@ -121,7 +121,7 @@ XWVPlugin.invokeNative = function(name) { if (t[1].slice(-1) == 'p') { // Return a Promise object for async operation args.unshift(name); - return Promise((function(args, resolve, reject) { + return new Promise((function(args, resolve, reject) { args[args.length - 1] = {'resolve': resolve, 'reject': reject}; XWVPlugin.invokeNative.apply(this, args); }).bind(this, args)); From be084c2043081ea1f4fd172254e636f149401d90 Mon Sep 17 00:00:00 2001 From: David Kim Date: Mon, 31 Oct 2016 04:09:53 +0800 Subject: [PATCH 29/44] Fixed #54: Migrate to Swift 3.0 Meanwhile, iOS 8.x support is removed, overlay support is disabled. --- .travis.yml | 4 +- README.md | 6 +- XWebView.podspec | 6 +- XWebView.xcodeproj/project.pbxproj | 27 +- .../xcshareddata/xcschemes/XWebView.xcscheme | 2 +- .../xcschemes/XWebViewTests.xcscheme | 2 +- XWebView/Info.plist | 2 +- XWebView/XWVBindingObject.swift | 86 +-- XWebView/XWVChannel.swift | 63 +- XWebView/XWVHttpConnection.swift | 152 ++--- XWebView/XWVHttpServer.swift | 141 ++--- XWebView/XWVInvocation.swift | 541 ++++++++++-------- XWebView/XWVJson.swift | 113 ++++ XWebView/XWVLogging.swift | 40 +- XWebView/XWVMetaObject.swift | 103 ++-- XWebView/XWVObject.swift | 62 +- XWebView/XWVScriptObject.swift | 62 +- XWebView/XWVScripting.swift | 16 +- XWebView/XWVUserScript.swift | 4 +- XWebView/XWebView.h | 28 - XWebView/XWebView.swift | 123 ++-- XWebViewTests/ConstructorPlugin.swift | 32 +- XWebViewTests/FunctionPlugin.swift | 16 +- XWebViewTests/Info.plist | 2 +- XWebViewTests/ObjectPlugin.swift | 71 +-- XWebViewTests/XWVInvocationTest.swift | 36 +- XWebViewTests/XWVMetaObjectTest.swift | 32 +- XWebViewTests/XWVScriptingTest.swift | 26 +- XWebViewTests/XWVTestCase.swift | 18 +- XWebViewTests/XWebViewTests.swift | 76 +-- XWebViewX/Info.plist | 2 +- XWebViewX/XWebViewX.h | 24 +- 32 files changed, 1008 insertions(+), 910 deletions(-) create mode 100644 XWebView/XWVJson.swift diff --git a/.travis.yml b/.travis.yml index a24be87..f0b8ca7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -osx_image: xcode7.3 +osx_image: xcode8 #xcode_sdk: iphonesimulator9.0 #xcode_project: XWebView.xcodeproj #xcode_scheme: XWebViewTests #xctool_args: "-destination 'platform=iOS Simulator,OS=8.1,name=iPhone 5s'" env: matrix: - - OS=8.1 - OS=9.0 + - OS=10.0 script: xcodebuild -scheme XWebViewTests -configuration Debug -destination "platform=iOS Simulator,OS=$OS,name=iPhone 5s" test diff --git a/README.md b/README.md index 6c48be8..3262d3d 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ For more documents, please go to the project [Wiki](../../wiki). ## Minimum Requirements: -* Development: Xcode 7.3 -* Deployment: iOS 8.0 +* Development: Xcode 8 +* Deployment: iOS 9.0 ## XWebView vs. Swift | Swift | XWebView | | ----- | ---------- | -| 3 | ? | +| 3 | 0.11.0 | | 2.3 | 0.10.0 | | 2.2 | 0.10.0 | diff --git a/XWebView.podspec b/XWebView.podspec index bf15e10..a2a9bfc 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.10.0" + s.version = "0.11.0" s.summary = "An extensible WebView (based on WKWebView) for iOS." s.description = <<-DESC @@ -65,10 +65,10 @@ Pod::Spec.new do |s| # # s.platform = :ios - s.platform = :ios, "8.0" + s.platform = :ios, "9.0" # When using multiple platforms - s.ios.deployment_target = "8.0" + s.ios.deployment_target = "9.0" # s.osx.deployment_target = "10.7" diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index beedc4e..1724910 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */; }; + EE9BDBB81DBC0953001714AD /* XWVJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BDBB71DBC0953001714AD /* XWVJson.swift */; }; EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */; }; EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */; }; @@ -99,6 +100,7 @@ EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVHttpServer.swift; path = XWebView/XWVHttpServer.swift; sourceTree = ""; }; EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVMetaObject.swift; path = XWebView/XWVMetaObject.swift; sourceTree = ""; }; EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; + EE9BDBB71DBC0953001714AD /* XWVJson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVJson.swift; path = XWebView/XWVJson.swift; sourceTree = ""; }; EEDF305F1B6555B900A21659 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVInvocation.swift; path = XWebView/XWVInvocation.swift; sourceTree = ""; }; EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScripting.swift; path = XWebView/XWVScripting.swift; sourceTree = ""; }; EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVObject.swift; path = XWebView/XWVObject.swift; sourceTree = ""; }; @@ -191,6 +193,7 @@ EE62683719FA323900EFC3F8 /* XWebView */ = { isa = PBXGroup; children = ( + EE9BDBB71DBC0953001714AD /* XWVJson.swift */, EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */, EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */, EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */, @@ -303,7 +306,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = XWebView; TargetAttributes = { 663C48C11CC034270031251A = { @@ -412,6 +415,7 @@ EE131CA71B5F900400A9E790 /* XWVUserScript.swift in Sources */, EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */, EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */, + EE9BDBB81DBC0953001714AD /* XWVJson.swift in Sources */, EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */, EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */, EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */, @@ -435,7 +439,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -459,7 +463,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -494,7 +498,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -507,7 +511,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -524,8 +528,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -535,6 +541,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -571,8 +578,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -581,6 +590,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -590,6 +600,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -600,6 +611,7 @@ EE62684C19FA323900EFC3F8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -611,13 +623,14 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; EE62684D19FA323900EFC3F8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -629,7 +642,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index 59b5064..516f22a 100644 --- a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme +++ b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -1,6 +1,6 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.5 + 0.11.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 9c51e15..c7e2b7e 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -28,7 +28,7 @@ final class XWVBindingObject : XWVScriptObject { bind() } - init?(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { + init?(namespace: String, channel: XWVChannel, arguments: [Any]?) { self.channel = channel super.init(namespace: namespace, webView: channel.webView!) let cls: AnyClass = channel.typeInfo.plugin @@ -48,9 +48,9 @@ final class XWVBindingObject : XWVScriptObject { arguments = [arguments] } - plugin = invoke(cls, selector: #selector(_SpecialSelectors.alloc), withArguments: []) as? AnyObject + plugin = invoke(#selector(NSProxy.alloc), of: cls) as AnyObject if plugin != nil { - plugin = performSelector(selector, withObjects: arguments) + plugin = performSelector(selector, with: arguments) as AnyObject! } guard plugin != nil else { log("!Failed to create instance for plugin class \(cls)") @@ -59,12 +59,12 @@ final class XWVBindingObject : XWVScriptObject { bind() syncProperties() - promise?.callMethod("resolve", withArguments: [self], completionHandler: nil) + promise?.callMethod("resolve", with: [self], completionHandler: nil) } deinit { (plugin as? XWVScripting)?.finalizeForScript?() - super.callMethod("dispose", withArguments: [true], completionHandler: nil) + super.callMethod("dispose", with: [true], completionHandler: nil) unbind() } @@ -72,82 +72,82 @@ final class XWVBindingObject : XWVScriptObject { // Start KVO guard let plugin = plugin as? NSObject else { return } channel.typeInfo.filter{ $1.isProperty }.forEach { - plugin.addObserver(self, forKeyPath: String($1.getter!), options: NSKeyValueObservingOptions.New, context: nil) + plugin.addObserver(self, forKeyPath: String(describing: $1.getter!), options: NSKeyValueObservingOptions.new, context: nil) } } private func unbind() { // Stop KVO guard plugin is NSObject else { return } channel.typeInfo.filter{ $1.isProperty }.forEach { - plugin.removeObserver(self, forKeyPath: String($1.getter!), context: nil) + plugin.removeObserver(self, forKeyPath: String(describing: $1.getter!), context: nil) } } private func syncProperties() { let script = channel.typeInfo.filter{ $1.isProperty }.reduce("") { - let val: AnyObject! = performSelector($1.1.getter!, withObjects: nil) + let val: Any! = performSelector($1.1.getter!, with: nil) return "\($0)\(namespace).$properties['\($1.0)'] = \(serialize(val));\n" } webView?.evaluateJavaScript(script, completionHandler: nil) } // Dispatch operation to plugin object - func invokeNativeMethod(name: String, withArguments arguments: [AnyObject]) { + func invokeNativeMethod(name: String, with arguments: [Any]) { guard let selector = channel.typeInfo[name]?.selector else { return } var args = arguments.map(wrapScriptObject) - if plugin is XWVScripting && name.isEmpty && selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:)) { + if plugin is XWVScripting && name.isEmpty && selector == #selector(XWVScripting.invokeDefaultMethod(withArguments:)) { args = [args]; } - performSelector(selector, withObjects: args, waitUntilDone: false) + _ = performSelector(selector, with: args, waitUntilDone: false) } - func updateNativeProperty(name: String, withValue value: AnyObject) { + func updateNativeProperty(name: String, with value: Any) { guard let setter = channel.typeInfo[name]?.setter else { return } - let val: AnyObject = wrapScriptObject(value) - performSelector(setter, withObjects: [val], waitUntilDone: false) + let val: Any = wrapScriptObject(value) + _ = performSelector(setter, with: [val], waitUntilDone: false) } // override methods of XWVScriptObject - override func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + override func callMethod(_ name: String, with arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { if let selector = channel.typeInfo[name]?.selector { - let result: AnyObject! = performSelector(selector, withObjects: arguments) + let result: Any! = performSelector(selector, with: arguments) completionHandler?(result, nil) } else { - super.callMethod(name, withArguments: arguments, completionHandler: completionHandler) + super.callMethod(name, with: arguments, completionHandler: completionHandler) } } - override func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject? { + override func callMethod(_ name: String, with arguments: [Any]?) throws -> Any? { if let selector = channel.typeInfo[name]?.selector { - return performSelector(selector, withObjects: arguments) + return performSelector(selector, with: arguments) } - return try super.callMethod(name, withArguments: arguments) + return try super.callMethod(name, with: arguments) } - override func value(forProperty name: String) -> AnyObject? { + override func value(for name: String) -> Any? { if let getter = channel.typeInfo[name]?.getter { - return performSelector(getter, withObjects: nil) + return performSelector(getter, with: nil) } - return super.value(forProperty: name) + return super.value(for: name) } - override func setValue(value: AnyObject?, forProperty name: String) { + override func setValue(_ value: Any?, for name: String) { if let setter = channel.typeInfo[name]?.setter { - performSelector(setter, withObjects: [value ?? NSNull()]) + _ = performSelector(setter, with: [value ?? NSNull()]) } else if channel.typeInfo[name] == nil { - super.setValue(value, forProperty: name) + super.setValue(value, for: name) } else { assertionFailure("Property '\(name)' is readonly") } } // KVO for syncing properties - override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let webView = webView, var prop = keyPath else { return } if channel.typeInfo[prop] == nil { - if let scriptNameForKey = (object.dynamicType as? XWVScripting.Type)?.scriptNameForKey { + if let scriptNameForKey = (type(of: object) as? XWVScripting.Type)?.scriptName(forKey:) { prop = prop.withCString(scriptNameForKey) ?? prop } assert(channel.typeInfo[prop] != nil) } - let script = "\(namespace).$properties['\(prop)'] = \(serialize(change?[NSKeyValueChangeNewKey]))" + let script = "\(namespace).$properties['\(prop)'] = \(serialize(change?[NSKeyValueChangeKey.newKey]))" webView.evaluateJavaScript(script, completionHandler: nil) } } @@ -159,27 +159,27 @@ extension XWVBindingObject { return key }() - private static var currentBindingObject: XWVBindingObject? { + fileprivate static var currentBindingObject: XWVBindingObject? { let ptr = pthread_getspecific(XWVBindingObject.key) guard ptr != nil else { return nil } - return unsafeBitCast(ptr, XWVBindingObject.self) + return unsafeBitCast(ptr, to: XWVBindingObject.self) } - private func performSelector(selector: Selector, withObjects arguments: [AnyObject]?, waitUntilDone wait: Bool = true) -> AnyObject! { + fileprivate func performSelector(_ selector: Selector, with arguments: [Any]?, waitUntilDone wait: Bool = true) -> Any! { var result: Any! = () - let trampoline: dispatch_block_t = { + let trampoline : () -> Void = { [weak self] in guard let plugin = self?.plugin else { return } - let args: [Any!] = arguments?.map{ $0 is NSNull ? nil : ($0 as Any) } ?? [] + let args: [Any?] = arguments?.map{ $0 is NSNull ? nil : ($0 as Any) } ?? [] let save = pthread_getspecific(XWVBindingObject.key) - pthread_setspecific(XWVBindingObject.key, unsafeAddressOf(self!)) - result = castToObjectFromAny(invoke(plugin, selector: selector, withArguments: args)) + pthread_setspecific(XWVBindingObject.key, Unmanaged.passUnretained(self!).toOpaque()) + result = invoke(selector, of: plugin, with: args) pthread_setspecific(XWVBindingObject.key, save) } if let queue = channel.queue { if !wait { - dispatch_async(queue, trampoline) - } else if dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) != dispatch_queue_get_label(queue) { - dispatch_sync(queue, trampoline) + queue.async(execute: trampoline) + } else if String(cString: __dispatch_queue_get_label(nil)) != queue.label { + queue.sync(execute: trampoline) } else { trampoline() } @@ -187,17 +187,17 @@ extension XWVBindingObject { if wait && CFRunLoopGetCurrent() === runLoop { trampoline() } else { - CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, trampoline) + CFRunLoopPerformBlock(runLoop, CFRunLoopMode.defaultMode.rawValue, trampoline) CFRunLoopWakeUp(runLoop) while wait && result is Void { - let reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3.0, true) - if reason != CFRunLoopRunResult.HandledSource { + let reason = CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 3.0, true) + if reason != CFRunLoopRunResult.handledSource { break } } } } - return result as? AnyObject + return result } } diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 5812ba3..45cfe2c 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -19,8 +19,8 @@ import WebKit public class XWVChannel : NSObject, WKScriptMessageHandler { private(set) public var identifier: String? - public let runLoop: NSRunLoop? - public let queue: dispatch_queue_t? + public let runLoop: RunLoop? + public let queue: DispatchQueue? private(set) public weak var webView: WKWebView? var typeInfo: XWVMetaObject! @@ -39,46 +39,45 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { return sequence.number } - private static var defaultQueue: dispatch_queue_t = { - let label = "org.xwebview.default-queue" - return dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL) + private static var defaultQueue: DispatchQueue = { + return DispatchQueue(label: "org.xwebview.default-queue") }() public convenience init(webView: WKWebView) { self.init(webView: webView, queue: XWVChannel.defaultQueue) } - public convenience init(webView: WKWebView, thread: NSThread) { - let selector = #selector(NSRunLoop.currentRunLoop) - let runLoop = invoke(NSRunLoop.self, selector: selector, withArguments: [], onThread: thread) as! NSRunLoop + public convenience init(webView: WKWebView, thread: Thread) { + let selector = #selector(getter: RunLoop.current) + let runLoop = invoke(selector, of: RunLoop.self, on: thread) as! RunLoop self.init(webView: webView, runLoop: runLoop) } - public init(webView: WKWebView, queue: dispatch_queue_t) { - assert(dispatch_queue_get_label(queue).memory != 0, "Queue must be labeled") + public init(webView: WKWebView, queue: DispatchQueue) { + assert(!queue.label.isEmpty, "Queue must be labeled") self.webView = webView self.queue = queue runLoop = nil webView.prepareForPlugin() } - public init(webView: WKWebView, runLoop: NSRunLoop) { + public init(webView: WKWebView, runLoop: RunLoop) { self.webView = webView self.runLoop = runLoop queue = nil webView.prepareForPlugin() } - public func bindPlugin(object: AnyObject, toNamespace namespace: String) -> XWVScriptObject? { + public func bindPlugin(_ object: AnyObject, toNamespace namespace: String) -> XWVScriptObject? { guard identifier == nil, let webView = webView else { return nil } let id = (object as? XWVScripting)?.channelIdentifier ?? String(XWVChannel.sequenceNumber) identifier = id - webView.configuration.userContentController.addScriptMessageHandler(self, name: id) - typeInfo = XWVMetaObject(plugin: object.dynamicType) + webView.configuration.userContentController.add(self, name: id) + typeInfo = XWVMetaObject(plugin: type(of: object)) principal = XWVBindingObject(namespace: namespace, channel: self, object: object) let script = WKUserScript(source: generateStubs(), - injectionTime: WKUserScriptInjectionTime.AtDocumentStart, + injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true) userScript = XWVUserScript(webView: webView, script: script) @@ -90,51 +89,51 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { guard let id = identifier else { return } let namespace = principal.namespace let plugin = principal.plugin - instances.removeAll(keepCapacity: false) - webView?.configuration.userContentController.removeScriptMessageHandlerForName(id) + instances.removeAll(keepingCapacity: false) + webView?.configuration.userContentController.removeScriptMessageHandler(forName: id) userScript = nil identifier = nil log("+Plugin object \(plugin) is unbound from \(namespace)") } - public func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // A workaround for crash when postMessage(undefined) - guard unsafeBitCast(message.body, COpaquePointer.self) != nil else { return } + //guard unsafeBitCast(message.body, to: OpaquePointer!.self) != nil else { return } - if let body = message.body as? [String: AnyObject], let opcode = body["$opcode"] as? String { - let target = (body["$target"] as? NSNumber)?.integerValue ?? 0 + if let body = message.body as? [String: Any], let opcode = body["$opcode"] as? String { + let target = (body["$target"] as? NSNumber)?.intValue ?? 0 if let object = instances[target] { if opcode == "-" { if target == 0 { // Dispose plugin unbind() - } else if let instance = instances.removeValueForKey(target) { + } else if let instance = instances.removeValue(forKey: target) { // Dispose instance log("+Instance \(target) is unbound from \(instance.namespace)") } else { log("?Invalid instance id: \(target)") } - } else if let member = typeInfo[opcode] where member.isProperty { + } else if let member = typeInfo[opcode], member.isProperty { // Update property - object.updateNativeProperty(opcode, withValue: body["$operand"] ?? NSNull()) - } else if let member = typeInfo[opcode] where member.isMethod { + object.updateNativeProperty(name: opcode, with: body["$operand"] ?? NSNull()) + } else if let member = typeInfo[opcode], member.isMethod { // Invoke method - if let args = (body["$operand"] ?? []) as? [AnyObject] { - object.invokeNativeMethod(opcode, withArguments: args) + if let args: [Any] = body["$operand"] as? [Any] { + object.invokeNativeMethod(name: opcode, with: args) } // else malformatted operand } else { log("?Invalid member name: \(opcode)") } } else if opcode == "+" { // Create instance - let args = body["$operand"] as? [AnyObject] + let args = body["$operand"] as? [Any] let namespace = "\(principal.namespace)[\(target)]" instances[target] = XWVBindingObject(namespace: namespace, channel: self, arguments: args) log("+Instance \(target) is bound to \(namespace)") } // else Unknown opcode } else if let obj = principal.plugin as? WKScriptMessageHandler { // Plugin claims for raw messages - obj.userContentController(userContentController, didReceiveScriptMessage: message) + obj.userContentController(userContentController, didReceive: message) } else { // discard unknown message log("-Unknown message: \(message.body)") @@ -142,12 +141,12 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { } private func generateStubs() -> String { - func generateMethod(key: String, this: String, prebind: Bool) -> String { + func generateMethod(_ key: String, this: String, prebind: Bool) -> String { let stub = "XWVPlugin.invokeNative.bind(\(this), '\(key)')" return prebind ? "\(stub);" : "function(){return \(stub).apply(null, arguments);}" } - func rewriteStub(stub: String, forKey key: String) -> String { - return (principal.plugin as? XWVScripting)?.rewriteGeneratedStub?(stub, forKey: key) ?? stub + func rewriteStub(_ stub: String, forKey key: String) -> String { + return (principal.plugin as? XWVScripting)?.rewriteStub?(stub, forKey: key) ?? stub } let prebind = !(typeInfo[""]?.isInitializer ?? false) diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift index 865e80e..eed7fc0 100644 --- a/XWebView/XWVHttpConnection.swift +++ b/XWebView/XWVHttpConnection.swift @@ -13,32 +13,32 @@ See the License for the specific language governing permissions and limitations under the License. */ - +/* import Foundation protocol XWVHttpConnectionDelegate { - func handleRequest(request: NSURLRequest) -> NSHTTPURLResponse - func didOpenConnection(connection: XWVHttpConnection) - func didCloseConnection(connection: XWVHttpConnection) + func handleRequest(_ request: URLRequest) -> HTTPURLResponse + func didOpenConnection(_ connection: XWVHttpConnection) + func didCloseConnection(_ connection: XWVHttpConnection) } final class XWVHttpConnection : NSObject { private let handle: CFSocketNativeHandle - private let delegate: XWVHttpConnectionDelegate - private var input: NSInputStream! - private var output: NSOutputStream! - private let bufferMaxSize = 64 * 1024 + fileprivate let delegate: XWVHttpConnectionDelegate + fileprivate var input: InputStream! + fileprivate var output: OutputStream! + fileprivate let bufferMaxSize = 64 * 1024 // input state - private var requestQueue = [NSURLRequest]() - private var inputBuffer: NSMutableData! - private var cursor: Int = 0 + fileprivate var requestQueue = [URLRequest]() + fileprivate var inputBuffer: Data! + fileprivate var cursor: Int = 0 // output state - private var outputBuffer: NSData! - private var bytesRemained: Int = 0 - private var fileHandle: NSFileHandle! - private var fileSize: Int = 0 + fileprivate var outputBuffer: Data! + fileprivate var bytesRemained: Int = 0 + fileprivate var fileHandle: FileHandle! + fileprivate var fileSize: Int = 0 init(handle: CFSocketNativeHandle, delegate: XWVHttpConnectionDelegate) { self.handle = handle @@ -47,24 +47,24 @@ final class XWVHttpConnection : NSObject { } func open() -> Bool { - let ptr1 = UnsafeMutablePointer?>.alloc(1) - let ptr2 = UnsafeMutablePointer?>.alloc(1) + let ptr1 = UnsafeMutablePointer?>.allocate(capacity: 1) + let ptr2 = UnsafeMutablePointer?>.allocate(capacity: 1) defer { - ptr1.dealloc(1) - ptr2.dealloc(1) + ptr1.deallocate(capacity: 1) + ptr2.deallocate(capacity: 1) } CFStreamCreatePairWithSocket(nil, handle, ptr1, ptr2) - if ptr1.memory == nil || ptr2.memory == nil { return false } + if ptr1.pointee == nil || ptr2.pointee == nil { return false } - input = ptr1.memory!.takeRetainedValue() - output = ptr2.memory!.takeRetainedValue() - CFReadStreamSetProperty(input, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) - CFWriteStreamSetProperty(output, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) + input = ptr1.pointee!.takeRetainedValue() + output = ptr2.pointee!.takeRetainedValue() + CFReadStreamSetProperty(input, CFStreamPropertyKey(kCFStreamPropertyShouldCloseNativeSocket), kCFBooleanTrue) + CFWriteStreamSetProperty(output, CFStreamPropertyKey(kCFStreamPropertyShouldCloseNativeSocket), kCFBooleanTrue) input.delegate = self output.delegate = self - input.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) - output.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + input.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) + output.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) input.open() output.open() delegate.didOpenConnection(self) @@ -80,21 +80,21 @@ final class XWVHttpConnection : NSObject { } } -extension XWVHttpConnection : NSStreamDelegate { - @objc func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { +extension XWVHttpConnection : StreamDelegate { + @objc func stream(aStream: Stream, handleEvent eventCode: Stream.Event) { switch eventCode { - case NSStreamEvent.OpenCompleted: + case Stream.Event.openCompleted: // Initialize input/output state. if aStream === input { - inputBuffer = NSMutableData(length: 512) - cursor = 0; + inputBuffer = Data(count: 512) + cursor = 0 } else { outputBuffer = nil fileHandle = nil fileSize = 0 } - case NSStreamEvent.HasBytesAvailable: + case Stream.Event.hasBytesAvailable: let base = UnsafeMutablePointer(inputBuffer.mutableBytes) let bytesReaded = input.read(base.advancedBy(cursor), maxLength: inputBuffer.length - cursor) guard bytesReaded > 0 else { break } @@ -106,11 +106,11 @@ extension XWVHttpConnection : NSStreamDelegate { // End of request header. ptr += 3 let data = inputBuffer.subdataWithRange(NSRange(bytesConsumed...base.distanceTo(ptr))) - if let request = NSMutableURLRequest(data: data) { + if let request = URLRequest(data: data) { requestQueue.insert(request, atIndex: 0) } else { // Bad request - requestQueue.insert(NSURLRequest(), atIndex: 0) + requestQueue.insert(URLRequest(), atIndex: 0) } bytesConsumed += data.length } @@ -118,28 +118,28 @@ extension XWVHttpConnection : NSStreamDelegate { } if bytesConsumed > 0 { // Move remained bytes to the begining. - inputBuffer.replaceBytesInRange(NSRange(0.. fileSize { // Send response header - off = outputBuffer.length - (bytesRemained - fileSize) + off = outputBuffer.count - (bytesRemained - fileSize) } else { // Send file content off = (fileSize - bytesRemained) % bufferMaxSize if off == 0 { - fileHandle.seekToFileOffset(UInt64(fileSize - bytesRemained)) - outputBuffer = fileHandle.readDataOfLength(bufferMaxSize) + fileHandle.seek(toFileOffset: UInt64(fileSize - bytesRemained)) + outputBuffer = fileHandle.readData(ofLength: bufferMaxSize) } } let ptr = UnsafePointer(outputBuffer.bytes) @@ -168,12 +168,12 @@ extension XWVHttpConnection : NSStreamDelegate { } if bytesSent < 0 { fallthrough } - case NSStreamEvent.ErrorOccurred: + case Stream.Event.errorOccurred: let error = aStream.streamError?.localizedDescription ?? "Unknown" log("!HTTP connection error: \(error)") fallthrough - case NSStreamEvent.EndEncountered: + case Stream.Event.endEncountered: fileHandle = nil inputBuffer = nil outputBuffer = nil @@ -186,16 +186,16 @@ extension XWVHttpConnection : NSStreamDelegate { } private extension String { - mutating func trim(@noescape predicate: (Character) -> Bool) { + mutating func trim(predicate: (Character) -> Bool) { if !isEmpty { var start = startIndex while start != endIndex && predicate(self[start]) { - start = start.successor() + start = index(after: start) } if start < endIndex { var end = endIndex repeat { - end = end.predecessor() + end = index(before: end) } while predicate(self[end]) self = self[start ... end] } else { @@ -205,7 +205,7 @@ private extension String { } } -private extension NSMutableURLRequest { +private extension URLRequest { private enum Version : String { case v1_0 = "HTTP/1.0" case v1_1 = "HTTP/1.1" @@ -220,18 +220,18 @@ private extension NSMutableURLRequest { case Options = "OPTIONS" case Trace = "TRACE" } - private var CRLF: NSData { + private var CRLF: Data { var CRLF: [UInt8] = [ 0x0d, 0x0a ] - return NSData(bytes: &CRLF, length: 2) + return Data(bytes: &CRLF, count: 2) } - convenience init?(data: NSData) { - self.init() + init?(data: NSData) { + //self.init() var cursor = 0 repeat { let range = NSRange(cursor.. 100 && statusCode < 600) - let reason = NSHTTPURLResponse.localizedStringForStatusCode(statusCode).capitalizedString + let reason = HTTPURLResponse.localizedString(forStatusCode: statusCode).capitalized let statusLine = "HTTP/1.1 \(statusCode) \(reason)\r\n" - let data = allHeaderFields.reduce(NSMutableData(data: statusLine.dataUsingEncoding(NSASCIIStringEncoding)!)) { - $0.appendData(($1.0 as! NSString).dataUsingEncoding(NSASCIIStringEncoding)!) - $0.appendData(": ".dataUsingEncoding(NSASCIIStringEncoding)!) - $0.appendData(($1.1 as! NSString).dataUsingEncoding(NSASCIIStringEncoding)!) - $0.appendData("\r\n".dataUsingEncoding(NSASCIIStringEncoding)!) + let data = allHeaderFields.reduce(NSMutableData(data: statusLine.data(using: String.Encoding.ascii)!)) { + $0.append(($1.0 as! NSString).data(using: String.Encoding.ascii.rawValue)!) + $0.append(": ".data(using: String.Encoding.ascii)!) + $0.append(($1.1 as! NSString).data(using: String.Encoding.ascii.rawValue)!) + $0.append("\r\n".data(using: String.Encoding.ascii)!) return $0 } - data.appendData("\r\n".dataUsingEncoding(NSASCIIStringEncoding)!) - return data + data.append("\r\n".data(using: String.Encoding.ascii)!) + return data as Data } -} +}*/ diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index 321dce8..ff461d8 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ - +/* import Foundation #if os(iOS) import UIKit @@ -23,41 +23,41 @@ import CoreServices #endif class XWVHttpServer : NSObject { - private var socket: CFSocketRef! - private var connections = Set() - private let overlays: [NSURL] + fileprivate var socket: CFSocket! + fileprivate var connections = Set() + fileprivate let overlays: [URL] private(set) var port: in_port_t = 0 - var rootURL: NSURL { + var rootURL: URL { return overlays.last! } - var overlayURLs: [NSURL] { - return overlays.dropLast().reverse() + var overlayURLs: [URL] { + return overlays.dropLast().reversed() } - init(rootURL: NSURL, overlayURLs: [NSURL]?) { - precondition(rootURL.fileURL) + init(rootURL: URL, overlayURLs: [URL]?) { + precondition(rootURL.isFileURL) var overlays = [rootURL] overlayURLs?.forEach { - precondition($0.fileURL) + precondition($0.isFileURL) overlays.append($0) } - self.overlays = overlays.reverse() + self.overlays = overlays.reversed() super.init() } - convenience init(rootURL: NSURL) { + convenience init(rootURL: URL) { self.init(rootURL: rootURL, overlayURLs: nil) } deinit { stop() } - private func listenOnPort(port: in_port_t) -> Bool { + private func listen(on port: in_port_t) -> Bool { guard socket == nil else { return false } - let info = UnsafeMutablePointer(unsafeAddressOf(self)) + let info = UnsafeMutableRawPointer(mutating: unsafeAddressOf(self)) var context = CFSocketContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) - let callbackType = CFSocketCallBackType.AcceptCallBack.rawValue + let callbackType = CFSocketCallBackType.acceptCallBack.rawValue socket = CFSocketCreate(nil, PF_INET, SOCK_STREAM, 0, callbackType, ServerAcceptCallBack, &context) guard socket != nil else { log("!Failed to create socket") @@ -65,23 +65,23 @@ class XWVHttpServer : NSObject { } var yes = UInt32(1) - setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, &yes, UInt32(sizeof(UInt32))) + setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, &yes, UInt32(MemoryLayout.size)) var sockaddr = sockaddr_in( - sin_len: UInt8(sizeof(sockaddr_in)), + sin_len: UInt8(MemoryLayout.size), sin_family: UInt8(AF_INET), sin_port: port.bigEndian, sin_addr: in_addr(s_addr: UInt32(0x7f000001).bigEndian), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - let data = NSData(bytes: &sockaddr, length: sizeof(sockaddr_in)) - guard CFSocketSetAddress(socket, data) == CFSocketError.Success else { - log("!Failed to listen on port \(port) \(String(UTF8String: strerror(errno))!)") + let data = Data(bytes: &sockaddr, count: MemoryLayout.size) + guard CFSocketSetAddress(socket, data as CFData!) == CFSocketError.success else { + log("!Failed to listen on port \(port) \(String(cString: strerror(errno)))") CFSocketInvalidate(socket) return false } let serverLoop = #selector(XWVHttpServer.serverLoop(_:)) - NSThread.detachNewThreadSelector(serverLoop, toTarget: self, withObject: nil) + Thread.detachNewThreadSelector(serverLoop, toTarget: self, with: nil) return true } @@ -102,33 +102,33 @@ class XWVHttpServer : NSObject { // Try to find a random port in registered ports range for _ in 0 ..< 100 { let port = in_port_t(arc4random() % (49152 - 1024) + 1024) - if listenOnPort(port) { + if listen(on: port) { self.port = port break } } - } else if listenOnPort(port) { + } else if listen(on: port) { self.port = port } guard self.port != 0 else { return false } #if os(iOS) - NSNotificationCenter.defaultCenter().addObserver(self, - selector: #selector(XWVHttpServer.suspend(_:)), - name: UIApplicationDidEnterBackgroundNotification, - object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, - selector: #selector(XWVHttpServer.resume(_:)), - name: UIApplicationWillEnterForegroundNotification, - object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(XWVHttpServer.suspend(_:)), + name: NSNotification.Name.UIApplicationDidEnterBackground, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(XWVHttpServer.resume(_:)), + name: NSNotification.Name.UIApplicationWillEnterForeground, + object: nil) #endif return true } func stop() { #if os(iOS) - NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) - NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) #endif port = 0 close() @@ -139,7 +139,7 @@ class XWVHttpServer : NSObject { log("+HTTP server is suspended") } func resume(_: NSNotification!) { - if listenOnPort(port) { + if listen(on: port) { log("+HTTP server is resumed") } } @@ -147,65 +147,69 @@ class XWVHttpServer : NSObject { func serverLoop(_: AnyObject) { let runLoop = CFRunLoopGetCurrent() let source = CFSocketCreateRunLoopSource(nil, socket, 0) - CFRunLoopAddSource(runLoop, source, kCFRunLoopCommonModes) + CFRunLoopAddSource(runLoop, source, CFRunLoopMode.commonModes) CFRunLoopRun() } } extension XWVHttpServer : XWVHttpConnectionDelegate { - func didOpenConnection(connection: XWVHttpConnection) { + func didOpenConnection(_ connection: XWVHttpConnection) { connections.insert(connection) } - func didCloseConnection(connection: XWVHttpConnection) { + func didCloseConnection(_ connection: XWVHttpConnection) { connections.remove(connection) } - func handleRequest(request: NSURLRequest) -> NSHTTPURLResponse { + func handleRequest(_ request: URLRequest) -> HTTPURLResponse { // Date format, see section 7.1.1.1 of RFC7231 - let dateFormatter = NSDateFormatter() - dateFormatter.locale = NSLocale(localeIdentifier: "en_US") - dateFormatter.timeZone = NSTimeZone(name: "GMT") + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.timeZone = TimeZone(abbreviation: "GMT") dateFormatter.dateFormat = "E, dd MMM yyyy HH:mm:ss z" - var headers: [String: String] = ["Date": dateFormatter.stringFromDate(NSDate())] + var headers: [String: String] = ["Date": dateFormatter.string(from: Date())] var statusCode = 500 - var fileURL = NSURL() - if request.URL == nil { + var fileURL: URL? = nil + if request.url == nil { // Bad request statusCode = 400 log("?Bad request") - } else if request.HTTPMethod == "GET" || request.HTTPMethod == "HEAD" { - let fileManager = NSFileManager.defaultManager() - let relativePath = String(request.URL!.path!.characters.dropFirst()) + } else if request.httpMethod == "GET" || request.httpMethod == "HEAD" { + let fileManager = FileManager.default + let relativePath = String(request.url!.path.characters.dropFirst()) for baseURL in overlays { var isDirectory: ObjCBool = false - var url = NSURL(string: relativePath, relativeToURL: baseURL)! - if fileManager.fileExistsAtPath(url.path!, isDirectory: &isDirectory) { - if isDirectory { + var url = URL(string: relativePath, relativeTo: baseURL)! + if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) { + if isDirectory.boolValue { + #if swift(>=3.0) + url = url.appendingPathComponent("index.html") + #else #if swift(>=2.3) url = url.URLByAppendingPathComponent("index.html")! #else url = url.URLByAppendingPathComponent("index.html") - #endif + #endif + #endif } - if fileManager.isReadableFileAtPath(url.path!) { + if fileManager.isReadableFile(atPath: url.path) { fileURL = url break } } } - if fileURL.path != nil { + if let fileURL = fileURL { statusCode = 200 - let attrs = try! fileManager.attributesOfItemAtPath(fileURL.path!) - headers["Content-Type"] = getMIMETypeByExtension(fileURL.pathExtension!) - headers["Content-Length"] = String(attrs[NSFileSize]!) - headers["Last-Modified"] = dateFormatter.stringFromDate(attrs[NSFileModificationDate] as! NSDate) - log("+\(request.HTTPMethod!) \(fileURL.path!)") + let attrs = try! fileManager.attributesOfItem(atPath: fileURL.path) + headers["Content-Type"] = getMIMETypeByExtension(extensionName: fileURL.pathExtension) + headers["Content-Length"] = String(describing: attrs[FileAttributeKey.size]!) + headers["Last-Modified"] = dateFormatter.string(from: attrs[FileAttributeKey.modificationDate] as! Date) + log("+\(request.httpMethod!) \(fileURL.path)") } else { // Not found statusCode = 404 - fileURL = NSURL() - log("-File NOT found for URL \(request.URL!)") + fileURL = nil + log("-File NOT found for URL \(request.url!)") } } else { // Method not allowed @@ -215,14 +219,15 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { if statusCode != 200 { headers["Content-Length"] = "0" } - return NSHTTPURLResponse(URL: fileURL, statusCode: statusCode, HTTPVersion: "HTTP/1.1", headerFields: headers)! + return HTTPURLResponse(url: fileURL!, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)! + // FIXME: return nil when fileURL == nil } } -private func ServerAcceptCallBack(socket: CFSocket!, type: CFSocketCallBackType, address: CFData!, data:UnsafePointer, info: UnsafeMutablePointer) { - let server = unsafeBitCast(info, XWVHttpServer.self) - let handle = UnsafePointer(data).memory - assert(socket === server.socket && type == CFSocketCallBackType.AcceptCallBack) +private func ServerAcceptCallBack(socket: CFSocket?, type: CFSocketCallBackType, address: CFData?, data:UnsafeRawPointer?, info: UnsafeMutableRawPointer?) { + let server = unsafeBitCast(info, to: XWVHttpServer.self) + let handle = UnsafePointer(data).pointee + assert(socket === server.socket && type == CFSocketCallBackType.acceptCallBack) let connection = XWVHttpConnection(handle: handle, delegate: server) connection.open() @@ -236,7 +241,7 @@ private func getMIMETypeByExtension(extensionName: String) -> String { var type: String! = mimeTypeCache[extensionName] if type == nil { // Get MIME type through system-declared uniform type identifier. - if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionName, nil), + if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionName as CFString, nil), let mt = UTTypeCopyPreferredTagWithClass(uti.takeRetainedValue(), kUTTagClassMIMEType) { type = mt.takeRetainedValue() as String } else { @@ -245,9 +250,9 @@ private func getMIMETypeByExtension(extensionName: String) -> String { } mimeTypeCache[extensionName] = type } - if type.lowercaseString.hasPrefix("text/") { + if type.lowercased().hasPrefix("text/") { // Assume text resource is UTF-8 encoding return type + "; charset=utf-8" } return type -} +}*/ diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index ab3ef8a..f87fd7d 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -17,151 +17,90 @@ import Foundation import ObjectiveC -public class XWVInvocation { - public final let target: AnyObject - private let thread: NSThread? - - public init(target: AnyObject, thread: NSThread? = nil) { - self.target = target - self.thread = thread - } - - public class func construct(`class`: AnyClass, initializer: Selector = #selector(NSObject.init), withArguments arguments: [Any!] = []) -> AnyObject? { - let alloc = #selector(_SpecialSelectors.alloc) - guard let obj = invoke(`class`, selector: alloc, withArguments: []) as? AnyObject else { - return nil - } - return invoke(obj, selector: initializer, withArguments: arguments) as? AnyObject - } - - public func call(selector: Selector, withArguments arguments: [Any!] = []) -> Any! { - return invoke(target, selector: selector, withArguments: arguments, onThread: thread) - } - // No callback support, so return value is expected to lose. - public func asyncCall(selector: Selector, withArguments arguments: [Any!] = []) { - invoke(target, selector: selector, withArguments: arguments, onThread: thread, waitUntilDone: false) - } - - // Syntactic sugar for calling method - public subscript (selector: Selector) -> (Any!...)->Any! { - return { - (args: Any!...)->Any! in - self.call(selector, withArguments: args) - } - } +@objc protocol NSMethodSignatureProtocol { + static func signature(objCTypes: UnsafePointer) -> NSMethodSignatureProtocol + func getArgumentType(atIndex idx: UInt) -> UnsafePointer + var numberOfArguments: UInt { get } + var frameLength: UInt { get } + var methodReturnType: UnsafePointer { get } + var methodReturnLength: UInt { get } + func isOneWay() -> ObjCBool } - -extension XWVInvocation { - // Property accessor - public func getProperty(name: String) -> Any! { - let getter = getterOfName(name) - assert(getter != Selector(), "Property '\(name)' does not exist") - return getter != Selector() ? call(getter) : Void() - } - public func setValue(value: Any!, forProperty name: String) { - let setter = setterOfName(name) - assert(setter != Selector(), "Property '\(name)' " + - (getterOfName(name) == nil ? "does not exist" : "is readonly")) - assert(!(value is Void)) - if setter != Selector() { - call(setter, withArguments: [value]) - } - } - - // Syntactic sugar for accessing property - public subscript (name: String) -> Any! { - get { - return getProperty(name) - } - set { - setValue(newValue, forProperty: name) - } - } - - private func getterOfName(name: String) -> Selector { - var getter = Selector() - let property = class_getProperty(target.dynamicType, name) - if property != nil { - let attr = property_copyAttributeValue(property, "G") - getter = Selector(attr == nil ? name : String(UTF8String: attr)!) - free(attr) - } - return getter - } - private func setterOfName(name: String) -> Selector { - var setter = Selector() - let property = class_getProperty(target.dynamicType, name) - if property != nil { - var attr = property_copyAttributeValue(property, "R") - if attr == nil { - attr = property_copyAttributeValue(property, "S") - if attr == nil { - setter = Selector("set\(String(name.characters.first!).uppercaseString)\(String(name.characters.dropFirst())):") - } else { - setter = Selector(String(UTF8String: attr)!) - } - } - free(attr) - } - return setter - } +@objc protocol NSInvocationProtocol { + static func invocation(methodSignature: AnyObject) -> NSInvocationProtocol + var selector: Selector { get set } + var target: AnyObject { get set } + func setArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) + func getArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) + var argumentsRetained: ObjCBool { get } + func retainArguments() + func setReturnValue(_ retLoc: UnsafeMutableRawPointer) + func getReturnValue(_ retLoc: UnsafeMutableRawPointer) + func invoke() + func invoke(target: AnyObject) + var methodSignature: NSMethodSignatureProtocol { get } } +var NSMethodSignature: NSMethodSignatureProtocol.Type = { + class_addProtocol(objc_lookUpClass("NSMethodSignature"), NSMethodSignatureProtocol.self) + return objc_lookUpClass("NSMethodSignature") as! NSMethodSignatureProtocol.Type +}() +var NSInvocation: NSInvocationProtocol.Type = { + class_addProtocol(objc_lookUpClass("NSInvocation"), NSInvocationProtocol.self) + return objc_lookUpClass("NSInvocation") as! NSInvocationProtocol.Type +}() -// Notice: The target method must strictly obey the Cocoa convention. -// Do NOT call method with explicit family control or parameter attribute of ARC. -// See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html -private let _NSInvocation: AnyClass = NSClassFromString("NSInvocation")! -private let _NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! -public func invoke(target: AnyObject, selector: Selector, withArguments arguments: [Any!], onThread thread: NSThread? = nil, waitUntilDone wait: Bool = true) -> Any! { - let method = class_getInstanceMethod(target.dynamicType, selector) - if method == nil { - // TODO: supports forwordingTargetForSelector: of NSObject? - (target as? NSObject)?.doesNotRecognizeSelector(selector) - // Not an NSObject, mimic the behavior of NSObject - let reason = "-[\(target.dynamicType) \(selector)]: unrecognized selector sent to instance \(unsafeAddressOf(target))" - withVaList([reason]) { NSLogv("%@", $0) } - NSException(name: NSInvalidArgumentException, reason: reason, userInfo: nil).raise() +@discardableResult public func invoke(_ selector: Selector, of target: AnyObject, with arguments: [Any?] = [], on thread: Thread? = nil, waitUntilDone wait: Bool = true) -> Any! { + let method = class_getInstanceMethod(type(of: target), selector) + guard method != nil else { + target.doesNotRecognizeSelector?(selector) + fatalError("Unrecognized selector -[\(target) \(selector)]") } - let sig = (_NSMethodSignature as! _NSMethodSignatureFactory).signatureWithObjCTypes(method_getTypeEncoding(method)) - let inv = (_NSInvocation as! _NSInvocationFactory).invocationWithMethodSignature(sig) + let sig = NSMethodSignature.signature(objCTypes: method_getTypeEncoding(method)) + let inv = NSInvocation.invocation(methodSignature: sig) // Setup arguments precondition(arguments.count + 2 <= Int(method_getNumberOfArguments(method)), - "Too many arguments for calling -[\(target.dynamicType) \(selector)]") - var args = [[Int]](count: arguments.count, repeatedValue: []) + "Too many arguments for calling -[\(type(of: target)) \(selector)]") + var args = [[Int]](repeating: [], count: arguments.count) for i in 0 ..< arguments.count { - let type = sig.getArgumentTypeAtIndex(i + 2) - let typeChar = Character(UnicodeScalar(UInt8(type[0]))) - - // Convert argument type to adapte requirement of method. - // Firstly, convert argument to appropriate object type. - var argument: Any! = castToObjectFromAny(arguments[i]) - assert(argument != nil || arguments[i] == nil, "Can't convert '\(arguments[i].dynamicType)' to object type") - if typeChar != "@", let obj: AnyObject = argument as? AnyObject { - // Convert back to scalar type as method requires. - argument = castToAnyFromObject(obj, withObjCType: type) - } - - if typeChar == "f", let float = argument as? Float { - // Float type shouldn't be promoted to double if it is not variadic. - args[i] = [ Int(unsafeBitCast(float, Int32.self)) ] - } else if let val = argument as? CVarArgType { - // Scalar(except float), pointer and Objective-C object types - args[i] = val._cVarArgEncoding - } else if let obj: AnyObject = argument as? AnyObject { - // Pure swift object type - args[i] = [ unsafeBitCast(unsafeAddressOf(obj), Int.self) ] + if let arg: Any = arguments[i] { + let code = sig.getArgumentType(atIndex: UInt(i) + 2) + let type = ObjCType(code: code) + if type == .object { + let obj: AnyObject + if let val = arg as? NSNumberConvertible { + obj = NSNumber(value: val) + } else { + obj = _bridgeAnythingToObjectiveC(arg) + } + _autorelease(obj) + args[i] = _encodeBitsAsWords(obj) + } else if type == .clazz, let cls = arg as? AnyClass { + args[i] = _encodeBitsAsWords(cls) + } else if type == .float, let float = arg as? Float { + // prevent to promot float type to double + args[i] = _encodeBitsAsWords(float) + } else if var val = arg as? CVarArg { + if (type(of: arg) as? AnyClass)?.isSubclass(of: NSNumber.self) == true { + // argument is an NSNumber object + if let v = (arg as! NSNumber).value(as: type) { + val = v + } + } + args[i] = val._cVarArgEncoding + } else { + let type = String(cString: code) + fatalError("Unable to convert argument \(i) from Swift type \(type(of: arg)) to ObjC type '\(type)'") + } } else { - // Nil or unsupported type - assert(argument == nil, "Unsupported argument type '\(String(UTF8String: type))'") - var align: Int = 0 - NSGetSizeAndAlignment(type, nil, &align) - args[i] = [Int](count: align / sizeof(Int), repeatedValue: 0) + // nil + args[i] = [Int(0)] } + args[i].withUnsafeBufferPointer { - inv.setArgument(UnsafeMutablePointer($0.baseAddress), atIndex: i + 2) + inv.setArgument(UnsafeMutablePointer(mutating: $0.baseAddress!), atIndex: i + 2) } } @@ -171,125 +110,153 @@ public func invoke(target: AnyObject, selector: Selector, withArguments argument } inv.selector = selector - if thread == nil || (thread == NSThread.currentThread() && wait) { - inv.invokeWithTarget(target) + if thread == nil || (thread == Thread.current && wait) { + inv.invoke(target: target) } else { - let selector = #selector(_SpecialSelectors.invokeWithTarget(_:)) + let selector = #selector(NSInvocationProtocol.invoke(target:)) inv.retainArguments() - inv.performSelector(selector, onThread: thread!, withObject: target, waitUntilDone: wait) + (inv as! NSObject).perform(selector, on: thread!, with: target, waitUntilDone: wait) guard wait else { return Void() } } if sig.methodReturnLength == 0 { return Void() } // Fetch the return value - let buffer = UnsafeMutablePointer.alloc(sig.methodReturnLength) + let buffer = UnsafeMutablePointer.allocate(capacity: Int(sig.methodReturnLength)) inv.getReturnValue(buffer) + let type = ObjCType(code: sig.methodReturnType) defer { - if sig.methodReturnType[0] == 0x40 && selector.returnsRetained { + if type == .object && selector.returnsRetained { // To balance the retained return value - Unmanaged.passUnretained(UnsafePointer(buffer).memory).release() + let obj = UnsafeRawPointer(buffer).load(as: AnyObject.self) + Unmanaged.passUnretained(obj).release() } - buffer.dealloc(sig.methodReturnLength) + buffer.deallocate(capacity: Int(sig.methodReturnLength)) } - return castToAnyFromBytes(buffer, withObjCType: sig.methodReturnType) + return type.loadValue(from: buffer) } - -// Convert byte array to specified Objective-C type -// See: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html -private func castToAnyFromBytes(bytes: UnsafePointer, withObjCType type: UnsafePointer) -> Any! { - switch Character(UnicodeScalar(UInt8(type[0]))) { - case "c": return UnsafePointer(bytes).memory - case "i": return UnsafePointer(bytes).memory - case "s": return UnsafePointer(bytes).memory - case "l": return UnsafePointer(bytes).memory - case "q": return UnsafePointer(bytes).memory - case "C": return UnsafePointer(bytes).memory - case "I": return UnsafePointer(bytes).memory - case "S": return UnsafePointer(bytes).memory - case "L": return UnsafePointer(bytes).memory - case "Q": return UnsafePointer(bytes).memory - case "f": return UnsafePointer(bytes).memory - case "d": return UnsafePointer(bytes).memory - case "B": return UnsafePointer(bytes).memory - case "v": assertionFailure("Why cast to Void type?") - case "*": return UnsafePointer(bytes) - case "@": return UnsafePointer(bytes).memory - case "#": return UnsafePointer(bytes).memory - case ":": return UnsafePointer(bytes).memory - case "^": return UnsafePointer(bytes).memory - default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") +public func createInstance(of class: AnyClass, by initializer: Selector = #selector(NSObject.init), with arguments: [Any?] = []) -> AnyObject? { + guard let obj = invoke(#selector(NSProxy.alloc), of: `class`) else { + return nil } - return Void() + return invoke(initializer, of: obj as AnyObject, with: arguments) as AnyObject } -// Convert AnyObject to specified Objective-C type -private func castToAnyFromObject(object: AnyObject, withObjCType type: UnsafePointer) -> Any! { - let num = object as? NSNumber - switch Character(UnicodeScalar(UInt8(type[0]))) { - case "c": return num?.charValue - case "i": return num?.intValue - case "s": return num?.shortValue - case "l": return num?.intValue - case "q": return num?.longLongValue - case "C": return num?.unsignedCharValue - case "I": return num?.unsignedIntValue - case "S": return num?.unsignedShortValue - case "L": return num?.unsignedIntValue - case "Q": return num?.unsignedLongLongValue - case "f": return num?.floatValue - case "d": return num?.doubleValue - case "B": return num?.boolValue - case "v": return Void() - case "*": return (object as? String)?.nulTerminatedUTF8.withUnsafeBufferPointer{ COpaquePointer($0.baseAddress) } - case ":": return object is String ? Selector(object as! String) : Selector() - case "@": return object - case "#": return object as? AnyClass - case "^": return (object as? NSValue)?.pointerValue - default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") - } - return nil -} +// See: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html +private enum ObjCType : CChar { + case char = 0x63 // 'c' + case int = 0x69 // 'i' + case short = 0x73 // 's' + case long = 0x6c // 'l' + case longlong = 0x71 // 'q' + case uchar = 0x43 // 'C' + case uint = 0x49 // 'I' + case ushort = 0x53 // 'S' + case ulong = 0x4c // 'L' + case ulonglong = 0x51 // 'Q' + case float = 0x66 // 'f' + case double = 0x64 // 'd' + case bool = 0x42 // 'B' + case void = 0x76 // 'v' + case string = 0x2a // '*' + case object = 0x40 // '@' + case clazz = 0x23 // '#' + case selector = 0x3a // ':' + case pointer = 0x5e // '^' + case unknown = 0x3f // '?' -// Convert Any value to appropriate Objective-C object -public func castToObjectFromAny(value: Any!) -> AnyObject! { - if value == nil || value is AnyObject { - // Some scalar types (Int, UInt, Bool, Float and Double) can be converted automatically by runtime. - return value as? AnyObject + init(code: UnsafePointer) { + var val = code.pointee + if val == 0x72 { + // skip const qualifier + val = code.successor().pointee + } + guard let type = ObjCType(rawValue: val) else { + fatalError("Unknown ObjC type code: \(String(cString: code))") + } + self = type } - switch value { - case let v as Int8: return NSNumber(char: v) - case let v as Int16: return NSNumber(short: v) - case let v as Int32: return NSNumber(int: v) - case let v as Int64: return NSNumber(longLong: v) - case let v as UInt8: return NSNumber(unsignedChar: v) - case let v as UInt16: return NSNumber(unsignedShort: v) - case let v as UInt32: return NSNumber(unsignedInt: v) - case let v as UInt64: return NSNumber(unsignedLongLong: v) - case let v as UnicodeScalar: return NSNumber(unsignedInt: v.value) - case let s as Selector: return String(s) - case let p as COpaquePointer: return NSValue(pointer: UnsafePointer(p)) - default: - assert(value is Void, "Can't convert '\(value.dynamicType)' to AnyObject") + func loadValue(from pointer: UnsafeRawPointer) -> Any! { + switch self { + case .char: return pointer.load(as: CChar.self) + case .int: return pointer.load(as: CInt.self) + case .short: return pointer.load(as: CShort.self) + case .long: return pointer.load(as: Int32.self) + case .longlong: return pointer.load(as: CLongLong.self) + case .uchar: return pointer.load(as: CUnsignedChar.self) + case .uint: return pointer.load(as: CUnsignedInt.self) + case .ushort: return pointer.load(as: CUnsignedShort.self) + case .ulong: return pointer.load(as: UInt32.self) + case .ulonglong: return pointer.load(as: CUnsignedLongLong.self) + case .float: return pointer.load(as: CFloat.self) + case .double: return pointer.load(as: CDouble.self) + case .bool: return pointer.load(as: CBool.self) + case .void: return Void() + case .string: return pointer.load(as: UnsafePointer.self) + case .object: return pointer.load(as: AnyObject!.self) + case .clazz: return pointer.load(as: AnyClass!.self) + case .selector: return pointer.load(as: Selector!.self) + case .pointer: return pointer.load(as: OpaquePointer.self) + case .unknown: fatalError("Unknown ObjC type") + } } - return nil } -// Additional Swift types which can be represented in C type. -extension Bool: CVarArgType { - public var _cVarArgEncoding: [Int] { - return [ Int(self) ] - } -} -extension UnicodeScalar: CVarArgType { - public var _cVarArgEncoding: [Int] { - return [ Int(self.value) ] +private protocol NSNumberConvertible {} +extension Int: NSNumberConvertible {} +extension Int8: NSNumberConvertible {} +extension Int16: NSNumberConvertible {} +extension Int32: NSNumberConvertible {} +extension Int64: NSNumberConvertible {} +extension UInt: NSNumberConvertible {} +extension UInt8: NSNumberConvertible {} +extension UInt16: NSNumberConvertible {} +extension UInt32: NSNumberConvertible {} +extension UInt64: NSNumberConvertible {} +extension Bool: NSNumberConvertible {} +extension Double: NSNumberConvertible {} +extension Float: NSNumberConvertible {} +extension UnicodeScalar: NSNumberConvertible {} + +private extension NSNumber { + func value(as type: ObjCType) -> CVarArg? { + switch type { + case .bool: return self.boolValue + case .char: return self.int8Value + case .int: return self.int32Value + case .short: return self.int16Value + case .long: return self.int32Value + case .longlong: return self.int64Value + case .uchar: return self.uint8Value + case .uint: return self.uint32Value + case .ushort: return self.uint16Value + case .ulong: return self.uint32Value + case .ulonglong: return self.uint64Value + case .float: return self.floatValue + case .double: return self.doubleValue + default: return nil + } } -} -extension Selector: CVarArgType { - public var _cVarArgEncoding: [Int] { - return [ unsafeBitCast(self, Int.self) ] + + convenience init(value: NSNumberConvertible) { + switch value { + case let v as Int: self.init(value: v) + case let v as Int8: self.init(value: v) + case let v as Int16: self.init(value: v) + case let v as Int32: self.init(value: v) + case let v as Int64: self.init(value: v) + case let v as UInt: self.init(value: v) + case let v as UInt8: self.init(value: v) + case let v as UInt16: self.init(value: v) + case let v as UInt32: self.init(value: v) + case let v as UInt64: self.init(value: v) + case let v as Bool: self.init(value: v) + case let v as Double: self.init(value: v) + case let v as Float: self.init(value: v) + case let v as UnicodeScalar: self.init(value: v.value) + default: fatalError("never reach here") + } } } @@ -311,13 +278,13 @@ private extension Selector { ] var family: Family { // See: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#id34 - var s = unsafeBitCast(self, UnsafePointer.self) - while s.memory == 0x5f { s += 1 } // skip underscore + var s = unsafeBitCast(self, to: UnsafePointer.self) + while s.pointee == 0x5f { s += 1 } // skip underscore for p in Selector.prefixes { - let lowercase: Range = 97...122 + let lowercase = CChar(97)...CChar(122) let l = p.count - if strncmp(s, p, l) == 0 && !lowercase.contains(s.advancedBy(l).memory) { - return Family(rawValue: s.memory)! + if strncmp(s, p, l) == 0 && !lowercase.contains(s.advanced(by: l).pointee) { + return Family(rawValue: s.pointee)! } } return .none @@ -326,3 +293,113 @@ private extension Selector { return family != .none } } + +// Additional Swift types which can be represented in C type. +extension CVarArg { + public var _cVarArgEncoding: [Int] { + return _encodeBitsAsWords(self) + } +} +extension Bool: CVarArg { + public var _cVarArgEncoding: [Int] { + return _encodeBitsAsWords(self) + } +} +extension UnicodeScalar: CVarArg {} +extension Selector: CVarArg {} +extension UnsafeRawPointer: CVarArg {} +extension UnsafeMutableRawPointer: CVarArg {} +extension UnsafeBufferPointer: CVarArg {} +extension UnsafeMutableBufferPointer: CVarArg {} + + +/////////////////////////////////////////////////////////////////////////////// + +public class XWVInvocation { + public final let target: AnyObject + private let thread: Thread? + + public init(target: AnyObject, thread: Thread? = nil) { + self.target = target + self.thread = thread + } + + @discardableResult public func call(_ selector: Selector, with arguments: [Any?] = []) -> Any! { + return invoke(selector, of: target, with: arguments, on: thread) + } + // No callback support, so return value is expected to lose. + public func asyncCall(_ selector: Selector, with arguments: [Any?] = []) { + invoke(selector, of: target, with: arguments, on: thread, waitUntilDone: false) + } + + // Syntactic sugar for calling method + public subscript (selector: Selector) -> (Any?...)->Any! { + return { + (args: Any?...)->Any! in + self.call(selector, with: args) + } + } +} + +extension XWVInvocation { + // Property accessor + public func value(of name: String) -> Any! { + guard let getter = getter(of: name) else { + assertionFailure("Property '\(name)' does not exist") + return Void() + } + return call(getter) + } + public func setValue(_ value: Any!, to name: String) { + guard let setter = setter(of: name) else { + assertionFailure("Property '\(name)' " + + (getter(of: name) == nil ? "does not exist" : "is readonly")) + return + } + precondition(!(value is Void)) + call(setter, with: [value]) + } + + // Syntactic sugar for accessing property + public subscript (name: String) -> Any! { + get { + return value(of: name) + } + set { + setValue(newValue, to: name) + } + } + + private func getter(of name: String) -> Selector? { + guard let property = class_getProperty(type(of: target), name) else { + return nil + } + guard let attr = property_copyAttributeValue(property, "G") else { + return Selector(name) + } + + // The property defines a custom getter selector name. + let getter = Selector(String(cString: attr)) + free(attr) + return getter + } + private func setter(of name: String) -> Selector? { + guard let property = class_getProperty(type(of: target), name) else { + return nil + } + + var setter: Selector? = nil + var attr = property_copyAttributeValue(property, "R") + if attr == nil { + attr = property_copyAttributeValue(property, "S") + if attr == nil { + setter = Selector("set\(String(name[name.startIndex]).uppercased())\(String(name.characters.dropFirst())):") + } else { + // The property defines a custom setter selector name. + setter = Selector(String(cString: attr!)) + } + } + free(attr) + return setter + } +} diff --git a/XWebView/XWVJson.swift b/XWebView/XWVJson.swift new file mode 100644 index 0000000..01850ef --- /dev/null +++ b/XWebView/XWVJson.swift @@ -0,0 +1,113 @@ +/* + Copyright 2015 XWebView + + Licensed 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 Foundation + +// JSON Array +func jsonify(_ array: T) -> String where T.Index == Int { + return "[" + array.map(jsonify).joined(separator: ", ") + "]" +} + +// JSON Object +func jsonify(_ object: T) -> String where T.Iterator.Element == (key: String, value: V) { + return "{" + object.map(jsonify).joined(separator: ", ") + "}" +} +private func jsonify(_ pair: (key: String, value: T)) -> String { + return jsonify(pair.key) + ":" + jsonify(pair.value) +} + +// JSON Number +func jsonify(_ integer: T) -> String { + return String(describing: integer) +} +func jsonify(_ float: T) -> String { + return String(describing: float) +} + +// JSON Boolean +func jsonify(_ bool: Bool) -> String { + return String(describing: bool) +} + +// JSON String +func jsonify(_ string: String) -> String { + return string.unicodeScalars.reduce("\"") { $0 + $1.jsonEscaped } + "\"" +} +func jsonify(_ char: Character) -> String { + return jsonify(String(char)) +} +private extension UnicodeScalar { + var jsonEscaped: String { + switch value { + case 0...7: fallthrough + case 11, 14, 15: return "\\u000" + String(value, radix: 16) + case 16...31: fallthrough + case 127...159: return "\\u00" + String(value, radix: 16) + case 8: return "\\b" + case 12: return "\\f" + case 39: return "'" + default: return escaped(asASCII: false) + } + } +} + +func jsonify(_ value: NSObject) -> String { + switch (value) { + case _ as NSNull: + return "null" + case let s as NSString: + return jsonify(String(s)) + case let n as NSNumber: + if CFGetTypeID(n) == CFBooleanGetTypeID() { + return n.boolValue.description + } + return n.stringValue + case let d as Data: + return d.withUnsafeBytes { + (ptr: UnsafePointer) -> String in + jsonify(UnsafeBufferPointer(start: ptr, count: d.count)) + } + default: + //fatalError("Unsupported type \(type(of: value))") + print("Unsupported type \(type(of: value))") + return "undefined" + } +} + +func jsonify(_ value: Any!) -> String { + switch (value) { + case nil: return "undefined" + case let b as Bool: return jsonify(b) + case let i as Int: return jsonify(i) + case let i as Int8: return jsonify(i) + case let i as Int16: return jsonify(i) + case let i as Int32: return jsonify(i) + case let i as Int64: return jsonify(i) + case let u as UInt: return jsonify(u) + case let u as UInt8: return jsonify(u) + case let u as UInt16: return jsonify(u) + case let u as UInt32: return jsonify(u) + case let u as UInt64: return jsonify(u) + case let f as Float: return jsonify(f) + case let f as Double: return jsonify(f) + case let s as String: return jsonify(s) + case let v as NSObject: return jsonify(v) + case is Void: return "undefined" + case let o as Optional: + guard case let .some(v) = o else { return "null" } + fatalError("Unsupported type \(type(of: v)) (of value \(v))") + } +} diff --git a/XWebView/XWVLogging.swift b/XWebView/XWVLogging.swift index 945dde9..77f97c9 100644 --- a/XWebView/XWVLogging.swift +++ b/XWebView/XWVLogging.swift @@ -16,13 +16,13 @@ import Darwin -public typealias asl_object_t = COpaquePointer +public typealias asl_object_t = OpaquePointer -@_silgen_name("asl_open") func asl_open(ident: UnsafePointer, _ facility: UnsafePointer, _ opts: UInt32) -> asl_object_t -@_silgen_name("asl_close") func asl_close(obj: asl_object_t) -@_silgen_name("asl_vlog") func asl_vlog(obj: asl_object_t, _ msg: asl_object_t, _ level: Int32, _ format: UnsafePointer, _ ap: CVaListPointer) -> Int32 -@_silgen_name("asl_add_output_file") func asl_add_output_file(client: asl_object_t, _ descriptor: Int32, _ msg_fmt: UnsafePointer, _ time_fmt: UnsafePointer, _ filter: Int32, _ text_encoding: Int32) -> Int32 -@_silgen_name("asl_set_output_file_filter") func asl_set_output_file_filter(asl: asl_object_t, _ descriptor: Int32, _ filter: Int32) -> Int32 +@_silgen_name("asl_open") func asl_open(_ ident: UnsafePointer?, _ facility: UnsafePointer?, _ opts: UInt32) -> asl_object_t? +@_silgen_name("asl_close") func asl_close(_ obj: asl_object_t) +@_silgen_name("asl_vlog") func asl_vlog(_ obj: asl_object_t, _ msg: asl_object_t?, _ level: Int32, _ format: UnsafePointer, _ ap: CVaListPointer) -> Int32 +@_silgen_name("asl_add_output_file") func asl_add_output_file(_ client: asl_object_t, _ descriptor: Int32, _ msg_fmt: UnsafePointer?, _ time_fmt: UnsafePointer?, _ filter: Int32, _ text_encoding: Int32) -> Int32 +@_silgen_name("asl_set_output_file_filter") func asl_set_output_file_filter(_ asl: asl_object_t, _ descriptor: Int32, _ filter: Int32) -> Int32 public class XWVLogging : XWVScripting { public enum Level : Int32 { @@ -38,15 +38,15 @@ public class XWVLogging : XWVScripting { private static let symbols : [Character] = [ "\0", "\0", "$", "!", "?", "-", "+", " " ] - private init?(symbol: Character) { - guard symbol != "\0", let value = Level.symbols.indexOf(symbol) else { + fileprivate init?(symbol: Character) { + guard symbol != "\0", let value = Level.symbols.index(of: symbol) else { return nil } self = Level(rawValue: Int32(value))! } } - public struct Filter : OptionSetType { + public struct Filter : OptionSet { private var value: Int32 public var rawValue: Int32 { return value @@ -68,14 +68,14 @@ public class XWVLogging : XWVScripting { public var filter: Filter { didSet { - asl_set_output_file_filter(client, STDERR_FILENO, filter.rawValue) + _ = asl_set_output_file_filter(client, STDERR_FILENO, filter.rawValue) } } private let client: asl_object_t private var lock: pthread_mutex_t = pthread_mutex_t() public init(facility: String, format: String? = nil) { - client = asl_open(nil, facility, 0) + client = asl_open(nil, facility, 0)! pthread_mutex_init(&lock, nil) #if DEBUG @@ -85,30 +85,30 @@ public class XWVLogging : XWVScripting { #endif let format = format ?? "$((Time)(lcl)) $(Facility) <$((Level)(char))>: $(Message)" - asl_add_output_file(client, STDERR_FILENO, format, "sec", filter.rawValue, 1) + _ = asl_add_output_file(client, STDERR_FILENO, format, "sec", filter.rawValue, 1) } deinit { asl_close(client) pthread_mutex_destroy(&lock) } - public func log(message: String, level: Level) { + public func log(_ message: String, level: Level) { pthread_mutex_lock(&lock) - asl_vlog(client, nil, level.rawValue, message, getVaList([])) + _ = asl_vlog(client, nil, level.rawValue, message, getVaList([])) pthread_mutex_unlock(&lock) } - public func log(message: String, level: Level? = nil) { + public func log(_ message: String, level: Level? = nil) { var msg = message var lvl = level ?? .Debug - if level == nil, let ch = msg.characters.first, l = Level(symbol: ch) { - msg = msg[msg.startIndex.successor() ..< msg.endIndex] + if level == nil, let ch = msg.characters.first, let l = Level(symbol: ch) { + msg = String(msg.characters.dropFirst()) lvl = l } log(msg, level: lvl) } - @objc public func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! { + @objc public func invokeDefaultMethod(withArguments args: [Any]!) -> Any! { guard args.count > 0 else { return nil } let message = args[0] as? String ?? "\(args[0])" var level: Level? = nil @@ -125,11 +125,11 @@ public class XWVLogging : XWVScripting { } private let logger = XWVLogging(facility: "org.xwebview.xwebview") -func log(message: String, level: XWVLogging.Level? = nil) { +func log(_ message: String, level: XWVLogging.Level? = nil) { logger.log(message, level: level) } -@noreturn func die(@autoclosure message: ()->String, file: StaticString = #file, line: UInt = #line) { +func die(_ message: @autoclosure ()->String, file: StaticString = #file, line: UInt = #line) -> Never { logger.log(message(), level: .Alert) fatalError(message, file: file, line: line) } diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index 630da60..7c26f92 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -17,10 +17,10 @@ import Foundation import ObjectiveC -class XWVMetaObject: CollectionType { +class XWVMetaObject { enum Member { case Method(selector: Selector, arity: Int32) - case Property(getter: Selector, setter: Selector) + case Property(getter: Selector, setter: Selector?) case Initializer(selector: Selector, arity: Int32) var isMethod: Bool { @@ -38,10 +38,8 @@ class XWVMetaObject: CollectionType { var selector: Selector? { switch self { case let .Method(selector, _): - assert(selector != Selector()) return selector case let .Initializer(selector, _): - assert(selector != Selector()) return selector default: return nil @@ -49,14 +47,12 @@ class XWVMetaObject: CollectionType { } var getter: Selector? { if case .Property(let getter, _) = self { - assert(getter != Selector()) return getter } return nil } var setter: Selector? { - if case .Property(let getter, let setter) = self { - assert(getter != Selector()) + if case .Property(_, let setter) = self { return setter } return nil @@ -84,33 +80,33 @@ class XWVMetaObject: CollectionType { } let plugin: AnyClass - private var members = [String: Member]() + fileprivate var members = [String: Member]() private static let exclusion: Set = { var methods = instanceMethods(forProtocol: XWVScripting.self) - methods.remove(#selector(XWVScripting.invokeDefaultMethodWithArguments(_:))) + methods.remove(#selector(XWVScripting.invokeDefaultMethod(withArguments:))) return methods.union([ - #selector(_SpecialSelectors.dealloc), - #selector(NSObject.copy as ()->AnyObject) + #selector(NSObject.deinit), + #selector(NSObject.copy) ]) }() init(plugin: AnyClass) { self.plugin = plugin - enumerateExcluding(self.dynamicType.exclusion) { + _ = enumerate(excluding: type(of: self).exclusion) { (name, member) -> Bool in var name = name var member = member switch member { case let .Method(selector, _): if let cls = plugin as? XWVScripting.Type { - if cls.isSelectorExcludedFromScript?(selector) ?? false { + if cls.isSelectorExcluded?(fromScript: selector) ?? false { return true } - if selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:)) { + if selector == #selector(XWVScripting.invokeDefaultMethod(withArguments:)) { member = .Method(selector: selector, arity: -1) name = "" } else { - name = cls.scriptNameForSelector?(selector) ?? name + name = cls.scriptName?(for: selector) ?? name } } else if name.characters.first == "_" { return true @@ -118,10 +114,10 @@ class XWVMetaObject: CollectionType { case .Property(_, _): if let cls = plugin as? XWVScripting.Type { - if let isExcluded = cls.isKeyExcludedFromScript where name.withCString(isExcluded) { + if let isExcluded = cls.isKeyExcluded(fromScript:), name.withCString(isExcluded) { return true } - if let scriptNameForKey = cls.scriptNameForKey { + if let scriptNameForKey = cls.scriptName(forKey:) { name = name.withCString(scriptNameForKey) ?? name } } else if name.characters.first == "_" { @@ -133,30 +129,30 @@ class XWVMetaObject: CollectionType { member = .Initializer(selector: selector, arity: -1) name = "" } else if let cls = plugin as? XWVScripting.Type { - name = cls.scriptNameForSelector?(selector) ?? name + name = cls.scriptName?(for: selector) ?? name } if !name.isEmpty { return true } } - assert(members.indexForKey(name) == nil, "Plugin class \(plugin) has a conflict in member name '\(name)'") + assert(members.index(forKey: name) == nil, "Plugin class \(plugin) has a conflict in member name '\(name)'") members[name] = member return true } } - private func enumerateExcluding(selectors: Set, @noescape callback: ((String, Member)->Bool)) -> Bool { + private func enumerate(excluding selectors: Set, callback: (String, Member)->Bool) -> Bool { var known = selectors // enumerate properties let propertyList = class_copyPropertyList(plugin, nil) - if propertyList != nil, var prop = Optional(propertyList) { + if var prop = propertyList { defer { free(propertyList) } - while prop.memory != nil { - let name = String(UTF8String: property_getName(prop.memory))! + while prop.pointee != nil { + let name = String(cString: property_getName(prop.pointee)) // get getter - var attr = property_copyAttributeValue(prop.memory, "G") - let getter = Selector(attr == nil ? name : String(UTF8String: attr)!) + var attr = property_copyAttributeValue(prop.pointee, "G") + let getter = Selector(attr == nil ? name : String(cString: attr!)) free(attr) if known.contains(getter) { prop = prop.successor() @@ -165,19 +161,19 @@ class XWVMetaObject: CollectionType { known.insert(getter) // get setter if readwrite - var setter = Selector() - attr = property_copyAttributeValue(prop.memory, "R") + var setter: Selector? = nil + attr = property_copyAttributeValue(prop.pointee, "R") if attr == nil { - attr = property_copyAttributeValue(prop.memory, "S") + attr = property_copyAttributeValue(prop.pointee, "S") if attr == nil { - setter = Selector("set\(String(name.characters.first!).uppercaseString)\(String(name.characters.dropFirst())):") + setter = Selector("set\(String(name[name.startIndex]).uppercased())\(String(name.characters.dropFirst())):") } else { - setter = Selector(String(UTF8String: attr)!) + setter = Selector(String(cString: attr!)) } - if known.contains(setter) { - setter = Selector() + if known.contains(setter!) { + setter = nil } else { - known.insert(setter) + known.insert(setter!) } } free(attr) @@ -192,12 +188,11 @@ class XWVMetaObject: CollectionType { // enumerate methods let methodList = class_copyMethodList(plugin, nil) - if methodList != nil, var method = Optional(methodList) { + if var method = methodList { defer { free(methodList) } - while method.memory != nil { - let sel = method_getName(method.memory) - if !known.contains(sel) && !sel.description.hasPrefix(".") { - let arity = Int32(method_getNumberOfArguments(method.memory) - 2) + while method.pointee != nil { + if let sel = method_getName(method.pointee), !known.contains(sel) && !sel.description.hasPrefix(".") { + let arity = Int32(method_getNumberOfArguments(method.pointee)) - 2 let member: Member if sel.description.hasPrefix("init") { member = Member.Initializer(selector: sel, arity: arity) @@ -205,7 +200,7 @@ class XWVMetaObject: CollectionType { member = Member.Method(selector: sel, arity: arity) } var name = sel.description - if let end = name.characters.indexOf(":") { + if let end = name.characters.index(of: ":") { name = name[name.startIndex ..< end] } if !callback(name, member) { @@ -217,28 +212,30 @@ class XWVMetaObject: CollectionType { } return true } -} -extension XWVMetaObject { - // SequenceType - typealias Generator = DictionaryGenerator - func generate() -> Generator { - return members.generate() + subscript (name: String) -> Member? { + return members[name] } +} - // CollectionType +extension XWVMetaObject: Collection { + // IndexableBase typealias Index = DictionaryIndex + typealias SubSequence = Slice> var startIndex: Index { return members.startIndex } var endIndex: Index { return members.endIndex } - subscript (position: Index) -> (String, Member) { - return members[position] + subscript (_ i: Index) -> (String, Member) { + return members[i] } - subscript (name: String) -> Member? { - return members[name] + subscript (_ range: Range) -> SubSequence { + return members[range] + } + func index(after i: Index) -> Index { + return members.index(after: i) } } @@ -246,9 +243,9 @@ private func instanceMethods(forProtocol aProtocol: Protocol) -> Set { var selectors = Set() for (req, inst) in [(true, true), (false, true)] { let methodList = protocol_copyMethodDescriptionList(aProtocol.self, req, inst, nil) - if methodList != nil, var desc = Optional(methodList) { - while desc.memory.name != nil { - selectors.insert(desc.memory.name) + if var desc = methodList { + while desc.pointee.name != nil { + selectors.insert(desc.pointee.name) desc = desc.successor() } free(methodList) diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index dce7c6f..939f272 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -18,7 +18,7 @@ import Foundation import WebKit private let webViewInvalidated = - NSError(domain: WKErrorDomain, code: WKErrorCode.WebViewInvalidated.rawValue, userInfo: nil) + NSError(domain: WKErrorDomain, code: WKError.webViewInvalidated.rawValue, userInfo: nil) public class XWVObject : NSObject { public let namespace: String @@ -67,20 +67,20 @@ public class XWVObject : NSObject { } // Evaluate JavaScript expression - public func evaluateExpression(expression: String) throws -> AnyObject? { + public func evaluateExpression(_ expression: String) throws -> Any? { guard let webView = webView else { throw webViewInvalidated } return wrapScriptObject(try webView.evaluateJavaScript(scriptForRetaining(expression))) } - public func evaluateExpression(expression: String, error: NSErrorPointer) -> AnyObject? { + public func evaluateExpression(_ expression: String, error: ErrorPointer) -> Any? { guard let webView = webView else { - if error != nil { error.memory = webViewInvalidated } + error?.pointee = webViewInvalidated return nil } return wrapScriptObject(webView.evaluateJavaScript(scriptForRetaining(expression), error: error)) } - public func evaluateExpression(expression: String, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + public func evaluateExpression(_ expression: String, completionHandler: ((Any?, Error?) -> Void)?) { guard let webView = webView else { completionHandler?(nil, webViewInvalidated) return @@ -90,20 +90,20 @@ public class XWVObject : NSObject { return } webView.evaluateJavaScript(scriptForRetaining(expression)) { - [weak self](result: AnyObject?, error: NSError?)->Void in + [weak self](result: Any?, error: Error?)->Void in completionHandler(self?.wrapScriptObject(result) ?? result, error) } } - private func scriptForRetaining(script: String) -> String { + private func scriptForRetaining(_ script: String) -> String { guard let origin = origin else { return script } return "\(origin.namespace).$retainObject(\(script))" } - func wrapScriptObject(object: AnyObject!) -> AnyObject! { + func wrapScriptObject(_ object: Any) -> Any { guard let origin = origin else { return object } - if let dict = object as? [String: AnyObject] where dict["$sig"] as? NSNumber == 0x5857574F { - if let num = dict["$ref"] as? NSNumber where num != 0 { - return XWVScriptObject(reference: num.integerValue, origin: origin) + if let dict = object as? [String: Any], dict["$sig"] as? NSNumber == 0x5857574F { + if let num = dict["$ref"] as? NSNumber, num != 0 { + return XWVScriptObject(reference: num.intValue, origin: origin) } else if let namespace = dict["$ns"] as? String { return XWVScriptObject(namespace: namespace, origin: origin) } @@ -111,36 +111,18 @@ public class XWVObject : NSObject { return object } - func serialize(object: AnyObject?) -> String { - var obj: AnyObject? = object - if let val = obj as? NSValue { - obj = val as? NSNumber ?? val.nonretainedObjectValue - } - - if let o = obj as? XWVObject { + func serialize(_ value: Any?) -> String { + switch value { + case let o as XWVObject: return o.namespace - } else if let s = obj as? String { - let d = try? NSJSONSerialization.dataWithJSONObject([s], options: NSJSONWritingOptions(rawValue: 0)) - let json = NSString(data: d!, encoding: NSUTF8StringEncoding)! - return json.substringWithRange(NSMakeRange(1, json.length - 2)) - } else if let n = obj as? NSNumber { - if CFGetTypeID(n) == CFBooleanGetTypeID() { - return n.boolValue.description - } - return n.stringValue - } else if let date = obj as? NSDate { - return "(new Date(\(date.timeIntervalSince1970 * 1000)))" - } else if let _ = obj as? NSData { - // TODO: map to Uint8Array object - } else if let a = obj as? [AnyObject] { - return "[" + a.map(serialize).joinWithSeparator(", ") + "]" - } else if let d = obj as? [String: AnyObject] { - return "{" + d.keys.map{"'\($0)': \(self.serialize(d[$0]!))"}.joinWithSeparator(", ") + "}" - } else if obj === NSNull() { - return "null" - } else if obj == nil { - return "undefined" + case let d as Date: + return "(new Date(\(d.timeIntervalSince1970 * 1000)))" + case let a as [Any?]: + return jsonify(a) + case let d as [String: Any?]: + return jsonify(d) + default: + return jsonify(value) } - return "'\(obj!.description)'" } } diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index a276d35..9a2212a 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -19,70 +19,72 @@ import WebKit public class XWVScriptObject : XWVObject { // JavaScript object operations - public func construct(arguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + public func construct(arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { let exp = "new " + scriptForCallingMethod(nil, arguments: arguments) evaluateExpression(exp, completionHandler: completionHandler) } - public func call(arguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + public func call(arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { let exp = scriptForCallingMethod(nil, arguments: arguments) evaluateExpression(exp, completionHandler: completionHandler) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?, completionHandler: ((AnyObject?, NSError?) -> Void)?) { + public func callMethod(_ name: String, with arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { let exp = scriptForCallingMethod(name, arguments: arguments) evaluateExpression(exp, completionHandler: completionHandler) } - public func construct(arguments arguments: [AnyObject]?) throws -> AnyObject { + public func construct(arguments: [Any]?) throws -> Any { let exp = "new \(scriptForCallingMethod(nil, arguments: arguments))" guard let result = try evaluateExpression(exp) else { - let code = WKErrorCode.JavaScriptExceptionOccurred.rawValue + let code = WKError.javaScriptExceptionOccurred.rawValue throw NSError(domain: WKErrorDomain, code: code, userInfo: nil) } return result } - public func call(arguments arguments: [AnyObject]?) throws -> AnyObject? { + public func call(arguments: [Any]?) throws -> Any? { return try evaluateExpression(scriptForCallingMethod(nil, arguments: arguments)) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?) throws -> AnyObject? { + public func callMethod(_ name: String, with arguments: [Any]?) throws -> Any? { return try evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) } - public func call(arguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject? { + public func call(arguments: [Any]?, error: NSErrorPointer) -> Any? { return evaluateExpression(scriptForCallingMethod(nil, arguments: arguments), error: error) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?, error: NSErrorPointer) -> AnyObject? { + public func callMethod(_ name: String, with arguments: [Any]?, error: NSErrorPointer) -> Any? { return evaluateExpression(scriptForCallingMethod(name, arguments: arguments), error: error) } - public func defineProperty(name: String, descriptor: [String:AnyObject]) -> AnyObject? { + public func defineProperty(_ name: String, descriptor: [String:Any]) -> Any? { let exp = "Object.defineProperty(\(namespace), \(name), \(serialize(descriptor)))" return try! evaluateExpression(exp) } - public func deleteProperty(name: String) -> Bool { - let result: AnyObject? = try! evaluateExpression("delete \(scriptForFetchingProperty(name))") + public func deleteProperty(_ name: String) -> Bool { + let result: Any? = try! evaluateExpression("delete \(scriptForFetchingProperty(name))") return (result as? NSNumber)?.boolValue ?? false } - public func hasProperty(name: String) -> Bool { - let result: AnyObject? = try! evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") + public func hasProperty(_ name: String) -> Bool { + let result: Any? = try! evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") return (result as? NSNumber)?.boolValue ?? false } - public func value(forProperty name: String) -> AnyObject? { + public func value(for name: String) -> Any? { return try! evaluateExpression(scriptForFetchingProperty(name)) } - public func setValue(value: AnyObject?, forProperty name:String) { + public func setValue(_ value: Any?, for name:String) { webView?.evaluateJavaScript(scriptForUpdatingProperty(name, value: value), completionHandler: nil) } - public func value(atIndex index: UInt) -> AnyObject? { + public func value(at index: UInt) -> Any? { return try! evaluateExpression("\(namespace)[\(index)]") } - public func setValue(value: AnyObject?, atIndex index: UInt) { + public func setValue(_ value: Any?, at index: UInt) { webView?.evaluateJavaScript("\(namespace)[\(index)] = \(serialize(value))", completionHandler: nil) } - private func scriptForFetchingProperty(name: String!) -> String { - if name == nil { + private func scriptForFetchingProperty(_ name: String?) -> String { + guard let name = name else { return namespace - } else if name.isEmpty { + } + + if name.isEmpty { return "\(namespace)['']" } else if let idx = Int(name) { return "\(namespace)[\(idx)]" @@ -90,31 +92,31 @@ public class XWVScriptObject : XWVObject { return "\(namespace).\(name)" } } - private func scriptForUpdatingProperty(name: String!, value: AnyObject?) -> String { + private func scriptForUpdatingProperty(_ name: String?, value: Any?) -> String { return scriptForFetchingProperty(name) + " = " + serialize(value) } - private func scriptForCallingMethod(name: String!, arguments: [AnyObject]?) -> String { + private func scriptForCallingMethod(_ name: String?, arguments: [Any]?) -> String { let args = arguments?.map(serialize) ?? [] - return scriptForFetchingProperty(name) + "(" + args.joinWithSeparator(", ") + ")" + return scriptForFetchingProperty(name) + "(" + args.joined(separator: ", ") + ")" } } extension XWVScriptObject { // Subscript as property accessor - public subscript(name: String) -> AnyObject? { + public subscript(name: String) -> Any? { get { - return value(forProperty: name) + return value(for: name) } set { - setValue(newValue, forProperty: name) + setValue(newValue, for: name) } } - public subscript(index: UInt) -> AnyObject? { + public subscript(index: UInt) -> Any? { get { - return value(atIndex: index) + return value(at: index) } set { - setValue(newValue, atIndex: index) + setValue(newValue, at: index) } } } diff --git a/XWebView/XWVScripting.swift b/XWebView/XWVScripting.swift index e329266..dcf48d0 100644 --- a/XWebView/XWVScripting.swift +++ b/XWebView/XWVScripting.swift @@ -17,13 +17,13 @@ import Foundation @objc public protocol XWVScripting : class { - optional var channelIdentifier: String { get } - optional func rewriteGeneratedStub(stub: String, forKey: String) -> String - optional func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! - optional func finalizeForScript() + @objc optional var channelIdentifier: String { get } + @objc optional func rewriteStub(_ stub: String, forKey: String) -> String + @objc optional func invokeDefaultMethod(withArguments args: [Any]!) -> Any! + @objc optional func finalizeForScript() - optional static func scriptNameForKey(name: UnsafePointer) -> String? - optional static func scriptNameForSelector(selector: Selector) -> String? - optional static func isSelectorExcludedFromScript(selector: Selector) -> Bool - optional static func isKeyExcludedFromScript(name: UnsafePointer) -> Bool + @objc optional static func scriptName(forKey key: UnsafePointer) -> String? + @objc optional static func scriptName(for selector: Selector) -> String? + @objc optional static func isSelectorExcluded(fromScript selector: Selector) -> Bool + @objc optional static func isKeyExcluded(fromScript name: UnsafePointer) -> Bool } diff --git a/XWebView/XWVUserScript.swift b/XWebView/XWVUserScript.swift index ef50abe..5e19f2c 100644 --- a/XWebView/XWVUserScript.swift +++ b/XWebView/XWVUserScript.swift @@ -42,7 +42,7 @@ class XWVUserScript { webView.configuration.userContentController.addUserScript(script) // inject into current context - if webView.URL != nil { + if webView.url != nil { webView.evaluateJavaScript(script.source) { if let error = $1 { log("!Failed to inject script. \(error)") @@ -61,7 +61,7 @@ class XWVUserScript { if $0 != self.script { controller.addUserScript($0) } } - if webView.URL != nil, let cleanup = cleanup { + if webView.url != nil, let cleanup = cleanup { // clean up in current context webView.evaluateJavaScript(cleanup, completionHandler: nil) } diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index 7d5dcf6..2f5a0fb 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -24,36 +24,8 @@ FOUNDATION_EXPORT const unsigned char XWebViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import -// The workaround for loading file URL on iOS 8.x. -#import -@interface WKWebView (XWebView) -- (nullable WKNavigation *)loadFileURL:(nonnull NSURL *)URL allowingReadAccessToURL:(nonnull NSURL *)readAccessURL; -@end - NS_ASSUME_NONNULL_BEGIN -// The workaround for using NSInvocation and NSMethodSignature in Swift. -@protocol _NSMethodSignatureFactory -- (NSMethodSignature *)signatureWithObjCTypes:(const char *)types; -@end -@interface NSMethodSignature (Swift) <_NSMethodSignatureFactory> -@end - -@protocol _NSInvocationFactory -- (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; -@end -@interface NSInvocation (Swift) <_NSInvocationFactory> -@end - -// Special selectors which can't be referenced directly in Swift. -@protocol _SpecialSelectors -// NSObject -- (instancetype)alloc; -- (void)dealloc; -// NSInvocation -- (void)invokeWithTarget:(id)target; -@end - // Special init which can't be reference directly in Swift, but cannot be a protocol either. @interface _InitSelector: NSObject // Init with script diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 11a7805..4390e1c 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -23,25 +23,25 @@ extension WKWebView { return XWVWindowObject(webView: self) } - public func loadPlugin(object: AnyObject, namespace: String) -> XWVScriptObject? { + @discardableResult public func loadPlugin(_ object: AnyObject, namespace: String) -> XWVScriptObject? { let channel = XWVChannel(webView: self) return channel.bindPlugin(object, toNamespace: namespace) } func prepareForPlugin() { - let key = unsafeAddressOf(XWVChannel) + let key = Unmanaged.passUnretained(XWVChannel.self).toOpaque() if objc_getAssociatedObject(self, key) != nil { return } - let bundle = NSBundle(forClass: XWVChannel.self) - guard let path = bundle.pathForResource("xwebview", ofType: "js"), - let source = try? NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) else { + let bundle = Bundle(for: XWVChannel.self) + guard let path = bundle.path(forResource: "xwebview", ofType: "js"), + let source = try? NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) else { die("Failed to read provision script: xwebview.js") } - let time = WKUserScriptInjectionTime.AtDocumentStart + let time = WKUserScriptInjectionTime.atDocumentStart let script = WKUserScript(source: source as String, injectionTime: time, forMainFrameOnly: true) let xwvplugin = XWVUserScript(webView: self, script: script, namespace: "XWVPlugin") objc_setAssociatedObject(self, key, xwvplugin, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - log("+WKWebView(\(unsafeAddressOf(self))) is ready for loading plugins") + log("+WKWebView(\(self)) is ready for loading plugins") } } @@ -49,30 +49,30 @@ extension WKWebView { // Synchronized evaluateJavaScript // It returns nil if script is a statement or its result is undefined. // So, Swift cannot map the throwing method to Objective-C method. - public func evaluateJavaScript(script: String) throws -> AnyObject? { - var result: AnyObject? - var error: NSError? + open func evaluateJavaScript(_ script: String) throws -> Any? { + var result: Any? + var error: Error? var done = false let timeout = 3.0 - if NSThread.isMainThread() { + if Thread.isMainThread { evaluateJavaScript(script) { - (obj: AnyObject?, err: NSError?)->Void in + (obj: Any?, err: Error?)->Void in result = obj error = err done = true } while !done { - let reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true) - if reason != CFRunLoopRunResult.HandledSource { + let reason = CFRunLoopRunInMode(CFRunLoopMode.defaultMode, timeout, true) + if reason != CFRunLoopRunResult.handledSource { break } } } else { let condition: NSCondition = NSCondition() - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async() { [weak self] in self?.evaluateJavaScript(script) { - (obj: AnyObject?, err: NSError?)->Void in + (obj: Any?, err: Error?)->Void in condition.lock() result = obj error = err @@ -83,7 +83,7 @@ extension WKWebView { } condition.lock() while !done { - if !condition.waitUntilDate(NSDate(timeIntervalSinceNow: timeout)) { + if !condition.wait(until: Date(timeIntervalSinceNow: timeout) as Date) { break } } @@ -97,100 +97,43 @@ extension WKWebView { } // Wrapper method of synchronized evaluateJavaScript for Objective-C - public func evaluateJavaScript(script: String, error: NSErrorPointer) -> AnyObject? { - var result: AnyObject? - var err: NSError? + open func evaluateJavaScript(_ script: String, error: ErrorPointer) -> Any? { + var result: Any? + var err: Error? do { result = try evaluateJavaScript(script) } catch let e as NSError { err = e } - if error != nil { error.memory = err } + error?.pointee = err as NSError? return result } } - +/* +@available(iOS 9.0, *) extension WKWebView { // Overlay support for loading file URL - public func loadFileURL(URL: NSURL, overlayURLs: [NSURL]? = nil) -> WKNavigation? { - guard overlayURLs?.count > 0 else { - return loadFileURL(URL, allowingReadAccessToURL: URL.baseURL!) + public func loadFileURL(_ URL: URL, overlayURLs: [URL]? = nil) -> WKNavigation? { + if let count = overlayURLs?.count, count > 0 { + return loadFileURL(URL, allowingReadAccessTo: URL.baseURL!) } - guard URL.fileURL && URL.baseURL != nil else { - assertionFailure("URL must be a relative file URL.") - return nil + guard URL.isFileURL && URL.baseURL != nil else { + fatalError("URL must be a relative file URL.") } guard let port = startHttpd(rootURL: URL.baseURL!, overlayURLs: overlayURLs) else { return nil } #if swift(>=2.3) - let url = NSURL(string: URL.resourceSpecifier!, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + let url = URL(string: URL.resourceSpecifier!, relativeTo: URL(string: "http://127.0.0.1:\(port)")) #else - let url = NSURL(string: URL.resourceSpecifier, relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) + let url = URL(string: URL.resourceSpecifier, relativeTo: URL(string: "http://127.0.0.1:\(port)")) #endif - return loadRequest(NSURLRequest(URL: url!)) - } -} - -extension WKWebView { - // WKWebView can't load file URL on iOS 8.x devices. - // We have to start an embedded http server for proxy. - // When running on iOS 8.x, we provide the same API as on iOS 9. - // On iOS 9 and above, we do nothing. - - // Swift 2 doesn't support override +load method of NSObject, override +initialize instead. - // See http://nshipster.com/swift-objc-runtime/ - private static var initialized: dispatch_once_t = 0 - public override class func initialize() { - //if #available(iOS 9, *) { return } - guard self == WKWebView.self else { return } - dispatch_once(&initialized) { - let selector = #selector(WKWebView.loadFileURL(_:allowingReadAccessToURL:)) - let method = class_getInstanceMethod(self, #selector(WKWebView._loadFileURL(_:allowingReadAccessToURL:))) - assert(method != nil) - if class_addMethod(self, selector, method_getImplementation(method), method_getTypeEncoding(method)) { - log("+Running on iOS 8.x") - method_exchangeImplementations( - class_getInstanceMethod(self, #selector(WKWebView.loadHTMLString(_:baseURL:))), - class_getInstanceMethod(self, #selector(WKWebView._loadHTMLString(_:baseURL:))) - ) - } - } + return loadRequest(URLRequest(URL: url!)) } - @objc private func _loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation? { - // readAccessURL must contain URL - let fileManager = NSFileManager.defaultManager() - var relationship: NSURLRelationship = NSURLRelationship.Other - _ = try? fileManager.getRelationship(&relationship, ofDirectoryAtURL: readAccessURL, toItemAtURL: URL) - guard URL.fileURL && readAccessURL.fileURL && relationship != NSURLRelationship.Other else { - assert(relationship != NSURLRelationship.Other, "readAccessURL must contain URL") - assert(URL.fileURL && readAccessURL.fileURL, "URL and readAccessURL must be file URLs") - return nil - } - - guard let port = startHttpd(rootURL: readAccessURL) else { return nil } - var path = URL.path![readAccessURL.path!.endIndex ..< URL.path!.endIndex] - if let query = URL.query { path += "?\(query)" } - if let fragment = URL.fragment { path += "#\(fragment)" } - let url = NSURL(string: path , relativeToURL: NSURL(string: "http://127.0.0.1:\(port)")) - return loadRequest(NSURLRequest(URL: url!)) - } - - @objc private func _loadHTMLString(html: String, baseURL: NSURL) -> WKNavigation? { - guard baseURL.fileURL else { - // call original method implementation - return _loadHTMLString(html, baseURL: baseURL) - } - - guard let port = startHttpd(rootURL: baseURL) else { return nil } - let url = NSURL(string: "http://127.0.0.1:\(port)/") - return loadHTMLString(html, baseURL: url) - } - - private func startHttpd(rootURL rootURL: NSURL, overlayURLs: [NSURL]? = nil) -> in_port_t? { + private func startHttpd(rootURL: URL, overlayURLs: [URL]? = nil) -> in_port_t? { let key = unsafeAddressOf(XWVHttpServer) if let httpd = objc_getAssociatedObject(self, key) as? XWVHttpServer { if httpd.rootURL == rootURL && httpd.overlayURLs == overlayURLs ?? [] { @@ -205,4 +148,4 @@ extension WKWebView { log("+HTTP server is started on port: \(httpd.port)") return httpd.port } -} +}*/ diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index 58d036d..21b190c 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -20,33 +20,33 @@ import XWebView class ConstructorPlugin : XWVTestCase { class Plugin0 : NSObject, XWVScripting { - init(expectation: AnyObject?) { + init(expectation: Any?) { if let e = expectation as? XWVScriptObject { - e.callMethod("fulfill", withArguments: nil, completionHandler: nil) + e.callMethod("fulfill", with: nil, completionHandler: nil) } } - class func scriptNameForSelector(selector: Selector) -> String? { + class func scriptName(for selector: Selector) -> String? { return selector == #selector(Plugin0.init(expectation:)) ? "" : nil } } class Plugin1 : NSObject, XWVScripting { - dynamic let property: Int + dynamic var property: Int init(value: Int) { property = value } - class func scriptNameForSelector(selector: Selector) -> String? { + class func scriptName(for selector: Selector) -> String? { return selector == #selector(Plugin1.init(value:)) ? "" : nil } } class Plugin2 : NSObject, XWVScripting { private let expectation: XWVScriptObject? - init(expectation: AnyObject?) { + init(expectation: Any?) { self.expectation = expectation as? XWVScriptObject } func finalizeForScript() { - expectation?.callMethod("fulfill", withArguments: nil, completionHandler: nil) + expectation?.callMethod("fulfill", with: nil, completionHandler: nil) } - class func scriptNameForSelector(selector: Selector) -> String? { + class func scriptName(for selector: Selector) -> String? { return selector == #selector(Plugin2.init(expectation:)) ? "" : nil } } @@ -56,29 +56,29 @@ class ConstructorPlugin : XWVTestCase { func testConstructor() { let desc = "constructor" let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testConstruction() { let desc = "construction" let script = "new \(namespace)(expectation('\(desc)'))" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testSyncProperties() { let desc = "syncProperties" let script = "(new \(namespace)(456)).then(function(o){if (o.property==456) fulfill('\(desc)');})" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin1(value: 123), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testFinalizeForScript() { let desc = "finalizeForScript" let script = "(new \(namespace)(expectation('\(desc)'))).then(function(o){o.dispose();})" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin2(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/FunctionPlugin.swift b/XWebViewTests/FunctionPlugin.swift index 5cda244..8c32ce7 100644 --- a/XWebViewTests/FunctionPlugin.swift +++ b/XWebViewTests/FunctionPlugin.swift @@ -28,7 +28,7 @@ class FunctionPlugin : XWVTestCase { func defaultMethod() { expectation?.fulfill() } - class func scriptNameForSelector(selector: Selector) -> String? { + class func scriptName(for selector: Selector) -> String? { return selector == #selector(Plugin.defaultMethod) ? "" : nil } } @@ -38,20 +38,20 @@ class FunctionPlugin : XWVTestCase { func testDefaultMethod() { let desc = "defaultMethod" let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testCallDefaultMethod() { - let expectation = expectationWithDescription("callDefaultMethod") - loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: "\(namespace)()") - waitForExpectationsWithTimeout(2, handler: nil) + let exp = expectation(description: "callDefaultMethod") + loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace)()") + waitForExpectations(timeout: 2, handler: nil) } func testPropertyOfDefaultMethod() { let desc = "propertyOfDefaultMethod" let script = "if (\(namespace).property == 123) fulfill('\(desc)');" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index e4789f0..bad4072 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.9.5 + 0.11.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index 814e5ae..6131f21 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -25,26 +25,27 @@ class ObjectPlugin : XWVTestCase { func method() { expectation?.fulfill() } - func method(argument argument: AnyObject?) { + func method(argument: Any?) { if argument as? String == "Yes" { expectation?.fulfill() } } - func method(Integer Integer: Int) { + func method(Integer: Int) { if Integer == 789 { expectation?.fulfill() } } - func method(callback callback: XWVScriptObject) { + func method(callback: XWVScriptObject) { callback.call(arguments: nil, completionHandler: nil) } - func method(promiseObject promiseObject: XWVScriptObject) { - promiseObject.callMethod("resolve", withArguments: nil, completionHandler: nil) + func method(promiseObject: XWVScriptObject) { + promiseObject.callMethod("resolve", with: nil, completionHandler: nil) } func method1() { guard let bindingObject = XWVScriptObject.bindingObject else { return } property = 456 - if (bindingObject["property"] as? NSNumber)?.integerValue == 456 { + //if (bindingObject["property"] as? NSNumber)?.intValue == 456 { + if bindingObject["property"] as? Int64 == 456 { expectation?.fulfill() } } @@ -58,72 +59,74 @@ class ObjectPlugin : XWVTestCase { func testFetchProperty() { let desc = "fetchProperty" let script = "if (\(namespace).property == 123) fulfill('\(desc)');" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testUpdateProperty() { - let expectation = expectationWithDescription("updateProperty") + let exp = expectation(description: "updateProperty") let object = Plugin(expectation: nil) loadPlugin(object, namespace: namespace, script: "\(namespace).property = 321") { $0.evaluateJavaScript("\(self.namespace).property") { - (obj: AnyObject?, err: NSError?)->Void in - if (obj as? NSNumber)?.integerValue == 321 && object.property == 321 { - expectation.fulfill() + (obj: Any?, err: Error?)->Void in + //if (obj as? NSNumber)?.intValue == 321 && object.property == 321 { + if obj as? Bool == true && object.property == 321 { + exp.fulfill() } } } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testSyncProperty() { - let expectation = expectationWithDescription("syncProperty") + let exp = expectation(description: "syncProperty") let object = Plugin(expectation: nil) loadPlugin(object, namespace: namespace, script: "") { object.property = 321 $0.evaluateJavaScript("\(self.namespace).property") { - (obj: AnyObject?, err: NSError?)->Void in - if (obj as? NSNumber)?.integerValue == 321 { - expectation.fulfill() + (obj: Any?, err: Error?)->Void in + //if (obj as? NSNumber)?.intValue == 321 { + if obj as? Bool == true { + exp.fulfill() } } } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testCallMethod() { - let expectation = expectationWithDescription("callMethod") - loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: "\(namespace).method()") - waitForExpectationsWithTimeout(2, handler: nil) + let exp = expectation(description: "callMethod") + loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).method()") + waitForExpectations(timeout: 2, handler: nil) } func testCallMethodWithArgument() { - let expectation = expectationWithDescription("callMethodWithArgument") - loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: "\(namespace).methodWithArgument('Yes')") - waitForExpectationsWithTimeout(2, handler: nil) + let exp = expectation(description: "callMethodWithArgument") + loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).methodWithArgument('Yes')") + waitForExpectations(timeout: 2, handler: nil) } func testCallMethodWithInteger() { - let expectation = expectationWithDescription("callMethodWithInteger") - loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: "\(namespace).methodWithInteger(789)") - waitForExpectationsWithTimeout(2, handler: nil) + let exp = expectation(description: "callMethodWithInteger") + loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).methodWithInteger(789)") + waitForExpectations(timeout: 2, handler: nil) } func testCallMethodWithCallback() { let desc = "callMethodWithCallback" let script = "\(namespace).methodWithCallback(function(){fulfill('\(desc)');})" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(3, handler: nil) + waitForExpectations(timeout: 3, handler: nil) } func testCallMethodWithPromise() { let desc = "callMethodWithPromise" let script = "\(namespace).methodWithPromiseObject().then(function(){fulfill('\(desc)');})" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(3, handler: nil) + waitForExpectations(timeout: 3, handler: nil) } func testScriptObject() { let desc = "scriptObject" - let expectation = expectationWithDescription(desc) - let plugin = Plugin(expectation: expectation) + let exp = expectation(description: desc) + let plugin = Plugin(expectation: exp) loadPlugin(plugin, namespace: namespace, script: "\(namespace).method1();") - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index db022ae..92f419f 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -18,9 +18,9 @@ import XCTest import XWebView class InvocationTarget: NSObject { - class LeakTest { + class LeakTest: NSObject { let expectation: XCTestExpectation - @objc init(expectation: XCTestExpectation) { + init(expectation: XCTestExpectation) { self.expectation = expectation } deinit { @@ -31,6 +31,7 @@ class InvocationTarget: NSObject { var integer: Int = 123 func dummy() {} + func nullable(_ v: Any?) -> Any? { return v } func echo(bool b: Bool) -> Bool { return b } func echo(int i: Int) -> Int { return i } func echo(int8 i8: Int8) -> Int8 { return i8 } @@ -49,11 +50,11 @@ class InvocationTarget: NSObject { func echo(selector s: Selector) -> Selector { return s } func echo(`class` c: AnyClass) -> AnyClass { return c } - func add(a: Int, _ b: Int) -> Int { return a + b } - func concat(a: String, _ b: String) -> String { return a + b } - func convert(num: NSNumber) -> Int { return num.integerValue } + func add(_ a: Int, _ b: Int) -> Int { return a + b } + func concat(_ a: String, _ b: String) -> String { return a + b } + func convert(_ num: NSNumber) -> Int { return num.intValue } - func _new(expectation: XCTestExpectation) -> AnyObject { + func _new(_ expectation: XCTestExpectation) -> LeakTest { return LeakTest(expectation: expectation) } } @@ -102,7 +103,7 @@ class InvocationTests : XCTestCase { XCTAssertTrue(inv[ #selector(InvocationTarget.echo(string:))]("abc") as? String == "abc") let selector = #selector(InvocationTarget.echo(selector:)) XCTAssertTrue(inv[selector](selector) as? Selector == selector) - let cls = self.dynamicType + let cls = type(of: self) XCTAssertTrue(inv[ #selector(InvocationTarget.echo(class:))](cls) as? AnyClass === cls) XCTAssertTrue(inv[ #selector(InvocationTarget.convert(_:))](UInt8(12)) as? XInt == 12) @@ -116,21 +117,26 @@ class InvocationTests : XCTestCase { XCTAssertTrue(inv["integer"] as? XInt == 321) } + func testNullable() { + XCTAssertTrue(inv[ #selector(InvocationTarget.nullable(_:))]("abc") as? String == "abc") + XCTAssertTrue(inv[ #selector(InvocationTarget.nullable(_:))](nil) == nil) + } + func testLeak1() { autoreleasepool { - let expectation = expectationWithDescription("leak") - let obj = inv[ #selector(InvocationTarget._new(_:))](expectation) as? InvocationTarget.LeakTest - XCTAssertEqual(expectation, obj!.expectation) + let exp = expectation(description: "leak") + let obj = inv[ #selector(InvocationTarget._new(_:))](exp) as? InvocationTarget.LeakTest + XCTAssertEqual(exp, obj!.expectation) } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testLeak2() { autoreleasepool { - let expectation = expectationWithDescription("leak") - let obj = XWVInvocation.construct(InvocationTarget.LeakTest.self, initializer: #selector(InvocationTarget.LeakTest.init(expectation:)), withArguments: [expectation]) as? InvocationTarget.LeakTest - XCTAssertEqual(expectation, obj!.expectation) + let exp = expectation(description: "leak") + let obj = createInstance(of: InvocationTarget.LeakTest.self, by: #selector(InvocationTarget.LeakTest.init(expectation:)), with: [exp]) as? InvocationTarget.LeakTest + XCTAssertEqual(exp, obj!.expectation) } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/XWVMetaObjectTest.swift b/XWebViewTests/XWVMetaObjectTest.swift index bd07996..1cf0f2a 100644 --- a/XWebViewTests/XWVMetaObjectTest.swift +++ b/XWebViewTests/XWVMetaObjectTest.swift @@ -22,7 +22,7 @@ class XWVMetaObjectTest: XCTestCase { class TestForMethod { @objc init() {} @objc func method() {} - @objc func method(argument argument: AnyObject?) {} + @objc func method(argument: Any?) {} @objc func _method() {} } let meta = XWVMetaObject(plugin: TestForMethod.self) @@ -53,15 +53,15 @@ class XWVMetaObjectTest: XCTestCase { let meta = XWVMetaObject(plugin: TestForProperty.self) if let member = meta["property"] { XCTAssertTrue(member.isProperty) - XCTAssertTrue(member.getter == Selector("property")) - XCTAssertTrue(member.setter == Selector("setProperty:")) + XCTAssertTrue(member.getter == #selector(getter: TestForProperty.property)) + XCTAssertTrue(member.setter == #selector(setter: TestForProperty.property)) } else { XCTFail() } if let member = meta["readonlyProperty"] { XCTAssertTrue(member.isProperty) - XCTAssertTrue(member.getter == Selector("readonlyProperty")) - XCTAssertTrue(member.setter == Selector()) + XCTAssertTrue(member.getter == #selector(getter: TestForProperty.readonlyProperty)) + XCTAssertTrue(member.setter == nil) } else { XCTFail() } @@ -70,8 +70,8 @@ class XWVMetaObjectTest: XCTestCase { func testForPromise() { class TestForPromise { - @objc func method(promiseObject promiseObject: XWVScriptObject) {} - @objc func method(argument argument: AnyObject?, promiseObject: XWVScriptObject) {} + @objc func method(promiseObject: XWVScriptObject) {} + @objc func method(argument: Any?, promiseObject: XWVScriptObject) {} } let meta = XWVMetaObject(plugin: TestForPromise.self) if let member = meta["methodWithPromiseObject"] { @@ -93,11 +93,11 @@ class XWVMetaObjectTest: XCTestCase { class TestForExclusion: XWVScripting { @objc let property = 0 @objc func method() {} - @objc class func isSelectorExcludedFromScript(selector: Selector) -> Bool { + @objc class func isSelectorExcluded(fromScript selector: Selector) -> Bool { return selector == #selector(TestForExclusion.method) } - @objc class func isKeyExcludedFromScript(name: UnsafePointer) -> Bool { - return String(UTF8String: name) == "property" + @objc class func isKeyExcluded(fromScript name: UnsafePointer) -> Bool { + return String(cString: name) == "property" } } let meta = XWVMetaObject(plugin: TestForExclusion.self) @@ -108,7 +108,7 @@ class XWVMetaObjectTest: XCTestCase { func testForFunction() { class TestForFunction : XWVScripting { @objc func defaultMethod() {} - @objc class func scriptNameForSelector(selector: Selector) -> String? { + @objc class func scriptName(for selector: Selector) -> String? { return selector == #selector(TestForFunction.defaultMethod) ? "" : nil } } @@ -124,14 +124,14 @@ class XWVMetaObjectTest: XCTestCase { func testForFunction2() { class TestForFunction : XWVScripting { - @objc func invokeDefaultMethodWithArguments(args: [AnyObject]!) -> AnyObject! { + @objc func invokeDefaultMethod(withArguments args: [Any]!) -> Any! { return nil } } let meta = XWVMetaObject(plugin: TestForFunction.self) if let member = meta[""] { XCTAssertTrue(member.isMethod) - XCTAssertTrue(member.selector == #selector(XWVScripting.invokeDefaultMethodWithArguments(_:))) + XCTAssertTrue(member.selector == #selector(XWVScripting.invokeDefaultMethod(withArguments:))) XCTAssertTrue(member.type == "") } else { XCTFail() @@ -140,8 +140,8 @@ class XWVMetaObjectTest: XCTestCase { func testForConstructor() { class TestForConstructor : XWVScripting { - @objc init(argument: AnyObject?) {} - @objc class func scriptNameForSelector(selector: Selector) -> String? { + @objc init(argument: Any?) {} + @objc class func scriptName(for selector: Selector) -> String? { return selector == #selector(TestForConstructor.init(argument:)) ? "" : nil } } @@ -157,7 +157,7 @@ class XWVMetaObjectTest: XCTestCase { func testForConstructor2() { class TestForConstructor { - @objc init(byScriptWithArguments: [AnyObject]) {} + @objc init(byScriptWithArguments: [Any]) {} } let meta = XWVMetaObject(plugin: TestForConstructor.self) if let member = meta[""] { diff --git a/XWebViewTests/XWVScriptingTest.swift b/XWebViewTests/XWVScriptingTest.swift index e1e6b87..ce3861d 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -24,7 +24,7 @@ class XWVScriptingTest : XWVTestCase { init(expectation: XCTestExpectation?) { self.expectation = expectation } - func rewriteGeneratedStub(stub: String, forKey key: String) -> String { + func rewriteStub(_ stub: String, forKey key: String) -> String { switch key { case ".global": return stub + "window.stub = true;\n" case ".local": return stub + "exports.abc = true;\n" @@ -34,43 +34,43 @@ class XWVScriptingTest : XWVTestCase { func finalizeForScript() { expectation?.fulfill() } - class func isSelectorExcludedFromScript(selector: Selector) -> Bool { + class func isSelectorExcluded(fromScript selector: Selector) -> Bool { return selector == #selector(Plugin.init(expectation:)) } - class func isKeyExcludedFromScript(name: UnsafePointer) -> Bool { - return String(UTF8String: name) == "expectation" + class func isKeyExcluded(fromScript name: UnsafePointer) -> Bool { + return String(cString: name) == "expectation" } } let namespace = "xwvtest" - func testRewriteGeneratedStub() { + func testRewriteStub() { let desc = "javascriptStub" let script = "if (window.stub && \(namespace).abc) fulfill('\(desc)');" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testFinalizeForScript() { let desc = "finalizeForScript" let script = "\(namespace).dispose()" - let expectation = expectationWithDescription(desc) + let expectation = super.expectation(description: desc) loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testIsSelectorExcluded() { let desc = "isSelectorExcluded" let script = "if (\(namespace).initWithExpectation == undefined) fulfill('\(desc)')" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testIsKeyExcluded() { let desc = "isKeyExcluded" let script = "if (!\(namespace).hasOwnProperty('expectation')) fulfill('\(desc)')" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index 96ce861..d5788ed 100644 --- a/XWebViewTests/XWVTestCase.swift +++ b/XWebViewTests/XWVTestCase.swift @@ -40,9 +40,9 @@ class XWVTestCase : XCTestCase, WKNavigationDelegate { "function expectation(name){return \(namespaceForExpectation)[name];}\n" let script = WKUserScript( source: source, - injectionTime: WKUserScriptInjectionTime.AtDocumentStart, + injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true) - webview = WKWebView(frame: CGRectZero, configuration: WKWebViewConfiguration()) + webview = WKWebView(frame: CGRect.zero, configuration: WKWebViewConfiguration()) webview.configuration.userContentController.addUserScript(script) webview.navigationDelegate = self } @@ -51,23 +51,23 @@ class XWVTestCase : XCTestCase, WKNavigationDelegate { super.tearDown() } - override func expectationWithDescription(description: String) -> XCTestExpectation { - let e = super.expectationWithDescription(description) + override func expectation(description: String) -> XCTestExpectation { + let e = super.expectation(description: description) webview.loadPlugin(e, namespace: "\(namespaceForExpectation).\(description)") return e } - func loadPlugin(object: NSObject, namespace: String, script: String) { + func loadPlugin(_ object: NSObject, namespace: String, script: String) { loadPlugin(object, namespace: namespace, script: script, onReady: nil) } - func loadPlugin(object: NSObject, namespace: String, script: String, onReady: ((WKWebView)->Void)?) { + func loadPlugin(_ object: NSObject, namespace: String, script: String, onReady: ((WKWebView)->Void)?) { self.onReady = onReady webview.loadPlugin(object, namespace: namespace) let html = "" webview.loadHTMLString(html, baseURL: nil) } - func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { onReady?(webView) } } @@ -77,8 +77,8 @@ class XWVTestCaseTest : XWVTestCase { } func testXWVTestCase() { let desc = "selftest" - _ = expectationWithDescription(desc) + _ = expectation(description: desc) loadPlugin(Plugin(), namespace: "xwvtest", script: "fulfill('\(desc)');") - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 89c7e36..37e8e45 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -23,17 +23,17 @@ class XWebViewTests: XWVTestCase { } func testWindowObject() { - let expectation = expectationWithDescription("testWindowObject") + let expectation = super.expectation(description: "testWindowObject") loadPlugin(Plugin(), namespace: "xwvtest", script: "") { if let math = $0.windowObject["Math"] as? XWVScriptObject, - num = try? math.callMethod("sqrt", withArguments: [9]), - result = (num as? NSNumber)?.integerValue where result == 3 { + let num = try? math.callMethod("sqrt", with: [9]), + let result = (num as? NSNumber)?.intValue, result == 3 { expectation.fulfill() } else { XCTFail("testWindowObject Failed") } } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } func testLoadPlugin() { @@ -42,56 +42,64 @@ class XWebViewTests: XWVTestCase { } } + @available(iOS 9.0, *) func testLoadFileURL() { - _ = expectationWithDescription("loadFileURL") - let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") + _ = expectation(description: "loadFileURL") + let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") - if let root = bundle?.bundleURL.URLByAppendingPathComponent("www") { - #if swift(>=2.3) - let url = root.URLByAppendingPathComponent("webviewTest.html")! + if let root = bundle?.bundleURL.appendingPathComponent("www") { + #if swift(>=3.0) + let url = root.appendingPathComponent("webviewTest.html") #else - let url = root.URLByAppendingPathComponent("webviewTest.html") + #if swift(>=2.3) + let url = root.URLByAppendingPathComponent("webviewTest.html")! + #else + let url = root.URLByAppendingPathComponent("webviewTest.html") + #endif #endif - XCTAssert(url.checkResourceIsReachableAndReturnError(nil), "HTML file not found") - webview.loadFileURL(url, allowingReadAccessToURL: root) - waitForExpectationsWithTimeout(2, handler: nil) + XCTAssert(try url.checkResourceIsReachable(), "HTML file not found") + webview.loadFileURL(url, allowingReadAccessTo: root) + waitForExpectations(timeout: 2, handler: nil) } } - +/* func testLoadFileURLWithOverlay() { - _ = expectationWithDescription("loadFileURLWithOverlay") - let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") - if let root = bundle?.bundleURL.URLByAppendingPathComponent("www") { + _ = expectation(description: "loadFileURLWithOverlay") + let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") + if let root = bundle?.bundleURL.appendingPathComponent("www") { // create overlay file in library directory - let library = try! NSFileManager.defaultManager().URLForDirectory( - NSSearchPathDirectory.LibraryDirectory, - inDomain: NSSearchPathDomainMask.UserDomainMask, - appropriateForURL: nil, + let library = try! FileManager.default.url( + for: FileManager.SearchPathDirectory.libraryDirectory, + in: FileManager.SearchPathDomainMask.userDomainMask, + appropriateFor: nil, create: true) - - #if swift(>=2.3) - var url = library.URLByAppendingPathComponent("webviewTest.html")! + #if swift(>=3) + var url = library.appendingPathComponent("webviewTest.html") #else - var url = library.URLByAppendingPathComponent("webviewTest.html") + #if swift(>=2.3) + var url = library.URLByAppendingPathComponent("webviewTest.html")! + #else + var url = library.URLByAppendingPathComponent("webviewTest.html") + #endif #endif let content = "" - try! content.writeToURL(url, atomically: false, encoding: NSUTF8StringEncoding) + try! content.write(to: url, atomically: false, encoding: String.Encoding.utf8) - url = NSURL(string: "webviewTest.html", relativeToURL: root)! + url = URL(string: "webviewTest.html", relativeToURL: root)! webview.loadFileURL(url, overlayURLs: [library]) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } - } + }*/ func testLoadHTMLStringWithBaseURL() { - _ = expectationWithDescription("loadHTMLStringWithBaseURL") - let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") - if let baseURL = bundle?.bundleURL.URLByAppendingPathComponent("www") { - XCTAssert(baseURL.checkResourceIsReachableAndReturnError(nil), "Directory not found") + _ = expectation(description: "loadHTMLStringWithBaseURL") + let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") + if let baseURL = bundle?.bundleURL.appendingPathComponent("www") { + XCTAssert(try baseURL.checkResourceIsReachable(), "Directory not found") webview.loadHTMLString("", baseURL: baseURL) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations(timeout: 2, handler: nil) } } } diff --git a/XWebViewX/Info.plist b/XWebViewX/Info.plist index 6344153..4d10262 100644 --- a/XWebViewX/Info.plist +++ b/XWebViewX/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.5 + 0.11.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewX/XWebViewX.h b/XWebViewX/XWebViewX.h index 8006ef1..31a1800 100644 --- a/XWebViewX/XWebViewX.h +++ b/XWebViewX/XWebViewX.h @@ -34,32 +34,10 @@ FOUNDATION_EXPORT const unsigned char XWebViewXVersionString[]; NS_ASSUME_NONNULL_BEGIN -// The workaround for using NSInvocation and NSMethodSignature in Swift. -@protocol _NSMethodSignatureFactory -- (NSMethodSignature *)signatureWithObjCTypes:(const char *)types; -@end -@interface NSMethodSignature (Swift) <_NSMethodSignatureFactory> -@end - -@protocol _NSInvocationFactory -- (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; -@end -@interface NSInvocation (Swift) <_NSInvocationFactory> -@end - -// Special selectors which can't be referenced directly in Swift. -@protocol _SpecialSelectors -// NSObject -- (instancetype)alloc; -- (void)dealloc; -// NSInvocation -- (void)invokeWithTarget:(id)target; -@end - // Special init which can't be reference directly in Swift, but cannot be a protocol either. @interface _InitSelector: NSObject // Init with script - (id)initByScriptWithArguments:(NSArray *)args; @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END From 6b71cef1d2f3a1f8070a7c8688e5cdb24945af9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Manuel?= Date: Thu, 17 Nov 2016 14:56:56 +0100 Subject: [PATCH 30/44] Fixed issues with nested dictionaries and arrays --- XWebView/XWVJson.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/XWebView/XWVJson.swift b/XWebView/XWVJson.swift index 01850ef..677c3d6 100644 --- a/XWebView/XWVJson.swift +++ b/XWebView/XWVJson.swift @@ -80,6 +80,10 @@ func jsonify(_ value: NSObject) -> String { (ptr: UnsafePointer) -> String in jsonify(UnsafeBufferPointer(start: ptr, count: d.count)) } + case let a as [Any?]: + return jsonify(a) + case let d as [String : Any?]: + return jsonify(d) default: //fatalError("Unsupported type \(type(of: value))") print("Unsupported type \(type(of: value))") From 8939e7562cc9c582ebc58dc770ebb64a752a0ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Manuel?= Date: Thu, 17 Nov 2016 15:04:11 +0100 Subject: [PATCH 31/44] Fix for carthage --- .../xcshareddata/xcschemes/XWebView.xcscheme | 24 ----- .../xcschemes/XWebViewTests.xcscheme | 99 ------------------- XWebView/Info.plist | 2 +- 3 files changed, 1 insertion(+), 124 deletions(-) delete mode 100644 XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme diff --git a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index 516f22a..e48a027 100644 --- a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme +++ b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -20,20 +20,6 @@ ReferencedContainer = "container:XWebView.xcodeproj"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/XWebView/Info.plist b/XWebView/Info.plist index 03d5355..21f24d9 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.0 + 0.11.1 CFBundleSignature ???? CFBundleVersion From 89b37e363ecea367a676b56477baf4c0178fbc98 Mon Sep 17 00:00:00 2001 From: David Kim Date: Sun, 27 Nov 2016 05:11:48 +0800 Subject: [PATCH 32/44] Overlay support for Swift 3.0 --- XWebView.xcodeproj/project.pbxproj | 4 +- XWebView/XWVHttpConnection.swift | 142 ++++++++++++++--------------- XWebView/XWVHttpServer.swift | 32 +++---- XWebView/XWebView.swift | 30 +++--- XWebViewTests/XWebViewTests.swift | 32 ++----- 5 files changed, 102 insertions(+), 138 deletions(-) diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 1724910..5b90432 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -554,7 +554,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -597,7 +597,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift index eed7fc0..2ca9678 100644 --- a/XWebView/XWVHttpConnection.swift +++ b/XWebView/XWVHttpConnection.swift @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* + import Foundation protocol XWVHttpConnectionDelegate { - func handleRequest(_ request: URLRequest) -> HTTPURLResponse + func handleRequest(_ request: URLRequest?) -> HTTPURLResponse func didOpenConnection(_ connection: XWVHttpConnection) func didCloseConnection(_ connection: XWVHttpConnection) } @@ -30,7 +30,7 @@ final class XWVHttpConnection : NSObject { fileprivate let bufferMaxSize = 64 * 1024 // input state - fileprivate var requestQueue = [URLRequest]() + fileprivate var requestQueue = [URLRequest?]() fileprivate var inputBuffer: Data! fileprivate var cursor: Int = 0 @@ -54,7 +54,9 @@ final class XWVHttpConnection : NSObject { ptr2.deallocate(capacity: 1) } CFStreamCreatePairWithSocket(nil, handle, ptr1, ptr2) - if ptr1.pointee == nil || ptr2.pointee == nil { return false } + guard ptr1.pointee != nil && ptr2.pointee != nil else { + return false + } input = ptr1.pointee!.takeRetainedValue() output = ptr2.pointee!.takeRetainedValue() @@ -81,7 +83,7 @@ final class XWVHttpConnection : NSObject { } extension XWVHttpConnection : StreamDelegate { - @objc func stream(aStream: Stream, handleEvent eventCode: Stream.Event) { + func stream(_ aStream: Stream, handle eventCode: Stream.Event) { switch eventCode { case Stream.Event.openCompleted: // Initialize input/output state. @@ -95,31 +97,25 @@ extension XWVHttpConnection : StreamDelegate { } case Stream.Event.hasBytesAvailable: - let base = UnsafeMutablePointer(inputBuffer.mutableBytes) - let bytesReaded = input.read(base.advancedBy(cursor), maxLength: inputBuffer.length - cursor) + let bytesReaded = inputBuffer.withUnsafeMutableBytes { + (base: UnsafeMutablePointer) -> Int in + input.read(base.advanced(by: cursor), maxLength: inputBuffer.count - cursor) + } guard bytesReaded > 0 else { break } + cursor += bytesReaded var bytesConsumed = 0 - var ptr = cursor > 3 ? base.advancedBy(cursor - 3): base - for _ in 0 ..< bytesReaded { - if UnsafePointer(ptr).memory == UInt32(bigEndian: 0x0d0a0d0a) { - // End of request header. - ptr += 3 - let data = inputBuffer.subdataWithRange(NSRange(bytesConsumed...base.distanceTo(ptr))) - if let request = URLRequest(data: data) { - requestQueue.insert(request, atIndex: 0) - } else { - // Bad request - requestQueue.insert(URLRequest(), atIndex: 0) - } - bytesConsumed += data.length - } - ptr = ptr.successor() + while let eoh = inputBuffer.range(of: Data([13, 10, 13, 10]), in: bytesConsumed.. 0 { // Move remained bytes to the begining. inputBuffer.replaceSubrange(0..(outputBuffer.bytes) - bytesSent = output.write(ptr.advancedBy(off), maxLength: outputBuffer.length - off) + bytesSent = outputBuffer.withUnsafeBytes{ + (ptr: UnsafePointer) -> Int in + output.write(ptr.advanced(by: off), maxLength: outputBuffer.count - off) + } bytesRemained -= bytesSent } while bytesRemained > 0 && output.hasSpaceAvailable && bytesSent > 0 if bytesRemained == 0 { @@ -220,50 +217,49 @@ private extension URLRequest { case Options = "OPTIONS" case Trace = "TRACE" } - private var CRLF: Data { - var CRLF: [UInt8] = [ 0x0d, 0x0a ] - return Data(bytes: &CRLF, count: 2) + private static var CRLF: Data { + return Data(bytes: [0x0d, 0x0a]) } - init?(data: NSData) { - //self.init() - var cursor = 0 - repeat { - let range = NSRange(cursor.. 100 && statusCode < 600) let reason = HTTPURLResponse.localizedString(forStatusCode: statusCode).capitalized - let statusLine = "HTTP/1.1 \(statusCode) \(reason)\r\n" - let data = allHeaderFields.reduce(NSMutableData(data: statusLine.data(using: String.Encoding.ascii)!)) { - $0.append(($1.0 as! NSString).data(using: String.Encoding.ascii.rawValue)!) - $0.append(": ".data(using: String.Encoding.ascii)!) - $0.append(($1.1 as! NSString).data(using: String.Encoding.ascii.rawValue)!) - $0.append("\r\n".data(using: String.Encoding.ascii)!) - return $0 - } - data.append("\r\n".data(using: String.Encoding.ascii)!) - return data as Data + let content = allHeaderFields.reduce("HTTP/1.1 \(statusCode) \(reason)\r\n") { + $0 + ($1.0 as! String) + ": " + ($1.1 as! String) + "\r\n" + } + "\r\n" + return content.data(using: String.Encoding.ascii)! } -}*/ +} diff --git a/XWebView/XWVHttpServer.swift b/XWebView/XWVHttpServer.swift index ff461d8..71ef661 100644 --- a/XWebView/XWVHttpServer.swift +++ b/XWebView/XWVHttpServer.swift @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* + import Foundation #if os(iOS) import UIKit @@ -55,7 +55,7 @@ class XWVHttpServer : NSObject { private func listen(on port: in_port_t) -> Bool { guard socket == nil else { return false } - let info = UnsafeMutableRawPointer(mutating: unsafeAddressOf(self)) + let info = Unmanaged.passUnretained(self).toOpaque() var context = CFSocketContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) let callbackType = CFSocketCallBackType.acceptCallBack.rawValue socket = CFSocketCreate(nil, PF_INET, SOCK_STREAM, 0, callbackType, ServerAcceptCallBack, &context) @@ -160,7 +160,7 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { connections.remove(connection) } - func handleRequest(_ request: URLRequest) -> HTTPURLResponse { + func handleRequest(_ request: URLRequest?) -> HTTPURLResponse { // Date format, see section 7.1.1.1 of RFC7231 let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US") @@ -170,11 +170,11 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { var headers: [String: String] = ["Date": dateFormatter.string(from: Date())] var statusCode = 500 var fileURL: URL? = nil - if request.url == nil { + if request == nil { // Bad request statusCode = 400 log("?Bad request") - } else if request.httpMethod == "GET" || request.httpMethod == "HEAD" { + } else if let request = request, request.httpMethod == "GET" || request.httpMethod == "HEAD" { let fileManager = FileManager.default let relativePath = String(request.url!.path.characters.dropFirst()) for baseURL in overlays { @@ -182,15 +182,7 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { var url = URL(string: relativePath, relativeTo: baseURL)! if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) { if isDirectory.boolValue { - #if swift(>=3.0) - url = url.appendingPathComponent("index.html") - #else - #if swift(>=2.3) - url = url.URLByAppendingPathComponent("index.html")! - #else - url = url.URLByAppendingPathComponent("index.html") - #endif - #endif + url = url.appendingPathComponent("index.html") } if fileManager.isReadableFile(atPath: url.path) { fileURL = url @@ -219,18 +211,18 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { if statusCode != 200 { headers["Content-Length"] = "0" } - return HTTPURLResponse(url: fileURL!, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)! - // FIXME: return nil when fileURL == nil + let url = fileURL ?? request?.url ?? URL(string: "nil")! + return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)! } } -private func ServerAcceptCallBack(socket: CFSocket?, type: CFSocketCallBackType, address: CFData?, data:UnsafeRawPointer?, info: UnsafeMutableRawPointer?) { +private func ServerAcceptCallBack(socket: CFSocket?, type: CFSocketCallBackType, address: CFData?, data: UnsafeRawPointer?, info: UnsafeMutableRawPointer?) { let server = unsafeBitCast(info, to: XWVHttpServer.self) - let handle = UnsafePointer(data).pointee assert(socket === server.socket && type == CFSocketCallBackType.acceptCallBack) + let handle = data!.load(as: CFSocketNativeHandle.self) let connection = XWVHttpConnection(handle: handle, delegate: server) - connection.open() + _ = connection.open() } private var mimeTypeCache = [ @@ -255,4 +247,4 @@ private func getMIMETypeByExtension(extensionName: String) -> String { return type + "; charset=utf-8" } return type -}*/ +} diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 4390e1c..4be47d8 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -109,32 +109,30 @@ extension WKWebView { return result } } -/* + @available(iOS 9.0, *) extension WKWebView { // Overlay support for loading file URL - public func loadFileURL(_ URL: URL, overlayURLs: [URL]? = nil) -> WKNavigation? { - if let count = overlayURLs?.count, count > 0 { - return loadFileURL(URL, allowingReadAccessTo: URL.baseURL!) + public func loadFileURL(_ url: URL, overlayURLs: [URL]? = nil) -> WKNavigation? { + if let count = overlayURLs?.count, count == 0 { + return loadFileURL(url, allowingReadAccessTo: url.baseURL!) } - guard URL.isFileURL && URL.baseURL != nil else { + guard url.isFileURL && url.baseURL != nil else { fatalError("URL must be a relative file URL.") } - guard let port = startHttpd(rootURL: URL.baseURL!, overlayURLs: overlayURLs) else { return nil } - - #if swift(>=2.3) - let url = URL(string: URL.resourceSpecifier!, relativeTo: URL(string: "http://127.0.0.1:\(port)")) - #else - let url = URL(string: URL.resourceSpecifier, relativeTo: URL(string: "http://127.0.0.1:\(port)")) - #endif - - return loadRequest(URLRequest(URL: url!)) + guard let port = startHttpd(rootURL: url.baseURL!, overlayURLs: overlayURLs) else { return nil } + + var res = url.relativePath + if let query = url.query { res += "?" + query } + if let fragment = url.fragment { res += "#" + fragment } + let url = URL(string: res , relativeTo: URL(string: "http://127.0.0.1:\(port)")) + return load(URLRequest(url: url!)) } private func startHttpd(rootURL: URL, overlayURLs: [URL]? = nil) -> in_port_t? { - let key = unsafeAddressOf(XWVHttpServer) + let key = Unmanaged.passUnretained(XWVHttpServer.self as AnyObject).toOpaque() if let httpd = objc_getAssociatedObject(self, key) as? XWVHttpServer { if httpd.rootURL == rootURL && httpd.overlayURLs == overlayURLs ?? [] { return httpd.port @@ -148,4 +146,4 @@ extension WKWebView { log("+HTTP server is started on port: \(httpd.port)") return httpd.port } -}*/ +} diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 37e8e45..df827e0 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -48,22 +48,14 @@ class XWebViewTests: XWVTestCase { let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") if let root = bundle?.bundleURL.appendingPathComponent("www") { - #if swift(>=3.0) - let url = root.appendingPathComponent("webviewTest.html") - #else - #if swift(>=2.3) - let url = root.URLByAppendingPathComponent("webviewTest.html")! - #else - let url = root.URLByAppendingPathComponent("webviewTest.html") - #endif - #endif - + let url = root.appendingPathComponent("webviewTest.html") XCTAssert(try url.checkResourceIsReachable(), "HTML file not found") webview.loadFileURL(url, allowingReadAccessTo: root) waitForExpectations(timeout: 2, handler: nil) } } -/* + + @available(iOS 9.0, *) func testLoadFileURLWithOverlay() { _ = expectation(description: "loadFileURLWithOverlay") let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") @@ -74,24 +66,16 @@ class XWebViewTests: XWVTestCase { in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true) - #if swift(>=3) - var url = library.appendingPathComponent("webviewTest.html") - #else - #if swift(>=2.3) - var url = library.URLByAppendingPathComponent("webviewTest.html")! - #else - var url = library.URLByAppendingPathComponent("webviewTest.html") - #endif - #endif - + var url = library.appendingPathComponent("webviewTest.html") + let content = "" try! content.write(to: url, atomically: false, encoding: String.Encoding.utf8) - url = URL(string: "webviewTest.html", relativeToURL: root)! - webview.loadFileURL(url, overlayURLs: [library]) + url = URL(string: "webviewTest.html", relativeTo: root)! + _ = webview.loadFileURL(url, overlayURLs: [library]) waitForExpectations(timeout: 2, handler: nil) } - }*/ + } func testLoadHTMLStringWithBaseURL() { _ = expectation(description: "loadHTMLStringWithBaseURL") From 6bf31245d118e02e6cf0186a5fe460c50729ae3a Mon Sep 17 00:00:00 2001 From: David Kim Date: Sun, 27 Nov 2016 05:27:47 +0800 Subject: [PATCH 33/44] Extend timeout for unit tests --- XWebViewTests/ConstructorPlugin.swift | 8 ++++---- XWebViewTests/FunctionPlugin.swift | 6 +++--- XWebViewTests/ObjectPlugin.swift | 18 +++++++++--------- XWebViewTests/XWVInvocationTest.swift | 4 ++-- XWebViewTests/XWVScriptingTest.swift | 8 ++++---- XWebViewTests/XWVTestCase.swift | 5 ++++- XWebViewTests/XWebViewTests.swift | 8 ++++---- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index 21b190c..244dd68 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -58,27 +58,27 @@ class ConstructorPlugin : XWVTestCase { let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" _ = expectation(description: desc) loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testConstruction() { let desc = "construction" let script = "new \(namespace)(expectation('\(desc)'))" _ = expectation(description: desc) loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testSyncProperties() { let desc = "syncProperties" let script = "(new \(namespace)(456)).then(function(o){if (o.property==456) fulfill('\(desc)');})" _ = expectation(description: desc) loadPlugin(Plugin1(value: 123), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testFinalizeForScript() { let desc = "finalizeForScript" let script = "(new \(namespace)(expectation('\(desc)'))).then(function(o){o.dispose();})" _ = expectation(description: desc) loadPlugin(Plugin2(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } diff --git a/XWebViewTests/FunctionPlugin.swift b/XWebViewTests/FunctionPlugin.swift index 8c32ce7..a056a1a 100644 --- a/XWebViewTests/FunctionPlugin.swift +++ b/XWebViewTests/FunctionPlugin.swift @@ -40,18 +40,18 @@ class FunctionPlugin : XWVTestCase { let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testCallDefaultMethod() { let exp = expectation(description: "callDefaultMethod") loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace)()") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testPropertyOfDefaultMethod() { let desc = "propertyOfDefaultMethod" let script = "if (\(namespace).property == 123) fulfill('\(desc)');" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index 6131f21..bed5322 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -61,7 +61,7 @@ class ObjectPlugin : XWVTestCase { let script = "if (\(namespace).property == 123) fulfill('\(desc)');" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testUpdateProperty() { let exp = expectation(description: "updateProperty") @@ -75,7 +75,7 @@ class ObjectPlugin : XWVTestCase { } } } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testSyncProperty() { let exp = expectation(description: "syncProperty") @@ -90,43 +90,43 @@ class ObjectPlugin : XWVTestCase { } } } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testCallMethod() { let exp = expectation(description: "callMethod") loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).method()") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testCallMethodWithArgument() { let exp = expectation(description: "callMethodWithArgument") loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).methodWithArgument('Yes')") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testCallMethodWithInteger() { let exp = expectation(description: "callMethodWithInteger") loadPlugin(Plugin(expectation: exp), namespace: namespace, script: "\(namespace).methodWithInteger(789)") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testCallMethodWithCallback() { let desc = "callMethodWithCallback" let script = "\(namespace).methodWithCallback(function(){fulfill('\(desc)');})" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 3, handler: nil) + waitForExpectations() } func testCallMethodWithPromise() { let desc = "callMethodWithPromise" let script = "\(namespace).methodWithPromiseObject().then(function(){fulfill('\(desc)');})" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 3, handler: nil) + waitForExpectations() } func testScriptObject() { let desc = "scriptObject" let exp = expectation(description: desc) let plugin = Plugin(expectation: exp) loadPlugin(plugin, namespace: namespace, script: "\(namespace).method1();") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index 92f419f..4664e9b 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -128,7 +128,7 @@ class InvocationTests : XCTestCase { let obj = inv[ #selector(InvocationTarget._new(_:))](exp) as? InvocationTarget.LeakTest XCTAssertEqual(exp, obj!.expectation) } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations(timeout: 3) } func testLeak2() { @@ -137,6 +137,6 @@ class InvocationTests : XCTestCase { let obj = createInstance(of: InvocationTarget.LeakTest.self, by: #selector(InvocationTarget.LeakTest.init(expectation:)), with: [exp]) as? InvocationTarget.LeakTest XCTAssertEqual(exp, obj!.expectation) } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations(timeout: 3) } } diff --git a/XWebViewTests/XWVScriptingTest.swift b/XWebViewTests/XWVScriptingTest.swift index ce3861d..a135477 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -49,7 +49,7 @@ class XWVScriptingTest : XWVTestCase { let script = "if (window.stub && \(namespace).abc) fulfill('\(desc)');" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testFinalizeForScript() { @@ -57,20 +57,20 @@ class XWVScriptingTest : XWVTestCase { let script = "\(namespace).dispose()" let expectation = super.expectation(description: desc) loadPlugin(Plugin(expectation: expectation), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testIsSelectorExcluded() { let desc = "isSelectorExcluded" let script = "if (\(namespace).initWithExpectation == undefined) fulfill('\(desc)')" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testIsKeyExcluded() { let desc = "isKeyExcluded" let script = "if (!\(namespace).hasOwnProperty('expectation')) fulfill('\(desc)')" _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index d5788ed..ede1873 100644 --- a/XWebViewTests/XWVTestCase.swift +++ b/XWebViewTests/XWVTestCase.swift @@ -56,6 +56,9 @@ class XWVTestCase : XCTestCase, WKNavigationDelegate { webview.loadPlugin(e, namespace: "\(namespaceForExpectation).\(description)") return e } + override func waitForExpectations(timeout: TimeInterval = 9, handler: XCWaitCompletionHandler? = nil) { + super.waitForExpectations(timeout: timeout, handler: handler) + } func loadPlugin(_ object: NSObject, namespace: String, script: String) { loadPlugin(object, namespace: namespace, script: script, onReady: nil) @@ -79,6 +82,6 @@ class XWVTestCaseTest : XWVTestCase { let desc = "selftest" _ = expectation(description: desc) loadPlugin(Plugin(), namespace: "xwvtest", script: "fulfill('\(desc)');") - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index df827e0..2307d7c 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -33,7 +33,7 @@ class XWebViewTests: XWVTestCase { XCTFail("testWindowObject Failed") } } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } func testLoadPlugin() { @@ -51,7 +51,7 @@ class XWebViewTests: XWVTestCase { let url = root.appendingPathComponent("webviewTest.html") XCTAssert(try url.checkResourceIsReachable(), "HTML file not found") webview.loadFileURL(url, allowingReadAccessTo: root) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } @@ -73,7 +73,7 @@ class XWebViewTests: XWVTestCase { url = URL(string: "webviewTest.html", relativeTo: root)! _ = webview.loadFileURL(url, overlayURLs: [library]) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } @@ -83,7 +83,7 @@ class XWebViewTests: XWVTestCase { if let baseURL = bundle?.bundleURL.appendingPathComponent("www") { XCTAssert(try baseURL.checkResourceIsReachable(), "Directory not found") webview.loadHTMLString("", baseURL: baseURL) - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations() } } } From fb209194a3c4f8d0760fa794fd9380ec03b64930 Mon Sep 17 00:00:00 2001 From: David Kim Date: Tue, 20 Dec 2016 03:16:58 +0800 Subject: [PATCH 34/44] Revert "Fix for carthage" This reverts commit 8939e7562cc9c582ebc58dc770ebb64a752a0ac4. --- .../xcshareddata/xcschemes/XWebView.xcscheme | 24 +++++ .../xcschemes/XWebViewTests.xcscheme | 99 +++++++++++++++++++ XWebView/Info.plist | 2 +- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme diff --git a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index e48a027..516f22a 100644 --- a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme +++ b/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:XWebView.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XWebView/Info.plist b/XWebView/Info.plist index 21f24d9..03d5355 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.1 + 0.11.0 CFBundleSignature ???? CFBundleVersion From 91ab7f962c82bac427bfa48ee08a87ec270886cc Mon Sep 17 00:00:00 2001 From: David Kim Date: Fri, 23 Dec 2016 01:42:12 +0800 Subject: [PATCH 35/44] Optimize XWVInvocation New feature of Swift 3.0.1: All of Swift's standard number types now bridge to Objective-C as NSNumber instances. --- XWebView/XWVInvocation.swift | 43 +----------------------- XWebViewTests/XWVInvocationTest.swift | 48 +++++++++++++-------------- 2 files changed, 25 insertions(+), 66 deletions(-) diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index f87fd7d..040130d 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -69,12 +69,7 @@ var NSInvocation: NSInvocationProtocol.Type = { let code = sig.getArgumentType(atIndex: UInt(i) + 2) let type = ObjCType(code: code) if type == .object { - let obj: AnyObject - if let val = arg as? NSNumberConvertible { - obj = NSNumber(value: val) - } else { - obj = _bridgeAnythingToObjectiveC(arg) - } + let obj: AnyObject = _bridgeAnythingToObjectiveC(arg) _autorelease(obj) args[i] = _encodeBitsAsWords(obj) } else if type == .clazz, let cls = arg as? AnyClass { @@ -203,22 +198,6 @@ private enum ObjCType : CChar { } } -private protocol NSNumberConvertible {} -extension Int: NSNumberConvertible {} -extension Int8: NSNumberConvertible {} -extension Int16: NSNumberConvertible {} -extension Int32: NSNumberConvertible {} -extension Int64: NSNumberConvertible {} -extension UInt: NSNumberConvertible {} -extension UInt8: NSNumberConvertible {} -extension UInt16: NSNumberConvertible {} -extension UInt32: NSNumberConvertible {} -extension UInt64: NSNumberConvertible {} -extension Bool: NSNumberConvertible {} -extension Double: NSNumberConvertible {} -extension Float: NSNumberConvertible {} -extension UnicodeScalar: NSNumberConvertible {} - private extension NSNumber { func value(as type: ObjCType) -> CVarArg? { switch type { @@ -238,26 +217,6 @@ private extension NSNumber { default: return nil } } - - convenience init(value: NSNumberConvertible) { - switch value { - case let v as Int: self.init(value: v) - case let v as Int8: self.init(value: v) - case let v as Int16: self.init(value: v) - case let v as Int32: self.init(value: v) - case let v as Int64: self.init(value: v) - case let v as UInt: self.init(value: v) - case let v as UInt8: self.init(value: v) - case let v as UInt16: self.init(value: v) - case let v as UInt32: self.init(value: v) - case let v as UInt64: self.init(value: v) - case let v as Bool: self.init(value: v) - case let v as Double: self.init(value: v) - case let v as Float: self.init(value: v) - case let v as UnicodeScalar: self.init(value: v.value) - default: fatalError("never reach here") - } - } } private extension Selector { diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index 4664e9b..a2ad197 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -82,44 +82,44 @@ class InvocationTests : XCTestCase { func testMethods() { XCTAssertTrue(inv[ #selector(InvocationTarget.dummy)]() is Void) #if arch(x86_64) || arch(arm64) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(bool:))](Bool(true)) as? Bool == true) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(bool:))](Bool(true)) as? Bool, true) #else // http://stackoverflow.com/questions/26459754/bool-encoding-wrong-from-nsmethodsignature - XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Int8 == 1) + XCTAssertEqual(inv[Selector("echoWithBool:")](Bool(true)) as? Int8, 1) #endif - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int:))](Int(-11)) as? XInt == -11) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int8:))](Int8(-22)) as? Int8 == -22) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int16:))](Int16(-33)) as? Int16 == -33) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int32:))](Int32(-44)) as? Int32 == -44) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(int64:))](Int64(-55)) as? Int64 == -55) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint:))](UInt(11)) as? XUInt == 11) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint8:))](UInt8(22)) as? UInt8 == 22) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint16:))](UInt16(33)) as? UInt16 == 33) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint32:))](UInt32(44)) as? UInt32 == 44) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(uint64:))](UInt64(55)) as? UInt64 == 55) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(float:))](Float(12.34)) as? Float == 12.34) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(double:))](Double(-56.78)) as? Double == -56.78) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(unicode:))](UnicodeScalar(78)) as? Int32 == 78) - XCTAssertTrue(inv[ #selector(InvocationTarget.echo(string:))]("abc") as? String == "abc") + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int:))](Int(-11)) as? XInt, -11) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int8:))](Int8(-22)) as? Int8, -22) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int16:))](Int16(-33)) as? Int16, -33) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int32:))](Int32(-44)) as? Int32, -44) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int64:))](Int64(-55)) as? Int64, -55) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(uint:))](UInt(11)) as? XUInt, 11) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(uint8:))](UInt8(22)) as? UInt8, 22) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(uint16:))](UInt16(33)) as? UInt16, 33) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(uint32:))](UInt32(44)) as? UInt32, 44) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(uint64:))](UInt64(55)) as? UInt64, 55) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(float:))](Float(12.34)) as? Float, 12.34) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(double:))](Double(-56.78)) as? Double, -56.78) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(unicode:))](UnicodeScalar(78)) as? Int32, 78) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(string:))]("abc") as? String, "abc") let selector = #selector(InvocationTarget.echo(selector:)) - XCTAssertTrue(inv[selector](selector) as? Selector == selector) + XCTAssertEqual(inv[selector](selector) as? Selector, selector) let cls = type(of: self) XCTAssertTrue(inv[ #selector(InvocationTarget.echo(class:))](cls) as? AnyClass === cls) - XCTAssertTrue(inv[ #selector(InvocationTarget.convert(_:))](UInt8(12)) as? XInt == 12) - XCTAssertTrue(inv[ #selector(InvocationTarget.add(_:_:))](2, 3) as? XInt == 5) - XCTAssertTrue(inv[ #selector(InvocationTarget.concat(_:_:))]("ab", "cd") as? String == "abcd") + XCTAssertEqual(inv[ #selector(InvocationTarget.convert(_:))](UInt8(12)) as? XInt, 12) + XCTAssertEqual(inv[ #selector(InvocationTarget.add(_:_:))](2, 3) as? XInt, 5) + XCTAssertEqual(inv[ #selector(InvocationTarget.concat(_:_:))]("ab", "cd") as? String, "abcd") } func testProperty() { - XCTAssertTrue(inv["integer"] as? XInt == 123) + XCTAssertEqual(inv["integer"] as? XInt, 123) inv["integer"] = 321 - XCTAssertTrue(inv["integer"] as? XInt == 321) + XCTAssertEqual(inv["integer"] as? XInt, 321) } func testNullable() { - XCTAssertTrue(inv[ #selector(InvocationTarget.nullable(_:))]("abc") as? String == "abc") - XCTAssertTrue(inv[ #selector(InvocationTarget.nullable(_:))](nil) == nil) + XCTAssertEqual(inv[ #selector(InvocationTarget.nullable(_:))]("abc") as? String, "abc") + XCTAssertNil(inv[ #selector(InvocationTarget.nullable(_:))](nil)) } func testLeak1() { From 454950082e228d49283245bb919f154d2678504c Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 24 Dec 2016 05:19:36 +0800 Subject: [PATCH 36/44] Improve JSON serialization --- XWebView.xcodeproj/project.pbxproj | 4 + XWebView/XWVBindingObject.swift | 10 ++- XWebView/XWVChannel.swift | 5 +- XWebView/XWVJson.swift | 116 ++++++++++++++++------------ XWebView/XWVObject.swift | 17 +---- XWebView/XWVScriptObject.swift | 15 ++-- XWebViewTests/XWVJsonTests.swift | 119 +++++++++++++++++++++++++++++ 7 files changed, 212 insertions(+), 74 deletions(-) create mode 100644 XWebViewTests/XWVJsonTests.swift diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 5b90432..1722ebc 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; }; EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; + EE5C89401E0C53CF00848B9A /* XWVJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */; }; EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; }; EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; @@ -92,6 +93,7 @@ EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVLogging.swift; path = XWebView/XWVLogging.swift; sourceTree = ""; }; EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; + EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJsonTests.swift; sourceTree = ""; }; EE62683519FA323900EFC3F8 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE62691319FA52D100EFC3F8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = XWebView/Info.plist; sourceTree = ""; }; EE62691C19FA52FC00EFC3F8 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWebView.h; path = XWebView/XWebView.h; sourceTree = ""; }; @@ -148,6 +150,7 @@ isa = PBXGroup; children = ( AB2273E41AA6FDA700F9207A /* www */, + EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */, EE3379381AE2E298009124A4 /* XWVTestCase.swift */, EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */, EE2F487C1AE4B8F40088AF67 /* ObjectPlugin.swift */, @@ -395,6 +398,7 @@ files = ( EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */, AB023EA51A8C506600580A2A /* XWebViewTests.swift in Sources */, + EE5C89401E0C53CF00848B9A /* XWVJsonTests.swift in Sources */, EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */, EE2F487F1AE4CD360088AF67 /* FunctionPlugin.swift in Sources */, EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */, diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index c7e2b7e..e26652d 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -85,7 +85,8 @@ final class XWVBindingObject : XWVScriptObject { private func syncProperties() { let script = channel.typeInfo.filter{ $1.isProperty }.reduce("") { let val: Any! = performSelector($1.1.getter!, with: nil) - return "\($0)\(namespace).$properties['\($1.0)'] = \(serialize(val));\n" + guard let json = jsonify(val) else { return "" } + return "\($0)\(namespace).$properties['\($1.0)'] = \(json);\n" } webView?.evaluateJavaScript(script, completionHandler: nil) } @@ -140,14 +141,17 @@ final class XWVBindingObject : XWVScriptObject { // KVO for syncing properties override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard let webView = webView, var prop = keyPath else { return } + guard let webView = webView, var prop = keyPath, let change = change, + let json = jsonify(change[NSKeyValueChangeKey.newKey]) else { + return + } if channel.typeInfo[prop] == nil { if let scriptNameForKey = (type(of: object) as? XWVScripting.Type)?.scriptName(forKey:) { prop = prop.withCString(scriptNameForKey) ?? prop } assert(channel.typeInfo[prop] != nil) } - let script = "\(namespace).$properties['\(prop)'] = \(serialize(change?[NSKeyValueChangeKey.newKey]))" + let script = "\(namespace).$properties['\(prop)'] = \(json)" webView.evaluateJavaScript(script, completionHandler: nil) } } diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 45cfe2c..63bc016 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -157,9 +157,8 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { if member.isMethod && !key.isEmpty { let method = generateMethod("\(key)\(member.type)", this: prebind ? "exports" : "this", prebind: prebind) stub = "exports.\(key) = \(method)" - } else if member.isProperty { - let value = principal.serialize(principal[key]) - stub = "XWVPlugin.defineProperty(exports, '\(key)', \(value), \(member.setter != nil));" + } else if member.isProperty, let json = jsonify(principal[key]) { + stub = "XWVPlugin.defineProperty(exports, '\(key)', \(json), \(member.setter != nil));" } else { return $0 } diff --git a/XWebView/XWVJson.swift b/XWebView/XWVJson.swift index 677c3d6..a54eac6 100644 --- a/XWebView/XWVJson.swift +++ b/XWebView/XWVJson.swift @@ -17,38 +17,39 @@ import Foundation // JSON Array -func jsonify(_ array: T) -> String where T.Index == Int { - return "[" + array.map(jsonify).joined(separator: ", ") + "]" +public func jsonify(_ array: T) -> String? + where T.Index: Integer { + // TODO: filter out values with negative index + return "[" + array.map{jsonify($0) ?? ""}.joined(separator: ",") + "]" } // JSON Object -func jsonify(_ object: T) -> String where T.Iterator.Element == (key: String, value: V) { - return "{" + object.map(jsonify).joined(separator: ", ") + "}" +public func jsonify(_ object: T) -> String? + where T.Iterator.Element == (key: String, value: V) { + return "{" + object.flatMap(jsonify).joined(separator: ",") + "}" } -private func jsonify(_ pair: (key: String, value: T)) -> String { - return jsonify(pair.key) + ":" + jsonify(pair.value) +private func jsonify(_ pair: (key: String, value: T)) -> String? { + guard let val = jsonify(pair.value) else { return nil } + return jsonify(pair.key)! + ":" + val } // JSON Number -func jsonify(_ integer: T) -> String { +public func jsonify(_ integer: T) -> String? { return String(describing: integer) } -func jsonify(_ float: T) -> String { +public func jsonify(_ float: T) -> String? { return String(describing: float) } // JSON Boolean -func jsonify(_ bool: Bool) -> String { +public func jsonify(_ bool: Bool) -> String? { return String(describing: bool) } // JSON String -func jsonify(_ string: String) -> String { +public func jsonify(_ string: String) -> String? { return string.unicodeScalars.reduce("\"") { $0 + $1.jsonEscaped } + "\"" } -func jsonify(_ char: Character) -> String { - return jsonify(String(char)) -} private extension UnicodeScalar { var jsonEscaped: String { switch value { @@ -64,54 +65,73 @@ private extension UnicodeScalar { } } -func jsonify(_ value: NSObject) -> String { + +@objc public protocol ObjCJSONStringable { + var jsonString: String? { get } +} +public protocol CustomJSONStringable { + var jsonString: String? { get } +} + +extension CustomJSONStringable where Self: RawRepresentable { + public var jsonString: String? { + return jsonify(rawValue) + } +} + +public func jsonify(_ value: Any?) -> String? { + guard let value = value else { return "null" } + switch (value) { - case _ as NSNull: + case is Void: + return "undefined" + case is NSNull: return "null" - case let s as NSString: - return jsonify(String(s)) + case let s as String: + return jsonify(s) case let n as NSNumber: if CFGetTypeID(n) == CFBooleanGetTypeID() { return n.boolValue.description } return n.stringValue + case let a as Array: + return jsonify(a) + case let d as Dictionary: + return jsonify(d) + case let s as CustomJSONStringable: + return s.jsonString + case let o as ObjCJSONStringable: + return o.jsonString case let d as Data: return d.withUnsafeBytes { - (ptr: UnsafePointer) -> String in - jsonify(UnsafeBufferPointer(start: ptr, count: d.count)) + (base: UnsafePointer) -> String? in + jsonify(UnsafeBufferPointer(start: base, count: d.count)) } - case let a as [Any?]: - return jsonify(a) - case let d as [String : Any?]: - return jsonify(d) default: - //fatalError("Unsupported type \(type(of: value))") - print("Unsupported type \(type(of: value))") - return "undefined" + let mirror = Mirror(reflecting: value) + guard let style = mirror.displayStyle else { return nil } + switch style { + case .optional: // nested optional + return jsonify(mirror.children.first?.value) + case .collection, .set, .tuple: // array-like type + return jsonify(mirror.children.map{$0.value}) + case .class, .dictionary, .struct: + return "{" + mirror.children.flatMap(jsonify).joined(separator: ",") + "}" + case .enum: + return jsonify(String(describing: value)) + } } } +private func jsonify(_ child: Mirror.Child) -> String? { + if let key = child.label { + return jsonify((key: key, value: child.value)) + } -func jsonify(_ value: Any!) -> String { - switch (value) { - case nil: return "undefined" - case let b as Bool: return jsonify(b) - case let i as Int: return jsonify(i) - case let i as Int8: return jsonify(i) - case let i as Int16: return jsonify(i) - case let i as Int32: return jsonify(i) - case let i as Int64: return jsonify(i) - case let u as UInt: return jsonify(u) - case let u as UInt8: return jsonify(u) - case let u as UInt16: return jsonify(u) - case let u as UInt32: return jsonify(u) - case let u as UInt64: return jsonify(u) - case let f as Float: return jsonify(f) - case let f as Double: return jsonify(f) - case let s as String: return jsonify(s) - case let v as NSObject: return jsonify(v) - case is Void: return "undefined" - case let o as Optional: - guard case let .some(v) = o else { return "null" } - fatalError("Unsupported type \(type(of: v)) (of value \(v))") + let m = Mirror(reflecting: child.value) + guard m.children.count == 2, m.displayStyle == .tuple, + let key = m.children.first!.value as? String else { + return nil } + let val = m.children[m.children.index(after: m.children.startIndex)].value + return jsonify((key: key, value: val)) } diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index 939f272..3b3bf93 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -110,19 +110,10 @@ public class XWVObject : NSObject { } return object } +} - func serialize(_ value: Any?) -> String { - switch value { - case let o as XWVObject: - return o.namespace - case let d as Date: - return "(new Date(\(d.timeIntervalSince1970 * 1000)))" - case let a as [Any?]: - return jsonify(a) - case let d as [String: Any?]: - return jsonify(d) - default: - return jsonify(value) - } +extension XWVObject : CustomJSONStringable { + public var jsonString: String? { + return namespace } } diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index 9a2212a..4f0c72d 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -54,7 +54,7 @@ public class XWVScriptObject : XWVObject { } public func defineProperty(_ name: String, descriptor: [String:Any]) -> Any? { - let exp = "Object.defineProperty(\(namespace), \(name), \(serialize(descriptor)))" + let exp = "Object.defineProperty(\(namespace), \(name), \(jsonify(descriptor)!))" return try! evaluateExpression(exp) } public func deleteProperty(_ name: String) -> Bool { @@ -70,13 +70,17 @@ public class XWVScriptObject : XWVObject { return try! evaluateExpression(scriptForFetchingProperty(name)) } public func setValue(_ value: Any?, for name:String) { - webView?.evaluateJavaScript(scriptForUpdatingProperty(name, value: value), completionHandler: nil) + guard let json = jsonify(value) else { return } + let script = scriptForFetchingProperty(name) + " = " + json + webView?.evaluateJavaScript(script, completionHandler: nil) } public func value(at index: UInt) -> Any? { return try! evaluateExpression("\(namespace)[\(index)]") } public func setValue(_ value: Any?, at index: UInt) { - webView?.evaluateJavaScript("\(namespace)[\(index)] = \(serialize(value))", completionHandler: nil) + guard let json = jsonify(value) else { return } + let script = "\(namespace)[\(index)] = \(json)" + webView?.evaluateJavaScript(script, completionHandler: nil) } private func scriptForFetchingProperty(_ name: String?) -> String { @@ -92,11 +96,8 @@ public class XWVScriptObject : XWVObject { return "\(namespace).\(name)" } } - private func scriptForUpdatingProperty(_ name: String?, value: Any?) -> String { - return scriptForFetchingProperty(name) + " = " + serialize(value) - } private func scriptForCallingMethod(_ name: String?, arguments: [Any]?) -> String { - let args = arguments?.map(serialize) ?? [] + let args = arguments?.map{jsonify($0) ?? ""} ?? [] return scriptForFetchingProperty(name) + "(" + args.joined(separator: ", ") + ")" } } diff --git a/XWebViewTests/XWVJsonTests.swift b/XWebViewTests/XWVJsonTests.swift new file mode 100644 index 0000000..80ddb6b --- /dev/null +++ b/XWebViewTests/XWVJsonTests.swift @@ -0,0 +1,119 @@ +/* + Copyright 2015 XWebView + + Licensed 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 XCTest +import XWebView + +class JsonTests : XCTestCase { + func testNull() { + XCTAssertEqual(jsonify(nil), "null") + XCTAssertEqual(jsonify(NSNull()), "null") + } + + func testBoolean() { + XCTAssertEqual(jsonify(true), "true") + XCTAssertEqual(jsonify(false), "false") + XCTAssertEqual(jsonify(NSNumber(value: true) as Any), "true") + } + + func testNumber() { + XCTAssertEqual(jsonify(-1), "-1") + XCTAssertEqual(jsonify(Int8(1)), "1") + XCTAssertEqual(jsonify(Float(1.1)), "1.1") + XCTAssertEqual(jsonify(Double(2.2)), "2.2") + XCTAssertEqual(jsonify(NSNumber(value: 1) as Any), "1") + } + + func testString() { + XCTAssertEqual(jsonify("abc"), "\"abc\"") + XCTAssertEqual(jsonify("`'\""), "\"`'\\\"\"") + XCTAssertEqual(jsonify("\u{8}\u{9}\u{a}\u{c}\u{d}"), "\"\\b\\t\\n\\f\\r\"") + XCTAssertEqual(jsonify("\u{b}\u{10}\u{1f}\u{20}"), "\"\\u000b\\u0010\\u001f \"") + } + + func testArray() { + XCTAssertEqual(jsonify([1,2,3]), "[1,2,3]") + XCTAssertEqual(jsonify(["a","b","c"]), "[\"a\",\"b\",\"c\"]") + XCTAssertEqual(jsonify([1,"b","c"]), "[1,\"b\",\"c\"]") + XCTAssertEqual(jsonify([1,"b",nil] as [Any?]), "[1,\"b\",null]") + XCTAssertEqual(jsonify((1,3,5,7,11,13,"a","b")), "[1,3,5,7,11,13,\"a\",\"b\"]") + XCTAssertEqual(jsonify(NSArray(arrayLiteral: 1,2,3)), "[1,2,3]") + XCTAssertEqual(jsonify(NSArray(arrayLiteral: "a","b","c")), "[\"a\",\"b\",\"c\"]") + XCTAssertEqual(jsonify(NSArray(arrayLiteral: "a",2,"c")), "[\"a\",2,\"c\"]") + } + + func testDictionary() { + XCTAssertEqual(jsonify(["a":1, "b":2, "c":3]), "{\"b\":2,\"a\":1,\"c\":3}") + XCTAssertEqual(jsonify(["a":"1", "b":"2", "c":"3"]), "{\"b\":\"2\",\"a\":\"1\",\"c\":\"3\"}") + XCTAssertEqual(jsonify(["a":1, "b":"x", "c":3]), "{\"b\":\"x\",\"a\":1,\"c\":3}") + XCTAssertEqual(jsonify(["a":1, "b":"x", "c":UnicodeScalar(10)!]), "{\"b\":\"x\",\"a\":1}") + XCTAssertEqual(jsonify(["a":1, "b":"x", "c":nil] as [String: Any?]), "{\"b\":\"x\",\"a\":1,\"c\":null}") + XCTAssertEqual(jsonify(["x":1, "y":0, "z":2] as Any), "{\"y\":0,\"x\":1,\"z\":2}") + let rect = CGRect(x: 1.1, y: 2.2, width:3.3, height: 4.4) + XCTAssertEqual(jsonify(rect), "{\"origin\":{\"x\":1.1,\"y\":2.2},\"size\":{\"width\":3.3,\"height\":4.4}}") + } + + func testData() { + var value = Double(42.13) + let data = withUnsafePointer(to: &value) { + Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: value)) + } + XCTAssertEqual(jsonify(data), "[113,61,10,215,163,16,69,64]") + } + + func testStructure() { + struct S1 { + var efg: UInt = 33 + } + struct S2 { + var abc: Int = 12 + var cde: String = "aa" + var yy: S1 = S1() + } + XCTAssertEqual(jsonify(S2()), "{\"abc\":12,\"cde\":\"aa\",\"yy\":{\"efg\":33}}") + } + + func testEnumeration() { + enum E0 { + case abc + case def + } + XCTAssertEqual(jsonify(E0.def), "\"def\"") + XCTAssertEqual(jsonify(Mirror.DisplayStyle.enum), "\"enum\"") + + enum E1 : Int, CustomJSONStringable { + case a = 123 + case b = 456 + } + XCTAssertEqual(jsonify(E1.b), "456") + enum E2 : String, CustomJSONStringable { + case a = "abc" + case b = "def" + } + XCTAssertEqual(jsonify(E2.b), "\"def\"") + } + + func testNestedOptional() { + XCTAssertEqual(jsonify(Optional(Optional(102) as Any)), "102") + XCTAssertEqual(jsonify(Optional(Optional(nil) as Any)), "null") + XCTAssertEqual(jsonify(Optional(Optional(nil) as Any) as Any), "null") + } + + func testMisc() { + XCTAssertNil(jsonify(UnicodeScalar(66))) + XCTAssertEqual(jsonify(()), "undefined") + } +} From 980787cd26dc49526e27e7b801e90ca6bde0a4b5 Mon Sep 17 00:00:00 2001 From: David Kim Date: Sat, 24 Dec 2016 09:08:21 +0800 Subject: [PATCH 37/44] Better support of JavaScript undefined type --- XWebView/XWVBindingObject.swift | 20 ++++---- XWebView/XWVObject.swift | 23 ++++----- XWebView/XWVScriptObject.swift | 80 +++++++++++++++---------------- XWebView/XWebView.swift | 28 +++++------ XWebViewTests/XWVTestCase.swift | 7 ++- XWebViewTests/XWebViewTests.swift | 22 +++++++++ 6 files changed, 101 insertions(+), 79 deletions(-) diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index e26652d..9019a63 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -109,7 +109,7 @@ final class XWVBindingObject : XWVScriptObject { } // override methods of XWVScriptObject - override func callMethod(_ name: String, with arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { + override func callMethod(_ name: String, with arguments: [Any]?, completionHandler: Handler) { if let selector = channel.typeInfo[name]?.selector { let result: Any! = performSelector(selector, with: arguments) completionHandler?(result, nil) @@ -117,17 +117,17 @@ final class XWVBindingObject : XWVScriptObject { super.callMethod(name, with: arguments, completionHandler: completionHandler) } } - override func callMethod(_ name: String, with arguments: [Any]?) throws -> Any? { + override func callMethod(_ name: String, with arguments: [Any]?) throws -> Any { if let selector = channel.typeInfo[name]?.selector { - return performSelector(selector, with: arguments) + return performSelector(selector, with: arguments) ?? NSNull() } return try super.callMethod(name, with: arguments) } - override func value(for name: String) -> Any? { + override func value(for name: String) throws -> Any { if let getter = channel.typeInfo[name]?.getter { - return performSelector(getter, with: nil) + return performSelector(getter, with: nil) ?? NSNull() } - return super.value(for: name) + return try super.value(for: name) } override func setValue(_ value: Any?, for name: String) { if let setter = channel.typeInfo[name]?.setter { @@ -168,8 +168,8 @@ extension XWVBindingObject { guard ptr != nil else { return nil } return unsafeBitCast(ptr, to: XWVBindingObject.self) } - fileprivate func performSelector(_ selector: Selector, with arguments: [Any]?, waitUntilDone wait: Bool = true) -> Any! { - var result: Any! = () + fileprivate func performSelector(_ selector: Selector, with arguments: [Any]?, waitUntilDone wait: Bool = true) -> Any? { + var result: Any? = undefined let trampoline : () -> Void = { [weak self] in guard let plugin = self?.plugin else { return } @@ -191,9 +191,11 @@ extension XWVBindingObject { if wait && CFRunLoopGetCurrent() === runLoop { trampoline() } else { + struct Unsolved {} + result = Unsolved() CFRunLoopPerformBlock(runLoop, CFRunLoopMode.defaultMode.rawValue, trampoline) CFRunLoopWakeUp(runLoop) - while wait && result is Void { + while wait && result is Unsolved { let reason = CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 3.0, true) if reason != CFRunLoopRunResult.handledSource { break diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index 3b3bf93..c357ff2 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -67,20 +67,15 @@ public class XWVObject : NSObject { } // Evaluate JavaScript expression - public func evaluateExpression(_ expression: String) throws -> Any? { + public func evaluateExpression(_ expression: String) throws -> Any { guard let webView = webView else { throw webViewInvalidated } - return wrapScriptObject(try webView.evaluateJavaScript(scriptForRetaining(expression))) + let result = try webView.syncEvaluateJavaScript(scriptForRetaining(expression)) + return wrapScriptObject(result) } - public func evaluateExpression(_ expression: String, error: ErrorPointer) -> Any? { - guard let webView = webView else { - error?.pointee = webViewInvalidated - return nil - } - return wrapScriptObject(webView.evaluateJavaScript(scriptForRetaining(expression), error: error)) - } - public func evaluateExpression(_ expression: String, completionHandler: ((Any?, Error?) -> Void)?) { + public typealias Handler = ((Any?, Error?) -> Void)? + public func evaluateExpression(_ expression: String, completionHandler: Handler) { guard let webView = webView else { completionHandler?(nil, webViewInvalidated) return @@ -91,7 +86,13 @@ public class XWVObject : NSObject { } webView.evaluateJavaScript(scriptForRetaining(expression)) { [weak self](result: Any?, error: Error?)->Void in - completionHandler(self?.wrapScriptObject(result) ?? result, error) + if let error = error { + completionHandler(nil, error) + } else if let result = result { + completionHandler(self?.wrapScriptObject(result) ?? result, nil) + } else { + completionHandler(undefined, error) + } } } private func scriptForRetaining(_ script: String) -> String { diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index 4f0c72d..b0867bd 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -18,64 +18,63 @@ import Foundation import WebKit public class XWVScriptObject : XWVObject { - // JavaScript object operations - public func construct(arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { - let exp = "new " + scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, completionHandler: completionHandler) + // asynchronized method calling + public func construct(arguments: [Any]?, completionHandler: Handler) { + let expr = "new " + expression(forMethod: nil, arguments: arguments) + evaluateExpression(expr, completionHandler: completionHandler) } - public func call(arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { - let exp = scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, completionHandler: completionHandler) + public func call(arguments: [Any]?, completionHandler: Handler) { + let expr = expression(forMethod: nil, arguments: arguments) + evaluateExpression(expr, completionHandler: completionHandler) } - public func callMethod(_ name: String, with arguments: [Any]?, completionHandler: ((Any?, Error?) -> Void)?) { - let exp = scriptForCallingMethod(name, arguments: arguments) - evaluateExpression(exp, completionHandler: completionHandler) + public func callMethod(_ name: String, with arguments: [Any]?, completionHandler: Handler) { + let expr = expression(forMethod: name, arguments: arguments) + evaluateExpression(expr, completionHandler: completionHandler) } - public func construct(arguments: [Any]?) throws -> Any { - let exp = "new \(scriptForCallingMethod(nil, arguments: arguments))" - guard let result = try evaluateExpression(exp) else { + // synchronized method calling + public func construct(arguments: [Any]?) throws -> XWVScriptObject { + let expr = "new" + expression(forMethod: nil, arguments: arguments) + guard let result = try evaluateExpression(expr) as? XWVScriptObject else { let code = WKError.javaScriptExceptionOccurred.rawValue throw NSError(domain: WKErrorDomain, code: code, userInfo: nil) } return result } - public func call(arguments: [Any]?) throws -> Any? { - return try evaluateExpression(scriptForCallingMethod(nil, arguments: arguments)) + public func call(arguments: [Any]?) throws -> Any { + return try evaluateExpression(expression(forMethod: nil, arguments: arguments)) } - public func callMethod(_ name: String, with arguments: [Any]?) throws -> Any? { - return try evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) - } - public func call(arguments: [Any]?, error: NSErrorPointer) -> Any? { - return evaluateExpression(scriptForCallingMethod(nil, arguments: arguments), error: error) - } - public func callMethod(_ name: String, with arguments: [Any]?, error: NSErrorPointer) -> Any? { - return evaluateExpression(scriptForCallingMethod(name, arguments: arguments), error: error) + public func callMethod(_ name: String, with arguments: [Any]?) throws -> Any { + return try evaluateExpression(expression(forMethod: name, arguments: arguments)) } - public func defineProperty(_ name: String, descriptor: [String:Any]) -> Any? { - let exp = "Object.defineProperty(\(namespace), \(name), \(jsonify(descriptor)!))" - return try! evaluateExpression(exp) + // property manipulation + public func defineProperty(_ name: String, descriptor: [String:Any]) throws -> Any { + let expr = "Object.defineProperty(\(namespace), \(name), \(jsonify(descriptor)!))" + return try evaluateExpression(expr) } public func deleteProperty(_ name: String) -> Bool { - let result: Any? = try! evaluateExpression("delete \(scriptForFetchingProperty(name))") + let expr = "delete " + expression(forProperty: name) + let result: Any? = try! evaluateExpression(expr) return (result as? NSNumber)?.boolValue ?? false } public func hasProperty(_ name: String) -> Bool { - let result: Any? = try! evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") + let expr = expression(forProperty: name) + " != undefined" + let result: Any? = try! evaluateExpression(expr) return (result as? NSNumber)?.boolValue ?? false } - public func value(for name: String) -> Any? { - return try! evaluateExpression(scriptForFetchingProperty(name)) + // property accessing + public func value(for name: String) throws -> Any { + return try evaluateExpression(expression(forProperty: name)) } public func setValue(_ value: Any?, for name:String) { guard let json = jsonify(value) else { return } - let script = scriptForFetchingProperty(name) + " = " + json + let script = expression(forProperty: name) + " = " + json webView?.evaluateJavaScript(script, completionHandler: nil) } - public func value(at index: UInt) -> Any? { - return try! evaluateExpression("\(namespace)[\(index)]") + public func value(at index: UInt) throws -> Any { + return try evaluateExpression("\(namespace)[\(index)]") } public func setValue(_ value: Any?, at index: UInt) { guard let json = jsonify(value) else { return } @@ -83,7 +82,8 @@ public class XWVScriptObject : XWVObject { webView?.evaluateJavaScript(script, completionHandler: nil) } - private func scriptForFetchingProperty(_ name: String?) -> String { + // expression generation + private func expression(forProperty name: String?) -> String { guard let name = name else { return namespace } @@ -96,25 +96,25 @@ public class XWVScriptObject : XWVObject { return "\(namespace).\(name)" } } - private func scriptForCallingMethod(_ name: String?, arguments: [Any]?) -> String { + private func expression(forMethod name: String?, arguments: [Any]?) -> String { let args = arguments?.map{jsonify($0) ?? ""} ?? [] - return scriptForFetchingProperty(name) + "(" + args.joined(separator: ", ") + ")" + return expression(forProperty: name) + "(" + args.joined(separator: ", ") + ")" } } extension XWVScriptObject { // Subscript as property accessor - public subscript(name: String) -> Any? { + public subscript(name: String) -> Any { get { - return value(for: name) + return (try? value(for: name)) ?? undefined } set { setValue(newValue, for: name) } } - public subscript(index: UInt) -> Any? { + public subscript(index: UInt) -> Any { get { - return value(at: index) + return (try? value(at: index)) ?? undefined } set { setValue(newValue, at: index) diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 4be47d8..5ffd915 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -45,11 +45,18 @@ extension WKWebView { } } +public typealias Undefined = Void +public var undefined: AnyObject = { + Void() as AnyObject +}() + extension WKWebView { + public static var undefined: AnyObject { + return XWebView.undefined + } + // Synchronized evaluateJavaScript - // It returns nil if script is a statement or its result is undefined. - // So, Swift cannot map the throwing method to Objective-C method. - open func evaluateJavaScript(_ script: String) throws -> Any? { + open func syncEvaluateJavaScript(_ script: String) throws -> Any { var result: Any? var error: Error? var done = false @@ -93,20 +100,7 @@ extension WKWebView { if !done { log("!Timeout to evaluate script: \(script)") } - return result - } - - // Wrapper method of synchronized evaluateJavaScript for Objective-C - open func evaluateJavaScript(_ script: String, error: ErrorPointer) -> Any? { - var result: Any? - var err: Error? - do { - result = try evaluateJavaScript(script) - } catch let e as NSError { - err = e - } - error?.pointee = err as NSError? - return result + return result ?? WKWebView.undefined } } diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index ede1873..6a6cb92 100644 --- a/XWebViewTests/XWVTestCase.swift +++ b/XWebViewTests/XWVTestCase.swift @@ -64,10 +64,13 @@ class XWVTestCase : XCTestCase, WKNavigationDelegate { loadPlugin(object, namespace: namespace, script: script, onReady: nil) } func loadPlugin(_ object: NSObject, namespace: String, script: String, onReady: ((WKWebView)->Void)?) { - self.onReady = onReady webview.loadPlugin(object, namespace: namespace) let html = "" - webview.loadHTMLString(html, baseURL: nil) + loadHTML(html, onReady: onReady); + } + func loadHTML(_ content: String, onReady: ((WKWebView)->Void)?) { + self.onReady = onReady + webview.loadHTMLString(content, baseURL: nil) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 2307d7c..32cb363 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -36,6 +36,28 @@ class XWebViewTests: XWVTestCase { waitForExpectations() } + func testSyncEvaluation() { + let expectation = super.expectation(description: "testSyncEvaluation") + loadHTML("") { + let result = try? $0.syncEvaluateJavaScript("1+2") + if let num = result as? Int, num == 3 { + expectation.fulfill() + } + } + waitForExpectations() + } + + func testUndefined() { + let expectation = super.expectation(description: "testUndefined") + loadHTML("") { + let result = try? $0.syncEvaluateJavaScript("undefined") + if result is Undefined { + expectation.fulfill() + } + } + waitForExpectations() + } + func testLoadPlugin() { if webview.loadPlugin(Plugin(), namespace: "xwvtest") == nil { XCTFail("testLoadPlugin Failed") From fd28f542745d102a32c02105bdbd2ad2a153bd61 Mon Sep 17 00:00:00 2001 From: David Kim Date: Sun, 25 Dec 2016 00:42:57 +0800 Subject: [PATCH 38/44] Bump to version 0.12.0 Xcode 8.2 is required. Xcode 8.1 may work, not test. --- .travis.yml | 2 +- README.md | 3 ++- XWebView.podspec | 2 +- XWebView/Info.plist | 2 +- XWebViewTests/Info.plist | 2 +- XWebViewX/Info.plist | 2 +- XWebViewX/XWebViewX.h | 16 ++++------------ 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0b8ca7..82893be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode8.2 #xcode_sdk: iphonesimulator9.0 #xcode_project: XWebView.xcodeproj #xcode_scheme: XWebViewTests diff --git a/README.md b/README.md index 3262d3d..09719ff 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,14 @@ For more documents, please go to the project [Wiki](../../wiki). ## Minimum Requirements: -* Development: Xcode 8 +* Development: Xcode 8.2 * Deployment: iOS 9.0 ## XWebView vs. Swift | Swift | XWebView | | ----- | ---------- | +| 3.0.2 | 0.12.0 | | 3 | 0.11.0 | | 2.3 | 0.10.0 | | 2.2 | 0.10.0 | diff --git a/XWebView.podspec b/XWebView.podspec index a2a9bfc..766497a 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.11.0" + s.version = "0.12.0" s.summary = "An extensible WebView (based on WKWebView) for iOS." s.description = <<-DESC diff --git a/XWebView/Info.plist b/XWebView/Info.plist index 03d5355..db84c71 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.0 + 0.12.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index bad4072..31b423a 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.11.0 + 0.12.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewX/Info.plist b/XWebViewX/Info.plist index 4d10262..df8aa5c 100644 --- a/XWebViewX/Info.plist +++ b/XWebViewX/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.0 + 0.12.0 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewX/XWebViewX.h b/XWebViewX/XWebViewX.h index 31a1800..de7dfe9 100644 --- a/XWebViewX/XWebViewX.h +++ b/XWebViewX/XWebViewX.h @@ -1,18 +1,18 @@ /* Copyright 2015 XWebView - + Licensed 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 @@ -24,14 +24,6 @@ FOUNDATION_EXPORT const unsigned char XWebViewXVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - -// The workaround for loading file URL on OS X 10.10.x. -#import -@interface WKWebView (XWebView) -- (nullable WKNavigation *)loadFileURL:(nonnull NSURL *)URL allowingReadAccessToURL:(nonnull NSURL *)readAccessURL; -@end - - NS_ASSUME_NONNULL_BEGIN // Special init which can't be reference directly in Swift, but cannot be a protocol either. From af05e4342ceb6b7b863e0dc46f75be2d6f580bbf Mon Sep 17 00:00:00 2001 From: David Kim Date: Sun, 8 Jan 2017 05:14:31 +0800 Subject: [PATCH 39/44] Improve support of MacOS (#70) --- .travis.yml | 12 +- XWebView.iOS.xcodeproj/project.pbxproj | 518 +++++++++++++ .../contents.xcworkspacedata | 2 +- .../xcshareddata/xcschemes/XWebView.xcscheme | 36 +- XWebView.macOS.xcodeproj/project.pbxproj | 523 +++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/XWebView.xcscheme | 42 +- XWebView.podspec | 5 +- XWebView.xcodeproj/project.pbxproj | 695 ------------------ XWebView/{Info.plist => Info.iOS.plist} | 0 .../Info.plist => XWebView/Info.macOS.plist | 0 XWebView/XWVBindingObject.swift | 2 +- XWebView/XWVLogging.swift | 1 + XWebView/XWVMetaObject.swift | 2 +- XWebView/XWebView.h | 15 +- XWebView/XWebView.swift | 2 +- XWebViewTests/XWVInvocationTest.swift | 4 +- XWebViewTests/XWebViewTests.swift | 14 +- XWebViewX/XWebViewX.h | 35 - 19 files changed, 1109 insertions(+), 806 deletions(-) create mode 100644 XWebView.iOS.xcodeproj/project.pbxproj rename {XWebView.xcodeproj => XWebView.iOS.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (69%) rename {XWebView.xcodeproj => XWebView.iOS.xcodeproj}/xcshareddata/xcschemes/XWebView.xcscheme (70%) create mode 100644 XWebView.macOS.xcodeproj/project.pbxproj create mode 100644 XWebView.macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme => XWebView.macOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme (69%) delete mode 100644 XWebView.xcodeproj/project.pbxproj rename XWebView/{Info.plist => Info.iOS.plist} (100%) rename XWebViewX/Info.plist => XWebView/Info.macOS.plist (100%) delete mode 100644 XWebViewX/XWebViewX.h diff --git a/.travis.yml b/.travis.yml index 82893be..37f2dfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c osx_image: xcode8.2 #xcode_sdk: iphonesimulator9.0 -#xcode_project: XWebView.xcodeproj -#xcode_scheme: XWebViewTests -#xctool_args: "-destination 'platform=iOS Simulator,OS=8.1,name=iPhone 5s'" +#xcode_project: XWebView.iOS.xcodeproj +#xcode_scheme: XWebView +#xctool_args: "-destination 'platform=iOS Simulator,OS=9.0,name=iPhone 5s'" env: matrix: - - OS=9.0 - - OS=10.0 -script: xcodebuild -scheme XWebViewTests -configuration Debug -destination "platform=iOS Simulator,OS=$OS,name=iPhone 5s" test + - PROJECT=XWebView.iOS.xcodeproj DESTINATION="platform=iOS Simulator,OS=9.0,name=iPhone 5s" + - PROJECT=XWebView.macOS.xcodeproj DESTINATION="platform=macOS,arch=x86_64" +script: xcodebuild -project "$PROJECT" -scheme XWebView -configuration Debug -destination "$DESTINATION" test diff --git a/XWebView.iOS.xcodeproj/project.pbxproj b/XWebView.iOS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..34d38d5 --- /dev/null +++ b/XWebView.iOS.xcodeproj/project.pbxproj @@ -0,0 +1,518 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + EE9BCB921E12EFE700206DC3 /* XWebView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9BCB881E12EFE600206DC3 /* XWebView.framework */; }; + EE9BCBB31E12F09D00206DC3 /* XWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9BCBA31E12F09D00206DC3 /* XWebView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE9BCBB41E12F09D00206DC3 /* xwebview.js in Resources */ = {isa = PBXBuildFile; fileRef = EE9BCBA41E12F09D00206DC3 /* xwebview.js */; }; + EE9BCBB51E12F09D00206DC3 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA51E12F09D00206DC3 /* XWebView.swift */; }; + EE9BCBB61E12F09D00206DC3 /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */; }; + EE9BCBB71E12F09D00206DC3 /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */; }; + EE9BCBB81E12F09D00206DC3 /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */; }; + EE9BCBB91E12F09D00206DC3 /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */; }; + EE9BCBBA1E12F09D00206DC3 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */; }; + EE9BCBBB1E12F09D00206DC3 /* XWVJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */; }; + EE9BCBBC1E12F09D00206DC3 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */; }; + EE9BCBBD1E12F09D00206DC3 /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */; }; + EE9BCBBE1E12F09D00206DC3 /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */; }; + EE9BCBBF1E12F09D00206DC3 /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */; }; + EE9BCBC01E12F09D00206DC3 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */; }; + EE9BCBC11E12F09D00206DC3 /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */; }; + EE9BCBD81E12F36500206DC3 /* ConstructorPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */; }; + EE9BCBD91E12F36900206DC3 /* FunctionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */; }; + EE9BCBDA1E12F36D00206DC3 /* ObjectPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */; }; + EE9BCBDB1E12F37100206DC3 /* XWebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */; }; + EE9BCBDC1E12F37400206DC3 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */; }; + EE9BCBDD1E12F37900206DC3 /* XWVJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */; }; + EE9BCBDE1E12F37D00206DC3 /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */; }; + EE9BCBDF1E12F38100206DC3 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */; }; + EE9BCBE01E12F38500206DC3 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */; }; + EE9BCBE41E12F4BE00206DC3 /* www in Resources */ = {isa = PBXBuildFile; fileRef = EE9BCBE21E12F4B700206DC3 /* www */; }; + EE9BCBE71E12FB7600206DC3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9BCBE61E12FB7600206DC3 /* WebKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + EE9BCB931E12EFE700206DC3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EE9BCB7F1E12EFE600206DC3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EE9BCB871E12EFE600206DC3; + remoteInfo = XWebView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + EE9BCB881E12EFE600206DC3 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + EE9BCBA21E12F09D00206DC3 /* Info.iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.iOS.plist; sourceTree = ""; }; + EE9BCBA31E12F09D00206DC3 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XWebView.h; sourceTree = ""; }; + EE9BCBA41E12F09D00206DC3 /* xwebview.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = xwebview.js; sourceTree = ""; }; + EE9BCBA51E12F09D00206DC3 /* XWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWebView.swift; sourceTree = ""; }; + EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVBindingObject.swift; sourceTree = ""; }; + EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVChannel.swift; sourceTree = ""; }; + EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVHttpConnection.swift; sourceTree = ""; }; + EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVHttpServer.swift; sourceTree = ""; }; + EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocation.swift; sourceTree = ""; }; + EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJson.swift; sourceTree = ""; }; + EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVLogging.swift; sourceTree = ""; }; + EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObject.swift; sourceTree = ""; }; + EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVObject.swift; sourceTree = ""; }; + EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScripting.swift; sourceTree = ""; }; + EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptObject.swift; sourceTree = ""; }; + EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVUserScript.swift; sourceTree = ""; }; + EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstructorPlugin.swift; sourceTree = ""; }; + EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionPlugin.swift; sourceTree = ""; }; + EE9BCBC41E12F20B00206DC3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPlugin.swift; sourceTree = ""; }; + EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWebViewTests.swift; sourceTree = ""; }; + EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; + EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJsonTests.swift; sourceTree = ""; }; + EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; + EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; + EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVTestCase.swift; sourceTree = ""; }; + EE9BCBE21E12F4B700206DC3 /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = ""; }; + EE9BCBE61E12FB7600206DC3 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EE9BCB841E12EFE600206DC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBE71E12FB7600206DC3 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8E1E12EFE700206DC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCB921E12EFE700206DC3 /* XWebView.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EE9BCB7E1E12EFE600206DC3 = { + isa = PBXGroup; + children = ( + EE9BCB8A1E12EFE600206DC3 /* XWebView */, + EE9BCB951E12EFE700206DC3 /* XWebViewTests */, + EE9BCB891E12EFE600206DC3 /* Products */, + EE9BCBE51E12FB7600206DC3 /* Frameworks */, + ); + sourceTree = ""; + }; + EE9BCB891E12EFE600206DC3 /* Products */ = { + isa = PBXGroup; + children = ( + EE9BCB881E12EFE600206DC3 /* XWebView.framework */, + EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + EE9BCB8A1E12EFE600206DC3 /* XWebView */ = { + isa = PBXGroup; + children = ( + EE9BCBA21E12F09D00206DC3 /* Info.iOS.plist */, + EE9BCBA31E12F09D00206DC3 /* XWebView.h */, + EE9BCBA41E12F09D00206DC3 /* xwebview.js */, + EE9BCBA51E12F09D00206DC3 /* XWebView.swift */, + EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */, + EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */, + EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */, + EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */, + EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */, + EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */, + EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */, + EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */, + EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */, + EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */, + EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */, + EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */, + ); + path = XWebView; + sourceTree = ""; + }; + EE9BCB951E12EFE700206DC3 /* XWebViewTests */ = { + isa = PBXGroup; + children = ( + EE9BCBE21E12F4B700206DC3 /* www */, + EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */, + EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */, + EE9BCBC41E12F20B00206DC3 /* Info.plist */, + EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */, + EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */, + EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */, + EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */, + EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */, + EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */, + EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */, + ); + path = XWebViewTests; + sourceTree = ""; + }; + EE9BCBE51E12FB7600206DC3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + EE9BCBE61E12FB7600206DC3 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + EE9BCB851E12EFE600206DC3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBB31E12F09D00206DC3 /* XWebView.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + EE9BCB871E12EFE600206DC3 /* XWebView */ = { + isa = PBXNativeTarget; + buildConfigurationList = EE9BCB9C1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebView" */; + buildPhases = ( + EE9BCB831E12EFE600206DC3 /* Sources */, + EE9BCB841E12EFE600206DC3 /* Frameworks */, + EE9BCB851E12EFE600206DC3 /* Headers */, + EE9BCB861E12EFE600206DC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XWebView; + productName = XWebView; + productReference = EE9BCB881E12EFE600206DC3 /* XWebView.framework */; + productType = "com.apple.product-type.framework"; + }; + EE9BCB901E12EFE700206DC3 /* XWebViewTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = EE9BCB9F1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebViewTests" */; + buildPhases = ( + EE9BCB8D1E12EFE700206DC3 /* Sources */, + EE9BCB8E1E12EFE700206DC3 /* Frameworks */, + EE9BCB8F1E12EFE700206DC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + EE9BCB941E12EFE700206DC3 /* PBXTargetDependency */, + ); + name = XWebViewTests; + productName = XWebViewTests; + productReference = EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EE9BCB7F1E12EFE600206DC3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = XWebView; + TargetAttributes = { + EE9BCB871E12EFE600206DC3 = { + CreatedOnToolsVersion = 8.2; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + EE9BCB901E12EFE700206DC3 = { + CreatedOnToolsVersion = 8.2; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = EE9BCB821E12EFE600206DC3 /* Build configuration list for PBXProject "XWebView.iOS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = EE9BCB7E1E12EFE600206DC3; + productRefGroup = EE9BCB891E12EFE600206DC3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EE9BCB871E12EFE600206DC3 /* XWebView */, + EE9BCB901E12EFE700206DC3 /* XWebViewTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + EE9BCB861E12EFE600206DC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBB41E12F09D00206DC3 /* xwebview.js in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8F1E12EFE700206DC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBE41E12F4BE00206DC3 /* www in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EE9BCB831E12EFE600206DC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBBF1E12F09D00206DC3 /* XWVScripting.swift in Sources */, + EE9BCBC01E12F09D00206DC3 /* XWVScriptObject.swift in Sources */, + EE9BCBB71E12F09D00206DC3 /* XWVChannel.swift in Sources */, + EE9BCBBB1E12F09D00206DC3 /* XWVJson.swift in Sources */, + EE9BCBB61E12F09D00206DC3 /* XWVBindingObject.swift in Sources */, + EE9BCBC11E12F09D00206DC3 /* XWVUserScript.swift in Sources */, + EE9BCBBD1E12F09D00206DC3 /* XWVMetaObject.swift in Sources */, + EE9BCBB51E12F09D00206DC3 /* XWebView.swift in Sources */, + EE9BCBB81E12F09D00206DC3 /* XWVHttpConnection.swift in Sources */, + EE9BCBBE1E12F09D00206DC3 /* XWVObject.swift in Sources */, + EE9BCBB91E12F09D00206DC3 /* XWVHttpServer.swift in Sources */, + EE9BCBBA1E12F09D00206DC3 /* XWVInvocation.swift in Sources */, + EE9BCBBC1E12F09D00206DC3 /* XWVLogging.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8D1E12EFE700206DC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBD91E12F36900206DC3 /* FunctionPlugin.swift in Sources */, + EE9BCBDF1E12F38100206DC3 /* XWVScriptingTest.swift in Sources */, + EE9BCBDE1E12F37D00206DC3 /* XWVMetaObjectTest.swift in Sources */, + EE9BCBDA1E12F36D00206DC3 /* ObjectPlugin.swift in Sources */, + EE9BCBDB1E12F37100206DC3 /* XWebViewTests.swift in Sources */, + EE9BCBD81E12F36500206DC3 /* ConstructorPlugin.swift in Sources */, + EE9BCBDC1E12F37400206DC3 /* XWVInvocationTest.swift in Sources */, + EE9BCBE01E12F38500206DC3 /* XWVTestCase.swift in Sources */, + EE9BCBDD1E12F37900206DC3 /* XWVJsonTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + EE9BCB941E12EFE700206DC3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EE9BCB871E12EFE600206DC3 /* XWebView */; + targetProxy = EE9BCB931E12EFE700206DC3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + EE9BCB9A1E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + EE9BCB9B1E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + EE9BCB9D1E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = XWebView/Info.iOS.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + EE9BCB9E1E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = XWebView/Info.iOS.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + EE9BCBA01E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = XWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + EE9BCBA11E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = XWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + EE9BCB821E12EFE600206DC3 /* Build configuration list for PBXProject "XWebView.iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCB9A1E12EFE700206DC3 /* Debug */, + EE9BCB9B1E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EE9BCB9C1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCB9D1E12EFE700206DC3 /* Debug */, + EE9BCB9E1E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EE9BCB9F1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebViewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCBA01E12EFE700206DC3 /* Debug */, + EE9BCBA11E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = EE9BCB7F1E12EFE600206DC3 /* Project object */; +} diff --git a/XWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/XWebView.iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 69% rename from XWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to XWebView.iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 5cc8a04..b5fcc85 100644 --- a/XWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/XWebView.iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:XWebView.iOS.xcodeproj"> diff --git a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme b/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme similarity index 70% rename from XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme rename to XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index 516f22a..e1c0ce7 100644 --- a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme +++ b/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -1,6 +1,6 @@ - - - - + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> @@ -46,20 +32,20 @@ skipped = "NO"> + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> @@ -78,10 +64,10 @@ + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> @@ -96,10 +82,10 @@ + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> diff --git a/XWebView.macOS.xcodeproj/project.pbxproj b/XWebView.macOS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4dee9a4 --- /dev/null +++ b/XWebView.macOS.xcodeproj/project.pbxproj @@ -0,0 +1,523 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + EE9BCB921E12EFE700206DC3 /* XWebView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9BCB881E12EFE600206DC3 /* XWebView.framework */; }; + EE9BCBB31E12F09D00206DC3 /* XWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = EE9BCBA31E12F09D00206DC3 /* XWebView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE9BCBB41E12F09D00206DC3 /* xwebview.js in Resources */ = {isa = PBXBuildFile; fileRef = EE9BCBA41E12F09D00206DC3 /* xwebview.js */; }; + EE9BCBB51E12F09D00206DC3 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA51E12F09D00206DC3 /* XWebView.swift */; }; + EE9BCBB61E12F09D00206DC3 /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */; }; + EE9BCBB71E12F09D00206DC3 /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */; }; + EE9BCBB81E12F09D00206DC3 /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */; }; + EE9BCBB91E12F09D00206DC3 /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */; }; + EE9BCBBA1E12F09D00206DC3 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */; }; + EE9BCBBB1E12F09D00206DC3 /* XWVJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */; }; + EE9BCBBC1E12F09D00206DC3 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */; }; + EE9BCBBD1E12F09D00206DC3 /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */; }; + EE9BCBBE1E12F09D00206DC3 /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */; }; + EE9BCBBF1E12F09D00206DC3 /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */; }; + EE9BCBC01E12F09D00206DC3 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */; }; + EE9BCBC11E12F09D00206DC3 /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */; }; + EE9BCBD81E12F36500206DC3 /* ConstructorPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */; }; + EE9BCBD91E12F36900206DC3 /* FunctionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */; }; + EE9BCBDA1E12F36D00206DC3 /* ObjectPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */; }; + EE9BCBDB1E12F37100206DC3 /* XWebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */; }; + EE9BCBDC1E12F37400206DC3 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */; }; + EE9BCBDD1E12F37900206DC3 /* XWVJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */; }; + EE9BCBDE1E12F37D00206DC3 /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */; }; + EE9BCBDF1E12F38100206DC3 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */; }; + EE9BCBE01E12F38500206DC3 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */; }; + EE9BCBE41E12F4BE00206DC3 /* www in Resources */ = {isa = PBXBuildFile; fileRef = EE9BCBE21E12F4B700206DC3 /* www */; }; + EE9BCBE71E12FB7600206DC3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9BCBE61E12FB7600206DC3 /* WebKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + EE9BCB931E12EFE700206DC3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EE9BCB7F1E12EFE600206DC3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EE9BCB871E12EFE600206DC3; + remoteInfo = XWebView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + EE9BCB881E12EFE600206DC3 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + EE9BCBA21E12F09D00206DC3 /* Info.macOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.macOS.plist; sourceTree = ""; }; + EE9BCBA31E12F09D00206DC3 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XWebView.h; sourceTree = ""; }; + EE9BCBA41E12F09D00206DC3 /* xwebview.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = xwebview.js; sourceTree = ""; }; + EE9BCBA51E12F09D00206DC3 /* XWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWebView.swift; sourceTree = ""; }; + EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVBindingObject.swift; sourceTree = ""; }; + EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVChannel.swift; sourceTree = ""; }; + EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVHttpConnection.swift; sourceTree = ""; }; + EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVHttpServer.swift; sourceTree = ""; }; + EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocation.swift; sourceTree = ""; }; + EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJson.swift; sourceTree = ""; }; + EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVLogging.swift; sourceTree = ""; }; + EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObject.swift; sourceTree = ""; }; + EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVObject.swift; sourceTree = ""; }; + EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScripting.swift; sourceTree = ""; }; + EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptObject.swift; sourceTree = ""; }; + EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVUserScript.swift; sourceTree = ""; }; + EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstructorPlugin.swift; sourceTree = ""; }; + EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionPlugin.swift; sourceTree = ""; }; + EE9BCBC41E12F20B00206DC3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPlugin.swift; sourceTree = ""; }; + EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWebViewTests.swift; sourceTree = ""; }; + EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; + EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJsonTests.swift; sourceTree = ""; }; + EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; + EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; + EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVTestCase.swift; sourceTree = ""; }; + EE9BCBE21E12F4B700206DC3 /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = ""; }; + EE9BCBE61E12FB7600206DC3 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EE9BCB841E12EFE600206DC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBE71E12FB7600206DC3 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8E1E12EFE700206DC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCB921E12EFE700206DC3 /* XWebView.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EE9BCB7E1E12EFE600206DC3 = { + isa = PBXGroup; + children = ( + EE9BCB8A1E12EFE600206DC3 /* XWebView */, + EE9BCB951E12EFE700206DC3 /* XWebViewTests */, + EE9BCB891E12EFE600206DC3 /* Products */, + EE9BCBE51E12FB7600206DC3 /* Frameworks */, + ); + sourceTree = ""; + }; + EE9BCB891E12EFE600206DC3 /* Products */ = { + isa = PBXGroup; + children = ( + EE9BCB881E12EFE600206DC3 /* XWebView.framework */, + EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + EE9BCB8A1E12EFE600206DC3 /* XWebView */ = { + isa = PBXGroup; + children = ( + EE9BCBA21E12F09D00206DC3 /* Info.macOS.plist */, + EE9BCBA31E12F09D00206DC3 /* XWebView.h */, + EE9BCBA41E12F09D00206DC3 /* xwebview.js */, + EE9BCBA51E12F09D00206DC3 /* XWebView.swift */, + EE9BCBA61E12F09D00206DC3 /* XWVBindingObject.swift */, + EE9BCBA71E12F09D00206DC3 /* XWVChannel.swift */, + EE9BCBA81E12F09D00206DC3 /* XWVHttpConnection.swift */, + EE9BCBA91E12F09D00206DC3 /* XWVHttpServer.swift */, + EE9BCBAA1E12F09D00206DC3 /* XWVInvocation.swift */, + EE9BCBAB1E12F09D00206DC3 /* XWVJson.swift */, + EE9BCBAC1E12F09D00206DC3 /* XWVLogging.swift */, + EE9BCBAD1E12F09D00206DC3 /* XWVMetaObject.swift */, + EE9BCBAE1E12F09D00206DC3 /* XWVObject.swift */, + EE9BCBAF1E12F09D00206DC3 /* XWVScripting.swift */, + EE9BCBB01E12F09D00206DC3 /* XWVScriptObject.swift */, + EE9BCBB11E12F09D00206DC3 /* XWVUserScript.swift */, + ); + path = XWebView; + sourceTree = ""; + }; + EE9BCB951E12EFE700206DC3 /* XWebViewTests */ = { + isa = PBXGroup; + children = ( + EE9BCBE21E12F4B700206DC3 /* www */, + EE9BCBC21E12F20B00206DC3 /* ConstructorPlugin.swift */, + EE9BCBC31E12F20B00206DC3 /* FunctionPlugin.swift */, + EE9BCBC41E12F20B00206DC3 /* Info.plist */, + EE9BCBC51E12F20B00206DC3 /* ObjectPlugin.swift */, + EE9BCBC71E12F20B00206DC3 /* XWebViewTests.swift */, + EE9BCBC81E12F20B00206DC3 /* XWVInvocationTest.swift */, + EE9BCBC91E12F20B00206DC3 /* XWVJsonTests.swift */, + EE9BCBCA1E12F20B00206DC3 /* XWVMetaObjectTest.swift */, + EE9BCBCB1E12F20B00206DC3 /* XWVScriptingTest.swift */, + EE9BCBCC1E12F20B00206DC3 /* XWVTestCase.swift */, + ); + path = XWebViewTests; + sourceTree = ""; + }; + EE9BCBE51E12FB7600206DC3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + EE9BCBE61E12FB7600206DC3 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + EE9BCB851E12EFE600206DC3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBB31E12F09D00206DC3 /* XWebView.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + EE9BCB871E12EFE600206DC3 /* XWebView */ = { + isa = PBXNativeTarget; + buildConfigurationList = EE9BCB9C1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebView" */; + buildPhases = ( + EE9BCB831E12EFE600206DC3 /* Sources */, + EE9BCB841E12EFE600206DC3 /* Frameworks */, + EE9BCB851E12EFE600206DC3 /* Headers */, + EE9BCB861E12EFE600206DC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XWebView; + productName = XWebView; + productReference = EE9BCB881E12EFE600206DC3 /* XWebView.framework */; + productType = "com.apple.product-type.framework"; + }; + EE9BCB901E12EFE700206DC3 /* XWebViewTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = EE9BCB9F1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebViewTests" */; + buildPhases = ( + EE9BCB8D1E12EFE700206DC3 /* Sources */, + EE9BCB8E1E12EFE700206DC3 /* Frameworks */, + EE9BCB8F1E12EFE700206DC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + EE9BCB941E12EFE700206DC3 /* PBXTargetDependency */, + ); + name = XWebViewTests; + productName = XWebViewTests; + productReference = EE9BCB911E12EFE700206DC3 /* XWebViewTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EE9BCB7F1E12EFE600206DC3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = XWebView; + TargetAttributes = { + EE9BCB871E12EFE600206DC3 = { + CreatedOnToolsVersion = 8.2; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + EE9BCB901E12EFE700206DC3 = { + CreatedOnToolsVersion = 8.2; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = EE9BCB821E12EFE600206DC3 /* Build configuration list for PBXProject "XWebView.macOS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = EE9BCB7E1E12EFE600206DC3; + productRefGroup = EE9BCB891E12EFE600206DC3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EE9BCB871E12EFE600206DC3 /* XWebView */, + EE9BCB901E12EFE700206DC3 /* XWebViewTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + EE9BCB861E12EFE600206DC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBB41E12F09D00206DC3 /* xwebview.js in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8F1E12EFE700206DC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBE41E12F4BE00206DC3 /* www in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EE9BCB831E12EFE600206DC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBBF1E12F09D00206DC3 /* XWVScripting.swift in Sources */, + EE9BCBC01E12F09D00206DC3 /* XWVScriptObject.swift in Sources */, + EE9BCBB71E12F09D00206DC3 /* XWVChannel.swift in Sources */, + EE9BCBBB1E12F09D00206DC3 /* XWVJson.swift in Sources */, + EE9BCBB61E12F09D00206DC3 /* XWVBindingObject.swift in Sources */, + EE9BCBC11E12F09D00206DC3 /* XWVUserScript.swift in Sources */, + EE9BCBBD1E12F09D00206DC3 /* XWVMetaObject.swift in Sources */, + EE9BCBB51E12F09D00206DC3 /* XWebView.swift in Sources */, + EE9BCBB81E12F09D00206DC3 /* XWVHttpConnection.swift in Sources */, + EE9BCBBE1E12F09D00206DC3 /* XWVObject.swift in Sources */, + EE9BCBB91E12F09D00206DC3 /* XWVHttpServer.swift in Sources */, + EE9BCBBA1E12F09D00206DC3 /* XWVInvocation.swift in Sources */, + EE9BCBBC1E12F09D00206DC3 /* XWVLogging.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE9BCB8D1E12EFE700206DC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EE9BCBD91E12F36900206DC3 /* FunctionPlugin.swift in Sources */, + EE9BCBDF1E12F38100206DC3 /* XWVScriptingTest.swift in Sources */, + EE9BCBDE1E12F37D00206DC3 /* XWVMetaObjectTest.swift in Sources */, + EE9BCBDA1E12F36D00206DC3 /* ObjectPlugin.swift in Sources */, + EE9BCBDB1E12F37100206DC3 /* XWebViewTests.swift in Sources */, + EE9BCBD81E12F36500206DC3 /* ConstructorPlugin.swift in Sources */, + EE9BCBDC1E12F37400206DC3 /* XWVInvocationTest.swift in Sources */, + EE9BCBE01E12F38500206DC3 /* XWVTestCase.swift in Sources */, + EE9BCBDD1E12F37900206DC3 /* XWVJsonTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + EE9BCB941E12EFE700206DC3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EE9BCB871E12EFE600206DC3 /* XWebView */; + targetProxy = EE9BCB931E12EFE700206DC3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + EE9BCB9A1E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + EE9BCB9B1E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + EE9BCB9D1E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = XWebView/Info.macOS.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + EE9BCB9E1E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = XWebView/Info.macOS.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + EE9BCBA01E12EFE700206DC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = XWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + EE9BCBA11E12EFE700206DC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = XWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + EE9BCB821E12EFE600206DC3 /* Build configuration list for PBXProject "XWebView.macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCB9A1E12EFE700206DC3 /* Debug */, + EE9BCB9B1E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EE9BCB9C1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCB9D1E12EFE700206DC3 /* Debug */, + EE9BCB9E1E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EE9BCB9F1E12EFE700206DC3 /* Build configuration list for PBXNativeTarget "XWebViewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EE9BCBA01E12EFE700206DC3 /* Debug */, + EE9BCBA11E12EFE700206DC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = EE9BCB7F1E12EFE600206DC3 /* Project object */; +} diff --git a/XWebView.macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/XWebView.macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..6921082 --- /dev/null +++ b/XWebView.macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme b/XWebView.macOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme similarity index 69% rename from XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme rename to XWebView.macOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index 6fb9ea3..d29e62d 100644 --- a/XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme +++ b/XWebView.macOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> @@ -32,20 +32,20 @@ skipped = "NO"> + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> @@ -64,10 +64,10 @@ + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> @@ -82,10 +82,10 @@ + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> diff --git a/XWebView.podspec b/XWebView.podspec index 766497a..7854f52 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.name = "XWebView" s.version = "0.12.0" - s.summary = "An extensible WebView (based on WKWebView) for iOS." + s.summary = "An extensible WebView (based on WKWebView)" s.description = <<-DESC XWebView is an extensible WebView which is built on top of WKWebView, @@ -65,11 +65,10 @@ Pod::Spec.new do |s| # # s.platform = :ios - s.platform = :ios, "9.0" # When using multiple platforms s.ios.deployment_target = "9.0" - # s.osx.deployment_target = "10.7" + s.osx.deployment_target = "10.11" # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj deleted file mode 100644 index 1722ebc..0000000 --- a/XWebView.xcodeproj/project.pbxproj +++ /dev/null @@ -1,695 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 663C48C51CC034270031251A /* XWebViewX.h in Headers */ = {isa = PBXBuildFile; fileRef = 663C48C41CC034270031251A /* XWebViewX.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 663C48CA1CC034F10031251A /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; }; - 663C48CB1CC034F50031251A /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; }; - 663C48CC1CC034F80031251A /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; - 663C48CD1CC034FC0031251A /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; - 663C48CE1CC035090031251A /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE131CA61B5F900400A9E790 /* XWVUserScript.swift */; }; - 663C48CF1CC0350C0031251A /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; - 663C48D01CC0350F0031251A /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */; }; - 663C48D11CC035130031251A /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; - 663C48D21CC035160031251A /* xwebview.js in Resources */ = {isa = PBXBuildFile; fileRef = EE174E771A0361CB00168D96 /* xwebview.js */; }; - 663C48D31CC035190031251A /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */; }; - 663C48D41CC0351D0031251A /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */; }; - 663C48D51CC035200031251A /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; - 663C48D61CC035230031251A /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */; }; - AB023EA51A8C506600580A2A /* XWebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB023EA41A8C506600580A2A /* XWebViewTests.swift */; }; - AB023EA61A8C506600580A2A /* XWebView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE62683519FA323900EFC3F8 /* XWebView.framework */; }; - AB023EBE1A8C8BC700580A2A /* XWebView.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EE62683519FA323900EFC3F8 /* XWebView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AB2273E51AA6FDA700F9207A /* www in Resources */ = {isa = PBXBuildFile; fileRef = AB2273E41AA6FDA700F9207A /* www */; }; - ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = EE62691C19FA52FC00EFC3F8 /* XWebView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */; }; - EE131CA71B5F900400A9E790 /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE131CA61B5F900400A9E790 /* XWVUserScript.swift */; }; - EE174E781A0361CB00168D96 /* xwebview.js in Resources */ = {isa = PBXBuildFile; fileRef = EE174E771A0361CB00168D96 /* xwebview.js */; }; - EE2F487D1AE4B8F40088AF67 /* ObjectPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2F487C1AE4B8F40088AF67 /* ObjectPlugin.swift */; }; - EE2F487F1AE4CD360088AF67 /* FunctionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2F487E1AE4CD360088AF67 /* FunctionPlugin.swift */; }; - EE2F48811AE4CE690088AF67 /* ConstructorPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2F48801AE4CE690088AF67 /* ConstructorPlugin.swift */; }; - EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3379381AE2E298009124A4 /* XWVTestCase.swift */; }; - EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; - EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; - EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */; }; - EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; - EE5C89401E0C53CF00848B9A /* XWVJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */; }; - EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; - EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */; }; - EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */; }; - EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; - EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */; }; - EE9BDBB81DBC0953001714AD /* XWVJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9BDBB71DBC0953001714AD /* XWVJson.swift */; }; - EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; - EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */; }; - EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */; }; - EEE6F9A81AE02F5000A2EC89 /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */; }; - EEF27EB71AFA1D89004740CF /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEF27EB61AFA1D89004740CF /* WebKit.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - AB023EA71A8C506600580A2A /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = EE62682C19FA323900EFC3F8 /* Project object */; - proxyType = 1; - remoteGlobalIDString = EE62683419FA323900EFC3F8; - remoteInfo = XWebView; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AB023EBD1A8C8BBE00580A2A /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - AB023EBE1A8C8BC700580A2A /* XWebView.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 663C48C21CC034270031251A /* XWebViewX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebViewX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 663C48C41CC034270031251A /* XWebViewX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XWebViewX.h; sourceTree = ""; }; - 663C48C61CC034270031251A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AB023EA01A8C506600580A2A /* XWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - AB023EA31A8C506600580A2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AB023EA41A8C506600580A2A /* XWebViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XWebViewTests.swift; sourceTree = ""; }; - AB2273E41AA6FDA700F9207A /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = ""; }; - EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVChannel.swift; path = XWebView/XWVChannel.swift; sourceTree = ""; }; - EE131CA61B5F900400A9E790 /* XWVUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVUserScript.swift; path = XWebView/XWVUserScript.swift; sourceTree = ""; }; - EE174E771A0361CB00168D96 /* xwebview.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = xwebview.js; path = XWebView/xwebview.js; sourceTree = ""; }; - EE2F487C1AE4B8F40088AF67 /* ObjectPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPlugin.swift; sourceTree = ""; }; - EE2F487E1AE4CD360088AF67 /* FunctionPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionPlugin.swift; sourceTree = ""; }; - EE2F48801AE4CE690088AF67 /* ConstructorPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstructorPlugin.swift; sourceTree = ""; }; - EE3379381AE2E298009124A4 /* XWVTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVTestCase.swift; sourceTree = ""; }; - EE33793D1AE56875009124A4 /* XWVScriptObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScriptObject.swift; path = XWebView/XWVScriptObject.swift; sourceTree = ""; }; - EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; - EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVLogging.swift; path = XWebView/XWVLogging.swift; sourceTree = ""; }; - EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; - EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVJsonTests.swift; sourceTree = ""; }; - EE62683519FA323900EFC3F8 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EE62691319FA52D100EFC3F8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = XWebView/Info.plist; sourceTree = ""; }; - EE62691C19FA52FC00EFC3F8 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWebView.h; path = XWebView/XWebView.h; sourceTree = ""; }; - EE62692019FA52FC00EFC3F8 /* XWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWebView.swift; path = XWebView/XWebView.swift; sourceTree = ""; }; - EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVHttpConnection.swift; path = XWebView/XWVHttpConnection.swift; sourceTree = ""; }; - EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVHttpServer.swift; path = XWebView/XWVHttpServer.swift; sourceTree = ""; }; - EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVMetaObject.swift; path = XWebView/XWVMetaObject.swift; sourceTree = ""; }; - EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; - EE9BDBB71DBC0953001714AD /* XWVJson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVJson.swift; path = XWebView/XWVJson.swift; sourceTree = ""; }; - EEDF305F1B6555B900A21659 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVInvocation.swift; path = XWebView/XWVInvocation.swift; sourceTree = ""; }; - EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScripting.swift; path = XWebView/XWVScripting.swift; sourceTree = ""; }; - EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVObject.swift; path = XWebView/XWVObject.swift; sourceTree = ""; }; - EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVBindingObject.swift; path = XWebView/XWVBindingObject.swift; sourceTree = ""; }; - EEF27EB61AFA1D89004740CF /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 663C48BE1CC034270031251A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AB023E9D1A8C506600580A2A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AB023EA61A8C506600580A2A /* XWebView.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EE62683119FA323900EFC3F8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - EEF27EB71AFA1D89004740CF /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 663C48C31CC034270031251A /* XWebViewX */ = { - isa = PBXGroup; - children = ( - 663C48C41CC034270031251A /* XWebViewX.h */, - 663C48C61CC034270031251A /* Info.plist */, - ); - path = XWebViewX; - sourceTree = ""; - }; - AB023EA11A8C506600580A2A /* XWebViewTests */ = { - isa = PBXGroup; - children = ( - AB2273E41AA6FDA700F9207A /* www */, - EE5C893F1E0C53CF00848B9A /* XWVJsonTests.swift */, - EE3379381AE2E298009124A4 /* XWVTestCase.swift */, - EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */, - EE2F487C1AE4B8F40088AF67 /* ObjectPlugin.swift */, - EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */, - EE2F487E1AE4CD360088AF67 /* FunctionPlugin.swift */, - EE2F48801AE4CE690088AF67 /* ConstructorPlugin.swift */, - AB023EA41A8C506600580A2A /* XWebViewTests.swift */, - EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */, - AB023EA21A8C506600580A2A /* Supporting Files */, - ); - path = XWebViewTests; - sourceTree = ""; - }; - AB023EA21A8C506600580A2A /* Supporting Files */ = { - isa = PBXGroup; - children = ( - AB023EA31A8C506600580A2A /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - EE62682B19FA323900EFC3F8 = { - isa = PBXGroup; - children = ( - EEF27EB61AFA1D89004740CF /* WebKit.framework */, - EE62683719FA323900EFC3F8 /* XWebView */, - AB023EA11A8C506600580A2A /* XWebViewTests */, - 663C48C31CC034270031251A /* XWebViewX */, - EE62683619FA323900EFC3F8 /* Products */, - ); - sourceTree = ""; - }; - EE62683619FA323900EFC3F8 /* Products */ = { - isa = PBXGroup; - children = ( - EE62683519FA323900EFC3F8 /* XWebView.framework */, - AB023EA01A8C506600580A2A /* XWebViewTests.xctest */, - 663C48C21CC034270031251A /* XWebViewX.framework */, - ); - name = Products; - sourceTree = ""; - }; - EE62683719FA323900EFC3F8 /* XWebView */ = { - isa = PBXGroup; - children = ( - EE9BDBB71DBC0953001714AD /* XWVJson.swift */, - EE4D1A771C04BF1700AC2339 /* XWVLogging.swift */, - EE8BA6E31BCBFFBC004421CA /* XWVHttpConnection.swift */, - EE8BA6E41BCBFFBC004421CA /* XWVHttpServer.swift */, - EEDF305F1B6555B900A21659 /* XWVInvocation.swift */, - EE131CA61B5F900400A9E790 /* XWVUserScript.swift */, - EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */, - EE62691C19FA52FC00EFC3F8 /* XWebView.h */, - EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */, - EE62692019FA52FC00EFC3F8 /* XWebView.swift */, - EE174E771A0361CB00168D96 /* xwebview.js */, - EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */, - EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */, - EE33793D1AE56875009124A4 /* XWVScriptObject.swift */, - EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */, - EE62683819FA323900EFC3F8 /* Supporting Files */, - ); - name = XWebView; - sourceTree = SOURCE_ROOT; - }; - EE62683819FA323900EFC3F8 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - EE62691319FA52D100EFC3F8 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 663C48BF1CC034270031251A /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 663C48C51CC034270031251A /* XWebViewX.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EE62683219FA323900EFC3F8 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 663C48C11CC034270031251A /* XWebViewX */ = { - isa = PBXNativeTarget; - buildConfigurationList = 663C48C71CC034270031251A /* Build configuration list for PBXNativeTarget "XWebViewX" */; - buildPhases = ( - 663C48BD1CC034270031251A /* Sources */, - 663C48BE1CC034270031251A /* Frameworks */, - 663C48BF1CC034270031251A /* Headers */, - 663C48C01CC034270031251A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = XWebViewX; - productName = XWebViewX; - productReference = 663C48C21CC034270031251A /* XWebViewX.framework */; - productType = "com.apple.product-type.framework"; - }; - AB023E9F1A8C506600580A2A /* XWebViewTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = AB023EAB1A8C506600580A2A /* Build configuration list for PBXNativeTarget "XWebViewTests" */; - buildPhases = ( - AB023E9C1A8C506600580A2A /* Sources */, - AB023E9D1A8C506600580A2A /* Frameworks */, - AB023E9E1A8C506600580A2A /* Resources */, - AB023EBD1A8C8BBE00580A2A /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - AB023EA81A8C506600580A2A /* PBXTargetDependency */, - ); - name = XWebViewTests; - productName = XWebViewTests; - productReference = AB023EA01A8C506600580A2A /* XWebViewTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - EE62683419FA323900EFC3F8 /* XWebView */ = { - isa = PBXNativeTarget; - buildConfigurationList = EE62684B19FA323900EFC3F8 /* Build configuration list for PBXNativeTarget "XWebView" */; - buildPhases = ( - EE62683019FA323900EFC3F8 /* Sources */, - EE62683119FA323900EFC3F8 /* Frameworks */, - EE62683219FA323900EFC3F8 /* Headers */, - EE62683319FA323900EFC3F8 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = XWebView; - productName = XWebView; - productReference = EE62683519FA323900EFC3F8 /* XWebView.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EE62682C19FA323900EFC3F8 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; - ORGANIZATIONNAME = XWebView; - TargetAttributes = { - 663C48C11CC034270031251A = { - CreatedOnToolsVersion = 7.3; - }; - AB023E9F1A8C506600580A2A = { - CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; - }; - EE62683419FA323900EFC3F8 = { - CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; - }; - }; - }; - buildConfigurationList = EE62682F19FA323900EFC3F8 /* Build configuration list for PBXProject "XWebView" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = EE62682B19FA323900EFC3F8; - productRefGroup = EE62683619FA323900EFC3F8 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - EE62683419FA323900EFC3F8 /* XWebView */, - AB023E9F1A8C506600580A2A /* XWebViewTests */, - 663C48C11CC034270031251A /* XWebViewX */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 663C48C01CC034270031251A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 663C48D21CC035160031251A /* xwebview.js in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AB023E9E1A8C506600580A2A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AB2273E51AA6FDA700F9207A /* www in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EE62683319FA323900EFC3F8 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EE174E781A0361CB00168D96 /* xwebview.js in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 663C48BD1CC034270031251A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 663C48CA1CC034F10031251A /* XWVLogging.swift in Sources */, - 663C48D01CC0350F0031251A /* XWVChannel.swift in Sources */, - 663C48D11CC035130031251A /* XWebView.swift in Sources */, - 663C48CD1CC034FC0031251A /* XWVInvocation.swift in Sources */, - 663C48CB1CC034F50031251A /* XWVHttpConnection.swift in Sources */, - 663C48CF1CC0350C0031251A /* XWVMetaObject.swift in Sources */, - 663C48D41CC0351D0031251A /* XWVObject.swift in Sources */, - 663C48CC1CC034F80031251A /* XWVHttpServer.swift in Sources */, - 663C48D61CC035230031251A /* XWVBindingObject.swift in Sources */, - 663C48D51CC035200031251A /* XWVScriptObject.swift in Sources */, - 663C48D31CC035190031251A /* XWVScripting.swift in Sources */, - 663C48CE1CC035090031251A /* XWVUserScript.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AB023E9C1A8C506600580A2A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */, - AB023EA51A8C506600580A2A /* XWebViewTests.swift in Sources */, - EE5C89401E0C53CF00848B9A /* XWVJsonTests.swift in Sources */, - EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */, - EE2F487F1AE4CD360088AF67 /* FunctionPlugin.swift in Sources */, - EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */, - EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */, - EE2F487D1AE4B8F40088AF67 /* ObjectPlugin.swift in Sources */, - EE2F48811AE4CE690088AF67 /* ConstructorPlugin.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - EE62683019FA323900EFC3F8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */, - EE8BA6E61BCBFFBC004421CA /* XWVHttpServer.swift in Sources */, - EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */, - EEE6F9A81AE02F5000A2EC89 /* XWVBindingObject.swift in Sources */, - EE131CA71B5F900400A9E790 /* XWVUserScript.swift in Sources */, - EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */, - EE8BA6E51BCBFFBC004421CA /* XWVHttpConnection.swift in Sources */, - EE9BDBB81DBC0953001714AD /* XWVJson.swift in Sources */, - EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */, - EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */, - EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */, - EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */, - EE4D1A781C04BF1700AC2339 /* XWVLogging.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - AB023EA81A8C506600580A2A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = EE62683419FA323900EFC3F8 /* XWebView */; - targetProxy = AB023EA71A8C506600580A2A /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 663C48C81CC034270031251A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = XWebViewX/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewX; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 663C48C91CC034270031251A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = XWebViewX/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewX; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - }; - name = Release; - }; - AB023EA91A8C506600580A2A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = XWebViewTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - AB023EAA1A8C506600580A2A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = XWebViewTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; - EE62684919FA323900EFC3F8 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - EE62684A19FA323900EFC3F8 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 1; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - EE62684C19FA323900EFC3F8 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = XWebView/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-DDEBUG"; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - EE62684D19FA323900EFC3F8 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = XWebView/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 663C48C71CC034270031251A /* Build configuration list for PBXNativeTarget "XWebViewX" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 663C48C81CC034270031251A /* Debug */, - 663C48C91CC034270031251A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AB023EAB1A8C506600580A2A /* Build configuration list for PBXNativeTarget "XWebViewTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AB023EA91A8C506600580A2A /* Debug */, - AB023EAA1A8C506600580A2A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EE62682F19FA323900EFC3F8 /* Build configuration list for PBXProject "XWebView" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EE62684919FA323900EFC3F8 /* Debug */, - EE62684A19FA323900EFC3F8 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EE62684B19FA323900EFC3F8 /* Build configuration list for PBXNativeTarget "XWebView" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EE62684C19FA323900EFC3F8 /* Debug */, - EE62684D19FA323900EFC3F8 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = EE62682C19FA323900EFC3F8 /* Project object */; -} diff --git a/XWebView/Info.plist b/XWebView/Info.iOS.plist similarity index 100% rename from XWebView/Info.plist rename to XWebView/Info.iOS.plist diff --git a/XWebViewX/Info.plist b/XWebView/Info.macOS.plist similarity index 100% rename from XWebViewX/Info.plist rename to XWebView/Info.macOS.plist diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 9019a63..e8b0530 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -44,7 +44,7 @@ final class XWVBindingObject : XWVScriptObject { promise = arguments.last as? XWVScriptObject arguments.removeLast() } - if selector == #selector(_InitSelector.init(byScriptWithArguments:)) { + if selector == Selector(("initByScriptWithArguments:")) { arguments = [arguments] } diff --git a/XWebView/XWVLogging.swift b/XWebView/XWVLogging.swift index 77f97c9..1efbb94 100644 --- a/XWebView/XWVLogging.swift +++ b/XWebView/XWVLogging.swift @@ -15,6 +15,7 @@ */ import Darwin +import Foundation public typealias asl_object_t = OpaquePointer diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index 7c26f92..5f09ca5 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -125,7 +125,7 @@ class XWVMetaObject { } case let .Initializer(selector, _): - if selector == #selector(_InitSelector.init(byScriptWithArguments:)) { + if selector == Selector(("initByScriptWithArguments:")) { member = .Initializer(selector: selector, arity: -1) name = "" } else if let cls = plugin as? XWVScripting.Type { diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index 2f5a0fb..b631c78 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -14,7 +14,12 @@ limitations under the License. */ +#import +#if TARGET_OS_IPHONE #import +#elif TARGET_OS_MAC +#import +#endif //! Project version number for XWebView. FOUNDATION_EXPORT double XWebViewVersionNumber; @@ -23,13 +28,3 @@ FOUNDATION_EXPORT double XWebViewVersionNumber; FOUNDATION_EXPORT const unsigned char XWebViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - -NS_ASSUME_NONNULL_BEGIN - -// Special init which can't be reference directly in Swift, but cannot be a protocol either. -@interface _InitSelector: NSObject -// Init with script -- (id)initByScriptWithArguments:(NSArray *)args; -@end - -NS_ASSUME_NONNULL_END diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 5ffd915..0bcf9ba 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -104,7 +104,7 @@ extension WKWebView { } } -@available(iOS 9.0, *) +@available(iOS 9.0, macOS 10.11, *) extension WKWebView { // Overlay support for loading file URL public func loadFileURL(_ url: URL, overlayURLs: [URL]? = nil) -> WKNavigation? { diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index a2ad197..551def1 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -81,11 +81,11 @@ class InvocationTests : XCTestCase { func testMethods() { XCTAssertTrue(inv[ #selector(InvocationTarget.dummy)]() is Void) - #if arch(x86_64) || arch(arm64) + #if arch(arm64) || (arch(x86_64) && os(iOS)) XCTAssertEqual(inv[ #selector(InvocationTarget.echo(bool:))](Bool(true)) as? Bool, true) #else // http://stackoverflow.com/questions/26459754/bool-encoding-wrong-from-nsmethodsignature - XCTAssertEqual(inv[Selector("echoWithBool:")](Bool(true)) as? Int8, 1) + XCTAssertEqual(inv[ #selector(InvocationTarget.echo(bool:))](Bool(true)) as? Int8, 1) #endif XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int:))](Int(-11)) as? XInt, -11) XCTAssertEqual(inv[ #selector(InvocationTarget.echo(int8:))](Int8(-22)) as? Int8, -22) diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 32cb363..3ebd737 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -64,20 +64,22 @@ class XWebViewTests: XWVTestCase { } } - @available(iOS 9.0, *) + #if !os(macOS) + @available(iOS 9.0, macOS 10.11, *) func testLoadFileURL() { _ = expectation(description: "loadFileURL") let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") - - if let root = bundle?.bundleURL.appendingPathComponent("www") { + + if let root = bundle?.resourceURL?.appendingPathComponent("www") { let url = root.appendingPathComponent("webviewTest.html") XCTAssert(try url.checkResourceIsReachable(), "HTML file not found") webview.loadFileURL(url, allowingReadAccessTo: root) waitForExpectations() } } + #endif - @available(iOS 9.0, *) + @available(iOS 9.0, macOS 10.11, *) func testLoadFileURLWithOverlay() { _ = expectation(description: "loadFileURLWithOverlay") let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") @@ -99,13 +101,15 @@ class XWebViewTests: XWVTestCase { } } + #if !os(macOS) func testLoadHTMLStringWithBaseURL() { _ = expectation(description: "loadHTMLStringWithBaseURL") let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") - if let baseURL = bundle?.bundleURL.appendingPathComponent("www") { + if let baseURL = bundle?.resourceURL?.appendingPathComponent("www") { XCTAssert(try baseURL.checkResourceIsReachable(), "Directory not found") webview.loadHTMLString("", baseURL: baseURL) waitForExpectations() } } + #endif } diff --git a/XWebViewX/XWebViewX.h b/XWebViewX/XWebViewX.h deleted file mode 100644 index de7dfe9..0000000 --- a/XWebViewX/XWebViewX.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed 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 - -//! Project version number for XWebViewX. -FOUNDATION_EXPORT double XWebViewXVersionNumber; - -//! Project version string for XWebViewX. -FOUNDATION_EXPORT const unsigned char XWebViewXVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - -NS_ASSUME_NONNULL_BEGIN - -// Special init which can't be reference directly in Swift, but cannot be a protocol either. -@interface _InitSelector: NSObject -// Init with script -- (id)initByScriptWithArguments:(NSArray *)args; -@end - -NS_ASSUME_NONNULL_END From 49cbfaf957012db058df055ab5f725d49823801f Mon Sep 17 00:00:00 2001 From: Denis Dzyubenko Date: Fri, 7 Apr 2017 15:19:23 +0200 Subject: [PATCH 40/44] Fixed compiling with Swift 3.1 (#80) Xcode 8.3 introduced Swift 3.1 which forbids to access deinitializers. The code started failing to compile with "Deinitializers cannot be accessed". Looking closer it seems the code in question isn't even needed, deinit defined in Swift is not exposed by class_copyMethodList() function. --- XWebView/XWVMetaObject.swift | 1 - XWebViewTests/XWVMetaObjectTest.swift | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index 5f09ca5..fe0e7d3 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -85,7 +85,6 @@ class XWVMetaObject { var methods = instanceMethods(forProtocol: XWVScripting.self) methods.remove(#selector(XWVScripting.invokeDefaultMethod(withArguments:))) return methods.union([ - #selector(NSObject.deinit), #selector(NSObject.copy) ]) }() diff --git a/XWebViewTests/XWVMetaObjectTest.swift b/XWebViewTests/XWVMetaObjectTest.swift index 1cf0f2a..2fa75a0 100644 --- a/XWebViewTests/XWVMetaObjectTest.swift +++ b/XWebViewTests/XWVMetaObjectTest.swift @@ -105,6 +105,25 @@ class XWVMetaObjectTest: XCTestCase { XCTAssertTrue(meta["method"] == nil) } + func testForSpecialExclusion() { + class TestForExclusion: XWVScripting { + @objc deinit { + print("ensuring deinit is not optimized out") + } + @objc func copy() -> Any { + return TestForExclusion() + } + @objc func copy(with zone: NSZone? = nil) -> Any { + return TestForExclusion() + } + @objc func method() {} + } + let meta = XWVMetaObject(plugin: TestForExclusion.self) + XCTAssertTrue(meta["dealloc"] == nil) + XCTAssertTrue(meta["deinit"] == nil) + XCTAssertTrue(meta["copy"] == nil) + } + func testForFunction() { class TestForFunction : XWVScripting { @objc func defaultMethod() {} From 6503a937b60a7f4ebd543bc0e38ce80fd4073cfa Mon Sep 17 00:00:00 2001 From: Denis Dzyubenko Date: Sat, 15 Apr 2017 20:47:46 +0200 Subject: [PATCH 41/44] Fixed compiler warning (#81) Swift 3.1 is quite strict on outputting optionals inside string interpolations and shows compiler warning "String interpolation produces a debug description for an optional value; did you mean to make this explicit?" --- XWebView/XWVChannel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 63bc016..4ac1b41 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -93,7 +93,7 @@ public class XWVChannel : NSObject, WKScriptMessageHandler { webView?.configuration.userContentController.removeScriptMessageHandler(forName: id) userScript = nil identifier = nil - log("+Plugin object \(plugin) is unbound from \(namespace)") + log("+Plugin object \(plugin?.description ?? "unknown") is unbound from \(namespace)") } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { From 554d0d749e3f92a12e2e672b54406aa088541ee1 Mon Sep 17 00:00:00 2001 From: David Kim Date: Mon, 24 Apr 2017 02:13:41 +0800 Subject: [PATCH 42/44] Bump to version 0.12.1 --- README.md | 1 + XWebView.podspec | 2 +- XWebView/Info.iOS.plist | 2 +- XWebView/Info.macOS.plist | 2 +- XWebViewTests/Info.plist | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 09719ff..063af3a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ For more documents, please go to the project [Wiki](../../wiki). | Swift | XWebView | | ----- | ---------- | +| 3.1 | 0.12.1 | | 3.0.2 | 0.12.0 | | 3 | 0.11.0 | | 2.3 | 0.10.0 | diff --git a/XWebView.podspec b/XWebView.podspec index 7854f52..ed440a8 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.12.0" + s.version = "0.12.1" s.summary = "An extensible WebView (based on WKWebView)" s.description = <<-DESC diff --git a/XWebView/Info.iOS.plist b/XWebView/Info.iOS.plist index db84c71..f7f7ce7 100644 --- a/XWebView/Info.iOS.plist +++ b/XWebView/Info.iOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.0 + 0.12.1 CFBundleSignature ???? CFBundleVersion diff --git a/XWebView/Info.macOS.plist b/XWebView/Info.macOS.plist index df8aa5c..d1d9139 100644 --- a/XWebView/Info.macOS.plist +++ b/XWebView/Info.macOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.0 + 0.12.1 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index 31b423a..6330ada 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.12.0 + 0.12.1 CFBundleSignature ???? CFBundleVersion From f73de317559f429441c1709470cc78526b643e22 Mon Sep 17 00:00:00 2001 From: David Kim Date: Wed, 25 Oct 2017 01:07:08 +0800 Subject: [PATCH 43/44] Migrate to Swift 3.2 (the halfway position of 4.0) (#91) --- .travis.yml | 2 +- XWebView.iOS.xcodeproj/project.pbxproj | 14 +++++++++++++- .../xcshareddata/xcschemes/XWebView.xcscheme | 4 +++- XWebView.macOS.xcodeproj/project.pbxproj | 14 +++++++++++++- .../xcshareddata/xcschemes/XWebView.xcscheme | 4 +++- XWebView/XWVBindingObject.swift | 2 +- XWebView/XWVHttpConnection.swift | 3 ++- XWebView/XWVMetaObject.swift | 15 ++++++++------- XWebView/XWVObject.swift | 6 +++--- XWebView/XWVScriptObject.swift | 4 ++-- XWebView/XWebView.swift | 11 +++++++++++ XWebViewTests/ObjectPlugin.swift | 6 ++---- 12 files changed, 62 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 37f2dfa..cd4a096 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8.2 +osx_image: xcode9 #xcode_sdk: iphonesimulator9.0 #xcode_project: XWebView.iOS.xcodeproj #xcode_scheme: XWebView diff --git a/XWebView.iOS.xcodeproj/project.pbxproj b/XWebView.iOS.xcodeproj/project.pbxproj index 34d38d5..57e01b9 100644 --- a/XWebView.iOS.xcodeproj/project.pbxproj +++ b/XWebView.iOS.xcodeproj/project.pbxproj @@ -224,7 +224,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = XWebView; TargetAttributes = { EE9BCB871E12EFE600206DC3 = { @@ -332,7 +332,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -340,7 +342,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -385,7 +391,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -393,7 +401,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme b/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index e1c0ce7..b7bc3f1 100644 --- a/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme +++ b/XWebView.iOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme @@ -1,6 +1,6 @@ ) -> Int in - input.read(base.advanced(by: cursor), maxLength: inputBuffer.count - cursor) + input.read(base.advanced(by: cursor), maxLength: count - cursor) } guard bytesReaded > 0 else { break } cursor += bytesReaded diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index fe0e7d3..f2b1375 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -218,20 +218,21 @@ class XWVMetaObject { } extension XWVMetaObject: Collection { - // IndexableBase + typealias Element = (key: String, value: Member) typealias Index = DictionaryIndex typealias SubSequence = Slice> + var startIndex: Index { return members.startIndex } var endIndex: Index { return members.endIndex } - subscript (_ i: Index) -> (String, Member) { - return members[i] + subscript (position: Index) -> Element { + return members[position] } - subscript (_ range: Range) -> SubSequence { - return members[range] + subscript (bounds: Range) -> SubSequence { + return members[bounds] } func index(after i: Index) -> Index { return members.index(after: i) @@ -243,8 +244,8 @@ private func instanceMethods(forProtocol aProtocol: Protocol) -> Set { for (req, inst) in [(true, true), (false, true)] { let methodList = protocol_copyMethodDescriptionList(aProtocol.self, req, inst, nil) if var desc = methodList { - while desc.pointee.name != nil { - selectors.insert(desc.pointee.name) + while let sel = desc.pointee.name { + selectors.insert(sel) desc = desc.successor() } free(methodList) diff --git a/XWebView/XWVObject.swift b/XWebView/XWVObject.swift index c357ff2..b3c95c6 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -63,7 +63,7 @@ public class XWVObject : NSObject { } else { return } - webView.evaluateJavaScript(script, completionHandler: nil) + webView.asyncEvaluateJavaScript(script, completionHandler: nil) } // Evaluate JavaScript expression @@ -81,10 +81,10 @@ public class XWVObject : NSObject { return } guard let completionHandler = completionHandler else { - webView.evaluateJavaScript(expression, completionHandler: nil) + webView.asyncEvaluateJavaScript(expression, completionHandler: nil) return } - webView.evaluateJavaScript(scriptForRetaining(expression)) { + webView.asyncEvaluateJavaScript(scriptForRetaining(expression)) { [weak self](result: Any?, error: Error?)->Void in if let error = error { completionHandler(nil, error) diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index b0867bd..f55f4ee 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -71,7 +71,7 @@ public class XWVScriptObject : XWVObject { public func setValue(_ value: Any?, for name:String) { guard let json = jsonify(value) else { return } let script = expression(forProperty: name) + " = " + json - webView?.evaluateJavaScript(script, completionHandler: nil) + webView?.asyncEvaluateJavaScript(script, completionHandler: nil) } public func value(at index: UInt) throws -> Any { return try evaluateExpression("\(namespace)[\(index)]") @@ -79,7 +79,7 @@ public class XWVScriptObject : XWVObject { public func setValue(_ value: Any?, at index: UInt) { guard let json = jsonify(value) else { return } let script = "\(namespace)[\(index)] = \(json)" - webView?.evaluateJavaScript(script, completionHandler: nil) + webView?.asyncEvaluateJavaScript(script, completionHandler: nil) } // expression generation diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index 0bcf9ba..e5b2538 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -55,6 +55,17 @@ extension WKWebView { return XWebView.undefined } + open func asyncEvaluateJavaScript(_ script: String, completionHandler handler: ((Any?, Error?) -> Swift.Void)? = nil) { + if Thread.isMainThread { + evaluateJavaScript(script, completionHandler: handler) + } else { + DispatchQueue.main.async() { + [weak self] in + self?.evaluateJavaScript(script, completionHandler: handler) + } + } + } + // Synchronized evaluateJavaScript open func syncEvaluateJavaScript(_ script: String) throws -> Any { var result: Any? diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index bed5322..fcf6e0a 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -69,8 +69,7 @@ class ObjectPlugin : XWVTestCase { loadPlugin(object, namespace: namespace, script: "\(namespace).property = 321") { $0.evaluateJavaScript("\(self.namespace).property") { (obj: Any?, err: Error?)->Void in - //if (obj as? NSNumber)?.intValue == 321 && object.property == 321 { - if obj as? Bool == true && object.property == 321 { + if (obj as? NSNumber)?.intValue == 321 && object.property == 321 { exp.fulfill() } } @@ -84,8 +83,7 @@ class ObjectPlugin : XWVTestCase { object.property = 321 $0.evaluateJavaScript("\(self.namespace).property") { (obj: Any?, err: Error?)->Void in - //if (obj as? NSNumber)?.intValue == 321 { - if obj as? Bool == true { + if (obj as? NSNumber)?.intValue == 321 { exp.fulfill() } } From 821312e8907e0f603f505fbe4e59da526f894510 Mon Sep 17 00:00:00 2001 From: David Kim Date: Thu, 26 Oct 2017 15:36:58 +0800 Subject: [PATCH 44/44] Migrate to Swift 4.0 --- XWebView.iOS.xcodeproj/project.pbxproj | 8 ++-- XWebView.macOS.xcodeproj/project.pbxproj | 8 ++-- XWebView/XWVBindingObject.swift | 4 +- XWebView/XWVHttpConnection.swift | 32 +++++++------- XWebView/XWVHttpServer.swift | 12 ++--- XWebView/XWVInvocation.swift | 34 +++++++------- XWebView/XWVJson.swift | 6 +-- XWebView/XWVLogging.swift | 4 +- XWebView/XWVMetaObject.swift | 51 +++++++++++---------- XWebViewTests/ConstructorPlugin.swift | 8 ++-- XWebViewTests/FunctionPlugin.swift | 4 +- XWebViewTests/ObjectPlugin.swift | 14 +++--- XWebViewTests/XWVInvocationTest.swift | 56 ++++++++++++------------ XWebViewTests/XWVScriptingTest.swift | 2 +- XWebViewTests/XWVTestCase.swift | 2 +- 15 files changed, 121 insertions(+), 124 deletions(-) diff --git a/XWebView.iOS.xcodeproj/project.pbxproj b/XWebView.iOS.xcodeproj/project.pbxproj index 57e01b9..011cceb 100644 --- a/XWebView.iOS.xcodeproj/project.pbxproj +++ b/XWebView.iOS.xcodeproj/project.pbxproj @@ -449,7 +449,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -468,7 +468,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -479,7 +479,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -490,7 +490,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/XWebView.macOS.xcodeproj/project.pbxproj b/XWebView.macOS.xcodeproj/project.pbxproj index aae2e22..f13d3a1 100644 --- a/XWebView.macOS.xcodeproj/project.pbxproj +++ b/XWebView.macOS.xcodeproj/project.pbxproj @@ -448,7 +448,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -469,7 +469,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -482,7 +482,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -495,7 +495,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.xwebview.XWebViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index 1c06097..4fd3c1c 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -40,7 +40,7 @@ final class XWVBindingObject : XWVScriptObject { var arguments = arguments?.map(wrapScriptObject) ?? [] var promise: XWVScriptObject? - if arity == Int32(arguments.count) - 1 || arity < 0 { + if arity == arguments.count - 1 || arity < 0 { promise = arguments.last as? XWVScriptObject arguments.removeLast() } @@ -168,7 +168,7 @@ extension XWVBindingObject { guard ptr != nil else { return nil } return unsafeBitCast(ptr, to: XWVBindingObject.self) } - fileprivate func performSelector(_ selector: Selector, with arguments: [Any]?, waitUntilDone wait: Bool = true) -> Any? { + private func performSelector(_ selector: Selector, with arguments: [Any]?, waitUntilDone wait: Bool = true) -> Any? { var result: Any? = undefined let trampoline : () -> Void = { [weak self] in diff --git a/XWebView/XWVHttpConnection.swift b/XWebView/XWVHttpConnection.swift index bf007f9..20f7330 100644 --- a/XWebView/XWVHttpConnection.swift +++ b/XWebView/XWVHttpConnection.swift @@ -24,21 +24,21 @@ protocol XWVHttpConnectionDelegate { final class XWVHttpConnection : NSObject { private let handle: CFSocketNativeHandle - fileprivate let delegate: XWVHttpConnectionDelegate - fileprivate var input: InputStream! - fileprivate var output: OutputStream! - fileprivate let bufferMaxSize = 64 * 1024 + private let delegate: XWVHttpConnectionDelegate + private var input: InputStream! + private var output: OutputStream! + private let bufferMaxSize = 64 * 1024 // input state - fileprivate var requestQueue = [URLRequest?]() - fileprivate var inputBuffer: Data! - fileprivate var cursor: Int = 0 + private var requestQueue = [URLRequest?]() + private var inputBuffer: Data! + private var cursor: Int = 0 // output state - fileprivate var outputBuffer: Data! - fileprivate var bytesRemained: Int = 0 - fileprivate var fileHandle: FileHandle! - fileprivate var fileSize: Int = 0 + private var outputBuffer: Data! + private var bytesRemained: Int = 0 + private var fileHandle: FileHandle! + private var fileSize: Int = 0 init(handle: CFSocketNativeHandle, delegate: XWVHttpConnectionDelegate) { self.handle = handle @@ -195,7 +195,7 @@ private extension String { repeat { end = index(before: end) } while predicate(self[end]) - self = self[start ... end] + self = String(self[start ... end]) } else { self = "" } @@ -227,7 +227,7 @@ private extension URLRequest { // parse request line if let line = String(data: data.subdata(in: 0..() - fileprivate let overlays: [URL] + private var connections = Set() + private let overlays: [URL] private(set) var port: in_port_t = 0 var rootURL: URL { @@ -134,17 +134,17 @@ class XWVHttpServer : NSObject { close() } - func suspend(_: NSNotification!) { + @objc func suspend(_: NSNotification!) { close() log("+HTTP server is suspended") } - func resume(_: NSNotification!) { + @objc func resume(_: NSNotification!) { if listen(on: port) { log("+HTTP server is resumed") } } - func serverLoop(_: AnyObject) { + @objc func serverLoop(_: AnyObject) { let runLoop = CFRunLoopGetCurrent() let source = CFSocketCreateRunLoopSource(nil, socket, 0) CFRunLoopAddSource(runLoop, source, CFRunLoopMode.commonModes) @@ -176,7 +176,7 @@ extension XWVHttpServer : XWVHttpConnectionDelegate { log("?Bad request") } else if let request = request, request.httpMethod == "GET" || request.httpMethod == "HEAD" { let fileManager = FileManager.default - let relativePath = String(request.url!.path.characters.dropFirst()) + let relativePath = String(request.url!.path.dropFirst()) for baseURL in overlays { var isDirectory: ObjCBool = false var url = URL(string: relativePath, relativeTo: baseURL)! diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift index 040130d..ba0eb64 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -18,7 +18,7 @@ import Foundation import ObjectiveC @objc protocol NSMethodSignatureProtocol { - static func signature(objCTypes: UnsafePointer) -> NSMethodSignatureProtocol + static func signature(objCTypes: UnsafePointer!) -> NSMethodSignatureProtocol? func getArgumentType(atIndex idx: UInt) -> UnsafePointer var numberOfArguments: UInt { get } var frameLength: UInt { get } @@ -29,7 +29,7 @@ import ObjectiveC @objc protocol NSInvocationProtocol { static func invocation(methodSignature: AnyObject) -> NSInvocationProtocol var selector: Selector { get set } - var target: AnyObject { get set } + var target: AnyObject? { get set } func setArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) func getArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) var argumentsRetained: ObjCBool { get } @@ -51,43 +51,41 @@ var NSInvocation: NSInvocationProtocol.Type = { }() @discardableResult public func invoke(_ selector: Selector, of target: AnyObject, with arguments: [Any?] = [], on thread: Thread? = nil, waitUntilDone wait: Bool = true) -> Any! { - let method = class_getInstanceMethod(type(of: target), selector) - guard method != nil else { + guard let method = class_getInstanceMethod(type(of: target), selector), + let sig = NSMethodSignature.signature(objCTypes: method_getTypeEncoding(method)) else { target.doesNotRecognizeSelector?(selector) fatalError("Unrecognized selector -[\(target) \(selector)]") } - - let sig = NSMethodSignature.signature(objCTypes: method_getTypeEncoding(method)) let inv = NSInvocation.invocation(methodSignature: sig) // Setup arguments - precondition(arguments.count + 2 <= Int(method_getNumberOfArguments(method)), + precondition(arguments.count + 2 <= method_getNumberOfArguments(method), "Too many arguments for calling -[\(type(of: target)) \(selector)]") var args = [[Int]](repeating: [], count: arguments.count) for i in 0 ..< arguments.count { if let arg: Any = arguments[i] { let code = sig.getArgumentType(atIndex: UInt(i) + 2) - let type = ObjCType(code: code) - if type == .object { + let octype = ObjCType(code: code) + if octype == .object { let obj: AnyObject = _bridgeAnythingToObjectiveC(arg) _autorelease(obj) args[i] = _encodeBitsAsWords(obj) - } else if type == .clazz, let cls = arg as? AnyClass { + } else if octype == .clazz, let cls = arg as? AnyClass { args[i] = _encodeBitsAsWords(cls) - } else if type == .float, let float = arg as? Float { + } else if octype == .float, let float = arg as? Float { // prevent to promot float type to double args[i] = _encodeBitsAsWords(float) } else if var val = arg as? CVarArg { if (type(of: arg) as? AnyClass)?.isSubclass(of: NSNumber.self) == true { // argument is an NSNumber object - if let v = (arg as! NSNumber).value(as: type) { + if let v = (arg as! NSNumber).value(as: octype) { val = v } } args[i] = val._cVarArgEncoding } else { - let type = String(cString: code) - fatalError("Unable to convert argument \(i) from Swift type \(type(of: arg)) to ObjC type '\(type)'") + let octype = String(cString: code) + fatalError("Unable to convert argument \(i) from Swift type \(type(of: arg)) to ObjC type '\(octype)'") } } else { // nil @@ -118,16 +116,16 @@ var NSInvocation: NSInvocationProtocol.Type = { // Fetch the return value let buffer = UnsafeMutablePointer.allocate(capacity: Int(sig.methodReturnLength)) inv.getReturnValue(buffer) - let type = ObjCType(code: sig.methodReturnType) + let octype = ObjCType(code: sig.methodReturnType) defer { - if type == .object && selector.returnsRetained { + if octype == .object && selector.returnsRetained { // To balance the retained return value let obj = UnsafeRawPointer(buffer).load(as: AnyObject.self) Unmanaged.passUnretained(obj).release() } buffer.deallocate(capacity: Int(sig.methodReturnLength)) } - return type.loadValue(from: buffer) + return octype.loadValue(from: buffer) } public func createInstance(of class: AnyClass, by initializer: Selector = #selector(NSObject.init), with arguments: [Any?] = []) -> AnyObject? { @@ -352,7 +350,7 @@ extension XWVInvocation { if attr == nil { attr = property_copyAttributeValue(property, "S") if attr == nil { - setter = Selector("set\(String(name[name.startIndex]).uppercased())\(String(name.characters.dropFirst())):") + setter = Selector("set\(name.prefix(1).uppercased())\(name.dropFirst()):") } else { // The property defines a custom setter selector name. setter = Selector(String(cString: attr!)) diff --git a/XWebView/XWVJson.swift b/XWebView/XWVJson.swift index a54eac6..d9ff33d 100644 --- a/XWebView/XWVJson.swift +++ b/XWebView/XWVJson.swift @@ -18,7 +18,7 @@ import Foundation // JSON Array public func jsonify(_ array: T) -> String? - where T.Index: Integer { + where T.Index: BinaryInteger { // TODO: filter out values with negative index return "[" + array.map{jsonify($0) ?? ""}.joined(separator: ",") + "]" } @@ -34,7 +34,7 @@ private func jsonify(_ pair: (key: String, value: T)) -> String? { } // JSON Number -public func jsonify(_ integer: T) -> String? { +public func jsonify(_ integer: T) -> String? { return String(describing: integer) } public func jsonify(_ float: T) -> String? { @@ -47,7 +47,7 @@ public func jsonify(_ bool: Bool) -> String? { } // JSON String -public func jsonify(_ string: String) -> String? { +public func jsonify(_ string: T) -> String? { return string.unicodeScalars.reduce("\"") { $0 + $1.jsonEscaped } + "\"" } private extension UnicodeScalar { diff --git a/XWebView/XWVLogging.swift b/XWebView/XWVLogging.swift index 1efbb94..f441fde 100644 --- a/XWebView/XWVLogging.swift +++ b/XWebView/XWVLogging.swift @@ -102,8 +102,8 @@ public class XWVLogging : XWVScripting { public func log(_ message: String, level: Level? = nil) { var msg = message var lvl = level ?? .Debug - if level == nil, let ch = msg.characters.first, let l = Level(symbol: ch) { - msg = String(msg.characters.dropFirst()) + if level == nil, let ch = msg.first, let l = Level(symbol: ch) { + msg = String(msg.dropFirst()) lvl = l } log(msg, level: lvl) diff --git a/XWebView/XWVMetaObject.swift b/XWebView/XWVMetaObject.swift index f2b1375..0027b39 100644 --- a/XWebView/XWVMetaObject.swift +++ b/XWebView/XWVMetaObject.swift @@ -80,7 +80,7 @@ class XWVMetaObject { } let plugin: AnyClass - fileprivate var members = [String: Member]() + private var members = [String: Member]() private static let exclusion: Set = { var methods = instanceMethods(forProtocol: XWVScripting.self) methods.remove(#selector(XWVScripting.invokeDefaultMethod(withArguments:))) @@ -107,7 +107,7 @@ class XWVMetaObject { } else { name = cls.scriptName?(for: selector) ?? name } - } else if name.characters.first == "_" { + } else if name.first == "_" { return true } @@ -119,7 +119,7 @@ class XWVMetaObject { if let scriptNameForKey = cls.scriptName(forKey:) { name = name.withCString(scriptNameForKey) ?? name } - } else if name.characters.first == "_" { + } else if name.first == "_" { return true } @@ -142,30 +142,34 @@ class XWVMetaObject { private func enumerate(excluding selectors: Set, callback: (String, Member)->Bool) -> Bool { var known = selectors + var count: UInt32 = 0 // enumerate properties - let propertyList = class_copyPropertyList(plugin, nil) - if var prop = propertyList { + if let propertyList = class_copyPropertyList(plugin, &count) { defer { free(propertyList) } - while prop.pointee != nil { - let name = String(cString: property_getName(prop.pointee)) + for i in 0 ..< Int(count) { + let name = String(cString: property_getName(propertyList[i])) // get getter - var attr = property_copyAttributeValue(prop.pointee, "G") - let getter = Selector(attr == nil ? name : String(cString: attr!)) - free(attr) + let getter: Selector + if let attr = property_copyAttributeValue(propertyList[i], "G") { + getter = Selector(String(cString: attr)) + free(attr) + } else { + getter = Selector(name) + } if known.contains(getter) { - prop = prop.successor() continue } known.insert(getter) // get setter if readwrite var setter: Selector? = nil - attr = property_copyAttributeValue(prop.pointee, "R") + var attr = property_copyAttributeValue(propertyList[i], "R") if attr == nil { - attr = property_copyAttributeValue(prop.pointee, "S") + attr = property_copyAttributeValue(propertyList[i], "S") if attr == nil { - setter = Selector("set\(String(name[name.startIndex]).uppercased())\(String(name.characters.dropFirst())):") + setter = Selector("set\(name.prefix(1).uppercased())\(name.dropFirst()):") + print(setter!.description) } else { setter = Selector(String(cString: attr!)) } @@ -181,32 +185,27 @@ class XWVMetaObject { if !callback(name, info) { return false } - prop = prop.successor() } } // enumerate methods - let methodList = class_copyMethodList(plugin, nil) - if var method = methodList { + if let methodList = class_copyMethodList(plugin, &count) { defer { free(methodList) } - while method.pointee != nil { - if let sel = method_getName(method.pointee), !known.contains(sel) && !sel.description.hasPrefix(".") { - let arity = Int32(method_getNumberOfArguments(method.pointee)) - 2 + for i in 0 ..< Int(count) { + let sel = method_getName(methodList[i]) + if !known.contains(sel) && !sel.description.hasPrefix(".") { + let arity = Int32(method_getNumberOfArguments(methodList[i])) - 2 let member: Member if sel.description.hasPrefix("init") { member = Member.Initializer(selector: sel, arity: arity) } else { member = Member.Method(selector: sel, arity: arity) } - var name = sel.description - if let end = name.characters.index(of: ":") { - name = name[name.startIndex ..< end] - } - if !callback(name, member) { + let name = sel.description.prefix(while: {$0 != ":"}) + if !callback(String(name), member) { return false } } - method = method.successor() } } return true diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index 244dd68..b448179 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -20,7 +20,7 @@ import XWebView class ConstructorPlugin : XWVTestCase { class Plugin0 : NSObject, XWVScripting { - init(expectation: Any?) { + @objc init(expectation: Any?) { if let e = expectation as? XWVScriptObject { e.callMethod("fulfill", with: nil, completionHandler: nil) } @@ -30,8 +30,8 @@ class ConstructorPlugin : XWVTestCase { } } class Plugin1 : NSObject, XWVScripting { - dynamic var property: Int - init(value: Int) { + @objc dynamic var property: Int + @objc init(value: Int) { property = value } class func scriptName(for selector: Selector) -> String? { @@ -40,7 +40,7 @@ class ConstructorPlugin : XWVTestCase { } class Plugin2 : NSObject, XWVScripting { private let expectation: XWVScriptObject? - init(expectation: Any?) { + @objc init(expectation: Any?) { self.expectation = expectation as? XWVScriptObject } func finalizeForScript() { diff --git a/XWebViewTests/FunctionPlugin.swift b/XWebViewTests/FunctionPlugin.swift index a056a1a..835fc70 100644 --- a/XWebViewTests/FunctionPlugin.swift +++ b/XWebViewTests/FunctionPlugin.swift @@ -20,12 +20,12 @@ import XWebView class FunctionPlugin : XWVTestCase { class Plugin : NSObject, XWVScripting { - dynamic var property = 123 + @objc dynamic var property = 123 private var expectation: XCTestExpectation? init(expectation: XCTestExpectation?) { self.expectation = expectation } - func defaultMethod() { + @objc func defaultMethod() { expectation?.fulfill() } class func scriptName(for selector: Selector) -> String? { diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index fcf6e0a..32a59fa 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -20,28 +20,28 @@ import XWebView class ObjectPlugin : XWVTestCase { class Plugin : NSObject { - dynamic var property = 123 + @objc dynamic var property = 123 private var expectation: XCTestExpectation?; - func method() { + @objc func method() { expectation?.fulfill() } - func method(argument: Any?) { + @objc func method(argument: Any?) { if argument as? String == "Yes" { expectation?.fulfill() } } - func method(Integer: Int) { + @objc func method(Integer: Int) { if Integer == 789 { expectation?.fulfill() } } - func method(callback: XWVScriptObject) { + @objc func method(callback: XWVScriptObject) { callback.call(arguments: nil, completionHandler: nil) } - func method(promiseObject: XWVScriptObject) { + @objc func method(promiseObject: XWVScriptObject) { promiseObject.callMethod("resolve", with: nil, completionHandler: nil) } - func method1() { + @objc func method1() { guard let bindingObject = XWVScriptObject.bindingObject else { return } property = 456 //if (bindingObject["property"] as? NSNumber)?.intValue == 456 { diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index 551def1..f64a88d 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -20,7 +20,7 @@ import XWebView class InvocationTarget: NSObject { class LeakTest: NSObject { let expectation: XCTestExpectation - init(expectation: XCTestExpectation) { + @objc init(expectation: XCTestExpectation) { self.expectation = expectation } deinit { @@ -28,33 +28,33 @@ class InvocationTarget: NSObject { } } - var integer: Int = 123 - - func dummy() {} - func nullable(_ v: Any?) -> Any? { return v } - func echo(bool b: Bool) -> Bool { return b } - func echo(int i: Int) -> Int { return i } - func echo(int8 i8: Int8) -> Int8 { return i8 } - func echo(int16 i16: Int16) -> Int16 { return i16 } - func echo(int32 i32: Int32) -> Int32 { return i32 } - func echo(int64 i64: Int64) -> Int64 { return i64 } - func echo(uint u: UInt) -> UInt { return u } - func echo(uint8 u8: UInt8) -> UInt8 { return u8 } - func echo(uint16 u16: UInt16) -> UInt16 { return u16 } - func echo(uint32 u32: UInt32) -> UInt32 { return u32 } - func echo(uint64 u64: UInt64) -> UInt64 { return u64 } - func echo(float f: Float) -> Float { return f } - func echo(double d: Double) -> Double { return d } - func echo(unicode u: UnicodeScalar) -> UnicodeScalar { return u } - func echo(string s: String) -> String { return s } - func echo(selector s: Selector) -> Selector { return s } - func echo(`class` c: AnyClass) -> AnyClass { return c } - - func add(_ a: Int, _ b: Int) -> Int { return a + b } - func concat(_ a: String, _ b: String) -> String { return a + b } - func convert(_ num: NSNumber) -> Int { return num.intValue } - - func _new(_ expectation: XCTestExpectation) -> LeakTest { + @objc dynamic var integer: Int = 123 + + @objc func dummy() {} + @objc func nullable(_ v: Any?) -> Any? { return v } + @objc func echo(bool b: Bool) -> Bool { return b } + @objc func echo(int i: Int) -> Int { return i } + @objc func echo(int8 i8: Int8) -> Int8 { return i8 } + @objc func echo(int16 i16: Int16) -> Int16 { return i16 } + @objc func echo(int32 i32: Int32) -> Int32 { return i32 } + @objc func echo(int64 i64: Int64) -> Int64 { return i64 } + @objc func echo(uint u: UInt) -> UInt { return u } + @objc func echo(uint8 u8: UInt8) -> UInt8 { return u8 } + @objc func echo(uint16 u16: UInt16) -> UInt16 { return u16 } + @objc func echo(uint32 u32: UInt32) -> UInt32 { return u32 } + @objc func echo(uint64 u64: UInt64) -> UInt64 { return u64 } + @objc func echo(float f: Float) -> Float { return f } + @objc func echo(double d: Double) -> Double { return d } + @objc func echo(unicode u: UnicodeScalar) -> UnicodeScalar { return u } + @objc func echo(string s: String) -> String { return s } + @objc func echo(selector s: Selector) -> Selector { return s } + @objc func echo(`class` c: AnyClass) -> AnyClass { return c } + + @objc func add(_ a: Int, _ b: Int) -> Int { return a + b } + @objc func concat(_ a: String, _ b: String) -> String { return a + b } + @objc func convert(_ num: NSNumber) -> Int { return num.intValue } + + @objc func _new(_ expectation: XCTestExpectation) -> LeakTest { return LeakTest(expectation: expectation) } } diff --git a/XWebViewTests/XWVScriptingTest.swift b/XWebViewTests/XWVScriptingTest.swift index a135477..b54f806 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -21,7 +21,7 @@ import XWebView class XWVScriptingTest : XWVTestCase { class Plugin : NSObject, XWVScripting { let expectation: XCTestExpectation? - init(expectation: XCTestExpectation?) { + @objc init(expectation: XCTestExpectation?) { self.expectation = expectation } func rewriteStub(_ stub: String, forKey key: String) -> String { diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index 6a6cb92..b451372 100644 --- a/XWebViewTests/XWVTestCase.swift +++ b/XWebViewTests/XWVTestCase.swift @@ -56,7 +56,7 @@ class XWVTestCase : XCTestCase, WKNavigationDelegate { webview.loadPlugin(e, namespace: "\(namespaceForExpectation).\(description)") return e } - override func waitForExpectations(timeout: TimeInterval = 9, handler: XCWaitCompletionHandler? = nil) { + override func waitForExpectations(timeout: TimeInterval = 15, handler: XCWaitCompletionHandler? = nil) { super.waitForExpectations(timeout: timeout, handler: handler) }