The bridge, native side
The bridge, native side
The web layer calls native handlers and receives native events. Both directions carry JSON as text, and nothing is marshalled for you.
Registering a handler
application.on("ide.projects.open", closure(params: string) => string {
let value: json::JsonValue = json::parse(params);
if value.kind() != "object" {
return `{"ok":false}`;
}
let object: json::JsonObject = value as json::JsonObject;
let path: string = field_string(object, "path");
if path.size() == 0 {
return `{"ok":false,"error":"path required"}`;
}
return `{"ok":true}`;
})The parameter is the raw JSON text of the call's params, or the literal null
when there were none. The return value is spliced verbatim into the reply.
Splicing untrusted text into a reply tears down the webview. If a value came from a subprocess or a file, parse it and re-serialize rather than pasting it:
let parsed: json::JsonValue = json::parse(output);
if parsed.kind() != "object" { return `{}`; }
return parsed.to_string();Escape every dynamic string with new json::JsonString(x).to_string().
By convention {"ok":false} is the universal failure reply and the web side
checks result.ok === true.
Pushing events
application.emit("ide.fs.change", `{"path":${path_json}}`)Naming convention in practice: ns.method for request and reply,
ns:event for a stream of pushes.
The threading rule
Every handler runs on the bridge dispatch thread, and dispatch is serialized. One slow handler blocks every other handler on every connection.
So a handler that does real work returns immediately and streams the result:
application.on("ide.build.start", closure[application](params: string) => string {
threads::new_thread(closure[application]() => void {
let child: process::Process? = spawn_build();
if child.is_value() {
let code: number = child.unwrap().wait();
application.emit("ide.build:event", `{"t":"done","code":${code}}`);
}
});
return `{"ok":true}`;
})App can be captured into a thread closure, and emit is safe to call from one.
The same applies to anything that blocks: a dialog, a network call, a file walk over a large tree. If it can take more than a few milliseconds, move it off the dispatch thread.
Streaming a child process
threads::new_thread(closure[application, child, event_name]() => void {
child.on_output(closure[application, event_name](line: string) => void {
let text: string = new json::JsonString(line).to_string();
application.emit(event_name, `{"t":"output","text":${text}}`);
});
let code: number = child.wait();
application.emit(event_name, `{"t":"status","state":"idle"}`);
})