diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index d3b6843..fbe9538 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -18,23 +18,34 @@ 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. + // (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. + // (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 + // native messaging would require us here to sit in the middle.) + let connection = NSXPCConnection(serviceName: "com.rsnous.TabFSService") connection.remoteObjectInterface = NSXPCInterface(with: TabFSServiceProtocol.self) connection.resume() let service = connection.remoteObjectProxyWithErrorHandler { error in - os_log(.default, "Received error: %{public}@", error as! CVarArg) + os_log(.default, "Received error: %{public}@", error as CVarArg) } as? TabFSServiceProtocol - // need this one XPC call to actually initialize the service - service?.upperCaseString("hello XPC") { response in - os_log(.default, "Response from XPC service: %{public}@", response) + // Need this one XPC call to actually initialize the service. + service?.start() { + os_log(.default, "Response from XPC service") // FIXME: report port back? let response = NSExtensionItem() - response.userInfo = [ "message": [ "aResponse to": "moop" ] ] + 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. 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 78ca235..7b7b1d3 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/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index 85b29f3..86c39f8 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -67,7 +67,7 @@ class TabFSService: NSObject, TabFSServiceProtocol { 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) as! CVarArg, err!.debugDescription as CVarArg) + os_log(.default, "req %{public}@ error: %{public}@", String(data: req, encoding: .utf8)!, err!.debugDescription as CVarArg) // FIXME: ERROR } })) @@ -81,6 +81,7 @@ class TabFSService: NSObject, TabFSServiceProtocol { return } + // Send the response back to tabfs.c. self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) self.fsInput.write(resp) read() @@ -89,15 +90,14 @@ class TabFSService: NSObject, TabFSServiceProtocol { read() } - // split new thread DispatchQueue.global(qos: .default).async { while true { - // read from them + // 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)) - // send to other side of WEBSOCKET if let handleRequest = handleRequest { + // Send the request over the WebSocket connection to background.js in browser. handleRequest(req) } else { // FIXME: ERROR @@ -107,9 +107,9 @@ class TabFSService: NSObject, TabFSServiceProtocol { // FIXME: disable auto termination } - func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void) { - let response = string.uppercased() - reply(response) + func start(withReply reply: @escaping () -> Void) { + // This XPC call is enough to just force the XPC service to be started. + reply() } } diff --git a/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift b/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift index 8aedd87..4dd6e77 100644 --- a/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift +++ b/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift @@ -8,5 +8,5 @@ import Foundation @objc public protocol TabFSServiceProtocol { - func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void) + func start(withReply reply: @escaping () -> Void) }