diff --git a/.travis.yml b/.travis.yml index 2f7e16a..cd4a096 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -osx_image: xcode7 +osx_image: xcode9 #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=8.1 - - OS=9.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/README.md b/README.md index 33b2629..063af3a 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. @@ -26,8 +30,19 @@ For more documents, please go to the project [Wiki](../../wiki). ## Minimum Requirements: -* Development: Xcode 7 -* Deployment: iOS 8.0 +* Development: Xcode 8.2 +* Deployment: iOS 9.0 + +## XWebView vs. Swift + +| Swift | XWebView | +| ----- | ---------- | +| 3.1 | 0.12.1 | +| 3.0.2 | 0.12.0 | +| 3 | 0.11.0 | +| 2.3 | 0.10.0 | +| 2.2 | 0.10.0 | + ## License diff --git a/XWebView.iOS.xcodeproj/project.pbxproj b/XWebView.iOS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..011cceb --- /dev/null +++ b/XWebView.iOS.xcodeproj/project.pbxproj @@ -0,0 +1,530 @@ +// !$*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 = 0900; + 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_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; + CLANG_WARN_EMPTY_BODY = YES; + 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; + "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_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; + CLANG_WARN_EMPTY_BODY = YES; + 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; + "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 = 4.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 = 4.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 = 4.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 = 4.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 59b5064..b7bc3f1 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"> @@ -40,26 +26,27 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> @@ -69,6 +56,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -78,10 +66,10 @@ + ReferencedContainer = "container:XWebView.iOS.xcodeproj"> @@ -96,10 +84,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..f13d3a1 --- /dev/null +++ b/XWebView.macOS.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*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 = 0900; + 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_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; + CLANG_WARN_EMPTY_BODY = YES; + 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; + 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_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; + CLANG_WARN_EMPTY_BODY = YES; + 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; + 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 = 4.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 = 4.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 = 4.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 = 4.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 68% rename from XWebView.xcodeproj/xcshareddata/xcschemes/XWebViewTests.xcscheme rename to XWebView.macOS.xcodeproj/xcshareddata/xcschemes/XWebView.xcscheme index 33e4279..7d86944 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"> @@ -26,26 +26,27 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> @@ -55,6 +56,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -64,10 +66,10 @@ + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> @@ -82,10 +84,10 @@ + BlueprintIdentifier = "EE9BCB871E12EFE600206DC3" + BuildableName = "XWebView.framework" + BlueprintName = "XWebView" + ReferencedContainer = "container:XWebView.macOS.xcodeproj"> diff --git a/XWebView.podspec b/XWebView.podspec index 80ab167..ed440a8 100644 --- a/XWebView.podspec +++ b/XWebView.podspec @@ -16,8 +16,8 @@ Pod::Spec.new do |s| # s.name = "XWebView" - s.version = "0.9.3" - s.summary = "An extensible WebView (based on WKWebView) for iOS." + s.version = "0.12.1" + 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, "8.0" # When using multiple platforms - s.ios.deployment_target = "8.0" - # s.osx.deployment_target = "10.7" + s.ios.deployment_target = "9.0" + s.osx.deployment_target = "10.11" # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # @@ -89,8 +88,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" + s.source_files = "XWebView/*.swift", "XWebView/XWebView.h" + # s.exclude_files = "Classes/Exclude" # s.public_header_files = "Classes/**/*.h" @@ -116,6 +115,7 @@ Pod::Spec.new do |s| # s.framework = "WebKit" + s.ios.framework = "MobileCoreServices" # s.frameworks = "SomeFramework", "AnotherFramework" # s.library = "iconv" @@ -133,13 +133,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*.{h,m}" - sp.private_header_files = "XWebView/XWVHttpConnection.h" - sp.framework = "MobileCoreServices" - end end diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj deleted file mode 100644 index 7cd6e87..0000000 --- a/XWebView.xcodeproj/project.pbxproj +++ /dev/null @@ -1,528 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 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 */; }; - 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 */; }; - 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 */; }; - 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 */ - 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 = ""; }; - 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 = ""; }; - 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 = ""; }; - 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 = ""; }; - 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 */ - 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 */ - AB023EA11A8C506600580A2A /* XWebViewTests */ = { - isa = PBXGroup; - children = ( - AB2273E41AA6FDA700F9207A /* www */, - 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 */, - EE62683619FA323900EFC3F8 /* Products */, - ); - sourceTree = ""; - }; - EE62683619FA323900EFC3F8 /* Products */ = { - isa = PBXGroup; - children = ( - EE62683519FA323900EFC3F8 /* XWebView.framework */, - AB023EA01A8C506600580A2A /* XWebViewTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - EE62683719FA323900EFC3F8 /* XWebView */ = { - isa = PBXGroup; - children = ( - 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 */, - 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 */ - EE62683219FA323900EFC3F8 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */, - EE7164911A716C9F00078FF9 /* XWVHttpServer.h in Headers */, - EE71648F1A716C9F00078FF9 /* XWVHttpConnection.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 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 = 0700; - LastUpgradeCheck = 0700; - ORGANIZATIONNAME = XWebView; - TargetAttributes = { - AB023E9F1A8C506600580A2A = { - CreatedOnToolsVersion = 6.1; - }; - EE62683419FA323900EFC3F8 = { - CreatedOnToolsVersion = 6.1; - }; - }; - }; - 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 */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 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 */ - AB023E9C1A8C506600580A2A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */, - AB023EA51A8C506600580A2A /* XWebViewTests.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 */, - 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 */, - 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; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - AB023EA81A8C506600580A2A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = EE62683419FA323900EFC3F8 /* XWebView */; - targetProxy = AB023EA71A8C506600580A2A /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 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"; - }; - 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)"; - }; - 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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - 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_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 = 8.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_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - 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_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 = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - EE62684C19FA323900EFC3F8 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - 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"; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - EE62684D19FA323900EFC3F8 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - 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"; - PRODUCT_BUNDLE_IDENTIFIER = "org.xwebview.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 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 96% rename from XWebView/Info.plist rename to XWebView/Info.iOS.plist index 600c65b..f7f7ce7 100644 --- a/XWebView/Info.plist +++ b/XWebView/Info.iOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.3 + 0.12.1 CFBundleSignature ???? CFBundleVersion diff --git a/XWebView/Info.macOS.plist b/XWebView/Info.macOS.plist new file mode 100644 index 0000000..d1d9139 --- /dev/null +++ b/XWebView/Info.macOS.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 0.12.1 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 XWebView. All rights reserved. + NSPrincipalClass + + + diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index c8fb8dc..4fd3c1c 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -17,146 +17,198 @@ import Foundation import ObjectiveC -class XWVBindingObject : XWVScriptObject { - let key = unsafeAddressOf(XWVScriptObject) - var object: AnyObject! +final class XWVBindingObject : XWVScriptObject { + unowned let channel: XWVChannel + var plugin: AnyObject! 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() + self.channel = channel + self.plugin = object + super.init(namespace: namespace, webView: channel.webView!) + bind() } - init(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { - super.init(namespace: namespace, channel: channel, origin: nil) + init?(namespace: String, channel: XWVChannel, arguments: [Any]?) { + 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 { - preconditionFailure("FATAL: Plugin is not a constructor") + log("!Plugin class \(cls) is not a constructor") + 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 == arguments.count - 1 || arity < 0 { + promise = arguments.last as? XWVScriptObject + arguments.removeLast() } - if selector == "initByScriptWithArguments:" { - args = [args] + if selector == Selector(("initByScriptWithArguments:")) { + 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() - syncProperties() - promise?.callMethod("resolve", withArguments: [self], resultHandler: 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) - script += "\(namespace).$properties['\(name)'] = \(serialize(val));\n" + + plugin = invoke(#selector(NSProxy.alloc), of: cls) as AnyObject + if plugin != nil { + plugin = performSelector(selector, with: arguments) as AnyObject! } - webView?.evaluateJavaScript(script, completionHandler: nil) + guard plugin != nil else { + log("!Failed to create instance for plugin class \(cls)") + return nil + } + + bind() + syncProperties() + promise?.callMethod("resolve", with: [self], completionHandler: nil) } deinit { - if (object as? XWVScripting)?.finalizeForScript != nil { - XWVInvocation(target: object)[Selector("finalizeForScript")]() + (plugin as? XWVScripting)?.finalizeForScript?() + super.callMethod("dispose", with: [true], completionHandler: nil) + unbind() + } + + private func bind() { + // Start KVO + guard let plugin = plugin as? NSObject else { return } + channel.typeInfo.filter{ $1.isProperty }.forEach { + plugin.addObserver(self, forKeyPath: String(describing: $1.getter!), options: NSKeyValueObservingOptions.new, context: nil) } - objc_setAssociatedObject(object, key, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) - stopKVO() + } + private func unbind() { + // Stop KVO + guard plugin is NSObject else { return } + channel.typeInfo.filter{ $1.isProperty }.forEach { + plugin.removeObserver(self, forKeyPath: String(describing: $1.getter!), context: nil) + } + } + private func syncProperties() { + let script = channel.typeInfo.filter{ $1.isProperty }.reduce("") { + let val: Any! = performSelector($1.1.getter!, with: nil) + guard let json = jsonify(val) else { return "" } + return "\($0)\(namespace).$properties['\($1.0)'] = \(json);\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 object is XWVScripting && name.isEmpty && selector == Selector("invokeDefaultMethodWithArguments:") { - args = [args ?? []]; - } - if channel.queue != nil { - dispatch_async(channel.queue) { - XWVInvocation(target: object).call(selector, withObjects: args) - } - } else { - // FIXME: Add NSThread support back while migrate to Swift 2.0 - XWVInvocation(target: object).call(selector, withObjects: args) - } + 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.invokeDefaultMethod(withArguments:)) { + args = [args]; } + _ = performSelector(selector, with: args, waitUntilDone: false) } - 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: object).call(setter, withObjects: [val]) - } - } else { - // FIXME: Add NSThread support back while migrate to Swift 2.0 - XWVInvocation(target: self.object)[name] = val - } - } + func updateNativeProperty(name: String, with value: Any) { + guard let setter = channel.typeInfo[name]?.setter else { return } + + let val: Any = wrapScriptObject(value) + _ = performSelector(setter, with: [val], waitUntilDone: false) } // override methods of XWVScriptObject - override func callMethod(name: String, withArguments arguments: [AnyObject]?, resultHandler: ((AnyObject!) -> Void)?) { + override func callMethod(_ name: String, with arguments: [Any]?, completionHandler: Handler) { if let selector = channel.typeInfo[name]?.selector { - let result: AnyObject! = XWVInvocation(target: object).call(selector, withObjects: arguments) - resultHandler?(result) + let result: Any! = performSelector(selector, with: arguments) + completionHandler?(result, nil) } else { - super.callMethod(name, withArguments: arguments, resultHandler: resultHandler) + super.callMethod(name, with: arguments, completionHandler: completionHandler) } } - override func callMethod(name: String, withArguments arguments: [AnyObject]?) -> AnyObject! { + override func callMethod(_ name: String, with arguments: [Any]?) throws -> Any { if let selector = channel.typeInfo[name]?.selector { - return XWVInvocation(target: object).call(selector, withObjects: arguments) + return performSelector(selector, with: arguments) ?? NSNull() } - return 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) throws -> Any { if let getter = channel.typeInfo[name]?.getter { - return XWVInvocation(target: object).call(getter, withObjects: nil) + return performSelector(getter, with: nil) ?? NSNull() } - return super.value(forProperty: name) + return try super.value(for: name) } - override func setValue(value: AnyObject?, forProperty name: String) { - if channel.typeInfo[name]?.setter != nil { - XWVInvocation(target: object)[name] = value + override func setValue(_ value: Any?, for name: String) { + if let setter = channel.typeInfo[name]?.setter { + _ = performSelector(setter, with: [value ?? NSNull()]) + } else if channel.typeInfo[name] == nil { + super.setValue(value, for: name) } else { - assert(channel.typeInfo[name] == nil, "Property '\(name)' is readonly") - super.setValue(value, forProperty: name) + assertionFailure("Property '\(name)' is readonly") } } // KVO for syncing properties - override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { - guard let webView = webView, var prop = keyPath else { return } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + 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 = (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]))" - webView.evaluateJavaScript(script, completionHandler: nil) + let script = "\(namespace).$properties['\(prop)'] = \(json)" + webView.asyncEvaluateJavaScript(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) - } +} + +extension XWVBindingObject { + private static var key: pthread_key_t = { + var key = pthread_key_t() + pthread_key_create(&key, nil) + return key + }() + + fileprivate static var currentBindingObject: XWVBindingObject? { + let ptr = pthread_getspecific(XWVBindingObject.key) + guard ptr != nil else { return nil } + return unsafeBitCast(ptr, to: XWVBindingObject.self) } - 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) + private 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 } + let args: [Any?] = arguments?.map{ $0 is NSNull ? nil : ($0 as Any) } ?? [] + let save = pthread_getspecific(XWVBindingObject.key) + 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 { + queue.async(execute: trampoline) + } else if String(cString: __dispatch_queue_get_label(nil)) != queue.label { + queue.sync(execute: trampoline) + } else { + trampoline() + } + } else if let runLoop = channel.runLoop?.getCFRunLoop() { + if wait && CFRunLoopGetCurrent() === runLoop { + trampoline() + } else { + struct Unsolved {} + result = Unsolved() + CFRunLoopPerformBlock(runLoop, CFRunLoopMode.defaultMode.rawValue, trampoline) + CFRunLoopWakeUp(runLoop) + while wait && result is Unsolved { + let reason = CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 3.0, true) + if reason != CFRunLoopRunResult.handledSource { + break + } + } + } } + return result } } -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/XWebView/XWVChannel.swift b/XWebView/XWVChannel.swift index 8f4fe16..4ac1b41 100644 --- a/XWebView/XWVChannel.swift +++ b/XWebView/XWVChannel.swift @@ -18,132 +18,169 @@ import Foundation import WebKit public class XWVChannel : NSObject, WKScriptMessageHandler { - public let name: String - public let thread: NSThread! - public let queue: dispatch_queue_t! + private(set) public var identifier: String? + public let runLoop: RunLoop? + public let queue: DispatchQueue? private(set) public weak var webView: WKWebView? var typeInfo: XWVMetaObject! 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{ static var number: UInt = 0 } - return ++sequence.number + sequence.number += 1 + return sequence.number } - public convenience init(name: String?, webView: WKWebView) { - let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL) - self.init(name: name, webView:webView, queue: queue) + 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: 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(name: String?, webView: WKWebView, queue: dispatch_queue_t) { - self.name = name ?? "\(XWVChannel.sequenceNumber)" + + public init(webView: WKWebView, queue: DispatchQueue) { + assert(!queue.label.isEmpty, "Queue must be labeled") self.webView = webView self.queue = queue - thread = nil + runLoop = nil webView.prepareForPlugin() } - - public init(name: String?, webView: WKWebView, thread: NSThread) { - self.name = name ?? "\(XWVChannel.sequenceNumber)" + + public init(webView: WKWebView, runLoop: RunLoop) { self.webView = webView - self.thread = thread + self.runLoop = runLoop queue = nil webView.prepareForPlugin() } - 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 } - - webView.configuration.userContentController.addScriptMessageHandler(self, name: name) - typeInfo = XWVMetaObject(plugin: object.dynamicType) - let plugin = XWVBindingObject(namespace: namespace, channel: self, object: object) - - let stub = generateStub(plugin) - let script = WKUserScript(source: (object as? XWVScripting)?.javascriptStub?(stub) ?? stub, - injectionTime: WKUserScriptInjectionTime.AtDocumentStart, + 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.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, forMainFrameOnly: true) userScript = XWVUserScript(webView: webView, script: script) - instances[0] = plugin - return plugin as XWVScriptObject + log("+Plugin object \(object) is bound to \(namespace) with channel \(id)") + return principal as XWVScriptObject } public func unbind() { - assert(typeInfo != nil, " Error: can't unbind inexistent plugin.") - instances.removeAll(keepCapacity: false) - webView?.configuration.userContentController.removeScriptMessageHandlerForName(name) + guard let id = identifier else { return } + let namespace = principal.namespace + let plugin = principal.plugin + instances.removeAll(keepingCapacity: false) + webView?.configuration.userContentController.removeScriptMessageHandler(forName: id) + userScript = nil + identifier = nil + log("+Plugin object \(plugin?.description ?? "unknown") is unbound from \(namespace)") } - public func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { - if let body = message.body as? [String: AnyObject], let opcode = body["$opcode"] as? String { - let target = (body["$target"] as? NSNumber)?.integerValue ?? 0 + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + // A workaround for crash when postMessage(undefined) + //guard unsafeBitCast(message.body, to: OpaquePointer!.self) != nil else { return } + + 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() - print(" Plugin was disposed") - } else { + } else if let instance = instances.removeValue(forKey: 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 { + } else if let member = typeInfo[opcode], member.isProperty { // Update property - object.updateNativeProperty(opcode, withValue: body["$operand"]) - } 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 - let args = body["$operand"] as? [AnyObject] - object.invokeNativeMethod(opcode, withArguments: args) - } // else Unknown opcode + 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 namespace = "\(instances[0]!.namespace)[\(target)]" + 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 = instances[0]!.object as? WKScriptMessageHandler { + } 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 - print(" WARNING: Unknown message: \(message.body)") + log("-Unknown message: \(message.body)") } } - 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)?.rewriteStub?(stub, forKey: key) ?? stub + } - var base = "null" - var prebind = true + 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 json = jsonify(principal[key]) { + stub = "XWVPlugin.defineProperty(exports, '\(key)', \(json), \(member.setter != nil));" + } else { + return $0 + } + return $0 + rewriteStub(stub, forKey: key) + "\n" + } + + 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('\(identifier!)', '\(principal.namespace)', \(base)));\n", + forKey: ".global" + ) } } 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..20f7330 --- /dev/null +++ b/XWebView/XWVHttpConnection.swift @@ -0,0 +1,276 @@ +/* + 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: 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: InputStream! + private var output: OutputStream! + private let bufferMaxSize = 64 * 1024 + + // input state + private var requestQueue = [URLRequest?]() + private var inputBuffer: Data! + private var cursor: Int = 0 + + // output state + 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 + self.delegate = delegate + super.init() + } + + func open() -> Bool { + let ptr1 = UnsafeMutablePointer?>.allocate(capacity: 1) + let ptr2 = UnsafeMutablePointer?>.allocate(capacity: 1) + defer { + ptr1.deallocate(capacity: 1) + ptr2.deallocate(capacity: 1) + } + CFStreamCreatePairWithSocket(nil, handle, ptr1, ptr2) + guard ptr1.pointee != nil && ptr2.pointee != nil else { + return false + } + + 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.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) + output.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) + input.open() + output.open() + delegate.didOpenConnection(self) + return true + } + + func close() { + input.close() + output.close() + input = nil + output = nil + delegate.didCloseConnection(self) + } +} + +extension XWVHttpConnection : StreamDelegate { + func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + switch eventCode { + case Stream.Event.openCompleted: + // Initialize input/output state. + if aStream === input { + inputBuffer = Data(count: 512) + cursor = 0 + } else { + outputBuffer = nil + fileHandle = nil + fileSize = 0 + } + + case Stream.Event.hasBytesAvailable: + let count = inputBuffer.count + let bytesReaded = inputBuffer.withUnsafeMutableBytes { + (base: UnsafeMutablePointer) -> Int in + input.read(base.advanced(by: cursor), maxLength: count - cursor) + } + guard bytesReaded > 0 else { break } + cursor += bytesReaded + + var bytesConsumed = 0 + while let eoh = inputBuffer.range(of: Data([13, 10, 13, 10]), in: bytesConsumed.. 0 { + // Move remained bytes to the begining. + inputBuffer.replaceSubrange(0.. fileSize { + // Send response header + off = outputBuffer.count - (bytesRemained - fileSize) + } else { + // Send file content + off = (fileSize - bytesRemained) % bufferMaxSize + if off == 0 { + fileHandle.seek(toFileOffset: UInt64(fileSize - bytesRemained)) + outputBuffer = fileHandle.readData(ofLength: bufferMaxSize) + } + } + 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 { + // Response has been sent completely. + fileHandle = nil + fileSize = 0 + outputBuffer = nil + } + if bytesSent < 0 { fallthrough } + + case Stream.Event.errorOccurred: + let error = aStream.streamError?.localizedDescription ?? "Unknown" + log("!HTTP connection error: \(error)") + fallthrough + + case Stream.Event.endEncountered: + fileHandle = nil + inputBuffer = nil + outputBuffer = nil + close() + + default: + break + } + } +} + +private extension String { + mutating func trim(predicate: (Character) -> Bool) { + if !isEmpty { + var start = startIndex + while start != endIndex && predicate(self[start]) { + start = index(after: start) + } + if start < endIndex { + var end = endIndex + repeat { + end = index(before: end) + } while predicate(self[end]) + self = String(self[start ... end]) + } else { + self = "" + } + } + } +} + +private extension URLRequest { + 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 static var CRLF: Data { + return Data(bytes: [0x0d, 0x0a]) + } + + init?(data: Data) { + guard var cursor = data.range(of: URLRequest.CRLF)?.lowerBound else { return nil } + + // parse request line + if let line = String(data: data.subdata(in: 0.. 100 && statusCode < 600) + let reason = HTTPURLResponse.localizedString(forStatusCode: statusCode).capitalized + 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.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..5793dd3 --- /dev/null +++ b/XWebView/XWVHttpServer.swift @@ -0,0 +1,250 @@ +/* + 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 { + fileprivate var socket: CFSocket! + private var connections = Set() + private let overlays: [URL] + private(set) var port: in_port_t = 0 + + var rootURL: URL { + return overlays.last! + } + var overlayURLs: [URL] { + return overlays.dropLast().reversed() + } + + init(rootURL: URL, overlayURLs: [URL]?) { + precondition(rootURL.isFileURL) + var overlays = [rootURL] + overlayURLs?.forEach { + precondition($0.isFileURL) + overlays.append($0) + } + self.overlays = overlays.reversed() + super.init() + } + convenience init(rootURL: URL) { + self.init(rootURL: rootURL, overlayURLs: nil) + } + deinit { + stop() + } + + private func listen(on port: in_port_t) -> Bool { + guard socket == nil else { return false } + + 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) + guard socket != nil else { + log("!Failed to create socket") + return false + } + + var yes = UInt32(1) + setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, &yes, UInt32(MemoryLayout.size)) + + var sockaddr = 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 = 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(_:)) + Thread.detachNewThreadSelector(serverLoop, toTarget: self, with: 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 listen(on: port) { + self.port = port + break + } + } + } else if listen(on: port) { + self.port = port + } + guard self.port != 0 else { return false } + + #if os(iOS) + 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) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) + #endif + port = 0 + close() + } + + @objc func suspend(_: NSNotification!) { + close() + log("+HTTP server is suspended") + } + @objc func resume(_: NSNotification!) { + if listen(on: port) { + log("+HTTP server is resumed") + } + } + + @objc func serverLoop(_: AnyObject) { + let runLoop = CFRunLoopGetCurrent() + let source = CFSocketCreateRunLoopSource(nil, socket, 0) + CFRunLoopAddSource(runLoop, source, CFRunLoopMode.commonModes) + CFRunLoopRun() + } +} + +extension XWVHttpServer : XWVHttpConnectionDelegate { + func didOpenConnection(_ connection: XWVHttpConnection) { + connections.insert(connection) + } + func didCloseConnection(_ connection: XWVHttpConnection) { + connections.remove(connection) + } + + func handleRequest(_ request: URLRequest?) -> HTTPURLResponse { + // Date format, see section 7.1.1.1 of RFC7231 + 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.string(from: Date())] + var statusCode = 500 + var fileURL: URL? = nil + if request == nil { + // Bad request + statusCode = 400 + log("?Bad request") + } else if let request = request, request.httpMethod == "GET" || request.httpMethod == "HEAD" { + let fileManager = FileManager.default + let relativePath = String(request.url!.path.dropFirst()) + for baseURL in overlays { + var isDirectory: ObjCBool = false + var url = URL(string: relativePath, relativeTo: baseURL)! + if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) { + if isDirectory.boolValue { + url = url.appendingPathComponent("index.html") + } + if fileManager.isReadableFile(atPath: url.path) { + fileURL = url + break + } + } + } + if let fileURL = fileURL { + statusCode = 200 + 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 = nil + log("-File NOT found for URL \(request.url!)") + } + } else { + // Method not allowed + statusCode = 405 + headers["Allow"] = "GET HEAD" + } + if statusCode != 200 { + headers["Content-Length"] = "0" + } + 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?) { + let server = unsafeBitCast(info, to: XWVHttpServer.self) + assert(socket === server.socket && type == CFSocketCallBackType.acceptCallBack) + + let handle = data!.load(as: CFSocketNativeHandle.self) + 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 as CFString, 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.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 291936a..ba0eb64 100644 --- a/XWebView/XWVInvocation.swift +++ b/XWebView/XWVInvocation.swift @@ -17,253 +17,346 @@ import Foundation import ObjectiveC -private let _NSInvocation: AnyClass = NSClassFromString("NSInvocation")! -private let _NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! +@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 +} +@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 } +} -public class XWVInvocation { - public final let target: AnyObject +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 +}() - public init(target: AnyObject) { - self.target = target +@discardableResult public func invoke(_ selector: Selector, of target: AnyObject, with arguments: [Any?] = [], on thread: Thread? = nil, waitUntilDone wait: Bool = true) -> Any! { + 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 inv = NSInvocation.invocation(methodSignature: sig) - 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() + // Setup arguments + 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 octype = ObjCType(code: code) + if octype == .object { + let obj: AnyObject = _bridgeAnythingToObjectiveC(arg) + _autorelease(obj) + args[i] = _encodeBitsAsWords(obj) + } else if octype == .clazz, let cls = arg as? AnyClass { + args[i] = _encodeBitsAsWords(cls) + } 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: octype) { + val = v + } + } + args[i] = val._cVarArgEncoding + } else { + let octype = String(cString: code) + fatalError("Unable to convert argument \(i) from Swift type \(type(of: arg)) to ObjC type '\(octype)'") + } + } else { + // nil + args[i] = [Int(0)] } - let sig = _NSMethodSignature.signatureWithObjCTypes(method_getTypeEncoding(method))! - let inv = _NSInvocation.invocationWithMethodSignature(sig) + args[i].withUnsafeBufferPointer { + inv.setArgument(UnsafeMutablePointer(mutating: $0.baseAddress!), atIndex: i + 2) + } + } - // 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]))) + if selector.family == .init_ { + // Self should be consumed for method belongs to init famlily + _ = Unmanaged.passRetained(target) + } + inv.selector = selector - // 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 thread == nil || (thread == Thread.current && wait) { + inv.invoke(target: target) + } else { + let selector = #selector(NSInvocationProtocol.invoke(target:)) + inv.retainArguments() + (inv as! NSObject).perform(selector, on: thread!, with: target, waitUntilDone: wait) + guard wait else { return Void() } + } + if sig.methodReturnLength == 0 { return Void() } - 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) - } + // Fetch the return value + let buffer = UnsafeMutablePointer.allocate(capacity: Int(sig.methodReturnLength)) + inv.getReturnValue(buffer) + let octype = ObjCType(code: sig.methodReturnType) + defer { + 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 octype.loadValue(from: buffer) +} + +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 invoke(initializer, of: obj as AnyObject, with: arguments) as AnyObject +} - inv.selector = selector - inv.invokeWithTarget(target) - if sig.methodReturnLength == 0 { return Void() } +// 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 // '?' - // 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) + init(code: UnsafePointer) { + var val = code.pointee + if val == 0x72 { + // skip const qualifier + val = code.successor().pointee } - return bitCast(buffer, toObjCType: sig.methodReturnType) + guard let type = ObjCType(rawValue: val) else { + fatalError("Unknown ObjC type code: \(String(cString: code))") + } + self = type + } + + 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") + } + } +} + +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 + } + } +} + +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, to: UnsafePointer.self) + while s.pointee == 0x5f { s += 1 } // skip underscore + for p in Selector.prefixes { + let lowercase = CChar(97)...CChar(122) + let l = p.count + if strncmp(s, p, l) == 0 && !lowercase.contains(s.advanced(by: l).pointee) { + return Family(rawValue: s.pointee)! + } + } + return .none + } + var returnsRetained: Bool { + 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 func call(selector: Selector, withArguments arguments: Any!...) -> Any! { - return call(selector, withArguments: arguments) + +/////////////////////////////////////////////////////////////////////////////// + +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 } - // Helper for Objective-C, accept ObjC 'id' instead of Swift 'Any' type for in/out parameters . - @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) + @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! { + public subscript (selector: Selector) -> (Any?...)->Any! { return { - (args: Any!...)->Any! in - self.call(selector, withArguments: args) + (args: Any?...)->Any! in + self.call(selector, with: args) } } } 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 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!, 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) + 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 getProperty(name) + return value(of: name) } set { - setValue(newValue, forProperty: name) + setValue(newValue, to: 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) + private func getter(of name: String) -> Selector? { + guard let property = class_getProperty(type(of: target), name) else { + return nil } - 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) + guard let attr = property_copyAttributeValue(property, "G") else { + return Selector(name) } - return setter - } -} -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() + // The property defines a custom getter selector name. + let getter = Selector(String(cString: attr)) + free(attr) + return getter } - - // 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))'") + private func setter(of name: String) -> Selector? { + guard let property = class_getProperty(type(of: target), name) else { + return nil } - 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 + var setter: Selector? = nil + var attr = property_copyAttributeValue(property, "R") + if attr == nil { + attr = property_copyAttributeValue(property, "S") + if attr == nil { + setter = Selector("set\(name.prefix(1).uppercased())\(name.dropFirst()):") + } else { + // The property defines a custom setter selector name. + setter = Selector(String(cString: attr!)) + } } - - 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 - } -} - -// 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) ] - } -} -extension Selector: CVarArgType { - public var _cVarArgEncoding: [Int] { - return [ unsafeBitCast(self, Int.self) ] + free(attr) + return setter } } diff --git a/XWebView/XWVJson.swift b/XWebView/XWVJson.swift new file mode 100644 index 0000000..d9ff33d --- /dev/null +++ b/XWebView/XWVJson.swift @@ -0,0 +1,137 @@ +/* + 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 +public func jsonify(_ array: T) -> String? + where T.Index: BinaryInteger { + // TODO: filter out values with negative index + return "[" + array.map{jsonify($0) ?? ""}.joined(separator: ",") + "]" +} + +// JSON Object +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? { + guard let val = jsonify(pair.value) else { return nil } + return jsonify(pair.key)! + ":" + val +} + +// JSON Number +public func jsonify(_ integer: T) -> String? { + return String(describing: integer) +} +public func jsonify(_ float: T) -> String? { + return String(describing: float) +} + +// JSON Boolean +public func jsonify(_ bool: Bool) -> String? { + return String(describing: bool) +} + +// JSON String +public func jsonify(_ string: T) -> String? { + return string.unicodeScalars.reduce("\"") { $0 + $1.jsonEscaped } + "\"" +} +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) + } + } +} + + +@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 is Void: + return "undefined" + case is NSNull: + return "null" + 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 { + (base: UnsafePointer) -> String? in + jsonify(UnsafeBufferPointer(start: base, count: d.count)) + } + default: + 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)) + } + + 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/XWVLogging.swift b/XWebView/XWVLogging.swift new file mode 100644 index 0000000..f441fde --- /dev/null +++ b/XWebView/XWVLogging.swift @@ -0,0 +1,136 @@ +/* + 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 +import Foundation + +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 + +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", "$", "!", "?", "-", "+", " " + ] + 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 : OptionSet { + 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.first, let l = Level(symbol: ch) { + msg = String(msg.dropFirst()) + lvl = l + } + log(msg, level: lvl) + } + + @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 + 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) +} + +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 b7a274d..0027b39 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 @@ -87,156 +83,158 @@ 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.invokeDefaultMethod(withArguments:))) return methods.union([ - Selector(".cxx_construct"), - Selector(".cxx_destruct"), - Selector("dealloc"), - Selector("copy") + #selector(NSObject.copy) ]) }() init(plugin: AnyClass) { self.plugin = plugin - enumerateExcluding(self.dynamicType.exclusion) { - (var name, var member) -> Bool in + _ = enumerate(excluding: type(of: self).exclusion) { + (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 { + if cls.isSelectorExcluded?(fromScript: selector) ?? false { return true } - if selector == Selector("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 == "_" { + } else if name.first == "_" { return true } 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 == "_" { + } else if name.first == "_" { return true } case let .Initializer(selector, _): - if selector == Selector("initByScriptWithArguments:") { + if selector == Selector(("initByScriptWithArguments:")) { 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, "Script name '\(name)' has conflict") + 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 + var count: UInt32 = 0 // enumerate properties - let properties = class_copyPropertyList(plugin, nil) - if properties != nil { - for var prop = properties; prop.memory != nil; prop = prop.successor() { - let name = String(UTF8String: property_getName(prop.memory))! + if let propertyList = class_copyPropertyList(plugin, &count) { + defer { free(propertyList) } + for i in 0 ..< Int(count) { + let name = String(cString: property_getName(propertyList[i])) // get getter - var attr = property_copyAttributeValue(prop.memory, "G") - let getter = Selector(attr == nil ? name : String(UTF8String: 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) { continue } known.insert(getter) // get setter if readwrite - var setter = Selector() - attr = property_copyAttributeValue(prop.memory, "R") + var setter: Selector? = nil + var attr = property_copyAttributeValue(propertyList[i], "R") if attr == nil { - attr = property_copyAttributeValue(prop.memory, "S") + attr = property_copyAttributeValue(propertyList[i], "S") if attr == nil { - setter = Selector("set\(String(name.characters.first!).uppercaseString)\(String(name.characters.dropFirst())):") + setter = Selector("set\(name.prefix(1).uppercased())\(name.dropFirst()):") + print(setter!.description) } 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) let info = Member.Property(getter: getter, setter: setter) if !callback(name, info) { - free(properties) return false } } - free(properties) } // enumerate methods - let methods = class_copyMethodList(plugin, nil) - if methods != nil { - for var method = methods; method.memory != nil; method = method.successor() { - let sel = method_getName(method.memory) - if !known.contains(sel) { - let arity = Int32(method_getNumberOfArguments(method.memory) - 2) + if let methodList = class_copyMethodList(plugin, &count) { + defer { free(methodList) } + 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) } - if !callback(sel.description, member) { - free(methods) + let name = sel.description.prefix(while: {$0 != ":"}) + if !callback(String(name), member) { return false } } } - free(methods) } 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 { + 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 (position: Index) -> (String, Member) { + subscript (position: Index) -> Element { return members[position] } - subscript (name: String) -> Member? { - return members[name] + subscript (bounds: Range) -> SubSequence { + return members[bounds] + } + func index(after i: Index) -> Index { + return members.index(after: i) } } @@ -244,9 +242,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() { - selectors.insert(desc.memory.name) + if var desc = methodList { + 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 cea1a5d..b3c95c6 100644 --- a/XWebView/XWVObject.swift +++ b/XWebView/XWVObject.swift @@ -17,104 +17,104 @@ import Foundation import WebKit +private let webViewInvalidated = + NSError(domain: WKErrorDomain, code: WKError.webViewInvalidated.rawValue, userInfo: nil) + 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? + private weak var origin: XWVObject? + private let reference: Int + + // initializer for plugin object. + init(namespace: String, webView: WKWebView) { + self.namespace = namespace + self.webView = webView + reference = 0 + super.init() + origin = self + } - // This object is a plugin object. - init(namespace: String, channel: XWVChannel, origin: XWVObject?) { + // initializer for script object with global namespace. + init(namespace: String, origin: XWVObject) { self.namespace = namespace - self.channel = channel + self.origin = origin + webView = origin.webView + reference = 0 super.init() - self.origin = 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 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 { - var script: String? - if reference == 0 { + guard let webView = webView else { return } + let script: String + if origin === self { script = "delete \(namespace)" - } else if origin != nil { + } else if reference != 0, let origin = origin { script = "\(origin.namespace).$releaseObject(\(reference))" + } else { + return } - if let webView = webView, let script = script { - webView.evaluateJavaScript(script, completionHandler: nil) - } + webView.asyncEvaluateJavaScript(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) + public func evaluateExpression(_ expression: String) throws -> Any { + guard let webView = webView else { + throw webViewInvalidated } - return nil + let result = try webView.syncEvaluateJavaScript(scriptForRetaining(expression)) + return wrapScriptObject(result) } - 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 typealias Handler = ((Any?, Error?) -> Void)? + public func evaluateExpression(_ expression: String, completionHandler: Handler) { + guard let webView = webView else { + completionHandler?(nil, webViewInvalidated) + return + } + guard let completionHandler = completionHandler else { + webView.asyncEvaluateJavaScript(expression, completionHandler: nil) + return + } + webView.asyncEvaluateJavaScript(scriptForRetaining(expression)) { + [weak self](result: Any?, error: Error?)->Void in + 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 { - return origin != nil ? "\(origin.namespace).$retainObject(\(script))" : script + private func scriptForRetaining(_ script: String) -> String { + guard let origin = origin else { return script } + return "\(origin.namespace).$retainObject(\(script))" } - 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) + func wrapScriptObject(_ object: Any) -> Any { + guard let origin = origin else { return object } + 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, channel: channel, origin: self) + return XWVScriptObject(namespace: namespace, origin: origin) } } - return object ?? NSNull() + 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 { - 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" - } - return "'\(obj!.description)'" +extension XWVObject : CustomJSONStringable { + public var jsonString: String? { + return namespace } } diff --git a/XWebView/XWVScriptObject.swift b/XWebView/XWVScriptObject.swift index edb39a8..f55f4ee 100644 --- a/XWebView/XWVScriptObject.swift +++ b/XWebView/XWVScriptObject.swift @@ -15,62 +15,80 @@ */ import Foundation +import WebKit public class XWVScriptObject : XWVObject { - // JavaScript object operations - public func construct(arguments arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { - let exp = "new " + scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + // 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 arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { - let exp = scriptForCallingMethod(nil, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + public func call(arguments: [Any]?, completionHandler: Handler) { + let expr = expression(forMethod: nil, arguments: arguments) + evaluateExpression(expr, completionHandler: completionHandler) } - public func callMethod(name: String, withArguments arguments: [AnyObject]?, resultHandler: ((AnyObject!)->Void)?) { - let exp = scriptForCallingMethod(name, arguments: arguments) - evaluateExpression(exp, onSuccess: resultHandler) + 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 arguments: [AnyObject]?) -> AnyObject! { - return evaluateExpression("new \(scriptForCallingMethod(nil, arguments: arguments))") + // 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 arguments: [AnyObject]?) -> AnyObject! { - return 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, withArguments arguments: [AnyObject]?) -> AnyObject! { - return evaluateExpression(scriptForCallingMethod(name, arguments: arguments)) + 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:AnyObject]) -> AnyObject? { - let exp = "Object.defineProperty(\(namespace), \(name), \(serialize(descriptor)))" - return evaluateExpression(exp) - } - public func deleteProperty(name: String) -> Bool { - let result: AnyObject? = evaluateExpression("delete \(scriptForFetchingProperty(name))") + // 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 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: AnyObject? = evaluateExpression("\(scriptForFetchingProperty(name)) != undefined") + public func hasProperty(_ name: String) -> Bool { + let expr = expression(forProperty: name) + " != undefined" + let result: Any? = try! evaluateExpression(expr) return (result as? NSNumber)?.boolValue ?? false } - public func value(forProperty name: String) -> AnyObject? { - return evaluateExpression(scriptForFetchingProperty(name)) + // property accessing + public func value(for name: String) throws -> Any { + return try evaluateExpression(expression(forProperty: name)) } - public func setValue(value: AnyObject?, forProperty name:String) { - webView?.evaluateJavaScript(scriptForUpdatingProperty(name, value: value), completionHandler: nil) + public func setValue(_ value: Any?, for name:String) { + guard let json = jsonify(value) else { return } + let script = expression(forProperty: name) + " = " + json + webView?.asyncEvaluateJavaScript(script, completionHandler: nil) } - public func value(atIndex index: UInt) -> AnyObject? { - return evaluateExpression("\(namespace)[\(index)]") + public func value(at index: UInt) throws -> Any { + return try evaluateExpression("\(namespace)[\(index)]") } - public func setValue(value: AnyObject?, atIndex index: UInt) { - webView?.evaluateJavaScript("\(namespace)[\(index)] = \(serialize(value))", completionHandler: nil) + public func setValue(_ value: Any?, at index: UInt) { + guard let json = jsonify(value) else { return } + let script = "\(namespace)[\(index)] = \(json)" + webView?.asyncEvaluateJavaScript(script, completionHandler: nil) } - private func scriptForFetchingProperty(name: String!) -> String { - if name == nil { + // expression generation + private func expression(forProperty 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)]" @@ -78,41 +96,36 @@ public class XWVScriptObject : XWVObject { return "\(namespace).\(name)" } } - private func scriptForUpdatingProperty(name: String!, value: AnyObject?) -> String { - return scriptForFetchingProperty(name) + " = " + serialize(value) - } - private func scriptForCallingMethod(name: String!, arguments: [AnyObject]?) -> String { - let args = arguments?.map(serialize) ?? [] - return scriptForFetchingProperty(name) + "(" + args.joinWithSeparator(", ") + ")" + private func expression(forMethod name: String?, arguments: [Any]?) -> String { + let args = arguments?.map{jsonify($0) ?? ""} ?? [] + return expression(forProperty: 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 (try? value(for: name)) ?? undefined } 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 (try? value(at: index)) ?? undefined } set { - setValue(newValue, atIndex: index) + setValue(newValue, at: index) } } } -extension XWVScriptObject { - // DOM objects - public var windowObject: XWVScriptObject { - return XWVScriptObject(namespace: "window", channel: self.channel, origin: self.origin) - } - public var documentObject: XWVScriptObject { - return XWVScriptObject(namespace: "document", channel: self.channel, origin: self.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/XWVScripting.swift b/XWebView/XWVScripting.swift index b14fe9f..dcf48d0 100644 --- a/XWebView/XWVScripting.swift +++ b/XWebView/XWVScripting.swift @@ -17,12 +17,13 @@ import Foundation @objc public protocol XWVScripting : class { - optional func javascriptStub(stub: 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 e89f626..5e19f2c 100644 --- a/XWebView/XWVUserScript.swift +++ b/XWebView/XWVUserScript.swift @@ -42,10 +42,10 @@ 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 { - print(" ERROR: Inject user script in context.\n\(error)") + 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 a154502..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,11 +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 - -#import "XWVHttpServer.h" - -// 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 diff --git a/XWebView/XWebView.swift b/XWebView/XWebView.swift index d1f0d79..e5b2538 100644 --- a/XWebView/XWebView.swift +++ b/XWebView/XWebView.swift @@ -19,59 +19,81 @@ import ObjectiveC import WebKit extension WKWebView { - public func loadPlugin(object: AnyObject, namespace: String) -> XWVScriptObject? { - let channel = XWVChannel(name: nil, webView: self) + public var windowObject: XWVScriptObject { + return XWVWindowObject(webView: self) + } + + @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 { - preconditionFailure("FATAL: Internal error") + 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(\(self)) is ready for loading plugins") } } +public typealias Undefined = Void +public var undefined: AnyObject = { + Void() as AnyObject +}() + extension WKWebView { + public static var undefined: AnyObject { + 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 - public func evaluateJavaScript(script: String, error: NSErrorPointer = nil) -> AnyObject? { - var result: AnyObject? + open func syncEvaluateJavaScript(_ 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 - if error != nil { - error.memory = err - } + 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 - if error != nil { - error.memory = err - } + error = err done = true condition.signal() condition.unlock() @@ -79,88 +101,54 @@ extension WKWebView { } condition.lock() while !done { - if !condition.waitUntilDate(NSDate(timeIntervalSinceNow: timeout)) { + if !condition.wait(until: Date(timeIntervalSinceNow: timeout) as Date) { break } } condition.unlock() } + if error != nil { throw error! } if !done { - print(" ERROR: Timeout to evaluate script.") + log("!Timeout to evaluate script: \(script)") } - return result + return result ?? WKWebView.undefined } } +@available(iOS 9.0, macOS 10.11, *) 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("loadFileURL:allowingReadAccessToURL:") - 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") - method_exchangeImplementations( - class_getInstanceMethod(self, Selector("loadHTMLString:baseURL:")), - class_getInstanceMethod(self, Selector("_loadHTMLString:baseURL:")) - ) - } + // 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!) } - } - @objc private func _loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation? { - precondition(URL.fileURL && readAccessURL.fileURL) - let fileManager = NSFileManager.defaultManager() - var relationship: NSURLRelationship = NSURLRelationship.Other - _ = try? fileManager.getRelationship(&relationship, ofDirectoryAtURL: readAccessURL, toItemAtURL: URL) - - 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!)); + guard url.isFileURL && url.baseURL != nil else { + fatalError("URL must be a relative file URL.") } - return nil - } - @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: url.baseURL!, overlayURLs: overlayURLs) else { return nil } - 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 + 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(documentRoot root: String) -> 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) + private func startHttpd(rootURL: URL, overlayURLs: [URL]? = nil) -> in_port_t? { + 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 + } + httpd.stop() } - return httpd!.port + + 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 } } diff --git a/XWebView/xwebview.js b/XWebView/xwebview.js index 3312dae..2bf8677 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': []}); @@ -120,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)); @@ -187,8 +188,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; @@ -204,3 +208,5 @@ XWVPlugin.prototype = { this.__proto__ = Object.getPrototypeOf(this.__proto__); } } + +XWVPlugin.context = new XWVPlugin(0); diff --git a/XWebViewTests/ConstructorPlugin.swift b/XWebViewTests/ConstructorPlugin.swift index aa89232..b448179 100644 --- a/XWebViewTests/ConstructorPlugin.swift +++ b/XWebViewTests/ConstructorPlugin.swift @@ -19,31 +19,35 @@ 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 { + @objc init(expectation: Any?) { + if let e = expectation as? XWVScriptObject { + e.callMethod("fulfill", with: nil, completionHandler: nil) + } } - init(value: Int) { - property = value + class func scriptName(for selector: Selector) -> String? { + return selector == #selector(Plugin0.init(expectation:)) ? "" : nil } - func finalizeForScript() { - if property == 456 { - scriptObject?.webView?.evaluateJavaScript("fulfill('finalizeForScript')", completionHandler: nil) - } + } + class Plugin1 : NSObject, XWVScripting { + @objc dynamic var property: Int + @objc init(value: Int) { + property = value } - class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithValue:") ? "" : nil + class func scriptName(for selector: Selector) -> String? { + return selector == #selector(Plugin1.init(value:)) ? "" : nil } } class Plugin2 : NSObject, XWVScripting { - override init() {} - init(expectation: AnyObject?) { - (expectation as? XWVScriptObject)?.callMethod("fulfill", withArguments: nil, resultHandler: nil) + private let expectation: XWVScriptObject? + @objc init(expectation: Any?) { + self.expectation = expectation as? XWVScriptObject + } + func finalizeForScript() { + expectation?.callMethod("fulfill", with: nil, completionHandler: nil) } - class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithExpectation:") ? "" : nil + class func scriptName(for selector: Selector) -> String? { + return selector == #selector(Plugin2.init(expectation:)) ? "" : nil } } @@ -52,36 +56,29 @@ class ConstructorPlugin : XWVTestCase { func testConstructor() { let desc = "constructor" let script = "if (\(namespace) instanceof Function) fulfill('\(desc)')" - _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + _ = expectation(description: desc) + loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) + waitForExpectations() } 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) - waitForExpectationsWithTimeout(2, handler: nil) - }*/ + _ = expectation(description: desc) + loadPlugin(Plugin0(expectation: nil), namespace: namespace, script: script) + waitForExpectations() + } 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) - waitForExpectationsWithTimeout(2, handler: nil) + _ = expectation(description: desc) + loadPlugin(Plugin1(value: 123), namespace: namespace, script: script) + waitForExpectations() } func testFinalizeForScript() { let desc = "finalizeForScript" - let script = "(new \(namespace)(456)).then(function(o){o.dispose();})" - _ = expectationWithDescription(desc) - loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + let script = "(new \(namespace)(expectation('\(desc)'))).then(function(o){o.dispose();})" + _ = expectation(description: desc) + loadPlugin(Plugin2(expectation: nil), namespace: namespace, script: script) + waitForExpectations() } } diff --git a/XWebViewTests/FunctionPlugin.swift b/XWebViewTests/FunctionPlugin.swift index b1a550f..835fc70 100644 --- a/XWebViewTests/FunctionPlugin.swift +++ b/XWebViewTests/FunctionPlugin.swift @@ -20,16 +20,16 @@ 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 scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("defaultMethod") ? "" : nil + 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() } 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() } 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() } } diff --git a/XWebViewTests/Info.plist b/XWebViewTests/Info.plist index 0853990..6330ada 100644 --- a/XWebViewTests/Info.plist +++ b/XWebViewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.9.3 + 0.12.1 CFBundleSignature ???? CFBundleVersion diff --git a/XWebViewTests/ObjectPlugin.swift b/XWebViewTests/ObjectPlugin.swift index 062c558..32a59fa 100644 --- a/XWebViewTests/ObjectPlugin.swift +++ b/XWebViewTests/ObjectPlugin.swift @@ -20,32 +20,33 @@ 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 argument: AnyObject?) { + @objc func method(argument: Any?) { if argument as? String == "Yes" { expectation?.fulfill() } } - func method(Integer Integer: Int) { + @objc func method(Integer: Int) { if Integer == 789 { expectation?.fulfill() } } - func method(callback callback: XWVScriptObject) { - callback.call(arguments: nil, resultHandler: nil) + @objc func method(callback: XWVScriptObject) { + callback.call(arguments: nil, completionHandler: nil) } - func method(promiseObject promiseObject: XWVScriptObject) { - promiseObject.callMethod("resolve", withArguments: nil, resultHandler: nil) + @objc func method(promiseObject: XWVScriptObject) { + promiseObject.callMethod("resolve", with: nil, completionHandler: nil) } - func windowObject() { - if let test: AnyObject = scriptObject?.windowObject["test"] { - if (test as? NSNumber)?.integerValue == 234 { - expectation?.fulfill() - } + @objc func method1() { + guard let bindingObject = XWVScriptObject.bindingObject else { return } + property = 456 + //if (bindingObject["property"] as? NSNumber)?.intValue == 456 { + if bindingObject["property"] as? Int64 == 456 { + expectation?.fulfill() } } init(expectation: XCTestExpectation?) { @@ -58,83 +59,72 @@ 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() } 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 { + exp.fulfill() } } } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations() } 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 { + exp.fulfill() } } } - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations() } 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() } 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() } 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() } 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() } 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() } func testScriptObject() { let desc = "scriptObject" - let expectation = expectationWithDescription(desc) - let plugin = Plugin(expectation: expectation) - loadPlugin(plugin as NSObject, namespace: namespace, script: "") { - (webView)->Void in - plugin.scriptObject?.callMethod("method", withArguments: nil) - return - } - 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) + let exp = expectation(description: desc) + let plugin = Plugin(expectation: exp) + loadPlugin(plugin, namespace: namespace, script: "\(namespace).method1();") + waitForExpectations() } } diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift index 3271d46..f64a88d 100644 --- a/XWebViewTests/XWVInvocationTest.swift +++ b/XWebViewTests/XWVInvocationTest.swift @@ -18,9 +18,9 @@ import XCTest import XWebView class InvocationTarget: NSObject { - class ObjectForLeakTest { + class LeakTest: NSObject { let expectation: XCTestExpectation - init(expectation: XCTestExpectation) { + @objc init(expectation: XCTestExpectation) { self.expectation = expectation } deinit { @@ -28,33 +28,34 @@ class InvocationTarget: NSObject { } } - var integer: Int = 123 - - func dummy() {} - 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.integerValue } - - func leak(expectation: XCTestExpectation) -> AnyObject { - return ObjectForLeakTest(expectation: expectation) + @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) } } @@ -70,45 +71,72 @@ 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) - XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Bool == true) - XCTAssertTrue(inv[Selector("echoWithInt:")](Int(-11)) as? Int64 == -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("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](selector) as? Selector == selector) - 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("concat::")]("ab", "cd") as? String == "abcd") + XCTAssertTrue(inv[ #selector(InvocationTarget.dummy)]() is Void) + #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(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) + 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:)) + XCTAssertEqual(inv[selector](selector) as? Selector, selector) + let cls = type(of: self) + XCTAssertTrue(inv[ #selector(InvocationTarget.echo(class:))](cls) as? AnyClass === cls) + + 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? Int64 == 123) + XCTAssertEqual(inv["integer"] as? XInt, 123) inv["integer"] = 321 - XCTAssertTrue(inv["integer"] as? Int64 == 321) + XCTAssertEqual(inv["integer"] as? XInt, 321) + } + + func testNullable() { + XCTAssertEqual(inv[ #selector(InvocationTarget.nullable(_:))]("abc") as? String, "abc") + XCTAssertNil(inv[ #selector(InvocationTarget.nullable(_:))](nil)) + } + + func testLeak1() { + autoreleasepool { + let exp = expectation(description: "leak") + let obj = inv[ #selector(InvocationTarget._new(_:))](exp) as? InvocationTarget.LeakTest + XCTAssertEqual(exp, obj!.expectation) + } + waitForExpectations(timeout: 3) } - 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 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: 3) } } 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") + } +} diff --git a/XWebViewTests/XWVMetaObjectTest.swift b/XWebViewTests/XWVMetaObjectTest.swift index 98d7753..2fa75a0 100644 --- a/XWebViewTests/XWVMetaObjectTest.swift +++ b/XWebViewTests/XWVMetaObjectTest.swift @@ -22,20 +22,20 @@ 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) 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() @@ -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,20 +70,20 @@ 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"] { 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() @@ -93,11 +93,11 @@ class XWVMetaObjectTest: XCTestCase { class TestForExclusion: XWVScripting { @objc let property = 0 @objc func method() {} - @objc class func isSelectorExcludedFromScript(selector: Selector) -> Bool { - return selector == Selector("method") + @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) @@ -105,17 +105,36 @@ 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() {} - @objc class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("defaultMethod") ? "" : nil + @objc class func scriptName(for selector: Selector) -> String? { + 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() @@ -124,14 +143,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("invokeDefaultMethodWithArguments:")) + XCTAssertTrue(member.selector == #selector(XWVScripting.invokeDefaultMethod(withArguments:))) XCTAssertTrue(member.type == "") } else { XCTFail() @@ -140,15 +159,15 @@ class XWVMetaObjectTest: XCTestCase { func testForConstructor() { class TestForConstructor : XWVScripting { - @objc init(argument: AnyObject?) {} - @objc class func scriptNameForSelector(selector: Selector) -> String? { - return selector == Selector("initWithArgument:") ? "" : nil + @objc init(argument: Any?) {} + @objc class func scriptName(for selector: Selector) -> String? { + 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() @@ -157,12 +176,12 @@ 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[""] { 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 050c865..b54f806 100644 --- a/XWebViewTests/XWVScriptingTest.swift +++ b/XWebViewTests/XWVScriptingTest.swift @@ -21,52 +21,56 @@ import XWebView class XWVScriptingTest : XWVTestCase { class Plugin : NSObject, XWVScripting { let expectation: XCTestExpectation? - init(expectation: XCTestExpectation?) { + @objc init(expectation: XCTestExpectation?) { self.expectation = expectation } - func javascriptStub(stub: String) -> String { - return stub + "\nwindow.stub = true;\n" + 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" + default: return stub + } } func finalizeForScript() { expectation?.fulfill() } - class func isSelectorExcludedFromScript(selector: Selector) -> Bool { - return selector == Selector("initWithExpectation:") + 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 testJavascriptStub() { + func testRewriteStub() { let desc = "javascriptStub" - let script = "if (window.stub) fulfill('\(desc)');" - _ = expectationWithDescription(desc) + let script = "if (window.stub && \(namespace).abc) fulfill('\(desc)');" + _ = expectation(description: desc) loadPlugin(Plugin(expectation: nil), namespace: namespace, script: script) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations() } 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() } 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() } 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() } } diff --git a/XWebViewTests/XWVTestCase.swift b/XWebViewTests/XWVTestCase.swift index 383c526..b451372 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 @@ -39,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 } @@ -50,23 +51,29 @@ 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 } + override func waitForExpectations(timeout: TimeInterval = 15, handler: XCWaitCompletionHandler? = nil) { + super.waitForExpectations(timeout: timeout, handler: handler) + } - 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)?) { - self.onReady = onReady + func loadPlugin(_ object: NSObject, namespace: String, script: String, onReady: ((WKWebView)->Void)?) { 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, didFinishNavigation navigation: WKNavigation!) { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { onReady?(webView) } } @@ -76,8 +83,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() } } diff --git a/XWebViewTests/XWebViewTests.swift b/XWebViewTests/XWebViewTests.swift index 4c88b71..3ebd737 100644 --- a/XWebViewTests/XWebViewTests.swift +++ b/XWebViewTests/XWebViewTests.swift @@ -22,30 +22,94 @@ class XWebViewTests: XWVTestCase { class Plugin : NSObject { } + func testWindowObject() { + let expectation = super.expectation(description: "testWindowObject") + loadPlugin(Plugin(), namespace: "xwvtest", script: "") { + if let math = $0.windowObject["Math"] as? XWVScriptObject, + let num = try? math.callMethod("sqrt", with: [9]), + let result = (num as? NSNumber)?.intValue, result == 3 { + expectation.fulfill() + } else { + XCTFail("testWindowObject Failed") + } + } + 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") } } + #if !os(macOS) + @available(iOS 9.0, macOS 10.11, *) func testLoadFileURL() { - _ = expectationWithDescription("loadFileURL") - let bundle = NSBundle(identifier:"org.xwebview.XWebViewTests") - if let root = bundle?.bundleURL.URLByAppendingPathComponent("www") { - let url = root.URLByAppendingPathComponent("webviewTest.html") - XCTAssert(url.checkResourceIsReachableAndReturnError(nil), "HTML file not found") - webview.loadFileURL(url, allowingReadAccessToURL: root) - waitForExpectationsWithTimeout(2, handler: nil) + _ = expectation(description: "loadFileURL") + let bundle = Bundle(identifier:"org.xwebview.XWebViewTests") + + 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, macOS 10.11, *) + func testLoadFileURLWithOverlay() { + _ = 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! FileManager.default.url( + for: FileManager.SearchPathDirectory.libraryDirectory, + in: FileManager.SearchPathDomainMask.userDomainMask, + appropriateFor: nil, + create: true) + var url = library.appendingPathComponent("webviewTest.html") + + let content = "" + try! content.write(to: url, atomically: false, encoding: String.Encoding.utf8) + + url = URL(string: "webviewTest.html", relativeTo: root)! + _ = webview.loadFileURL(url, overlayURLs: [library]) + waitForExpectations() } } + #if !os(macOS) 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?.resourceURL?.appendingPathComponent("www") { + XCTAssert(try baseURL.checkResourceIsReachable(), "Directory not found") webview.loadHTMLString("", baseURL: baseURL) - waitForExpectationsWithTimeout(2, handler: nil) + waitForExpectations() } } + #endif }