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