#lang scribble/manual @title{Structure and behavior of @tt{rktwebview_qt}} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @section{Overview} @tt{rktwebview_qt} is a Qt-based backend that provides embedded webviews for applications written in Racket. The library exposes a small C-compatible API intended to be used through the Racket FFI. Internally, the implementation is written in C++ and uses Qt WebEngine. The backend provides: @itemlist[#:style 'compact @item{creation of browser windows} @item{navigation to URLs or HTML content} @item{execution of JavaScript} @item{return values from JavaScript} @item{delivery of browser and window events} @item{native dialogs such as file choosers and message boxes} ] The implementation is intentionally organized so that the external interface remains simple and language-neutral while the Qt-specific logic is hidden inside the runtime controller. @section{Requirements} The @tt{rktwebview_qt} backend requires Qt version @tt{6.10.2} or newer. The backend is built against Qt WebEngine and depends on the runtime components provided by Qt. Earlier Qt versions are not supported. Applications using the backend must therefore ensure that a compatible Qt runtime (≥ @tt{6.10.2}) is available. In packaged builds the required Qt runtime is normally bundled together with the backend library. @subsection{Compatibility} The backend requires Qt version @tt{6.10.2} or newer. This requirement is due to the use of the method @tt{setAdditionalTrustedCertificates} provided by @tt{QWebEngineProfileBuilder}. This functionality allows the backend to install additional trusted certificates for a context. The mechanism is used to support trusted self-signed certificates for local services. Earlier Qt versions do not provide this functionality and are therefore not supported. Packaged builds typically include the required Qt runtime components. @section{Layers} The system consists of several layers. @subsection{C ABI Layer} The file @tt{rktwebview.h} defines the public API. This layer provides a stable C-compatible interface that can easily be consumed from Racket through FFI. Important characteristics of the ABI include: @itemlist[#:style 'compact @item{numeric handles represent windows and browser contexts} @item{result codes indicate success or failure} @item{data returned from the library is represented as @tt{rkt_data_t}} @item{event callbacks deliver JSON strings describing events} ] The C layer is implemented in @tt{rktwebview.cpp}. These functions mainly forward calls to a singleton instance of the internal runtime class @tt{Rktwebview_qt}. @section{Runtime Controller} The class @tt{Rktwebview_qt} acts as the central runtime controller. Its responsibilities include: @itemlist[#:style 'compact @item{owning the Qt application instance} @item{managing https contexts} @item{managing open windows} @item{dispatching commands} @item{forwarding events to the host application} ] Internally, it maintains several registries: @itemlist[#:style 'compact @item{a map from window handles to @tt{WebviewWindow} instances} @item{a map from context identifiers to @tt{QWebEngineProfile} objects} @item{a map from window handles to callback functions} ] Each API call that manipulates a window or browser state is forwarded through this controller. @section{HTTP(s) Contexts} A http(s) context represents a shared WebEngine profile. Contexts are created using @tt{rkt_webview_new_context}. Each context contains: @itemlist[#:style 'compact @item{a @tt{QWebEngineProfile}} @item{optional injected JavaScript code} @item{optional trusted certificates} ] Multiple windows normally share the same context. This allows cookies, configuration, and injected scripts to be reused. A context normally represents a https server that can be reached on a certain (local) Url. @section{Window and View Classes} Two Qt classes implement the actual browser window. @subsection{@tt{WebviewWindow}} @tt{WebviewWindow} derives from @tt{QMainWindow}. It represents the native top-level window. Responsibilities include: @itemlist[#:style 'compact @item{hosting the browser widget} @item{reporting window events such as show, hide, move, and resize} @item{handling close requests} @item{reporting navigation events} @item{forwarding JavaScript events} ] Window geometry changes are slightly delayed through timers so that the host application receives fewer intermediate events. @subsection{@tt{WebViewQt}} @tt{WebViewQt} derives from @tt{QWebEngineView}. This class mainly associates the Qt widget with a numeric handle used by the external API. @section{Command Dispatch} Many API functions are implemented using a small command-dispatch mechanism. A command object represents a pending request. The request is delivered to the Qt event system using a custom Qt event. The runtime controller receives the event and executes the corresponding operation. This design ensures that GUI operations occur inside the Qt event loop while the external API remains synchronous. @section{Event Processing} Qt events are processed through repeated calls to @tt{rkt_webview_process_events}, which internally calls @tt{QApplication::processEvents}. This allows the host runtime to control how frequently GUI events are processed. @section{Navigation} Two main navigation operations are provided. @itemlist[#:style 'compact @item{@tt{rkt_webview_set_url} loads a URL} @item{@tt{rkt_webview_set_html} loads raw HTML} ] When page loading finishes, a @tt{"page-loaded"} event is emitted. The event contains a boolean field indicating whether loading succeeded. @section{JavaScript Execution} JavaScript execution is provided through two functions. @subsection{Fire-and-Forget Execution} @tt{rkt_webview_run_js} executes JavaScript without returning a result. This is useful for DOM manipulation or triggering page behavior. @subsection{Synchronous Execution with Result} @tt{rkt_webview_call_js} evaluates JavaScript and returns a result object. The JavaScript is wrapped in a small function that captures either the result value or an exception. The result is returned as a JSON object containing: @itemlist[#:style 'compact @item{@tt{oke}} @item{@tt{result}} @item{@tt{exn}} ] The native side waits for the asynchronous Qt callback before returning the value to the host application. @section{JavaScript Event Bridge} Communication from JavaScript to the host application uses a small event queue implemented in injected JavaScript. When a browser context is created, the runtime injects support code defining the following objects: @itemlist[#:style 'compact @item{@tt{window.rkt_event_queue}} @item{@tt{window.rkt_send_event(obj)}} @item{@tt{window.rkt_get_events()}} ] The queue stores JavaScript-generated events until the native side retrieves them. @subsection{Event Queue} When page code wants to emit an event, it calls: @verbatim|{ window.rkt_send_event(obj) }| The object is appended to the queue. The queue can be retrieved using: @verbatim|{ window.rkt_get_events() }| This function returns a JSON array and clears the queue. @subsection{Native Notification} To notify the native side that events are waiting, the injected code creates a hidden iframe named @tt{rkt-evt-frame}. The iframe is used only as a signalling mechanism. After adding an event to the queue, the script attempts to call: @verbatim|{ frameWindow.print() }| Qt receives this through the signal @tt{QWebEnginePage::printRequestedByFrame}. The window implementation checks whether the frame name is @tt{"rkt-evt-frame"}. If so, it calls @tt{processJsEvents} to retrieve the queued events. This results in the following sequence: @itemlist[#:style 'compact @item{JavaScript calls @tt{rkt_send_event}.} @item{The event is added to the queue.} @item{The hidden iframe triggers a print request.} @item{Qt receives the request.} @item{The native side calls @tt{rkt_get_events}.} @item{Each event object is delivered to the host.} ] The mechanism is therefore not a simple native polling loop. It is better described as a queued JavaScript event bridge with a lightweight native wake-up signal. @subsection{Delivered Event Format} JavaScript-originated events are forwarded to the host as structured JSON. Each event uses a generic envelope: @verbatim|{ { "evt": "js-evt", "js-evt": { ... } } }| The inner object is the original JavaScript event payload. This design keeps the native API simple while allowing arbitrary JavaScript objects to be delivered to the host application. @section{Window Lifecycle} Windows are created using @tt{rkt_webview_create}. Each window receives a callback function used to deliver events. Important lifecycle events include: @itemlist[#:style 'compact @item{@tt{"show"}} @item{@tt{"hide"}} @item{@tt{"move"}} @item{@tt{"resize"}} @item{@tt{"closed"}} ] If the user attempts to close a window directly, the close request is converted into a @tt{"can-close?"} event so that the host application can decide whether the window should actually close. @section{Native Dialogs} The backend also exposes native dialogs including: @itemlist[#:style 'compact @item{directory selection} @item{file open} @item{file save} @item{message boxes} ] The results of these dialogs are delivered as events so that the host application remains in control of the user interface flow. @section{Certificates} Contexts may optionally install trusted certificates. Additionally, a window may specify a special Organizational Unit token used to automatically trust certain self-signed certificates. This mechanism is useful when communicating with local development services. @section{Memory Ownership} Data returned from the library and (javascript) events originating from the library are allocated on the native side and must be released by the host using @tt{rkt_webview_free_data}. This avoids exposing C++ ownership semantics across the FFI boundary. @section{Summary} The @tt{rktwebview_qt} backend provides a compact architecture for embedding Qt WebEngine inside Racket applications. The design allows Racket programs to implement desktop applications using HTML and JavaScript for the user interface while keeping application logic in the host runtime.