346 lines
10 KiB
Racket
346 lines
10 KiB
Racket
#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. |