From 341ae6d477ae79b087d302f7de9ccac33f4bef55 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 8 Feb 2021 04:30:36 -0800 Subject: safari: more cleanup, start on README.md --- extension/safari/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 extension/safari/README.md (limited to 'extension/safari/README.md') diff --git a/extension/safari/README.md b/extension/safari/README.md new file mode 100644 index 0000000..730cf82 --- /dev/null +++ b/extension/safari/README.md @@ -0,0 +1,22 @@ +## TabFS for Safari + +This support is a work in progress (as are these instructions). + +Safari's extension support is pretty messy. You will need: + +- Xcode installed +- Safari 14 or newer +- macOS 10.15 Catalina or newer + +Enable the Develop menu in Safari, then Develop -> Allow Unsigned +Extensions. + +Open the Xcode project `TabFS/TabFS.xcodeproj` in this directory. Run +the project. It should open a TabFS app and install the extension in +Safari. + +Enable the extension in Safari Preferences, grant it access to all +sites. It should be running now! (?) + +Check the `fs/mnt` folder of the TabFS repo on your computer to see if +it's mounted. -- cgit v1.2.3 From 2f639e2a0276efb3dbb8a470e61ee8ffc2503f5b Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 8 Feb 2021 12:12:43 -0800 Subject: safari: TabFSServer subprocess that can live long. fixes bug where fs would die after a minute or two --- extension/safari/README.md | 9 ++ .../SafariWebExtensionHandler.swift | 2 +- .../safari/TabFS/TabFS.xcodeproj/project.pbxproj | 120 ++++++++++++++++++--- .../UserInterfaceState.xcuserstate | Bin 61421 -> 61961 bytes .../xcschemes/xcschememanagement.plist | 9 +- extension/safari/TabFS/TabFSServer/main.swift | 113 +++++++++++++++++++ .../safari/TabFS/TabFSService/TabFSService.swift | 104 ++---------------- 7 files changed, 241 insertions(+), 116 deletions(-) create mode 100644 extension/safari/TabFS/TabFSServer/main.swift (limited to 'extension/safari/README.md') diff --git a/extension/safari/README.md b/extension/safari/README.md index 730cf82..9ceafdc 100644 --- a/extension/safari/README.md +++ b/extension/safari/README.md @@ -20,3 +20,12 @@ sites. It should be running now! (?) Check the `fs/mnt` folder of the TabFS repo on your computer to see if it's mounted. + +### tips + +- To open Web inspector: Safari -> Develop menu -> Web Extension + Background Pages -> TabFS + +- You need to rebuild if you change background.js. This is pretty + annoying. + diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index fbe9538..75790fa 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -16,7 +16,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { guard let message = item.userInfo?["message"] as? [AnyHashable: Any] else { return } guard message["op"] as! String == "safari_did_connect" else { return } - + // The XPC service is a subprocess that lives outside the macOS App Sandbox. // (Safari extension native code, including this file, has to live in the sandbox.) // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server. diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj b/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj index d274516..5495f1d 100644 --- a/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj +++ b/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj @@ -9,10 +9,11 @@ /* Begin PBXBuildFile section */ F028D2B625D0B7370095C2D5 /* TabFSService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B525D0B7370095C2D5 /* TabFSService.swift */; }; F028D2B825D0B7370095C2D5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B725D0B7370095C2D5 /* main.swift */; }; - F028D2BC25D0B7370095C2D5 /* TabFSService.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F028D2D725D0B8500095C2D5 /* TabFSServiceProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B325D0B7370095C2D5 /* TabFSServiceProtocols.swift */; }; F028D2DE25D0B8590095C2D5 /* TabFSServiceProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B325D0B7370095C2D5 /* TabFSServiceProtocols.swift */; }; F028D2ED25D106F10095C2D5 /* TabFSService.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + F028D30125D17B080095C2D5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D30025D17B080095C2D5 /* main.swift */; }; + F028D34525D17D6A0095C2D5 /* TabFSServer in CopyFiles */ = {isa = PBXBuildFile; fileRef = F028D2FE25D17B080095C2D5 /* TabFSServer */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F04429F625C7507200D998A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04429F525C7507200D998A5 /* AppDelegate.swift */; }; F04429F925C7507200D998A5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F04429F725C7507200D998A5 /* Main.storyboard */; }; F04429FB25C7507200D998A5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04429FA25C7507200D998A5 /* ViewController.swift */; }; @@ -27,12 +28,12 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - F028D2BA25D0B7370095C2D5 /* PBXContainerItemProxy */ = { + F028D33725D17D100095C2D5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F04429E925C7507200D998A5 /* Project object */; proxyType = 1; - remoteGlobalIDString = F028D2B025D0B7370095C2D5; - remoteInfo = TabFSService; + remoteGlobalIDString = F028D2FD25D17B080095C2D5; + remoteInfo = TabFSServer; }; F0442A0525C7507400D998A5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -44,24 +45,32 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - F028D2BD25D0B7370095C2D5 /* Embed XPC Services */ = { + F028D2E525D106BB0095C2D5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; dstSubfolderSpec = 16; files = ( - F028D2BC25D0B7370095C2D5 /* TabFSService.xpc in Embed XPC Services */, + F028D2ED25D106F10095C2D5 /* TabFSService.xpc in CopyFiles */, ); - name = "Embed XPC Services"; runOnlyForDeploymentPostprocessing = 0; }; - F028D2E525D106BB0095C2D5 /* CopyFiles */ = { + F028D2FC25D17B080095C2D5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; - dstSubfolderSpec = 16; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; files = ( - F028D2ED25D106F10095C2D5 /* TabFSService.xpc in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; + F028D30E25D17BD20095C2D5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + F028D34525D17D6A0095C2D5 /* TabFSServer in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,6 +93,8 @@ F028D2B525D0B7370095C2D5 /* TabFSService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabFSService.swift; sourceTree = ""; }; F028D2B725D0B7370095C2D5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; F028D2B925D0B7370095C2D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F028D2FE25D17B080095C2D5 /* TabFSServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TabFSServer; sourceTree = BUILT_PRODUCTS_DIR; }; + F028D30025D17B080095C2D5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; F04429F125C7507200D998A5 /* TabFS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TabFS.app; sourceTree = BUILT_PRODUCTS_DIR; }; F04429F425C7507200D998A5 /* TabFS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TabFS.entitlements; sourceTree = ""; }; F04429F525C7507200D998A5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -110,6 +121,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F028D2FB25D17B080095C2D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F04429EE25C7507200D998A5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -139,12 +157,21 @@ path = TabFSService; sourceTree = ""; }; + F028D2FF25D17B080095C2D5 /* TabFSServer */ = { + isa = PBXGroup; + children = ( + F028D30025D17B080095C2D5 /* main.swift */, + ); + path = TabFSServer; + sourceTree = ""; + }; F04429E825C7507200D998A5 = { isa = PBXGroup; children = ( F04429F325C7507200D998A5 /* TabFS */, F0442A0A25C7507400D998A5 /* TabFS Extension */, F028D2B225D0B7370095C2D5 /* TabFSService */, + F028D2FF25D17B080095C2D5 /* TabFSServer */, F0442A0725C7507400D998A5 /* Frameworks */, F04429F225C7507200D998A5 /* Products */, ); @@ -156,6 +183,7 @@ F04429F125C7507200D998A5 /* TabFS.app */, F0442A0325C7507400D998A5 /* TabFS Extension.appex */, F028D2B125D0B7370095C2D5 /* TabFSService.xpc */, + F028D2FE25D17B080095C2D5 /* TabFSServer */, ); name = Products; sourceTree = ""; @@ -214,16 +242,35 @@ F028D2AD25D0B7370095C2D5 /* Sources */, F028D2AE25D0B7370095C2D5 /* Frameworks */, F028D2AF25D0B7370095C2D5 /* Resources */, + F028D30E25D17BD20095C2D5 /* CopyFiles */, ); buildRules = ( ); dependencies = ( + F028D33825D17D100095C2D5 /* PBXTargetDependency */, ); name = TabFSService; productName = TabFSService; productReference = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; productType = "com.apple.product-type.xpc-service"; }; + F028D2FD25D17B080095C2D5 /* TabFSServer */ = { + isa = PBXNativeTarget; + buildConfigurationList = F028D30425D17B090095C2D5 /* Build configuration list for PBXNativeTarget "TabFSServer" */; + buildPhases = ( + F028D2FA25D17B080095C2D5 /* Sources */, + F028D2FB25D17B080095C2D5 /* Frameworks */, + F028D2FC25D17B080095C2D5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TabFSServer; + productName = TabFSServer; + productReference = F028D2FE25D17B080095C2D5 /* TabFSServer */; + productType = "com.apple.product-type.tool"; + }; F04429F025C7507200D998A5 /* TabFS */ = { isa = PBXNativeTarget; buildConfigurationList = F0442A1525C7507400D998A5 /* Build configuration list for PBXNativeTarget "TabFS" */; @@ -232,13 +279,11 @@ F04429EE25C7507200D998A5 /* Frameworks */, F04429EF25C7507200D998A5 /* Resources */, F0442A1425C7507400D998A5 /* Embed App Extensions */, - F028D2BD25D0B7370095C2D5 /* Embed XPC Services */, ); buildRules = ( ); dependencies = ( F0442A0625C7507400D998A5 /* PBXTargetDependency */, - F028D2BB25D0B7370095C2D5 /* PBXTargetDependency */, ); name = TabFS; productName = TabFS; @@ -275,6 +320,9 @@ F028D2B025D0B7370095C2D5 = { CreatedOnToolsVersion = 12.1; }; + F028D2FD25D17B080095C2D5 = { + CreatedOnToolsVersion = 12.1; + }; F04429F025C7507200D998A5 = { CreatedOnToolsVersion = 12.1; }; @@ -299,6 +347,7 @@ F04429F025C7507200D998A5 /* TabFS */, F0442A0225C7507400D998A5 /* TabFS Extension */, F028D2B025D0B7370095C2D5 /* TabFSService */, + F028D2FD25D17B080095C2D5 /* TabFSServer */, ); }; /* End PBXProject section */ @@ -344,6 +393,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F028D2FA25D17B080095C2D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F028D30125D17B080095C2D5 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F04429ED25C7507200D998A5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -365,10 +422,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F028D2BB25D0B7370095C2D5 /* PBXTargetDependency */ = { + F028D33825D17D100095C2D5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F028D2B025D0B7370095C2D5 /* TabFSService */; - targetProxy = F028D2BA25D0B7370095C2D5 /* PBXContainerItemProxy */; + target = F028D2FD25D17B080095C2D5 /* TabFSServer */; + targetProxy = F028D33725D17D100095C2D5 /* PBXContainerItemProxy */; }; F0442A0625C7507400D998A5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -421,6 +478,28 @@ }; name = Release; }; + F028D30225D17B080095C2D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75YA78K5AM; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + F028D30325D17B080095C2D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75YA78K5AM; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; F0442A0F25C7507400D998A5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -628,6 +707,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F028D30425D17B090095C2D5 /* Build configuration list for PBXNativeTarget "TabFSServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F028D30225D17B080095C2D5 /* Debug */, + F028D30325D17B080095C2D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F04429EC25C7507200D998A5 /* Build configuration list for PBXProject "TabFS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate index 7b7b1d3..db4d28a 100644 Binary files a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate and b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist index 9296675..e89e3dc 100644 --- a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,13 +7,18 @@ TabFS.xcscheme_^#shared#^_ orderHint - 0 + 2 - TabFSService.xcscheme_^#shared#^_ + TabFSServer.xcscheme_^#shared#^_ orderHint 1 + TabFSService.xcscheme_^#shared#^_ + + orderHint + 0 + diff --git a/extension/safari/TabFS/TabFSServer/main.swift b/extension/safari/TabFS/TabFSServer/main.swift new file mode 100644 index 0000000..245bfb1 --- /dev/null +++ b/extension/safari/TabFS/TabFSServer/main.swift @@ -0,0 +1,113 @@ +// +// main.swift +// TabFSServer +// +// Created by Omar Rizwan on 2/8/21. +// + +import Foundation +import Network +import os.log + +class TabFSServer { + + var fs: Process! + var fsInput: FileHandle! + var fsOutput: FileHandle! + func startFs() { + let fileURL = URL(fileURLWithPath: #filePath) + let repoURL = fileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() + + fs = Process() + fs.executableURL = repoURL.appendingPathComponent("fs").appendingPathComponent("tabfs") + fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() + + fs.arguments = [] + + let inputPipe = Pipe(), outputPipe = Pipe() + fs.standardInput = inputPipe + fs.standardOutput = outputPipe + + fsInput = inputPipe.fileHandleForWriting + fsOutput = outputPipe.fileHandleForReading + + try! fs.run() + } + + var ws: NWListener! + func startWs() { + // TODO: randomly generate port and report back to caller? + let port = NWEndpoint.Port(rawValue: 9991)! + + let parameters = NWParameters(tls: nil) + parameters.allowLocalEndpointReuse = true + parameters.includePeerToPeer = true + // for security ? so people outside your computer can't hijack TabFS at least + parameters.requiredInterfaceType = .loopback + + let opts = NWProtocolWebSocket.Options() + opts.autoReplyPing = true + parameters.defaultProtocolStack.applicationProtocols.insert(opts, at: 0) + + ws = try! NWListener(using: parameters, on: port) + ws.start(queue: .main) + } + + init() { + startFs() + startWs() + + var handleRequest: ((_ req: Data) -> Void)? + ws.newConnectionHandler = { conn in + conn.start(queue: .main) + + handleRequest = { req in + let metaData = NWProtocolWebSocket.Metadata(opcode: .text) + let context = NWConnection.ContentContext(identifier: "context", metadata: [metaData]) + conn.send(content: req, contentContext: context, completion: .contentProcessed({ err in + if err != nil { + os_log(.default, "req %{public}@ error: %{public}@", String(data: req, encoding: .utf8)!, err!.debugDescription as CVarArg) + // FIXME: ERROR + } + })) + } + + func read() { + conn.receiveMessage { (resp, context, isComplete, err) in + guard let resp = resp else { + // FIXME err + os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) + return + } + + // Send the response back to tabfs.c. + self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) + self.fsInput.write(resp) + read() + } + } + read() + } + + DispatchQueue.global(qos: .default).async { + while true { + // Blocking read from the tabfs process. + let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) } + let req = self.fsOutput.readData(ofLength: Int(length)) + + if let handleRequest = handleRequest { + // Send the request over the WebSocket connection to background.js in browser. + handleRequest(req) + } else { + // FIXME: ERROR + } + } + } + + // FIXME: notify + + } +} + +let server = TabFSServer() +dispatchMain() diff --git a/extension/safari/TabFS/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index 86c39f8..4a86dd6 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -10,105 +10,15 @@ import Network import os.log class TabFSService: NSObject, TabFSServiceProtocol { - var fs: Process! - var fsInput: FileHandle! - var fsOutput: FileHandle! - func startFs() { - let fileURL = URL(fileURLWithPath: #filePath) - let repoURL = fileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() - - fs = Process() - fs.executableURL = repoURL.appendingPathComponent("fs").appendingPathComponent("tabfs") - fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() - - fs.arguments = [] - - let inputPipe = Pipe(), outputPipe = Pipe() - fs.standardInput = inputPipe - fs.standardOutput = outputPipe - - fsInput = inputPipe.fileHandleForWriting - fsOutput = outputPipe.fileHandleForReading - - try! fs.run() - } - - var ws: NWListener! - func startWs() { - // TODO: randomly generate port and report back to caller? - let port = NWEndpoint.Port(rawValue: 9991)! - - let parameters = NWParameters(tls: nil) - parameters.allowLocalEndpointReuse = true - parameters.includePeerToPeer = true - // for security ? so people outside your computer can't hijack TabFS at least - parameters.requiredInterfaceType = .loopback - - let opts = NWProtocolWebSocket.Options() - opts.autoReplyPing = true - parameters.defaultProtocolStack.applicationProtocols.insert(opts, at: 0) - - ws = try! NWListener(using: parameters, on: port) - ws.start(queue: .main) - } - - override init() { - super.init() - - startFs() - startWs() - - var handleRequest: ((_ req: Data) -> Void)? - ws.newConnectionHandler = { conn in - conn.start(queue: .main) - - handleRequest = { req in - let metaData = NWProtocolWebSocket.Metadata(opcode: .text) - let context = NWConnection.ContentContext(identifier: "context", metadata: [metaData]) - conn.send(content: req, contentContext: context, completion: .contentProcessed({ err in - if err != nil { - os_log(.default, "req %{public}@ error: %{public}@", String(data: req, encoding: .utf8)!, err!.debugDescription as CVarArg) - // FIXME: ERROR - } - })) - } - - func read() { - conn.receiveMessage { (resp, context, isComplete, err) in - guard let resp = resp else { - // FIXME err - os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) - return - } - - // Send the response back to tabfs.c. - self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) - self.fsInput.write(resp) - read() - } - } - read() - } - - DispatchQueue.global(qos: .default).async { - while true { - // Blocking read from the tabfs process. - let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) } - let req = self.fsOutput.readData(ofLength: Int(length)) - - if let handleRequest = handleRequest { - // Send the request over the WebSocket connection to background.js in browser. - handleRequest(req) - } else { - // FIXME: ERROR - } - } - } - // FIXME: disable auto termination - } - func start(withReply reply: @escaping () -> Void) { // This XPC call is enough to just force the XPC service to be started. + os_log("HELLO") + let server = Process() + os_log("HOW ARE YOU?") + server.executableURL = Bundle.main.url(forResource: "TabFSServer", withExtension: "")! + os_log("I AM GOOD") + server.launch() + os_log("GREAT") reply() } } -- cgit v1.2.3 From 0f2ab4b4de7e828757091e323be1caab0a70b770 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 8 Feb 2021 13:45:26 -0800 Subject: safari: fix some races when you reload Web inspector, make ws connection retry --- extension/background.js | 25 ++++++++++++----- extension/safari/README.md | 10 +++++-- .../SafariWebExtensionHandler.swift | 17 +++++++---- .../UserInterfaceState.xcuserstate | Bin 61961 -> 64115 bytes extension/safari/TabFS/TabFSServer/main.swift | 9 +++--- .../safari/TabFS/TabFSService/TabFSService.swift | 31 ++++++++++++++++++--- 6 files changed, 68 insertions(+), 24 deletions(-) (limited to 'extension/safari/README.md') diff --git a/extension/background.js b/extension/background.js index 62ec7d0..e31a4c4 100644 --- a/extension/background.js +++ b/extension/background.js @@ -720,15 +720,26 @@ function tryConnect() { if (chrome.runtime.getURL('/').startsWith('safari-web-extension://')) { // Safari-only chrome.runtime.sendNativeMessage('com.rsnous.tabfs', {op: 'safari_did_connect'}, resp => { console.log(resp); - const socket = new WebSocket('ws://localhost:9991'); - socket.addEventListener('message', event => { - onMessage(JSON.parse(event.data)); - }); + let socket; + function connectSocket(checkAfterTime) { + socket = new WebSocket('ws://localhost:9991'); + socket.addEventListener('message', event => { + onMessage(JSON.parse(event.data)); + }); + + port = { postMessage(message) { + socket.send(JSON.stringify(message)); + } }; - port = { postMessage(message) { - socket.send(JSON.stringify(message)); - } }; + setTimeout(() => { + if (socket.readyState !== 1) { + console.log('ws connection failed, retrying in', checkAfterTime); + connectSocket(checkAfterTime * 2); + } + }, checkAfterTime); + } + connectSocket(200); }); return; } diff --git a/extension/safari/README.md b/extension/safari/README.md index 9ceafdc..8a47d23 100644 --- a/extension/safari/README.md +++ b/extension/safari/README.md @@ -24,8 +24,12 @@ it's mounted. ### tips - To open Web inspector: Safari -> Develop menu -> Web Extension - Background Pages -> TabFS + Background Pages -> TabFS. -- You need to rebuild if you change background.js. This is pretty - annoying. + Refreshing this inspector should reload the tabfs filesystem, also. + +- You need to rebuild in Xcode any time you change background.js + (because the extension files are copied into the extension, rather + than running directly from folder as in Firefox and Chrome). This is + pretty annoying. diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index 75790fa..dc30cc4 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -17,16 +17,20 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { guard message["op"] as! String == "safari_did_connect" else { return } - // The XPC service is a subprocess that lives outside the macOS App Sandbox. + // The XPC service is a process that can live outside the macOS App Sandbox. // (Safari extension native code, including this file, has to live in the sandbox.) // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server. - // We only use one native message to bootstrap the XPC service, then do all communications - // to that service (which in turn talks to tabfs.c) over WebSocket instead. + // We only use one native message, to bootstrap the XPC service (TabFSService). + // Then the XPC service starts TabFSServer. TabFSServer is separate because + // XPC services get killed by the OS after a minute or two; TabFSServer + // is just a normal process that can live on. It talks straight + // to background.js (which in turn talks to tabfs.c) over a WebSocket. + // (Safari makes doing native messaging quite painful, so we try to avoid it. // It forces the browser to pop to front if you message Safari in the obvious way, // for instance: https://developer.apple.com/forums/thread/122232 - // And with the WebSocket, the XPC service can talk straight to background.js, whereas + // And with the WebSocket, the server can talk straight to background.js, whereas // native messaging would require us here to sit in the middle.) let connection = NSXPCConnection(serviceName: "com.rsnous.TabFSService") @@ -44,8 +48,9 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { // FIXME: report port back? let response = NSExtensionItem() response.userInfo = [ "message": "alive" ] - // This response (over native messaging) prompts background.js to - // connect to the WebSocket server that the XPC service should now be running. + // This response (over native messaging) will prompt background.js to + // connect to the WebSocket server of TabFSServer, which should + // now be running. context.completeRequest(returningItems: [response]) { (what) in print(what) } diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate index db4d28a..96586ef 100644 Binary files a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate and b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/extension/safari/TabFS/TabFSServer/main.swift b/extension/safari/TabFS/TabFSServer/main.swift index 245bfb1..76da2a0 100644 --- a/extension/safari/TabFS/TabFSServer/main.swift +++ b/extension/safari/TabFS/TabFSServer/main.swift @@ -75,8 +75,9 @@ class TabFSServer { func read() { conn.receiveMessage { (resp, context, isComplete, err) in guard let resp = resp else { - // FIXME err - os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) + if let err = err { + os_log(.default, "resp error: %{public}@", err.debugDescription as CVarArg) + } return } @@ -104,10 +105,10 @@ class TabFSServer { } } - // FIXME: notify - + print("OK") } } let server = TabFSServer() + dispatchMain() diff --git a/extension/safari/TabFS/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index 4a86dd6..5bf55ee 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -12,13 +12,36 @@ import os.log class TabFSService: NSObject, TabFSServiceProtocol { func start(withReply reply: @escaping () -> Void) { // This XPC call is enough to just force the XPC service to be started. - os_log("HELLO") + + // kill old copies of TabFSServer + let killall = Process() + killall.launchPath = "/usr/bin/killall" + killall.arguments = ["TabFSServer"] + killall.launch() + killall.waitUntilExit() + + // spin until old TabFSServer (if any) is gone + while true { + let pgrep = Process() + pgrep.launchPath = "/usr/bin/pgrep" + pgrep.arguments = ["TabFSServer"] + pgrep.launch() + pgrep.waitUntilExit() + if pgrep.terminationStatus != 0 { break } + + Thread.sleep(forTimeInterval: 0.01) + } + let server = Process() - os_log("HOW ARE YOU?") + let serverOutput = Pipe() server.executableURL = Bundle.main.url(forResource: "TabFSServer", withExtension: "")! - os_log("I AM GOOD") + server.standardOutput = serverOutput server.launch() - os_log("GREAT") + + // FIXME: should we wait for some signal that the server is ready? + // right now, background.js will just periodically retry until it can connect. + + // tell background.js to try to connect. reply() } } -- cgit v1.2.3