115 lines
4.6 KiB
Racket
115 lines
4.6 KiB
Racket
#lang scribble/manual
|
|
|
|
@defmodule{racket-webview/internals}
|
|
|
|
@title{Qt WebView Backend Architecture}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@section{Overview}
|
|
|
|
The runtime consists of two processes. The Racket process owns the public API,
|
|
contexts used by the local HTTPS server, handle wrappers, and user callbacks.
|
|
The helper process @tt{rktwebview_prg} owns @tt{QApplication}, Qt widgets,
|
|
@tt{QWebEngineProfile} objects, and all browser state.
|
|
|
|
The processes communicate through standard pipes. There is no shared library,
|
|
shared-memory queue, or callback from native code into the Racket runtime.
|
|
|
|
@section{Standard Streams}
|
|
|
|
The streams have fixed roles:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{child stdin carries commands from Racket}
|
|
@item{child stdout carries the handshake, command results, and events}
|
|
@item{child stderr carries diagnostic logging}]
|
|
|
|
Messages on stdin and stdout are compact UTF-8 JSON objects, one object per
|
|
line. Stdout must not contain ordinary logging because the Racket protocol
|
|
reader treats every value there as a protocol record.
|
|
|
|
@section{Protocol Compatibility}
|
|
|
|
The command identifiers in @tt{rkt_protocol.h} and the JSON objects used as
|
|
command payloads are unchanged. The process transport adds an outer message
|
|
object with a type and, for commands and results, a request identifier.
|
|
|
|
A command has this form:
|
|
|
|
@verbatim|{{"type":"command","id":17,"command":6,"data":{"wv":1,"url":"https://example.test"}}}|
|
|
|
|
A corresponding result has this form:
|
|
|
|
@verbatim|{{"type":"result","id":17,"result":0,"data":null}}|
|
|
|
|
The request identifier prevents concurrent calls from consuming each other's
|
|
results. It is not visible in the public API.
|
|
|
|
@section{Command Execution}
|
|
|
|
A Racket caller registers a private response channel, writes a command while
|
|
holding the transport write lock, and waits on that channel.
|
|
|
|
The Qt process has a command-reader thread. That thread parses stdin records and
|
|
calls the existing @tt{Rktwebview_qt} methods. Those methods post commands to the
|
|
Qt GUI thread and wait for completion. The command-reader thread then writes the
|
|
result record to stdout.
|
|
|
|
The Racket stdout reader is the only thread that reads protocol output. It uses
|
|
the request identifier to forward each result to the correct waiting caller.
|
|
|
|
@section{Event Delivery}
|
|
|
|
Qt events remain the original JSON strings. The Qt callback converts them only
|
|
into this transport record:
|
|
|
|
@verbatim|{{"type":"event","wv":1,"data":"{...original event JSON...}"}}|
|
|
|
|
The stdout reader captures the callback associated with the handle and places
|
|
the callback plus event string on an asynchronous Racket channel. A separate
|
|
event thread invokes user callbacks. Therefore a slow user callback cannot block
|
|
protocol parsing or prevent command results from being received.
|
|
|
|
Capturing the callback while the event record is read is important for window
|
|
closure. Qt writes the @tt{"closed"} event before the result of the close command.
|
|
The handle can be removed immediately after the result reaches its caller, but
|
|
the already captured callback still receives the event.
|
|
|
|
@section{Contexts and Handles}
|
|
|
|
A context corresponds to a @tt{QWebEngineProfile} and is identified by an
|
|
integer. A webview or tray icon is also identified by an integer handle. Only
|
|
these numbers cross the process boundary; Qt object pointers never do.
|
|
|
|
The existing Racket @tt{rkt-wv} structure is retained so the higher layers do
|
|
not need to change.
|
|
|
|
@section{Lifecycle and Failure Handling}
|
|
|
|
The backend writes a protocol-version handshake after initialization. Racket
|
|
waits for this handshake before sending application commands.
|
|
|
|
For normal shutdown, Racket closes open handles, sends the existing quit
|
|
command, waits for its result, closes the child's stdin, and waits for the child
|
|
process and reader threads.
|
|
|
|
If Racket terminates unexpectedly, the operating system closes the stdin pipe.
|
|
The command-reader thread sees EOF, closes all Qt windows, and quits the Qt event
|
|
loop. This replaces the former shared-memory alive and alive-ack queues.
|
|
|
|
If stdout closes unexpectedly, Racket marks all handles invalid and delivers an
|
|
exception to every command still waiting for a result.
|
|
|
|
@section{Logging}
|
|
|
|
The Qt backend writes its existing log messages to stderr. Racket forwards that
|
|
stream to its current error port. The @racket[rkt-webview-info] result reports
|
|
@tt{"stderr"} as the backend log destination and retains zero-valued
|
|
shared-memory metrics for compatibility with callers that display those fields.
|
|
|
|
@section{Development Override}
|
|
|
|
Setting @tt{RKT_WEBVIEW_PRG} to a backend executable bypasses the installed
|
|
release directory. This makes it possible to test a newly compiled backend
|
|
without repackaging or changing the downloader first.
|