├── .assets └── bundles.png ├── .gitignore ├── LICENSE.md ├── Phage Injector ├── Base.lproj │ └── SafariExtensionViewController.xib ├── Info.plist ├── Phage_Injector.entitlements ├── SafariExtensionHandler.swift ├── SafariExtensionViewController.swift ├── ToolbarItemIcon.pdf ├── page-script.js └── script.js ├── Phage.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Phage ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── ContentView.swift ├── Info.plist ├── Phage.entitlements └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── PhageCore ├── Info.plist ├── PhageData.swift ├── PhageDataBundle.swift ├── PhageDataFile.swift └── PhageDependencies.swift ├── PhageCoreTests ├── Info.plist └── PhageCoreTests.swift ├── PhageTests ├── Info.plist └── PhageTests.swift ├── PhageUITests ├── Info.plist └── PhageUITests.swift └── README.md /.assets/bundles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/.assets/bundles.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /ignore/ 2 | /build/ 3 | xcuserdata/ 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018 cpsdqs 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Phage Injector/Base.lproj/SafariExtensionViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Phage Injector/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Phage Injector 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionPointIdentifier 28 | com.apple.Safari.extension 29 | NSExtensionPrincipalClass 30 | $(PRODUCT_MODULE_NAME).SafariExtensionHandler 31 | SFSafariContentScript 32 | 33 | 34 | Script 35 | script.js 36 | 37 | 38 | SFSafariToolbarItem 39 | 40 | Action 41 | Popover 42 | Identifier 43 | Button 44 | Image 45 | ToolbarItemIcon.pdf 46 | Label 47 | Phage 48 | 49 | SFSafariWebsiteAccess 50 | 51 | Level 52 | All 53 | 54 | 55 | NSHumanReadableCopyright 56 | Copyright © 2019 cpsdqs. All rights reserved. 57 | NSHumanReadableDescription 58 | Injects scripts and stylesheets. 59 | TeamIdentifierPrefix 60 | $(TeamIdentifierPrefix) 61 | 62 | 63 | -------------------------------------------------------------------------------- /Phage Injector/Phage_Injector.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)net.cloudwithlightning.phage 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Phage Injector/SafariExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariExtensionHandler.swift 3 | // Phage Extension 4 | // 5 | // Created by cpsdqs on 2019-06-12. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | import PhageCore 11 | import Combine 12 | 13 | class SafariExtensionHandler: SFSafariExtensionHandler { 14 | 15 | let data = PhageData() 16 | 17 | override init() { 18 | super.init() 19 | } 20 | 21 | /// Runs the action once on every page. 22 | /// - Parameter action: the action to run on every page 23 | private func performOnAllPages(action: @escaping (SFSafariPage) -> Void) { 24 | SFSafariApplication.getAllWindows { windows in 25 | for window in windows { 26 | window.getAllTabs { tabs in 27 | for tab in tabs { 28 | tab.getActivePage { page in 29 | if let page = page { 30 | action(page) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | private func serializeBundle(name: String, matching url: URL) -> [String: Any]? { 40 | guard let bundle = data.bundles[name] else { return nil } 41 | 42 | var scripts: [[String: Any]] = [] 43 | var styles: [[String: Any]] = [] 44 | 45 | for (name, file) in bundle.files { 46 | switch file.contents() { 47 | case .some(.javascript(let section)): 48 | if section.matches(url: url) { 49 | var prelude = "" 50 | for dependency in file.dependencies() { 51 | let stringEscapedName = dependency 52 | .replacingOccurrences(of: "\\", with: "\\\\") 53 | .replacingOccurrences(of: "\"", with: "\\\"") 54 | .replacingOccurrences(of: "\n", with: "\\\n") 55 | if let url = URL(string: dependency) { 56 | if let data = data.dependencies.getDependencyContents(of: url) { 57 | let commentEscapedName = dependency.replacingOccurrences(of: "*/", with: "* /") 58 | prelude.append("\n/* Dependency: \(commentEscapedName) */\n") 59 | prelude.append(data) 60 | } else { 61 | prelude.append(";alert(\"[Phage]\n\nMissing dependency “\(stringEscapedName).” Use the Phage app to download it\");\n") 62 | } 63 | } else { 64 | prelude.append(";console.error(\"[Phage] invalid dependency \(stringEscapedName)\");\n") 65 | } 66 | } 67 | 68 | scripts.append([ 69 | "name": name, 70 | "prelude": prelude, 71 | "contents": section.contents, 72 | "inPageContext": file.inPageContext 73 | ]) 74 | } 75 | case .some(.stylesheets(let sections)): 76 | for (i, section) in sections.enumerated() { 77 | if section.matches(url: url) { 78 | styles.append([ 79 | "id": [name, i], 80 | "contents": section.contents, 81 | ]) 82 | } 83 | } 84 | case .none: 85 | break 86 | } 87 | } 88 | 89 | if scripts.isEmpty && styles.isEmpty { 90 | return nil 91 | } 92 | 93 | scripts.sort { (lhs, rhs) in 94 | let lhsName = lhs["name"] as! String 95 | let rhsName = rhs["name"] as! String 96 | return lhsName.lexicographicallyPrecedes(rhsName) 97 | } 98 | 99 | return [ 100 | "id": name, 101 | "scripts": scripts, 102 | "styles": styles, 103 | ] 104 | } 105 | 106 | func serializeBundlesMatching(url: URL) -> [[String: Any]] { 107 | var bundles: [[String: Any]] = [] 108 | for (name, bundle) in data.bundles { 109 | if bundle.disabled { 110 | continue 111 | } 112 | if let serialized = serializeBundle(name: name, matching: url) { 113 | bundles.append(serialized) 114 | } 115 | } 116 | return bundles 117 | } 118 | 119 | // MARK: Bundle update handling 120 | var version = 0 121 | 122 | func dispatchUpdateNotification() { 123 | version = version + 1 124 | NSLog("dispatching update notification") 125 | performOnAllPages { page in 126 | page.dispatchMessageToScript(withName: "updateAvailable", userInfo: [:]) 127 | } 128 | } 129 | 130 | func sendCompleteUpdate(to page: SFSafariPage, url: URL, session: String) { 131 | page.dispatchMessageToScript(withName: "updateStyles", userInfo: [ 132 | "sessionID": session, 133 | "updated": serializeBundlesMatching(url: url), 134 | "replace": true, 135 | ]) 136 | } 137 | 138 | // MARK: SFSafariExtensionHandling 139 | 140 | override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { 141 | switch messageName { 142 | case "initInjector": 143 | guard let urlString = userInfo?["url"] as? String, 144 | let sessionID = userInfo?["sessionID"] as? String else { return } 145 | 146 | guard let url = URL(string: urlString) else { return } 147 | 148 | let bundles = serializeBundlesMatching(url: url) 149 | // NSLog("received init for page at \(url), sending bundles (\(bundles.count))") 150 | 151 | page.dispatchMessageToScript(withName: "initInjector", userInfo: [ 152 | "sessionID": sessionID, 153 | "bundles": bundles, 154 | "version": version, 155 | ]) 156 | case "updateRequest": 157 | guard let urlString = userInfo?["url"] as? String, 158 | let sessionID = userInfo?["sessionID"] as? String else { return } 159 | 160 | guard let url = URL(string: urlString) else { return } 161 | 162 | NSLog("received update request for page at \(url)") 163 | 164 | sendCompleteUpdate(to: page, url: url, session: sessionID) 165 | default: 166 | NSLog("Script sent unknown message \(messageName)?") 167 | break 168 | } 169 | } 170 | 171 | override func toolbarItemClicked(in window: SFSafariWindow) { 172 | // This method will be called when your toolbar item is clicked. 173 | NSLog("The extension's toolbar item was clicked") 174 | } 175 | 176 | override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { 177 | // This is called when Safari's state changed in some way that would require the extension's toolbar item to be validated again. 178 | validationHandler(true, "") 179 | } 180 | 181 | override func popoverViewController() -> SFSafariExtensionViewController { 182 | return SafariExtensionViewController.shared 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /Phage Injector/SafariExtensionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariExtensionViewController.swift 3 | // Phage Injector 4 | // 5 | // Created by cpsdqs on 2019-06-17. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | import SwiftUI 11 | 12 | class SafariExtensionViewController: SFSafariExtensionViewController { 13 | 14 | static let shared: SafariExtensionViewController = { 15 | let shared = SafariExtensionViewController() 16 | shared.preferredContentSize = NSSize(width:320, height:240) 17 | return shared 18 | }() 19 | 20 | override func viewDidLoad() { 21 | let hostingView = NSHostingView(rootView: PopoutView()) 22 | view.addSubview(hostingView) 23 | hostingView.translatesAutoresizingMaskIntoConstraints = false 24 | hostingView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 25 | hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 26 | hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 27 | hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 28 | } 29 | 30 | } 31 | 32 | struct PopoutView : View { 33 | var body: some View { 34 | VStack { 35 | Button(action: { 36 | SFSafariApplication.getActiveWindow { window in 37 | window?.getActiveTab { tab in 38 | tab?.getActivePage { page in 39 | page?.dispatchMessageToScript(withName: "forceUpdate", userInfo: [ 40 | "action": "single" 41 | ]) 42 | } 43 | } 44 | } 45 | }) { 46 | Text("Update stylesheets") 47 | } 48 | }.padding(8) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Phage Injector/ToolbarItemIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage Injector/ToolbarItemIcon.pdf -------------------------------------------------------------------------------- /Phage Injector/page-script.js: -------------------------------------------------------------------------------- 1 | // Phage Injector content script in page context 2 | 3 | { 4 | const EVT_NAME_IN = '__PHAGE_INJECTOR__EXT' 5 | const EVT_NAME_OUT = '__PHAGE_INJECTOR__PAGE' 6 | 7 | globalThis.addEventListener(EVT_NAME_IN, event => { 8 | if (!event.detail || typeof event.detail !== 'object') return 9 | if (event.detail.type === 'evalPageScript') { 10 | const fn = new Function(event.detail.source); 11 | fn.apply(window, []) 12 | } 13 | }) 14 | 15 | globalThis.dispatchEvent(new globalThis.CustomEvent(EVT_NAME_OUT, { 16 | detail: { 17 | type: 'initPageInjector' 18 | } 19 | })) 20 | } 21 | -------------------------------------------------------------------------------- /Phage Injector/script.js: -------------------------------------------------------------------------------- 1 | // Phage Injector content script in extension context 2 | 3 | ;(function() { 4 | // contents of this string must not contain single quotes or backslashes (see wrapScript): 5 | const phageConsoleStyle = 'color:white;background:black;padding:2px;border-radius:4px' 6 | 7 | if (globalThis.__phageInjectorSessionID) { 8 | console.warn( 9 | `%cPhage%c content script was injected twice? ignoring`, 10 | phageConsoleStyle, 11 | '' 12 | ) 13 | return 14 | } 15 | const injectorSessionID = Math.random().toString(36) 16 | globalThis.__phageInjectorSessionID = injectorSessionID 17 | 18 | // TODO: handle push/popstate? 19 | 20 | let injectionTries = 0 21 | 22 | ;(function inject () { 23 | injectionTries++ 24 | if (!window.document) { 25 | // try again later 26 | setTimeout(inject, 100 * 2 ** injectionTries) 27 | return 28 | } 29 | 30 | if (document.readyState !== 'complete' && location.protocol === 'about:') { 31 | // iframes that haven’t loaded yet will *navigate* from about:blank 32 | // to the target page so injection needs to be deferred to load time 33 | document.addEventListener('load', inject) 34 | return 35 | } 36 | 37 | safari.extension.dispatchMessage('initInjector', { 38 | url: window.location.href, 39 | sessionID: injectorSessionID, 40 | isTopLevel: window.top === window 41 | }) 42 | 43 | let forceUpdateInterval = null 44 | 45 | function requestUpdate () { 46 | safari.extension.dispatchMessage('updateRequest', { 47 | url: window.location.href, 48 | sessionID: injectorSessionID, 49 | }) 50 | } 51 | 52 | safari.self.addEventListener('message', event => { 53 | if (event.name === 'initInjector') { 54 | if (event.message.sessionID !== injectorSessionID) return 55 | 56 | for (const bundle of event.message.bundles) { 57 | injectScripts(bundle.id, bundle.scripts) 58 | injectStyles(bundle.id, bundle.styles) 59 | } 60 | } else if (event.name === 'forceUpdate') { 61 | if (event.message.action === 'single') { 62 | requestUpdate() 63 | } else if (event.message.action === 'begin' && forceUpdateInterval === null) { 64 | forceUpdateInterval = setInterval(requestUpdate, 1000) 65 | } else if (event.message.action === 'end' && forceUpdateInterval !== null) { 66 | clearInterval(forceUpdateInterval) 67 | } 68 | } else if (event.name === 'updateStyles') { 69 | if (event.message.sessionID !== injectorSessionID) return 70 | 71 | if (event.message.replace) { 72 | removeAllStyles() 73 | } else for (const bundleID of event.message.removed) { 74 | removeStyles(bundleID); 75 | } 76 | for (const bundle of event.message.updated) { 77 | injectStyles(bundle.id, bundle.styles); 78 | } 79 | } 80 | }) 81 | })() 82 | 83 | function injectScripts (bundle, scripts) { 84 | let i = 0 85 | for (const script of scripts) { 86 | if (script.inPageContext) { 87 | initPageContext().then(ctx => { 88 | ctx.injectScript(wrapScript(script.name, script.prelude, script.contents)); 89 | }) 90 | } else if (script.asScriptTag) { 91 | const node = document.createElement('script') 92 | node.textContent = wrapScript(script.name, script.prelude, script.contents) 93 | node.id = `phagejs-${bundle}-${i}` 94 | 95 | if (document.head) document.head.appendChild(node) 96 | else document.addEventListener('load', () => document.head.appendChild(node)) 97 | } else { 98 | const fn = new Function(wrapScript(script.name, script.prelude, script.contents)) 99 | fn.apply(window, []) 100 | } 101 | i++ 102 | } 103 | } 104 | 105 | let pageContextPromise = null 106 | function initPageContext () { 107 | const EVT_NAME_IN = '__PHAGE_INJECTOR__PAGE' 108 | const EVT_NAME_OUT = '__PHAGE_INJECTOR__EXT' 109 | if (!pageContextPromise) { 110 | pageContextPromise = new Promise(resolve => { 111 | const pageScript = document.createElement('script') 112 | pageScript.src = safari.extension.baseURI + 'page-script.js' 113 | if (document.head) document.head.appendChild(pageScript) 114 | else document.addEventListener('load', () => document.head.appendChild(node)) 115 | 116 | 117 | const injectPageScript = source => { 118 | globalThis.dispatchEvent(new globalThis.CustomEvent(EVT_NAME_OUT, { 119 | detail: { 120 | type: 'evalPageScript', 121 | source 122 | } 123 | })) 124 | } 125 | 126 | globalThis.addEventListener(EVT_NAME_IN, event => { 127 | if (!event.detail || typeof event.detail !== 'object') return 128 | if (event.detail.type === 'initPageInjector') { 129 | resolve({ 130 | injectScript: injectPageScript 131 | }) 132 | } 133 | }) 134 | }) 135 | } 136 | return pageContextPromise 137 | } 138 | 139 | function wrapScript (name, prelude, contents) { 140 | name = name.replace(/\\/g, '\\\\').replace(/`/g, '\\`') 141 | return `/* Phage injected script */ 142 | try { 143 | ${prelude} 144 | } catch (err) { 145 | console.error( 146 | \`%cPhage%c Script error for %c${name}%c in required code\`, 147 | '${phageConsoleStyle}', 148 | '', 149 | 'font-weight:bold', 150 | '', 151 | err 152 | ) 153 | } 154 | try { 155 | ${contents} 156 | } catch (err) { 157 | console.error( 158 | \`%cPhage%c Script error for %c${name}%c\`, 159 | '${phageConsoleStyle}', 160 | '', 161 | 'font-weight:bold', 162 | '', 163 | err 164 | ) 165 | } 166 | ` 167 | } 168 | 169 | const injectedStyles = {} 170 | let injectIntoBody = false 171 | 172 | function injectStyles (bundle, styles) { 173 | // remove existing styles first 174 | removeStyles(bundle) 175 | 176 | const nodes = [] 177 | let i = 0 178 | for (const style of styles) { 179 | const node = document.createElement('style') 180 | node.textContent = style.contents 181 | node.id = `phagecss-${bundle}-${i++}` 182 | nodes.push(node) 183 | } 184 | 185 | injectedStyles[bundle] = nodes 186 | 187 | if (injectIntoBody) { 188 | for (const node of nodes) document.body.appendChild(node) 189 | } else if (document.head) { 190 | for (const node of nodes) document.head.appendChild(node) 191 | } else { 192 | document.addEventListener('load', () => { 193 | // inject if still valid 194 | if (injectedStyles[bundle] === nodes) { 195 | for (const node of nodes) if (!node.parentNode) document.body.appendChild(node) 196 | } 197 | }) 198 | } 199 | } 200 | 201 | window.addEventListener('DOMContentLoaded', () => { 202 | injectIntoBody = true 203 | for (const bundle in injectedStyles) { 204 | // move styles to the body so they have precedence 205 | for (const node of injectedStyles[bundle]) { 206 | if (node.parentNode) node.parentNode.removeChild(node) 207 | document.body.appendChild(node) 208 | } 209 | } 210 | }) 211 | 212 | function removeStyles (bundle) { 213 | if (injectedStyles[bundle]) { 214 | for (const node of injectedStyles[bundle]) { 215 | if (node.parentNode) node.parentNode.removeChild(node) 216 | } 217 | } 218 | delete injectedStyles[bundle] 219 | } 220 | 221 | function removeAllStyles () { 222 | for (const bundle in injectedStyles) removeStyles(bundle) 223 | } 224 | })() 225 | -------------------------------------------------------------------------------- /Phage.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0F31D8F622B7DE26009D305A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F31D8F522B7DE26009D305A /* Cocoa.framework */; }; 11 | 0F31D8F922B7DE26009D305A /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */; }; 12 | 0F31D8FB22B7DE26009D305A /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */; }; 13 | 0F31D8FE22B7DE26009D305A /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */; }; 14 | 0F31D90122B7DE26009D305A /* script.js in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D90022B7DE26009D305A /* script.js */; }; 15 | 0F31D90322B7DE26009D305A /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */; }; 16 | 0F31D90722B7DE26009D305A /* Phage Injector.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0F31D8F322B7DE26009D305A /* Phage Injector.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17 | 0FA5315E22B7D33000715AB8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */; }; 18 | 0FA5316022B7D33000715AB8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5315F22B7D33000715AB8 /* ContentView.swift */; }; 19 | 0FA5316222B7D33100715AB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316122B7D33100715AB8 /* Assets.xcassets */; }; 20 | 0FA5316522B7D33100715AB8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */; }; 21 | 0FA5316822B7D33100715AB8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316622B7D33100715AB8 /* Main.storyboard */; }; 22 | 0FA5317422B7D33200715AB8 /* PhageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5317322B7D33200715AB8 /* PhageTests.swift */; }; 23 | 0FA5317F22B7D33200715AB8 /* PhageUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */; }; 24 | 0FDFB3D522B7EA8200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; }; 25 | 0FDFB3DC22B7EA8200213CC7 /* PhageCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */; }; 26 | 0FDFB3E122B7EA8200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; }; 27 | 0FDFB3E222B7EA8200213CC7 /* PhageCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28 | 0FDFB3EA22B7EA9200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; }; 29 | 0FDFB3EB22B7EA9200213CC7 /* PhageCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 30 | 0FDFB3F022B7EAF700213CC7 /* PhageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */; }; 31 | 0FDFB3F422B8A10900213CC7 /* PhageDataBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */; }; 32 | 0FDFB3F622B93C7E00213CC7 /* PhageDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */; }; 33 | 0FFB95BE22C2291D008D2FAE /* PhageDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */; }; 34 | 4E2B09BE27741C7E00B3E691 /* page-script.js in Resources */ = {isa = PBXBuildFile; fileRef = 4E2B09BD27741C7E00B3E691 /* page-script.js */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | 0F31D90522B7DE26009D305A /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 0F31D8F222B7DE26009D305A; 43 | remoteInfo = "Phage Injector"; 44 | }; 45 | 0FA5317022B7D33200715AB8 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 0FA5315922B7D33000715AB8; 50 | remoteInfo = Phage; 51 | }; 52 | 0FA5317B22B7D33200715AB8 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 55 | proxyType = 1; 56 | remoteGlobalIDString = 0FA5315922B7D33000715AB8; 57 | remoteInfo = Phage; 58 | }; 59 | 0FDFB3D622B7EA8200213CC7 /* PBXContainerItemProxy */ = { 60 | isa = PBXContainerItemProxy; 61 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 62 | proxyType = 1; 63 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7; 64 | remoteInfo = PhageCore; 65 | }; 66 | 0FDFB3D822B7EA8200213CC7 /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 69 | proxyType = 1; 70 | remoteGlobalIDString = 0FA5315922B7D33000715AB8; 71 | remoteInfo = Phage; 72 | }; 73 | 0FDFB3DF22B7EA8200213CC7 /* PBXContainerItemProxy */ = { 74 | isa = PBXContainerItemProxy; 75 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 76 | proxyType = 1; 77 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7; 78 | remoteInfo = PhageCore; 79 | }; 80 | 0FDFB3EC22B7EA9200213CC7 /* PBXContainerItemProxy */ = { 81 | isa = PBXContainerItemProxy; 82 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */; 83 | proxyType = 1; 84 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7; 85 | remoteInfo = PhageCore; 86 | }; 87 | /* End PBXContainerItemProxy section */ 88 | 89 | /* Begin PBXCopyFilesBuildPhase section */ 90 | 0F31D90822B7DE26009D305A /* Embed App Extensions */ = { 91 | isa = PBXCopyFilesBuildPhase; 92 | buildActionMask = 2147483647; 93 | dstPath = ""; 94 | dstSubfolderSpec = 13; 95 | files = ( 96 | 0F31D90722B7DE26009D305A /* Phage Injector.appex in Embed App Extensions */, 97 | ); 98 | name = "Embed App Extensions"; 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | 0FDFB3E622B7EA8200213CC7 /* Embed Frameworks */ = { 102 | isa = PBXCopyFilesBuildPhase; 103 | buildActionMask = 2147483647; 104 | dstPath = ""; 105 | dstSubfolderSpec = 10; 106 | files = ( 107 | 0FDFB3E222B7EA8200213CC7 /* PhageCore.framework in Embed Frameworks */, 108 | ); 109 | name = "Embed Frameworks"; 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | 0FDFB3EE22B7EA9200213CC7 /* Embed Frameworks */ = { 113 | isa = PBXCopyFilesBuildPhase; 114 | buildActionMask = 2147483647; 115 | dstPath = ""; 116 | dstSubfolderSpec = 10; 117 | files = ( 118 | 0FDFB3EB22B7EA9200213CC7 /* PhageCore.framework in Embed Frameworks */, 119 | ); 120 | name = "Embed Frameworks"; 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXCopyFilesBuildPhase section */ 124 | 125 | /* Begin PBXFileReference section */ 126 | 0F31D8F322B7DE26009D305A /* Phage Injector.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Phage Injector.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 127 | 0F31D8F522B7DE26009D305A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 128 | 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; }; 129 | 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; }; 130 | 0F31D8FD22B7DE26009D305A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = ""; }; 131 | 0F31D8FF22B7DE26009D305A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 132 | 0F31D90022B7DE26009D305A /* script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = script.js; sourceTree = ""; }; 133 | 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; 134 | 0F31D90422B7DE26009D305A /* Phage_Injector.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Phage_Injector.entitlements; sourceTree = ""; }; 135 | 0FA5315A22B7D33000715AB8 /* Phage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Phage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 136 | 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 137 | 0FA5315F22B7D33000715AB8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 138 | 0FA5316122B7D33100715AB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 139 | 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 140 | 0FA5316722B7D33100715AB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 141 | 0FA5316922B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 142 | 0FA5316A22B7D33200715AB8 /* Phage.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Phage.entitlements; sourceTree = ""; }; 143 | 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 144 | 0FA5317322B7D33200715AB8 /* PhageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageTests.swift; sourceTree = ""; }; 145 | 0FA5317522B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 146 | 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 147 | 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageUITests.swift; sourceTree = ""; }; 148 | 0FA5318022B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 149 | 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhageCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 150 | 0FDFB3CF22B7EA8200213CC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 151 | 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 152 | 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageCoreTests.swift; sourceTree = ""; }; 153 | 0FDFB3DD22B7EA8200213CC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 154 | 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageData.swift; sourceTree = ""; }; 155 | 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDataBundle.swift; sourceTree = ""; }; 156 | 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDataFile.swift; sourceTree = ""; }; 157 | 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDependencies.swift; sourceTree = ""; }; 158 | 4E2B09BD27741C7E00B3E691 /* page-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "page-script.js"; sourceTree = ""; }; 159 | /* End PBXFileReference section */ 160 | 161 | /* Begin PBXFrameworksBuildPhase section */ 162 | 0F31D8F022B7DE26009D305A /* Frameworks */ = { 163 | isa = PBXFrameworksBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 0F31D8F622B7DE26009D305A /* Cocoa.framework in Frameworks */, 167 | 0FDFB3EA22B7EA9200213CC7 /* PhageCore.framework in Frameworks */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | 0FA5315722B7D33000715AB8 /* Frameworks */ = { 172 | isa = PBXFrameworksBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 0FDFB3E122B7EA8200213CC7 /* PhageCore.framework in Frameworks */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | 0FA5316C22B7D33200715AB8 /* Frameworks */ = { 180 | isa = PBXFrameworksBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | 0FA5317722B7D33200715AB8 /* Frameworks */ = { 187 | isa = PBXFrameworksBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | 0FDFB3C922B7EA8200213CC7 /* Frameworks */ = { 194 | isa = PBXFrameworksBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | 0FDFB3D122B7EA8200213CC7 /* Frameworks */ = { 201 | isa = PBXFrameworksBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 0FDFB3D522B7EA8200213CC7 /* PhageCore.framework in Frameworks */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXFrameworksBuildPhase section */ 209 | 210 | /* Begin PBXGroup section */ 211 | 0F31D8F422B7DE26009D305A /* Frameworks */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 0F31D8F522B7DE26009D305A /* Cocoa.framework */, 215 | ); 216 | name = Frameworks; 217 | sourceTree = ""; 218 | }; 219 | 0F31D8F722B7DE26009D305A /* Phage Injector */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */, 223 | 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */, 224 | 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */, 225 | 0F31D8FF22B7DE26009D305A /* Info.plist */, 226 | 0F31D90022B7DE26009D305A /* script.js */, 227 | 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */, 228 | 0F31D90422B7DE26009D305A /* Phage_Injector.entitlements */, 229 | 4E2B09BD27741C7E00B3E691 /* page-script.js */, 230 | ); 231 | path = "Phage Injector"; 232 | sourceTree = ""; 233 | }; 234 | 0FA5315122B7D33000715AB8 = { 235 | isa = PBXGroup; 236 | children = ( 237 | 0FA5315C22B7D33000715AB8 /* Phage */, 238 | 0F31D8F722B7DE26009D305A /* Phage Injector */, 239 | 0FDFB3CD22B7EA8200213CC7 /* PhageCore */, 240 | 0FA5317222B7D33200715AB8 /* PhageTests */, 241 | 0FA5317D22B7D33200715AB8 /* PhageUITests */, 242 | 0FDFB3DA22B7EA8200213CC7 /* PhageCoreTests */, 243 | 0F31D8F422B7DE26009D305A /* Frameworks */, 244 | 0FA5315B22B7D33000715AB8 /* Products */, 245 | ); 246 | sourceTree = ""; 247 | }; 248 | 0FA5315B22B7D33000715AB8 /* Products */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | 0FA5315A22B7D33000715AB8 /* Phage.app */, 252 | 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */, 253 | 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */, 254 | 0F31D8F322B7DE26009D305A /* Phage Injector.appex */, 255 | 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */, 256 | 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */, 257 | ); 258 | name = Products; 259 | sourceTree = ""; 260 | }; 261 | 0FA5315C22B7D33000715AB8 /* Phage */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */, 265 | 0FA5315F22B7D33000715AB8 /* ContentView.swift */, 266 | 0FA5316122B7D33100715AB8 /* Assets.xcassets */, 267 | 0FA5316622B7D33100715AB8 /* Main.storyboard */, 268 | 0FA5316922B7D33200715AB8 /* Info.plist */, 269 | 0FA5316A22B7D33200715AB8 /* Phage.entitlements */, 270 | 0FA5316322B7D33100715AB8 /* Preview Content */, 271 | ); 272 | path = Phage; 273 | sourceTree = ""; 274 | }; 275 | 0FA5316322B7D33100715AB8 /* Preview Content */ = { 276 | isa = PBXGroup; 277 | children = ( 278 | 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */, 279 | ); 280 | path = "Preview Content"; 281 | sourceTree = ""; 282 | }; 283 | 0FA5317222B7D33200715AB8 /* PhageTests */ = { 284 | isa = PBXGroup; 285 | children = ( 286 | 0FA5317322B7D33200715AB8 /* PhageTests.swift */, 287 | 0FA5317522B7D33200715AB8 /* Info.plist */, 288 | ); 289 | path = PhageTests; 290 | sourceTree = ""; 291 | }; 292 | 0FA5317D22B7D33200715AB8 /* PhageUITests */ = { 293 | isa = PBXGroup; 294 | children = ( 295 | 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */, 296 | 0FA5318022B7D33200715AB8 /* Info.plist */, 297 | ); 298 | path = PhageUITests; 299 | sourceTree = ""; 300 | }; 301 | 0FDFB3CD22B7EA8200213CC7 /* PhageCore */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | 0FDFB3CF22B7EA8200213CC7 /* Info.plist */, 305 | 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */, 306 | 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */, 307 | 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */, 308 | 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */, 309 | ); 310 | path = PhageCore; 311 | sourceTree = ""; 312 | }; 313 | 0FDFB3DA22B7EA8200213CC7 /* PhageCoreTests */ = { 314 | isa = PBXGroup; 315 | children = ( 316 | 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */, 317 | 0FDFB3DD22B7EA8200213CC7 /* Info.plist */, 318 | ); 319 | path = PhageCoreTests; 320 | sourceTree = ""; 321 | }; 322 | /* End PBXGroup section */ 323 | 324 | /* Begin PBXHeadersBuildPhase section */ 325 | 0FDFB3C722B7EA8200213CC7 /* Headers */ = { 326 | isa = PBXHeadersBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | /* End PBXHeadersBuildPhase section */ 333 | 334 | /* Begin PBXNativeTarget section */ 335 | 0F31D8F222B7DE26009D305A /* Phage Injector */ = { 336 | isa = PBXNativeTarget; 337 | buildConfigurationList = 0F31D90B22B7DE26009D305A /* Build configuration list for PBXNativeTarget "Phage Injector" */; 338 | buildPhases = ( 339 | 0F31D8EF22B7DE26009D305A /* Sources */, 340 | 0F31D8F022B7DE26009D305A /* Frameworks */, 341 | 0F31D8F122B7DE26009D305A /* Resources */, 342 | 0FDFB3EE22B7EA9200213CC7 /* Embed Frameworks */, 343 | ); 344 | buildRules = ( 345 | ); 346 | dependencies = ( 347 | 0FDFB3ED22B7EA9200213CC7 /* PBXTargetDependency */, 348 | ); 349 | name = "Phage Injector"; 350 | productName = "Phage Injector"; 351 | productReference = 0F31D8F322B7DE26009D305A /* Phage Injector.appex */; 352 | productType = "com.apple.product-type.app-extension"; 353 | }; 354 | 0FA5315922B7D33000715AB8 /* Phage */ = { 355 | isa = PBXNativeTarget; 356 | buildConfigurationList = 0FA5318322B7D33200715AB8 /* Build configuration list for PBXNativeTarget "Phage" */; 357 | buildPhases = ( 358 | 0FA5315622B7D33000715AB8 /* Sources */, 359 | 0FA5315722B7D33000715AB8 /* Frameworks */, 360 | 0FA5315822B7D33000715AB8 /* Resources */, 361 | 0F31D90822B7DE26009D305A /* Embed App Extensions */, 362 | 0FDFB3E622B7EA8200213CC7 /* Embed Frameworks */, 363 | ); 364 | buildRules = ( 365 | ); 366 | dependencies = ( 367 | 0F31D90622B7DE26009D305A /* PBXTargetDependency */, 368 | 0FDFB3E022B7EA8200213CC7 /* PBXTargetDependency */, 369 | ); 370 | name = Phage; 371 | productName = Phage; 372 | productReference = 0FA5315A22B7D33000715AB8 /* Phage.app */; 373 | productType = "com.apple.product-type.application"; 374 | }; 375 | 0FA5316E22B7D33200715AB8 /* PhageTests */ = { 376 | isa = PBXNativeTarget; 377 | buildConfigurationList = 0FA5318622B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageTests" */; 378 | buildPhases = ( 379 | 0FA5316B22B7D33200715AB8 /* Sources */, 380 | 0FA5316C22B7D33200715AB8 /* Frameworks */, 381 | 0FA5316D22B7D33200715AB8 /* Resources */, 382 | ); 383 | buildRules = ( 384 | ); 385 | dependencies = ( 386 | 0FA5317122B7D33200715AB8 /* PBXTargetDependency */, 387 | ); 388 | name = PhageTests; 389 | productName = PhageTests; 390 | productReference = 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */; 391 | productType = "com.apple.product-type.bundle.unit-test"; 392 | }; 393 | 0FA5317922B7D33200715AB8 /* PhageUITests */ = { 394 | isa = PBXNativeTarget; 395 | buildConfigurationList = 0FA5318922B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageUITests" */; 396 | buildPhases = ( 397 | 0FA5317622B7D33200715AB8 /* Sources */, 398 | 0FA5317722B7D33200715AB8 /* Frameworks */, 399 | 0FA5317822B7D33200715AB8 /* Resources */, 400 | ); 401 | buildRules = ( 402 | ); 403 | dependencies = ( 404 | 0FA5317C22B7D33200715AB8 /* PBXTargetDependency */, 405 | ); 406 | name = PhageUITests; 407 | productName = PhageUITests; 408 | productReference = 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */; 409 | productType = "com.apple.product-type.bundle.ui-testing"; 410 | }; 411 | 0FDFB3CB22B7EA8200213CC7 /* PhageCore */ = { 412 | isa = PBXNativeTarget; 413 | buildConfigurationList = 0FDFB3E322B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCore" */; 414 | buildPhases = ( 415 | 0FDFB3C722B7EA8200213CC7 /* Headers */, 416 | 0FDFB3C822B7EA8200213CC7 /* Sources */, 417 | 0FDFB3C922B7EA8200213CC7 /* Frameworks */, 418 | 0FDFB3CA22B7EA8200213CC7 /* Resources */, 419 | ); 420 | buildRules = ( 421 | ); 422 | dependencies = ( 423 | ); 424 | name = PhageCore; 425 | productName = PhageCore; 426 | productReference = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; 427 | productType = "com.apple.product-type.framework"; 428 | }; 429 | 0FDFB3D322B7EA8200213CC7 /* PhageCoreTests */ = { 430 | isa = PBXNativeTarget; 431 | buildConfigurationList = 0FDFB3E722B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCoreTests" */; 432 | buildPhases = ( 433 | 0FDFB3D022B7EA8200213CC7 /* Sources */, 434 | 0FDFB3D122B7EA8200213CC7 /* Frameworks */, 435 | 0FDFB3D222B7EA8200213CC7 /* Resources */, 436 | ); 437 | buildRules = ( 438 | ); 439 | dependencies = ( 440 | 0FDFB3D722B7EA8200213CC7 /* PBXTargetDependency */, 441 | 0FDFB3D922B7EA8200213CC7 /* PBXTargetDependency */, 442 | ); 443 | name = PhageCoreTests; 444 | productName = PhageCoreTests; 445 | productReference = 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */; 446 | productType = "com.apple.product-type.bundle.unit-test"; 447 | }; 448 | /* End PBXNativeTarget section */ 449 | 450 | /* Begin PBXProject section */ 451 | 0FA5315222B7D33000715AB8 /* Project object */ = { 452 | isa = PBXProject; 453 | attributes = { 454 | LastSwiftUpdateCheck = 1100; 455 | LastUpgradeCheck = 1100; 456 | ORGANIZATIONNAME = cpsdqs; 457 | TargetAttributes = { 458 | 0F31D8F222B7DE26009D305A = { 459 | CreatedOnToolsVersion = 11.0; 460 | }; 461 | 0FA5315922B7D33000715AB8 = { 462 | CreatedOnToolsVersion = 11.0; 463 | }; 464 | 0FA5316E22B7D33200715AB8 = { 465 | CreatedOnToolsVersion = 11.0; 466 | TestTargetID = 0FA5315922B7D33000715AB8; 467 | }; 468 | 0FA5317922B7D33200715AB8 = { 469 | CreatedOnToolsVersion = 11.0; 470 | TestTargetID = 0FA5315922B7D33000715AB8; 471 | }; 472 | 0FDFB3CB22B7EA8200213CC7 = { 473 | CreatedOnToolsVersion = 11.0; 474 | LastSwiftMigration = 1100; 475 | }; 476 | 0FDFB3D322B7EA8200213CC7 = { 477 | CreatedOnToolsVersion = 11.0; 478 | }; 479 | }; 480 | }; 481 | buildConfigurationList = 0FA5315522B7D33000715AB8 /* Build configuration list for PBXProject "Phage" */; 482 | compatibilityVersion = "Xcode 9.3"; 483 | developmentRegion = en; 484 | hasScannedForEncodings = 0; 485 | knownRegions = ( 486 | en, 487 | Base, 488 | ); 489 | mainGroup = 0FA5315122B7D33000715AB8; 490 | productRefGroup = 0FA5315B22B7D33000715AB8 /* Products */; 491 | projectDirPath = ""; 492 | projectRoot = ""; 493 | targets = ( 494 | 0FA5315922B7D33000715AB8 /* Phage */, 495 | 0F31D8F222B7DE26009D305A /* Phage Injector */, 496 | 0FA5316E22B7D33200715AB8 /* PhageTests */, 497 | 0FA5317922B7D33200715AB8 /* PhageUITests */, 498 | 0FDFB3CB22B7EA8200213CC7 /* PhageCore */, 499 | 0FDFB3D322B7EA8200213CC7 /* PhageCoreTests */, 500 | ); 501 | }; 502 | /* End PBXProject section */ 503 | 504 | /* Begin PBXResourcesBuildPhase section */ 505 | 0F31D8F122B7DE26009D305A /* Resources */ = { 506 | isa = PBXResourcesBuildPhase; 507 | buildActionMask = 2147483647; 508 | files = ( 509 | 4E2B09BE27741C7E00B3E691 /* page-script.js in Resources */, 510 | 0F31D90322B7DE26009D305A /* ToolbarItemIcon.pdf in Resources */, 511 | 0F31D8FE22B7DE26009D305A /* SafariExtensionViewController.xib in Resources */, 512 | 0F31D90122B7DE26009D305A /* script.js in Resources */, 513 | ); 514 | runOnlyForDeploymentPostprocessing = 0; 515 | }; 516 | 0FA5315822B7D33000715AB8 /* Resources */ = { 517 | isa = PBXResourcesBuildPhase; 518 | buildActionMask = 2147483647; 519 | files = ( 520 | 0FA5316822B7D33100715AB8 /* Main.storyboard in Resources */, 521 | 0FA5316522B7D33100715AB8 /* Preview Assets.xcassets in Resources */, 522 | 0FA5316222B7D33100715AB8 /* Assets.xcassets in Resources */, 523 | ); 524 | runOnlyForDeploymentPostprocessing = 0; 525 | }; 526 | 0FA5316D22B7D33200715AB8 /* Resources */ = { 527 | isa = PBXResourcesBuildPhase; 528 | buildActionMask = 2147483647; 529 | files = ( 530 | ); 531 | runOnlyForDeploymentPostprocessing = 0; 532 | }; 533 | 0FA5317822B7D33200715AB8 /* Resources */ = { 534 | isa = PBXResourcesBuildPhase; 535 | buildActionMask = 2147483647; 536 | files = ( 537 | ); 538 | runOnlyForDeploymentPostprocessing = 0; 539 | }; 540 | 0FDFB3CA22B7EA8200213CC7 /* Resources */ = { 541 | isa = PBXResourcesBuildPhase; 542 | buildActionMask = 2147483647; 543 | files = ( 544 | ); 545 | runOnlyForDeploymentPostprocessing = 0; 546 | }; 547 | 0FDFB3D222B7EA8200213CC7 /* Resources */ = { 548 | isa = PBXResourcesBuildPhase; 549 | buildActionMask = 2147483647; 550 | files = ( 551 | ); 552 | runOnlyForDeploymentPostprocessing = 0; 553 | }; 554 | /* End PBXResourcesBuildPhase section */ 555 | 556 | /* Begin PBXSourcesBuildPhase section */ 557 | 0F31D8EF22B7DE26009D305A /* Sources */ = { 558 | isa = PBXSourcesBuildPhase; 559 | buildActionMask = 2147483647; 560 | files = ( 561 | 0F31D8FB22B7DE26009D305A /* SafariExtensionViewController.swift in Sources */, 562 | 0F31D8F922B7DE26009D305A /* SafariExtensionHandler.swift in Sources */, 563 | ); 564 | runOnlyForDeploymentPostprocessing = 0; 565 | }; 566 | 0FA5315622B7D33000715AB8 /* Sources */ = { 567 | isa = PBXSourcesBuildPhase; 568 | buildActionMask = 2147483647; 569 | files = ( 570 | 0FA5316022B7D33000715AB8 /* ContentView.swift in Sources */, 571 | 0FA5315E22B7D33000715AB8 /* AppDelegate.swift in Sources */, 572 | ); 573 | runOnlyForDeploymentPostprocessing = 0; 574 | }; 575 | 0FA5316B22B7D33200715AB8 /* Sources */ = { 576 | isa = PBXSourcesBuildPhase; 577 | buildActionMask = 2147483647; 578 | files = ( 579 | 0FA5317422B7D33200715AB8 /* PhageTests.swift in Sources */, 580 | ); 581 | runOnlyForDeploymentPostprocessing = 0; 582 | }; 583 | 0FA5317622B7D33200715AB8 /* Sources */ = { 584 | isa = PBXSourcesBuildPhase; 585 | buildActionMask = 2147483647; 586 | files = ( 587 | 0FA5317F22B7D33200715AB8 /* PhageUITests.swift in Sources */, 588 | ); 589 | runOnlyForDeploymentPostprocessing = 0; 590 | }; 591 | 0FDFB3C822B7EA8200213CC7 /* Sources */ = { 592 | isa = PBXSourcesBuildPhase; 593 | buildActionMask = 2147483647; 594 | files = ( 595 | 0FDFB3F022B7EAF700213CC7 /* PhageData.swift in Sources */, 596 | 0FDFB3F622B93C7E00213CC7 /* PhageDataFile.swift in Sources */, 597 | 0FFB95BE22C2291D008D2FAE /* PhageDependencies.swift in Sources */, 598 | 0FDFB3F422B8A10900213CC7 /* PhageDataBundle.swift in Sources */, 599 | ); 600 | runOnlyForDeploymentPostprocessing = 0; 601 | }; 602 | 0FDFB3D022B7EA8200213CC7 /* Sources */ = { 603 | isa = PBXSourcesBuildPhase; 604 | buildActionMask = 2147483647; 605 | files = ( 606 | 0FDFB3DC22B7EA8200213CC7 /* PhageCoreTests.swift in Sources */, 607 | ); 608 | runOnlyForDeploymentPostprocessing = 0; 609 | }; 610 | /* End PBXSourcesBuildPhase section */ 611 | 612 | /* Begin PBXTargetDependency section */ 613 | 0F31D90622B7DE26009D305A /* PBXTargetDependency */ = { 614 | isa = PBXTargetDependency; 615 | target = 0F31D8F222B7DE26009D305A /* Phage Injector */; 616 | targetProxy = 0F31D90522B7DE26009D305A /* PBXContainerItemProxy */; 617 | }; 618 | 0FA5317122B7D33200715AB8 /* PBXTargetDependency */ = { 619 | isa = PBXTargetDependency; 620 | target = 0FA5315922B7D33000715AB8 /* Phage */; 621 | targetProxy = 0FA5317022B7D33200715AB8 /* PBXContainerItemProxy */; 622 | }; 623 | 0FA5317C22B7D33200715AB8 /* PBXTargetDependency */ = { 624 | isa = PBXTargetDependency; 625 | target = 0FA5315922B7D33000715AB8 /* Phage */; 626 | targetProxy = 0FA5317B22B7D33200715AB8 /* PBXContainerItemProxy */; 627 | }; 628 | 0FDFB3D722B7EA8200213CC7 /* PBXTargetDependency */ = { 629 | isa = PBXTargetDependency; 630 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */; 631 | targetProxy = 0FDFB3D622B7EA8200213CC7 /* PBXContainerItemProxy */; 632 | }; 633 | 0FDFB3D922B7EA8200213CC7 /* PBXTargetDependency */ = { 634 | isa = PBXTargetDependency; 635 | target = 0FA5315922B7D33000715AB8 /* Phage */; 636 | targetProxy = 0FDFB3D822B7EA8200213CC7 /* PBXContainerItemProxy */; 637 | }; 638 | 0FDFB3E022B7EA8200213CC7 /* PBXTargetDependency */ = { 639 | isa = PBXTargetDependency; 640 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */; 641 | targetProxy = 0FDFB3DF22B7EA8200213CC7 /* PBXContainerItemProxy */; 642 | }; 643 | 0FDFB3ED22B7EA9200213CC7 /* PBXTargetDependency */ = { 644 | isa = PBXTargetDependency; 645 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */; 646 | targetProxy = 0FDFB3EC22B7EA9200213CC7 /* PBXContainerItemProxy */; 647 | }; 648 | /* End PBXTargetDependency section */ 649 | 650 | /* Begin PBXVariantGroup section */ 651 | 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */ = { 652 | isa = PBXVariantGroup; 653 | children = ( 654 | 0F31D8FD22B7DE26009D305A /* Base */, 655 | ); 656 | name = SafariExtensionViewController.xib; 657 | sourceTree = ""; 658 | }; 659 | 0FA5316622B7D33100715AB8 /* Main.storyboard */ = { 660 | isa = PBXVariantGroup; 661 | children = ( 662 | 0FA5316722B7D33100715AB8 /* Base */, 663 | ); 664 | name = Main.storyboard; 665 | sourceTree = ""; 666 | }; 667 | /* End PBXVariantGroup section */ 668 | 669 | /* Begin XCBuildConfiguration section */ 670 | 0F31D90922B7DE26009D305A /* Debug */ = { 671 | isa = XCBuildConfiguration; 672 | buildSettings = { 673 | CODE_SIGN_ENTITLEMENTS = "Phage Injector/Phage_Injector.entitlements"; 674 | CODE_SIGN_STYLE = Automatic; 675 | DEVELOPMENT_TEAM = S2DETNM9XB; 676 | ENABLE_HARDENED_RUNTIME = YES; 677 | INFOPLIST_FILE = "Phage Injector/Info.plist"; 678 | LD_RUNPATH_SEARCH_PATHS = ( 679 | "$(inherited)", 680 | "@executable_path/../Frameworks", 681 | "@executable_path/../../../../Frameworks", 682 | ); 683 | PRODUCT_BUNDLE_IDENTIFIER = "net.cloudwithlightning.Phage.Phage-Injector"; 684 | PRODUCT_NAME = "$(TARGET_NAME)"; 685 | SKIP_INSTALL = YES; 686 | SWIFT_VERSION = 5.0; 687 | }; 688 | name = Debug; 689 | }; 690 | 0F31D90A22B7DE26009D305A /* Release */ = { 691 | isa = XCBuildConfiguration; 692 | buildSettings = { 693 | CODE_SIGN_ENTITLEMENTS = "Phage Injector/Phage_Injector.entitlements"; 694 | CODE_SIGN_STYLE = Automatic; 695 | DEVELOPMENT_TEAM = S2DETNM9XB; 696 | ENABLE_HARDENED_RUNTIME = YES; 697 | INFOPLIST_FILE = "Phage Injector/Info.plist"; 698 | LD_RUNPATH_SEARCH_PATHS = ( 699 | "$(inherited)", 700 | "@executable_path/../Frameworks", 701 | "@executable_path/../../../../Frameworks", 702 | ); 703 | PRODUCT_BUNDLE_IDENTIFIER = "net.cloudwithlightning.Phage.Phage-Injector"; 704 | PRODUCT_NAME = "$(TARGET_NAME)"; 705 | SKIP_INSTALL = YES; 706 | SWIFT_VERSION = 5.0; 707 | }; 708 | name = Release; 709 | }; 710 | 0FA5318122B7D33200715AB8 /* Debug */ = { 711 | isa = XCBuildConfiguration; 712 | buildSettings = { 713 | ALWAYS_SEARCH_USER_PATHS = NO; 714 | CLANG_ANALYZER_NONNULL = YES; 715 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 716 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 717 | CLANG_CXX_LIBRARY = "libc++"; 718 | CLANG_ENABLE_MODULES = YES; 719 | CLANG_ENABLE_OBJC_ARC = YES; 720 | CLANG_ENABLE_OBJC_WEAK = YES; 721 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 722 | CLANG_WARN_BOOL_CONVERSION = YES; 723 | CLANG_WARN_COMMA = YES; 724 | CLANG_WARN_CONSTANT_CONVERSION = YES; 725 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 726 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 727 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 728 | CLANG_WARN_EMPTY_BODY = YES; 729 | CLANG_WARN_ENUM_CONVERSION = YES; 730 | CLANG_WARN_INFINITE_RECURSION = YES; 731 | CLANG_WARN_INT_CONVERSION = YES; 732 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 733 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 734 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 735 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 736 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 737 | CLANG_WARN_STRICT_PROTOTYPES = YES; 738 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 739 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 740 | CLANG_WARN_UNREACHABLE_CODE = YES; 741 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 742 | COPY_PHASE_STRIP = NO; 743 | DEBUG_INFORMATION_FORMAT = dwarf; 744 | ENABLE_STRICT_OBJC_MSGSEND = YES; 745 | ENABLE_TESTABILITY = YES; 746 | GCC_C_LANGUAGE_STANDARD = gnu11; 747 | GCC_DYNAMIC_NO_PIC = NO; 748 | GCC_NO_COMMON_BLOCKS = YES; 749 | GCC_OPTIMIZATION_LEVEL = 0; 750 | GCC_PREPROCESSOR_DEFINITIONS = ( 751 | "DEBUG=1", 752 | "$(inherited)", 753 | ); 754 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 755 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 756 | GCC_WARN_UNDECLARED_SELECTOR = YES; 757 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 758 | GCC_WARN_UNUSED_FUNCTION = YES; 759 | GCC_WARN_UNUSED_VARIABLE = YES; 760 | MACOSX_DEPLOYMENT_TARGET = 10.15; 761 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 762 | MTL_FAST_MATH = YES; 763 | ONLY_ACTIVE_ARCH = YES; 764 | SDKROOT = macosx; 765 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 766 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 767 | }; 768 | name = Debug; 769 | }; 770 | 0FA5318222B7D33200715AB8 /* Release */ = { 771 | isa = XCBuildConfiguration; 772 | buildSettings = { 773 | ALWAYS_SEARCH_USER_PATHS = NO; 774 | CLANG_ANALYZER_NONNULL = YES; 775 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 776 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 777 | CLANG_CXX_LIBRARY = "libc++"; 778 | CLANG_ENABLE_MODULES = YES; 779 | CLANG_ENABLE_OBJC_ARC = YES; 780 | CLANG_ENABLE_OBJC_WEAK = YES; 781 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 782 | CLANG_WARN_BOOL_CONVERSION = YES; 783 | CLANG_WARN_COMMA = YES; 784 | CLANG_WARN_CONSTANT_CONVERSION = YES; 785 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 786 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 787 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 788 | CLANG_WARN_EMPTY_BODY = YES; 789 | CLANG_WARN_ENUM_CONVERSION = YES; 790 | CLANG_WARN_INFINITE_RECURSION = YES; 791 | CLANG_WARN_INT_CONVERSION = YES; 792 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 793 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 794 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 795 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 796 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 797 | CLANG_WARN_STRICT_PROTOTYPES = YES; 798 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 799 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 800 | CLANG_WARN_UNREACHABLE_CODE = YES; 801 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 802 | COPY_PHASE_STRIP = NO; 803 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 804 | ENABLE_NS_ASSERTIONS = NO; 805 | ENABLE_STRICT_OBJC_MSGSEND = YES; 806 | GCC_C_LANGUAGE_STANDARD = gnu11; 807 | GCC_NO_COMMON_BLOCKS = YES; 808 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 809 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 810 | GCC_WARN_UNDECLARED_SELECTOR = YES; 811 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 812 | GCC_WARN_UNUSED_FUNCTION = YES; 813 | GCC_WARN_UNUSED_VARIABLE = YES; 814 | MACOSX_DEPLOYMENT_TARGET = 10.15; 815 | MTL_ENABLE_DEBUG_INFO = NO; 816 | MTL_FAST_MATH = YES; 817 | SDKROOT = macosx; 818 | SWIFT_COMPILATION_MODE = wholemodule; 819 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 820 | }; 821 | name = Release; 822 | }; 823 | 0FA5318422B7D33200715AB8 /* Debug */ = { 824 | isa = XCBuildConfiguration; 825 | buildSettings = { 826 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 827 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 828 | CODE_SIGN_ENTITLEMENTS = Phage/Phage.entitlements; 829 | CODE_SIGN_STYLE = Automatic; 830 | COMBINE_HIDPI_IMAGES = YES; 831 | DEVELOPMENT_ASSET_PATHS = "Phage/Preview\\ Content"; 832 | DEVELOPMENT_TEAM = S2DETNM9XB; 833 | ENABLE_HARDENED_RUNTIME = YES; 834 | ENABLE_PREVIEWS = YES; 835 | INFOPLIST_FILE = Phage/Info.plist; 836 | LD_RUNPATH_SEARCH_PATHS = ( 837 | "$(inherited)", 838 | "@executable_path/../Frameworks", 839 | ); 840 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.Phage; 841 | PRODUCT_NAME = "$(TARGET_NAME)"; 842 | SWIFT_VERSION = 5.0; 843 | }; 844 | name = Debug; 845 | }; 846 | 0FA5318522B7D33200715AB8 /* Release */ = { 847 | isa = XCBuildConfiguration; 848 | buildSettings = { 849 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 850 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 851 | CODE_SIGN_ENTITLEMENTS = Phage/Phage.entitlements; 852 | CODE_SIGN_STYLE = Automatic; 853 | COMBINE_HIDPI_IMAGES = YES; 854 | DEVELOPMENT_ASSET_PATHS = "Phage/Preview\\ Content"; 855 | DEVELOPMENT_TEAM = S2DETNM9XB; 856 | ENABLE_HARDENED_RUNTIME = YES; 857 | ENABLE_PREVIEWS = YES; 858 | INFOPLIST_FILE = Phage/Info.plist; 859 | LD_RUNPATH_SEARCH_PATHS = ( 860 | "$(inherited)", 861 | "@executable_path/../Frameworks", 862 | ); 863 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.Phage; 864 | PRODUCT_NAME = "$(TARGET_NAME)"; 865 | SWIFT_VERSION = 5.0; 866 | }; 867 | name = Release; 868 | }; 869 | 0FA5318722B7D33200715AB8 /* Debug */ = { 870 | isa = XCBuildConfiguration; 871 | buildSettings = { 872 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 873 | BUNDLE_LOADER = "$(TEST_HOST)"; 874 | CODE_SIGN_STYLE = Automatic; 875 | COMBINE_HIDPI_IMAGES = YES; 876 | DEVELOPMENT_TEAM = S2DETNM9XB; 877 | INFOPLIST_FILE = PhageTests/Info.plist; 878 | LD_RUNPATH_SEARCH_PATHS = ( 879 | "$(inherited)", 880 | "@executable_path/../Frameworks", 881 | "@loader_path/../Frameworks", 882 | ); 883 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageTests; 884 | PRODUCT_NAME = "$(TARGET_NAME)"; 885 | SWIFT_VERSION = 5.0; 886 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Phage.app/Contents/MacOS/Phage"; 887 | }; 888 | name = Debug; 889 | }; 890 | 0FA5318822B7D33200715AB8 /* Release */ = { 891 | isa = XCBuildConfiguration; 892 | buildSettings = { 893 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 894 | BUNDLE_LOADER = "$(TEST_HOST)"; 895 | CODE_SIGN_STYLE = Automatic; 896 | COMBINE_HIDPI_IMAGES = YES; 897 | DEVELOPMENT_TEAM = S2DETNM9XB; 898 | INFOPLIST_FILE = PhageTests/Info.plist; 899 | LD_RUNPATH_SEARCH_PATHS = ( 900 | "$(inherited)", 901 | "@executable_path/../Frameworks", 902 | "@loader_path/../Frameworks", 903 | ); 904 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageTests; 905 | PRODUCT_NAME = "$(TARGET_NAME)"; 906 | SWIFT_VERSION = 5.0; 907 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Phage.app/Contents/MacOS/Phage"; 908 | }; 909 | name = Release; 910 | }; 911 | 0FA5318A22B7D33200715AB8 /* Debug */ = { 912 | isa = XCBuildConfiguration; 913 | buildSettings = { 914 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 915 | CODE_SIGN_STYLE = Automatic; 916 | COMBINE_HIDPI_IMAGES = YES; 917 | DEVELOPMENT_TEAM = S2DETNM9XB; 918 | INFOPLIST_FILE = PhageUITests/Info.plist; 919 | LD_RUNPATH_SEARCH_PATHS = ( 920 | "$(inherited)", 921 | "@executable_path/../Frameworks", 922 | "@loader_path/../Frameworks", 923 | ); 924 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageUITests; 925 | PRODUCT_NAME = "$(TARGET_NAME)"; 926 | SWIFT_VERSION = 5.0; 927 | TEST_TARGET_NAME = Phage; 928 | }; 929 | name = Debug; 930 | }; 931 | 0FA5318B22B7D33200715AB8 /* Release */ = { 932 | isa = XCBuildConfiguration; 933 | buildSettings = { 934 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 935 | CODE_SIGN_STYLE = Automatic; 936 | COMBINE_HIDPI_IMAGES = YES; 937 | DEVELOPMENT_TEAM = S2DETNM9XB; 938 | INFOPLIST_FILE = PhageUITests/Info.plist; 939 | LD_RUNPATH_SEARCH_PATHS = ( 940 | "$(inherited)", 941 | "@executable_path/../Frameworks", 942 | "@loader_path/../Frameworks", 943 | ); 944 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageUITests; 945 | PRODUCT_NAME = "$(TARGET_NAME)"; 946 | SWIFT_VERSION = 5.0; 947 | TEST_TARGET_NAME = Phage; 948 | }; 949 | name = Release; 950 | }; 951 | 0FDFB3E422B7EA8200213CC7 /* Debug */ = { 952 | isa = XCBuildConfiguration; 953 | buildSettings = { 954 | APPLICATION_EXTENSION_API_ONLY = YES; 955 | CLANG_ENABLE_MODULES = YES; 956 | CODE_SIGN_STYLE = Automatic; 957 | COMBINE_HIDPI_IMAGES = YES; 958 | CURRENT_PROJECT_VERSION = 1; 959 | DEFINES_MODULE = YES; 960 | DEVELOPMENT_TEAM = S2DETNM9XB; 961 | DYLIB_COMPATIBILITY_VERSION = 1; 962 | DYLIB_CURRENT_VERSION = 1; 963 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 964 | FRAMEWORK_VERSION = A; 965 | INFOPLIST_FILE = PhageCore/Info.plist; 966 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 967 | LD_RUNPATH_SEARCH_PATHS = ( 968 | "$(inherited)", 969 | "@executable_path/../Frameworks", 970 | "@loader_path/Frameworks", 971 | ); 972 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCore; 973 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 974 | SKIP_INSTALL = YES; 975 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 976 | SWIFT_VERSION = 5.0; 977 | VERSIONING_SYSTEM = "apple-generic"; 978 | VERSION_INFO_PREFIX = ""; 979 | }; 980 | name = Debug; 981 | }; 982 | 0FDFB3E522B7EA8200213CC7 /* Release */ = { 983 | isa = XCBuildConfiguration; 984 | buildSettings = { 985 | APPLICATION_EXTENSION_API_ONLY = YES; 986 | CLANG_ENABLE_MODULES = YES; 987 | CODE_SIGN_STYLE = Automatic; 988 | COMBINE_HIDPI_IMAGES = YES; 989 | CURRENT_PROJECT_VERSION = 1; 990 | DEFINES_MODULE = YES; 991 | DEVELOPMENT_TEAM = S2DETNM9XB; 992 | DYLIB_COMPATIBILITY_VERSION = 1; 993 | DYLIB_CURRENT_VERSION = 1; 994 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 995 | FRAMEWORK_VERSION = A; 996 | INFOPLIST_FILE = PhageCore/Info.plist; 997 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 998 | LD_RUNPATH_SEARCH_PATHS = ( 999 | "$(inherited)", 1000 | "@executable_path/../Frameworks", 1001 | "@loader_path/Frameworks", 1002 | ); 1003 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCore; 1004 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 1005 | SKIP_INSTALL = YES; 1006 | SWIFT_VERSION = 5.0; 1007 | VERSIONING_SYSTEM = "apple-generic"; 1008 | VERSION_INFO_PREFIX = ""; 1009 | }; 1010 | name = Release; 1011 | }; 1012 | 0FDFB3E822B7EA8200213CC7 /* Debug */ = { 1013 | isa = XCBuildConfiguration; 1014 | buildSettings = { 1015 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1016 | CODE_SIGN_STYLE = Automatic; 1017 | COMBINE_HIDPI_IMAGES = YES; 1018 | DEVELOPMENT_TEAM = S2DETNM9XB; 1019 | INFOPLIST_FILE = PhageCoreTests/Info.plist; 1020 | LD_RUNPATH_SEARCH_PATHS = ( 1021 | "$(inherited)", 1022 | "@executable_path/../Frameworks", 1023 | "@loader_path/../Frameworks", 1024 | ); 1025 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCoreTests; 1026 | PRODUCT_NAME = "$(TARGET_NAME)"; 1027 | SWIFT_VERSION = 5.0; 1028 | }; 1029 | name = Debug; 1030 | }; 1031 | 0FDFB3E922B7EA8200213CC7 /* Release */ = { 1032 | isa = XCBuildConfiguration; 1033 | buildSettings = { 1034 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1035 | CODE_SIGN_STYLE = Automatic; 1036 | COMBINE_HIDPI_IMAGES = YES; 1037 | DEVELOPMENT_TEAM = S2DETNM9XB; 1038 | INFOPLIST_FILE = PhageCoreTests/Info.plist; 1039 | LD_RUNPATH_SEARCH_PATHS = ( 1040 | "$(inherited)", 1041 | "@executable_path/../Frameworks", 1042 | "@loader_path/../Frameworks", 1043 | ); 1044 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCoreTests; 1045 | PRODUCT_NAME = "$(TARGET_NAME)"; 1046 | SWIFT_VERSION = 5.0; 1047 | }; 1048 | name = Release; 1049 | }; 1050 | /* End XCBuildConfiguration section */ 1051 | 1052 | /* Begin XCConfigurationList section */ 1053 | 0F31D90B22B7DE26009D305A /* Build configuration list for PBXNativeTarget "Phage Injector" */ = { 1054 | isa = XCConfigurationList; 1055 | buildConfigurations = ( 1056 | 0F31D90922B7DE26009D305A /* Debug */, 1057 | 0F31D90A22B7DE26009D305A /* Release */, 1058 | ); 1059 | defaultConfigurationIsVisible = 0; 1060 | defaultConfigurationName = Release; 1061 | }; 1062 | 0FA5315522B7D33000715AB8 /* Build configuration list for PBXProject "Phage" */ = { 1063 | isa = XCConfigurationList; 1064 | buildConfigurations = ( 1065 | 0FA5318122B7D33200715AB8 /* Debug */, 1066 | 0FA5318222B7D33200715AB8 /* Release */, 1067 | ); 1068 | defaultConfigurationIsVisible = 0; 1069 | defaultConfigurationName = Release; 1070 | }; 1071 | 0FA5318322B7D33200715AB8 /* Build configuration list for PBXNativeTarget "Phage" */ = { 1072 | isa = XCConfigurationList; 1073 | buildConfigurations = ( 1074 | 0FA5318422B7D33200715AB8 /* Debug */, 1075 | 0FA5318522B7D33200715AB8 /* Release */, 1076 | ); 1077 | defaultConfigurationIsVisible = 0; 1078 | defaultConfigurationName = Release; 1079 | }; 1080 | 0FA5318622B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageTests" */ = { 1081 | isa = XCConfigurationList; 1082 | buildConfigurations = ( 1083 | 0FA5318722B7D33200715AB8 /* Debug */, 1084 | 0FA5318822B7D33200715AB8 /* Release */, 1085 | ); 1086 | defaultConfigurationIsVisible = 0; 1087 | defaultConfigurationName = Release; 1088 | }; 1089 | 0FA5318922B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageUITests" */ = { 1090 | isa = XCConfigurationList; 1091 | buildConfigurations = ( 1092 | 0FA5318A22B7D33200715AB8 /* Debug */, 1093 | 0FA5318B22B7D33200715AB8 /* Release */, 1094 | ); 1095 | defaultConfigurationIsVisible = 0; 1096 | defaultConfigurationName = Release; 1097 | }; 1098 | 0FDFB3E322B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCore" */ = { 1099 | isa = XCConfigurationList; 1100 | buildConfigurations = ( 1101 | 0FDFB3E422B7EA8200213CC7 /* Debug */, 1102 | 0FDFB3E522B7EA8200213CC7 /* Release */, 1103 | ); 1104 | defaultConfigurationIsVisible = 0; 1105 | defaultConfigurationName = Release; 1106 | }; 1107 | 0FDFB3E722B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCoreTests" */ = { 1108 | isa = XCConfigurationList; 1109 | buildConfigurations = ( 1110 | 0FDFB3E822B7EA8200213CC7 /* Debug */, 1111 | 0FDFB3E922B7EA8200213CC7 /* Release */, 1112 | ); 1113 | defaultConfigurationIsVisible = 0; 1114 | defaultConfigurationName = Release; 1115 | }; 1116 | /* End XCConfigurationList section */ 1117 | }; 1118 | rootObject = 0FA5315222B7D33000715AB8 /* Project object */; 1119 | } 1120 | -------------------------------------------------------------------------------- /Phage.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Phage.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Phage/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Phage 4 | // 5 | // Created by cpsdqs on 2019-06-17. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwiftUI 11 | import PhageCore 12 | 13 | @NSApplicationMain 14 | class AppDelegate : NSObject, NSApplicationDelegate { 15 | 16 | var window: NSWindow! 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | window = NSWindow( 20 | contentRect: NSRect(x: 0, y: 0, width: 600, height: 480), 21 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 22 | backing: .buffered, defer: false) 23 | window.center() 24 | window.setFrameAutosaveName("Main Window") 25 | window.title = "Phage" 26 | 27 | window.contentView = NSHostingView(rootView: ContentView()) 28 | 29 | window.makeKeyAndOrderFront(nil) 30 | } 31 | 32 | func applicationWillTerminate(_ aNotification: Notification) { 33 | } 34 | 35 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 36 | return true 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Phage/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Phage/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /Phage/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Phage 4 | // 5 | // Created by cpsdqs on 2019-06-17. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import PhageCore 11 | 12 | struct ContentView : View { 13 | @ObservedObject var data = PhageData() 14 | 15 | private func sortedBundles() -> [PhageDataBundle] { 16 | return data.bundles.sorted(by: { (arg0, arg1) -> Bool in 17 | let (lhs, _) = arg0 18 | let (rhs, _) = arg1 19 | return lhs.lexicographicallyPrecedes(rhs) 20 | }).map({ (arg0) -> PhageDataBundle in 21 | let (_, value) = arg0 22 | return value 23 | }) 24 | } 25 | 26 | var body: some View { 27 | NavigationView { 28 | List { 29 | HStack(alignment: .firstTextBaseline) { 30 | Text("Bundles").font(.title).bold() 31 | Spacer() 32 | Button(action: { 33 | NSWorkspace.shared.open(bundlesURL) 34 | }) { 35 | Text("Show in Finder") 36 | } 37 | } 38 | ForEach(sortedBundles()) { bundle in SidebarBundle(bundle: bundle, data: self.data) } 39 | if data.bundles.count == 0 { 40 | HStack { 41 | Spacer() 42 | Text("No bundles").foregroundColor(.gray) 43 | Spacer() 44 | } 45 | } 46 | }.frame(width: 300) 47 | } 48 | } 49 | } 50 | 51 | struct SidebarBundle : View { 52 | private struct BundleToggle : View { 53 | @Binding var enabled: Bool 54 | var body: some View { 55 | // using the checkbox toggle style doesn't play nice with the computed binding for 56 | // some reason 57 | Toggle(isOn: $enabled) { 58 | EmptyView() 59 | }.toggleStyle(SwitchToggleStyle()) 60 | } 61 | } 62 | 63 | @ObservedObject var bundle: PhageDataBundle 64 | @State var hackyDisabledProxy = false 65 | var data: PhageData 66 | 67 | var body: some View { 68 | NavigationLink( 69 | destination: 70 | BundleView(dependencies: self.data.dependencies, bundle: bundle) 71 | .frame(minWidth: 300) 72 | ) { 73 | HStack { 74 | VStack(alignment: .leading) { 75 | Text(bundle.url.lastPathComponent).foregroundColor(hackyDisabledProxy ? .secondary : nil) 76 | if !hackyDisabledProxy { 77 | Text("Files: \(bundle.files.count)").foregroundColor(.secondary) 78 | } 79 | }.frame(minHeight: 32).animation(.easeOut) 80 | Spacer() 81 | BundleToggle(enabled: Binding(get: { 82 | !self.bundle.disabled 83 | }, set: { enabled in 84 | self.bundle.disabled = !enabled 85 | self.hackyDisabledProxy = self.bundle.disabled 86 | })).onAppear { 87 | self.hackyDisabledProxy = self.bundle.disabled 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | struct BundleView : View { 95 | private struct MaybeNone : View { 96 | var count: Int 97 | var text: String 98 | var body: some View { 99 | if count == 0 { 100 | return AnyView(Text(text).frame(maxWidth: .infinity).padding(.bottom)) 101 | } else { 102 | return AnyView(EmptyView()) 103 | } 104 | } 105 | } 106 | 107 | var dependencies: PhageDependencies 108 | @ObservedObject var bundle: PhageDataBundle 109 | 110 | var body: some View { 111 | List { 112 | HStack(alignment: .lastTextBaseline) { 113 | Text(bundle.url.lastPathComponent).font(.title).bold().padding(.top) 114 | Spacer() 115 | Button(action: { 116 | NSWorkspace.shared.open(self.bundle.url) 117 | }) { 118 | Text("Open") 119 | } 120 | } 121 | Spacer() 122 | 123 | Text("Files").font(.headline).bold() 124 | MaybeNone(count: bundle.files.count, text: "No files") 125 | ForEach(Array(bundle.files.values.sorted(by: { a, b in 126 | a.url.lastPathComponent.lexicographicallyPrecedes(b.url.lastPathComponent) 127 | }))) { item in 128 | BundleFileView(file: item) 129 | } 130 | Spacer() 131 | 132 | Text("Dependencies").font(.headline).bold() 133 | MaybeNone(count: bundle.dependencies().count, text: "No dependencies") 134 | ForEach(bundle.dependencies().map { IdURL($0) }) { item in 135 | DependencyView(dependencies: self.dependencies, url: item.url) 136 | } 137 | } 138 | } 139 | } 140 | 141 | struct BundleFileView : View { 142 | var file: PhageDataFile 143 | 144 | var body: some View { 145 | VStack(alignment: .leading) { 146 | HStack { 147 | Text(file.url.lastPathComponent).bold() 148 | Spacer() 149 | Button(action: { 150 | NSWorkspace.shared.openFile(self.file.url.path) 151 | }) { 152 | Text("Edit") 153 | } 154 | } 155 | FileContents(contents: file.contents()) 156 | if file.type == .javascript { 157 | ScriptFileAttributes(file: file) 158 | } 159 | }.padding() 160 | .background(Color(NSColor.controlBackgroundColor).cornerRadius(8)) 161 | } 162 | } 163 | 164 | private struct ForEachIdTag { 165 | var id: Int 166 | var data: T 167 | } 168 | 169 | struct FileContents : View { 170 | var contents: PhageDataFile.Contents? 171 | var body: some View { 172 | switch contents { 173 | case .none: 174 | return AnyView(Text("could not read file contents").foregroundColor(.secondary)) 175 | case .some(.javascript(let section)): 176 | return AnyView(SectionView(section: section)) 177 | case .some(.stylesheets(let sections)): 178 | if sections.isEmpty { 179 | return AnyView(Text("no stylesheet sections").foregroundColor(.secondary)) 180 | } 181 | return AnyView(VStack(alignment: .leading) { 182 | ForEach(sections.enumerated().map({ (i, s) in ForEachIdTag(id: i, data: s) }), id: \.id) { item in 183 | SectionView(stylesheetNumber: item.id + 1, section: item.data) 184 | } 185 | }) 186 | } 187 | } 188 | } 189 | 190 | struct SectionView : View { 191 | private struct Title: View { 192 | var number: Int? 193 | var body: some View { 194 | if let number = number { 195 | return Text("Stylesheet Section #\(number)") 196 | } else { 197 | return Text("Script") 198 | } 199 | } 200 | } 201 | 202 | var stylesheetNumber: Int? 203 | var section: PhageCore.Section 204 | 205 | var body: some View { 206 | VStack(alignment: .leading) { 207 | Title(number: stylesheetNumber) 208 | VStack(alignment: .leading) { 209 | ForEach(section.rules.enumerated().map({ (i, s) in ForEachIdTag(id: i, data: s) }), id: \.id) { rule in 210 | SectionMatchRule(rule: rule.data) 211 | } 212 | }.padding(.leading).padding(.top, 4) 213 | } 214 | } 215 | } 216 | 217 | struct SectionMatchRule : View { 218 | private struct Tag : View { 219 | var text: String 220 | var body: some View { 221 | Text(text) 222 | .font(.system(size: 10)) 223 | .padding(2) 224 | .cornerRadius(4) 225 | .background(Color(NSColor.windowBackgroundColor).cornerRadius(4)) 226 | } 227 | } 228 | private struct Value : View { 229 | var value: String 230 | var body: some View { 231 | Text(value) 232 | .font(.system(size: 11, weight: .regular, design: .monospaced)) 233 | .padding(2) 234 | } 235 | } 236 | 237 | var rule: PhageCore.MatchRule 238 | 239 | var body: some View { 240 | switch rule { 241 | case .domain(let domain): 242 | return HStack { Tag(text: "Domain"); Value(value: domain) } 243 | case .exact(let exact): 244 | return HStack { Tag(text: "Exact"); Value(value: exact) } 245 | case .glob(let glob): 246 | return HStack { Tag(text: "Glob"); Value(value: glob) } 247 | case .prefix(let prefix): 248 | return HStack { Tag(text: "Prefix"); Value(value: prefix) } 249 | case .regexp(let regexp): 250 | return HStack { Tag(text: "RegExp"); Value(value: regexp) } 251 | } 252 | } 253 | } 254 | 255 | struct ScriptFileAttributes : View { 256 | var file: PhageDataFile 257 | 258 | var body: some View { 259 | VStack(alignment: .leading) { 260 | Toggle("Inject into page context", isOn: Binding(get: { 261 | file.inPageContext 262 | }, set: { val in 263 | file.inPageContext = val 264 | })) 265 | } 266 | } 267 | } 268 | 269 | struct DependencyView : View { 270 | var dependencies: PhageDependencies 271 | var url: URL 272 | 273 | @State var loading = false 274 | @State var loaded = false 275 | 276 | var body: some View { 277 | HStack { 278 | VStack(alignment: .leading) { 279 | Text(url.lastPathComponent) 280 | Text(url.absoluteString).font(Font.system(size: 10)) 281 | } 282 | Spacer() 283 | if loading { 284 | Text("Loading") 285 | } else if loaded { 286 | Text("Loaded") 287 | } 288 | }.onTapGesture { 289 | debugPrint("Loading dependency \(self.url)") 290 | self.loading = true 291 | self.dependencies.loadDependency(at: self.url) { data in 292 | if data != nil { 293 | debugPrint("Loaded") 294 | self.loaded = true 295 | self.loading = false 296 | } else { 297 | debugPrint("Failed to load") 298 | self.loading = false 299 | } 300 | } 301 | }.onAppear { 302 | self.loaded = self.dependencies.getDependencyContents(of: self.url) != nil 303 | } 304 | } 305 | } 306 | 307 | #if DEBUG 308 | struct ContentView_Previews : PreviewProvider { 309 | static var previews: some View { 310 | ContentView() 311 | } 312 | } 313 | #endif 314 | 315 | class IdURL : Identifiable { 316 | var url: URL 317 | 318 | var id: String { 319 | return url.absoluteString 320 | } 321 | 322 | init(_ url: URL) { 323 | self.url = url 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /Phage/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2019 cpsdqs. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | TeamIdentifierPrefix 38 | $(TeamIdentifierPrefix) 39 | 40 | 41 | -------------------------------------------------------------------------------- /Phage/Phage.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)net.cloudwithlightning.phage 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Phage/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PhageCore/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2019 cpsdqs. All rights reserved. 23 | TeamIdentifierPrefix 24 | $(TeamIdentifierPrefix) 25 | 26 | 27 | -------------------------------------------------------------------------------- /PhageCore/PhageData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageData.swift 3 | // PhageCore 4 | // 5 | // Created by cpsdqs on 2019-06-17. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | public let appGroupID = Bundle.main.infoDictionary!["TeamIdentifierPrefix"] as! String + "net.cloudwithlightning.phage" 13 | public let bundlesURL = FileManager.default 14 | .containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! 15 | .appendingPathComponent("bundles") 16 | public let dependenciesURL = FileManager.default 17 | .containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! 18 | .appendingPathComponent("dependencies") 19 | 20 | /// Observes Phage user data, i.e. scripts and stylesheets. 21 | public class PhageData : NSObject, NSFilePresenter, ObservableObject { 22 | 23 | public var dependencies: PhageDependencies! = nil 24 | 25 | public override init() { 26 | super.init() 27 | 28 | dependencies = PhageDependencies(owner: self) 29 | NSFileCoordinator.addFilePresenter(self) 30 | 31 | reload() 32 | } 33 | 34 | // MARK: - Bundle Handling 35 | 36 | @Published public var bundles: [String:PhageDataBundle] = [:] 37 | 38 | /// Reloads all bundles. 39 | func reload() { 40 | bundles = [:] 41 | 42 | let directoryContents = try? FileManager.default.contentsOfDirectory( 43 | at: bundlesURL, 44 | includingPropertiesForKeys: nil, 45 | options: .skipsHiddenFiles 46 | ) 47 | 48 | if let directoryContents = directoryContents { 49 | for subitemURL in directoryContents { 50 | let _ = tryEnsureBundle(enclosing: subitemURL) 51 | } 52 | } 53 | } 54 | 55 | /// Returns the enclosing bundle’s URL and the remaining subpath. 56 | func bundleURLAndSubpath(enclosing url: URL) -> (URL, [String])? { 57 | var pathComponents = url.pathComponents 58 | for component in bundlesURL.pathComponents { 59 | // check if path matches and pop front 60 | if pathComponents.first == component { 61 | pathComponents.removeFirst() 62 | } else { 63 | return nil 64 | } 65 | } 66 | if pathComponents.isEmpty { 67 | return nil 68 | } 69 | let bundleURL = bundlesURL.appendingPathComponent(pathComponents.removeFirst()) 70 | return (bundleURL, pathComponents) 71 | } 72 | 73 | func bundleName(for bundleURL: URL) -> String { 74 | return bundleURL.lastPathComponent 75 | } 76 | 77 | func tryEnsureBundle(enclosing url: URL) -> Bool { 78 | if let (bundleURL, _) = bundleURLAndSubpath(enclosing: url) { 79 | let name = bundleName(for: bundleURL) 80 | if bundles[name] == nil { 81 | if let bundle = PhageDataBundle(at: bundleURL) { 82 | bundles[name] = bundle 83 | return true 84 | } 85 | } else { 86 | return true 87 | } 88 | } 89 | return false 90 | } 91 | 92 | func didDeleteBundle(at url: URL) { 93 | let name = bundleName(for: url) 94 | if bundles[name] != nil { 95 | bundles.removeValue(forKey: name) 96 | } 97 | } 98 | 99 | func didDeleteBundleFile(at bundleURL: URL, subPath: [String]) { 100 | let name = bundleName(for: bundleURL) 101 | if tryEnsureBundle(enclosing: bundleURL) { 102 | if subPath.count == 1 { 103 | // TODO: what about deeper items? 104 | bundles[name]!.deletedFile(at: bundleURL.appendingPathComponent(subPath[0])) 105 | } 106 | } 107 | } 108 | 109 | func didChangeBundleFile(at bundleURL: URL, subPath: [String]) { 110 | let name = bundleName(for: bundleURL) 111 | if tryEnsureBundle(enclosing: bundleURL) { 112 | if subPath.count == 1 { 113 | // TODO: what about deeper items? 114 | bundles[name]!.updatedFile(at: bundleURL.appendingPathComponent(subPath[0])) 115 | } 116 | } 117 | } 118 | 119 | // MARK: - NSFilePresenter 120 | 121 | public var presentedItemURL: URL? { 122 | get { 123 | return bundlesURL 124 | } 125 | } 126 | public var presentedItemOperationQueue: OperationQueue = OperationQueue.main 127 | 128 | public func accommodatePresentedItemDeletion(completionHandler: @escaping (Error?) -> Void) { 129 | reload() 130 | } 131 | public func presentedItemDidMove(to newURL: URL) { 132 | reload() 133 | } 134 | public func presentedItemDidChange() { 135 | reload() 136 | } 137 | 138 | public func accommodatePresentedSubitemDeletion(at url: URL, completionHandler: @escaping (Error?) -> Void) { 139 | if let (bundleURL, subPath) = bundleURLAndSubpath(enclosing: url) { 140 | if subPath.isEmpty { 141 | // bundle has been deleted 142 | didDeleteBundle(at: bundleURL) 143 | } else { 144 | didDeleteBundleFile(at: bundleURL, subPath: subPath) 145 | } 146 | } 147 | } 148 | public func presentedSubitemDidAppear(at url: URL) { 149 | // just forward to didChange because it’s handled the same way 150 | presentedSubitemDidChange(at: url) 151 | } 152 | public func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL) { 153 | accommodatePresentedSubitemDeletion(at: oldURL) { _ in } 154 | // this is fine because presentedSubitemDidChange(at:) checks if it’s a valid url 155 | presentedSubitemDidChange(at: newURL) 156 | } 157 | public func presentedSubitemDidChange(at url: URL) { 158 | // may have been deleted instead 159 | if !FileManager.default.fileExists(atPath: url.path) { 160 | accommodatePresentedSubitemDeletion(at: url, completionHandler: { _ in }) 161 | return 162 | } 163 | 164 | if let (bundleURL, subPath) = bundleURLAndSubpath(enclosing: url) { 165 | if subPath.isEmpty { 166 | // a bundle was added (or changed, somehow?) 167 | let _ = tryEnsureBundle(enclosing: bundleURL) 168 | } else { 169 | didChangeBundleFile(at: bundleURL, subPath: subPath) 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /PhageCore/PhageDataBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageDataBundle.swift 3 | // PhageCore 4 | // 5 | // Created by cpsdqs on 2019-06-18. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | public class PhageDataBundle : NSObject, ObservableObject, Identifiable { 13 | 14 | public let url: URL 15 | @Published public var files: [String:PhageDataFile] = [:] 16 | public var disabled: Bool { 17 | get { 18 | // check if the disabled xattr exists 19 | return url.withUnsafeFileSystemRepresentation { path in 20 | let code = getxattr(path, "disabled", nil, 0, 0, 0) 21 | // if getxattr fails that probably means the attribute doesn't exist 22 | return code != -1 23 | } 24 | } 25 | set(disabled) { 26 | url.withUnsafeFileSystemRepresentation { path in 27 | if disabled { 28 | let value = "yes".data(using: .utf8)! 29 | let result = value.withUnsafeBytes { data in 30 | setxattr(path, "disabled", data.baseAddress, data.count, 0, 0) 31 | } 32 | if result < 0 { 33 | NSLog("Failed to set disabled state on bundle at \(url)") 34 | } 35 | } else { 36 | removexattr(path, "disabled", 0) 37 | } 38 | } 39 | } 40 | } 41 | 42 | init?(at url: URL) { 43 | self.url = url 44 | 45 | super.init() 46 | 47 | guard let contents = try? FileManager.default.contentsOfDirectory( 48 | at: url, 49 | includingPropertiesForKeys: nil, 50 | options: .skipsHiddenFiles 51 | ) else { return nil } 52 | 53 | for subitemURL in contents { 54 | updatedFile(at: subitemURL) 55 | } 56 | } 57 | 58 | public func dependencies() -> [URL] { 59 | var dependencies: [URL] = [] 60 | for (_, file) in files { 61 | dependencies.append(contentsOf: file.dependencies() 62 | .map { URL(string: $0) } 63 | .filter { $0 != nil } 64 | .map { $0! }) 65 | } 66 | return dependencies 67 | } 68 | 69 | /// Handles a new or changed file. 70 | /// - Parameter url: a top-level subitem of this bundle. Will not be checked for validity 71 | func updatedFile(at url: URL) { 72 | let name = url.lastPathComponent 73 | 74 | if let file = PhageDataFile(at: url) { 75 | files[name] = file 76 | } 77 | } 78 | 79 | func deletedFile(at url: URL) { 80 | let name = url.lastPathComponent 81 | files.removeValue(forKey: name) 82 | } 83 | 84 | // MARK: - Identifiable 85 | public var id: URL { 86 | get { 87 | return url 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /PhageCore/PhageDataFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageDataFile.swift 3 | // PhageCore 4 | // 5 | // Created by cpsdqs on 2019-06-18. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class PhageDataFile : NSObject, Identifiable { 12 | public let url: URL 13 | public let type: FileType 14 | 15 | init?(at url: URL) { 16 | let pathExtension = url.pathExtension 17 | 18 | let type: PhageDataFile.FileType 19 | 20 | switch pathExtension { 21 | case "js": 22 | type = .javascript 23 | case "css": 24 | type = .stylesheet 25 | default: 26 | // unsupported 27 | return nil 28 | } 29 | 30 | self.url = url 31 | self.type = type 32 | } 33 | 34 | var cachedContents: Contents? 35 | var cachedDependencies: [String]? 36 | 37 | public func contents() -> Contents? { 38 | if let contents = cachedContents { 39 | return contents 40 | } 41 | 42 | guard let rawContents = loadRawContents() else { return nil } 43 | 44 | switch type { 45 | case .javascript: 46 | if let meta = parseScriptMetadata(for: rawContents) { 47 | cachedContents = .javascript(Section(rules: meta.matches, contents: rawContents)) 48 | cachedDependencies = meta.requires 49 | } 50 | case .stylesheet: 51 | cachedContents = .stylesheets(parseCSSFileSections(rawContents)) 52 | } 53 | 54 | return cachedContents 55 | } 56 | 57 | public func dependencies() -> [String] { 58 | let _ = contents() 59 | return cachedDependencies ?? [] 60 | } 61 | 62 | func loadRawContents() -> String? { 63 | if let data = FileManager.default.contents(atPath: url.path) { 64 | if let string = String(data: data, encoding: .utf8) { 65 | return string 66 | } 67 | } 68 | 69 | return nil 70 | } 71 | 72 | public var inPageContext: Bool { 73 | get { 74 | // check if the disabled xattr exists 75 | return url.withUnsafeFileSystemRepresentation { path in 76 | let code = getxattr(path, "inPageContext", nil, 0, 0, 0) 77 | // if getxattr fails that probably means the attribute doesn't exist 78 | return code != -1 79 | } 80 | } 81 | set(inPageContext) { 82 | url.withUnsafeFileSystemRepresentation { path in 83 | if inPageContext { 84 | let value = "yes".data(using: .utf8)! 85 | let result = value.withUnsafeBytes { data in 86 | setxattr(path, "inPageContext", data.baseAddress, data.count, 0, 0) 87 | } 88 | if result < 0 { 89 | NSLog("Failed to set inPageContext flag on file at \(url)") 90 | } 91 | } else { 92 | removexattr(path, "inPageContext", 0) 93 | } 94 | } 95 | } 96 | } 97 | 98 | // MARK: - Identifiable 99 | public var id: URL { 100 | get { 101 | return url 102 | } 103 | } 104 | 105 | public enum FileType { 106 | case javascript 107 | case stylesheet 108 | } 109 | 110 | public enum Contents { 111 | case javascript(Section) 112 | case stylesheets([Section]) 113 | } 114 | } 115 | 116 | /// Various types of URL matching. 117 | public enum MatchRule: Equatable { 118 | /// Matches a glob, such as `https://example.com/**/test` 119 | case glob(String) 120 | /// Matches an exact URL. 121 | case exact(String) 122 | /// Matches if the URL starts with the given prefix. 123 | case prefix(String) 124 | /// Matches if the URL is on the given domain or subdomain. 125 | case domain(String) 126 | /// Matches using the regular expression. 127 | case regexp(String) 128 | 129 | func match(_ url: URL) -> Bool { 130 | switch self { 131 | case .glob(let pattern): 132 | let regex = globToRegex(pattern) 133 | return MatchRule.regexp(regex).match(url) 134 | case .exact(let pattern): 135 | return url.absoluteString == pattern 136 | case .prefix(let pattern): 137 | return url.absoluteString.starts(with: pattern) 138 | case .domain(let pattern): 139 | return url.host == pattern || (url.host?.hasSuffix("." + pattern) ?? false) 140 | case .regexp(let pattern): 141 | if let regexp = try? NSRegularExpression(pattern: pattern, options: .init()) { 142 | let urlString = url.absoluteString 143 | return regexp.firstMatch(in: urlString, options: .init(), range: NSMakeRange(0, urlString.count)) != nil 144 | } else { 145 | return false 146 | } 147 | } 148 | } 149 | } 150 | 151 | func globToRegex(_ glob: String) -> String { 152 | var regex = "" 153 | 154 | var index = glob.startIndex 155 | var mayDoDoubleStar = true 156 | while index < glob.endIndex { 157 | let c = glob[index] 158 | switch c { 159 | case "?": 160 | regex.append("[^/]") 161 | mayDoDoubleStar = false 162 | case "*": 163 | if mayDoDoubleStar { 164 | if glob[index...].starts(with: "**/") || glob[index...] == "**" { 165 | regex.append("[^/]*(?:/[^/])*") 166 | index = glob.index(index, offsetBy: 2) 167 | mayDoDoubleStar = false 168 | continue 169 | } 170 | } 171 | regex.append("[^/]*") 172 | case "/": 173 | regex.append("/") 174 | mayDoDoubleStar = true 175 | case "[": 176 | var i = glob.index(index, offsetBy: 1) 177 | var chars = "" 178 | while i < glob.endIndex && (glob[i] != "]" || chars.isEmpty) { 179 | let c = glob[i] 180 | if chars.isEmpty && c == "!" { 181 | chars.append("^") 182 | } else { 183 | // TODO: ranges 184 | switch c { 185 | case "\\": 186 | chars.append("\\\\") 187 | case "^": 188 | chars.append("\\^") 189 | default: 190 | chars.append(c) 191 | } 192 | } 193 | i = glob.index(i, offsetBy: 1) 194 | } 195 | if i < glob.endIndex && glob[i] == "]" { 196 | regex.append("[\(chars)]") 197 | index = glob.index(i, offsetBy: 1) 198 | continue 199 | } 200 | default: 201 | regex.append(c) 202 | } 203 | index = glob.index(index, offsetBy: 1) 204 | } 205 | 206 | return regex 207 | } 208 | 209 | public struct Section { 210 | public let rules: [MatchRule] 211 | public let contents: String 212 | 213 | public func matches(url: URL) -> Bool { 214 | if rules.isEmpty { 215 | return true 216 | } 217 | for rule in rules { 218 | if rule.match(url) { 219 | return true 220 | } 221 | } 222 | return false 223 | } 224 | } 225 | 226 | public struct ScriptMeta { 227 | var name = "(unnamed)" 228 | var namespace = "?" 229 | var description = "?" 230 | var version = "?" 231 | var author = "?" 232 | var matches: [MatchRule] = [] 233 | var requires: [String] = [] 234 | } 235 | 236 | /// A simple CSS parser that extracts @document sections. 237 | func parseCSSFileSections(_ contents: String) -> [Section] { 238 | var sectionRanges: [Range] = [] 239 | 240 | func consumeComment(in string: String, at startIndex: String.Index) -> String.Index? { 241 | let substr = string[startIndex...] 242 | if substr.starts(with: "/*") { 243 | return substr.range(of: "*/")?.upperBound 244 | } 245 | return nil 246 | } 247 | 248 | func consumeString(in string: String, at startIndex: String.Index, escaping delimiter: Character) -> String.Index? { 249 | var index = startIndex 250 | while index < string.endIndex { 251 | let c = string[index] 252 | switch c { 253 | case "\\": 254 | // escape next character 255 | index = string.index(index, offsetBy: 1) 256 | case delimiter: 257 | // end of string 258 | return index 259 | default: 260 | break 261 | } 262 | index = string.index(index, offsetBy: 1) 263 | } 264 | return nil 265 | } 266 | 267 | func matchAtDocumentToken(at index: String.Index) -> String.Index? { 268 | let options = ["@document", "@-moz-document"] 269 | let substr = contents[index...] 270 | for option in options { 271 | if substr.starts(with: option) { 272 | return contents.index(index, offsetBy: option.count) 273 | } 274 | } 275 | return nil 276 | } 277 | 278 | func consumeTillMatchingBrace(at startIndex: String.Index) -> String.Index { 279 | var braceDepth = 0 280 | 281 | var index = startIndex 282 | while index < contents.endIndex { 283 | let c = contents[index] 284 | if let newIndex = consumeComment(in: contents, at: index) { 285 | index = newIndex 286 | continue 287 | } 288 | switch c { 289 | case "{": 290 | braceDepth += 1 291 | case "}": 292 | braceDepth -= 1 293 | if braceDepth <= 0 { 294 | return contents.index(index, offsetBy: 1) 295 | } 296 | case "'": 297 | let startIndex = contents.index(index, offsetBy: 1) 298 | if let newIndex = consumeString(in: contents, at: startIndex, escaping: "'") { 299 | index = contents.index(newIndex, offsetBy: 1) 300 | continue 301 | } 302 | case "\"": 303 | let startIndex = contents.index(index, offsetBy: 1) 304 | if let newIndex = consumeString(in: contents, at: startIndex, escaping: "\"") { 305 | index = contents.index(newIndex, offsetBy: 1) 306 | continue 307 | } 308 | default: 309 | break 310 | } 311 | index = contents.index(index, offsetBy: 1) 312 | } 313 | return index 314 | } 315 | 316 | var index = contents.startIndex 317 | while index < contents.endIndex { 318 | let c = contents[index] 319 | 320 | if let newIndex = consumeComment(in: contents, at: index) { 321 | index = newIndex 322 | continue 323 | } 324 | 325 | if !c.isWhitespace { 326 | if let newIndex = matchAtDocumentToken(at: index) { 327 | let lowerBound = newIndex 328 | let upperBound = consumeTillMatchingBrace(at: newIndex) 329 | index = upperBound 330 | sectionRanges.append(lowerBound.. ScriptMeta? { 479 | let startRe = try! NSRegularExpression(pattern: "^//\\s*==UserScript==$", options: []) 480 | let endRe = try! NSRegularExpression(pattern: "^//\\s*==/UserScript==$", options: []) 481 | var inMetaBlock = false 482 | var metaBlock: [String] = [] 483 | script.enumerateLines() { line, stop in 484 | let fullRange = NSRange(location: 0, length: line.count) 485 | if let _ = startRe.firstMatch(in: line, options: [], range: fullRange) { 486 | inMetaBlock = true 487 | } else if let _ = endRe.firstMatch(in: line, options: [], range: fullRange) { 488 | inMetaBlock = false 489 | } else if inMetaBlock { 490 | metaBlock.append(line) 491 | } 492 | } 493 | 494 | var metadata = ScriptMeta() 495 | 496 | for line in metaBlock { 497 | if !line.starts(with: "//") { 498 | return nil 499 | } 500 | var line = line 501 | line.removeSubrange(line.startIndex.. String { 21 | return url.absoluteString.addingPercentEncoding(withAllowedCharacters: .alphanumerics)! 22 | } 23 | 24 | func fileToURL(name: String) -> URL? { 25 | if let name = name.removingPercentEncoding { 26 | return URL(string: name) 27 | } 28 | return nil 29 | } 30 | 31 | public func getDependencyContents(of url: URL) -> String? { 32 | if let data = FileManager.default.contents( 33 | atPath: dependenciesURL.appendingPathComponent(urlToFile(url: url)).path) { 34 | if let string = String(data: data, encoding: .utf8) { 35 | return string 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | var urlSession: URLSession? 42 | 43 | public func loadDependency(at url: URL, completion: @escaping (String?) -> Void) { 44 | if urlSession == nil { 45 | urlSession = URLSession(configuration: .ephemeral) 46 | } 47 | 48 | let task = urlSession!.dataTask(with: url) { (data, response, error) in 49 | if let data = data { 50 | FileManager.default.createFile(atPath: dependenciesURL.appendingPathComponent(self.urlToFile(url: url)).path, contents: data, attributes: nil) 51 | completion(String(data: data, encoding: .utf8)) 52 | } else { 53 | completion(nil) 54 | } 55 | } 56 | task.resume() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PhageCoreTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhageCoreTests/PhageCoreTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageCoreTests.swift 3 | // PhageCoreTests 4 | // 5 | // Created by cpsdqs on 2019-06-17. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PhageCore 11 | 12 | class PhageCoreTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testCSSParsing() { 23 | let a = """ 24 | hello { world { @document { this should be ignored } } } 25 | 26 | @document { 27 | this should not be ignored 28 | } 29 | #test {} 30 | @-moz-document url(http://example.com) { 31 | neither should this 32 | } 33 | @document url-prefix("test\\""), url('a') { 34 | nor this 35 | } 36 | """ 37 | let ar = parseCSSFileSections(a) 38 | XCTAssertEqual(ar[0].rules.count, 0) 39 | XCTAssertEqual(ar[0].contents.trimmingCharacters(in: .whitespacesAndNewlines), "this should not be ignored") 40 | XCTAssertEqual(ar[1].rules.count, 1) 41 | XCTAssertEqual(ar[1].rules[0], .exact("http://example.com")) 42 | XCTAssertEqual(ar[1].contents.trimmingCharacters(in: .whitespacesAndNewlines), "neither should this") 43 | XCTAssertEqual(ar[2].rules.count, 2) 44 | XCTAssertEqual(ar[2].rules[0], .prefix("test\"")) 45 | XCTAssertEqual(ar[2].rules[1], .exact("a")) 46 | XCTAssertEqual(ar[2].contents.trimmingCharacters(in: .whitespacesAndNewlines), "nor this") 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /PhageTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhageTests/PhageTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageTests.swift 3 | // PhageTests 4 | // 5 | // Created by cpsdqs on 17.06.19. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Phage 11 | 12 | class PhageTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /PhageUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhageUITests/PhageUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhageUITests.swift 3 | // PhageUITests 4 | // 5 | // Created by cpsdqs on 17.06.19. 6 | // Copyright © 2019 cpsdqs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PhageUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phage 2 | A very simple Safari app extension that injects userscripts and userstyles into websites. 3 | 4 | Compared to e.g. Greasemonkey there are lots of features missing it’s very lightweight. 5 | 6 | ![Screenshot of the Phage app](.assets/bundles.png) 7 | 8 | ## Short Guide 9 | Unlike most other userscript extensions, this extension will load your scripts and styles directly from the file system. 10 | Userscripts and userstyles are simply js and css files organized into bundles. 11 | Bundles are simply folders that are be placed inside the Phage `bundles` folder. 12 | To view this folder, click on “Show in Finder” in the Phage app. 13 | 14 | Userscripts should have a [Greasemonkey metadata block](https://wiki.greasespot.net/Metadata_Block). 15 | The following tags are supported: 16 | 17 | - `@match `: for matching URLs 18 | - `@require `: this tag will create a dependency, which can be loaded in the bundle’s detail view from the Phage app. (You can also find all loaded dependencies in the `dependencies` folder in the `bundles` folder’s parent directory) 19 | 20 | Currently, there is no Userscript API. 21 | 22 | Userstyles should place all style declarations inside a [`@document`](https://developer.mozilla.org/en-US/docs/Web/CSS/@document) declaration (`@-moz-document` will also work). 23 | Any rules outside will be ignored. 24 | 25 | ## Requirements 26 | - Xcode 11 27 | - macOS 10.15 28 | 29 | To build, simply open the project in Xcode, set your signing team in Signing & Capabilities, and run xcodebuild. 30 | --------------------------------------------------------------------------------