safari: fix some races when you reload Web inspector, make ws connection retry

This commit is contained in:
Omar Rizwan 2021-02-08 13:45:26 -08:00
parent 2f639e2a02
commit 0f2ab4b4de
6 changed files with 68 additions and 24 deletions

View file

@ -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;
}

View file

@ -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.

View file

@ -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)
}

View file

@ -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()

View file

@ -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()
}
}