'; return document.body.innerHTML;");
+ //printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
+ //rkt_webview_free_data(r);
}
if (i == 7) {
@@ -75,7 +75,7 @@ int main(int argc, char *argv[])
rkt_webview_run_js(wv1, buf);
}
- if (i == 24) {
+ if (i == 15) {
rkt_webview_close(wv2);
}
i += 1;
diff --git a/rktwebview_qt/rktwebview_qt.cpp b/rktwebview_qt/rktwebview_qt.cpp
index 1f95674..f383536 100644
--- a/rktwebview_qt/rktwebview_qt.cpp
+++ b/rktwebview_qt/rktwebview_qt.cpp
@@ -352,23 +352,10 @@ void Rktwebview_qt::processCommand(Command *cmd)
}
}
-void Rktwebview_qt::processJsEventQueues()
-{
- QList wvs = _views.keys();
- int i, N;
- for(i = 0, N = wvs.length(); i < N; i++) {
- int wv = wvs[i];
- if (_views.contains(wv)) {
- WebviewWindow *win = _views[wv];
- win->processJsEvents();
- }
- }
-}
-
void Rktwebview_qt::removeView(int id)
{
if (_views.contains(id)) {
- WebviewWindow *win = _views[id];
+ //WebviewWindow *win = _views[id];
_views.remove(id);
_view_js_callbacks.remove(id);
}
@@ -394,9 +381,14 @@ rkt_wv_context_t Rktwebview_qt::newContext(const char *boilerplate_js, const cha
QString name = QString::asprintf("profile-%d", _context_counter);
QString code = "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n"
+ "window.rkt_evt_frame_el = null;\n"
+ "window.rkt_evt_frame_win = null;\n"
"window.rkt_send_event = function(obj) {\n"
- " console.log('Sending event: ' + obj);\n"
+ " //console.log('Sending event: ' + obj);\n"
" window.rkt_event_queue.push(obj);\n"
+ " if (window.rkt_evt_frame_el) {\n"
+ " window.rkt_evt_frame_win.print();\n"
+ " }\n"
"};\n"
"window.rkt_get_events = function() {\n"
" let q = window.rkt_event_queue;\n"
@@ -404,6 +396,22 @@ rkt_wv_context_t Rktwebview_qt::newContext(const char *boilerplate_js, const cha
" let json_q = JSON.stringify(q);\n"
" return json_q;\n"
"};\n"
+ "// add hidden hover element to body if necessary\n"
+ "setInterval(function () {\n"
+ " if (window.rkt_evt_frame_el === null || window.rkt_evt_frame_el === undefined) {\n"
+ " window.rkt_evt_frame_el = document.createElement('iframe');\n"
+ " window.rkt_evt_frame_el.style.display = 'none';\n"
+ " window.rkt_evt_frame_el.setAttribute('id', 'rkt-evt-frame');\n"
+ " window.rkt_evt_frame_el.setAttribute('name', 'rkt-evt-frame');\n"
+ " document.body.append(window.rkt_evt_frame_el);\n"
+ " window.rkt_evt_frame_win = window.rkt_evt_frame_el.contentWindow;\n"
+ " } else {"
+ " if (window.rkt_event_queue.length > 0) {\n"
+ " window.rkt_evt_frame_win.print();\n"
+ " }\n"
+ " }\n"
+ "},\n"
+ "10);\n"
"";
QList scripts;
@@ -767,24 +775,14 @@ void Rktwebview_qt::onPageLoad(rktwebview_t w)
void Rktwebview_qt::pageLoaded(rktwebview_t w, bool ok)
{
- /*runJs(w,
- "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n"
- "window.rkt_send_event = function(obj) {\n"
- " console.log('Sending event: ' + obj);\n"
- " window.rkt_event_queue.push(obj);\n"
- "};\n"
- "window.rkt_get_events = function() {\n"
- " let q = window.rkt_event_queue;\n"
- " window.rkt_event_queue = [];\n"
- " let json_q = JSON.stringify(q);\n"
- " return json_q;\n"
- "};\n"
- );
- */
-
if (!ok) {
// Inject code of the profile to this page
WebviewWindow *win = _views[w];
+
+ if (win->navigationEventSent()) {
+ return;
+ }
+
QWebEngineProfile *p = win->profile();
QWebEngineScriptCollection *col = p->scripts();
QList l = col->toList();
@@ -906,9 +904,6 @@ Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) :
_evt_loop_depth = 0;
_app = new QApplication(_argc, _argv);
- connect(&_process_events, &QTimer::timeout, this, &Rktwebview_qt::processJsEventQueues);
- _process_events.start(5);
-
// See Qt 6.10 remark at doEvents.
//connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop);
diff --git a/rktwebview_qt/rktwebview_qt.h b/rktwebview_qt/rktwebview_qt.h
index 30e4c6a..d9a9f6d 100644
--- a/rktwebview_qt/rktwebview_qt.h
+++ b/rktwebview_qt/rktwebview_qt.h
@@ -47,9 +47,6 @@ private:
void runJs(rktwebview_t wv, const char *js);
result_t doWindow(rktwebview_t w, int cmd, result_t on_error);
-public slots:
- void processJsEventQueues();
-
private slots:
void stopEventloop();
diff --git a/rktwebview_qt/webviewwindow.cpp b/rktwebview_qt/webviewwindow.cpp
index 815ccc3..e146621 100644
--- a/rktwebview_qt/webviewwindow.cpp
+++ b/rktwebview_qt/webviewwindow.cpp
@@ -52,6 +52,7 @@ WebviewWindow::WebviewWindow(QWebEngineProfile *profile, WebviewWindow *parent)
_resized = 0;
_profile = profile;
+ _navigation_event_sent = false;
if (parent != nullptr) {
setWindowModality(Qt::WindowModality::WindowModal);
@@ -67,11 +68,14 @@ void WebviewWindow::navigationRequested(QWebEngineNavigationRequest &req)
QWebEngineNavigationRequest::NavigationType t = req.navigationType();
if (t == QWebEngineNavigationRequest::NavigationType::TypedNavigation ||
t == QWebEngineNavigationRequest::RedirectNavigation) {
+ _navigation_event_sent = false;
req.accept();
} else {
EventContainer e("navigation-request");
- e["url"] = req.url().toString();
+ QString u = req.url().toString();
+
+ e["url"] = u;
QString type;
switch (req.navigationType()) {
@@ -94,6 +98,8 @@ void WebviewWindow::navigationRequested(QWebEngineNavigationRequest &req)
e["type"] = type;
_container->triggerEvent(_view->id(), e);
+ _navigation_event_sent = true;
+
req.reject();
}
}
@@ -208,6 +214,11 @@ void WebviewWindow::setOUToken(const QString &token)
_ou_token = token;
}
+bool WebviewWindow::navigationEventSent()
+{
+ return _navigation_event_sent;
+}
+
void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
{
_container = c;
@@ -216,36 +227,6 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
QWebEnginePage *page = _view->page();
- /*
- if (_profile == nullptr) {
- page = _view->page();
- } else {
- page = new QWebEnginePage(_profile, this);
- _view->setPage(page);
- }
-
- // Inject event handling code for the javascript side
- QWebEngineScriptCollection &col = page->scripts();
- QWebEngineScript evt_script;
- evt_script.setInjectionPoint(QWebEngineScript::DocumentReady);
- evt_script.setName("rkt_webview_event_handling");
- evt_script.setSourceCode(
- "window.rkt_event_queue = [];\n"
- "window.rkt_send_event = function(obj) {\n"
- " console.log('Sending event: ' + obj);\n"
- " window.rkt_event_queue.push(obj);\n"
- "};\n"
- "window.rkt_get_events = function() {\n"
- " let q = window.rkt_event_queue;\n"
- " window.rkt_event_queue = [];\n"
- " let json_q = JSON.stringify(q);\n"
- " return json_q;\n"
- "};\n"
- );
- evt_script.setWorldId(QWebEngineScript::ApplicationWorld);
- //col.insert(evt_script);
- */
-
connect(page, &QWebEnginePage::loadFinished, this, [this](bool ok) {
_container->pageLoaded(_view->id(), ok);
});
@@ -255,6 +236,12 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
connect(page, &QWebEnginePage::navigationRequested, this, &WebviewWindow::navigationRequested);
connect(page, &QWebEnginePage::certificateError, this, &WebviewWindow::handleCertificate);
+
+ connect(page, &QWebEnginePage::windowCloseRequested, this, &WebviewWindow::closeView);
+
+ connect(page, &QWebEnginePage::linkHovered, this, &WebviewWindow::linkHovered);
+
+ connect(page, &QWebEnginePage::printRequestedByFrame, this, &WebviewWindow::evtRequested);
}
WebViewQt *WebviewWindow::view()
@@ -306,6 +293,21 @@ void WebviewWindow::openDevTools()
});
}
+void WebviewWindow::linkHovered(const QUrl &u)
+{
+ QString s = u.toString();
+ EventContainer hover_event("link-hovered");
+ hover_event["url"] = s;
+ _container->triggerEvent(_view->id(), hover_event);
+}
+
+void WebviewWindow::evtRequested(QWebEngineFrame frame)
+{
+ if (frame.name() == "rkt-evt-frame") {
+ processJsEvents();
+ }
+}
+
void WebviewWindow::moveEvent(QMoveEvent *event)
{
diff --git a/rktwebview_qt/webviewwindow.h b/rktwebview_qt/webviewwindow.h
index cd87103..9416542 100644
--- a/rktwebview_qt/webviewwindow.h
+++ b/rktwebview_qt/webviewwindow.h
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
class WebViewQt;
class Rktwebview_qt;
@@ -37,24 +38,28 @@ private:
QWebEngineProfile *_profile;
+ bool _navigation_event_sent;
+
private slots:
void handleCertificate(const QWebEngineCertificateError &certificateError);
void navigationRequested(QWebEngineNavigationRequest &req);
public slots:
void processJsEvents();
+ void closeView();
protected:
void closeEvent(QCloseEvent *evt);
public:
- void closeView();
bool windowCreated();
int moveCount();
int resizeCount();
void setOUToken(const QString &token);
+ bool navigationEventSent();
+
public:
void addView(WebViewQt *v, Rktwebview_qt *c);
WebViewQt *view();
@@ -70,6 +75,8 @@ public:
private slots:
void triggerResize();
void triggerMove();
+ void linkHovered(const QUrl &u);
+ void evtRequested(QWebEngineFrame frame);
public:
QWebEngineProfile *profile();
diff --git a/scrbl/racket-webview-qt.scrbl b/scrbl/racket-webview-qt.scrbl
new file mode 100644
index 0000000..9d9fa86
--- /dev/null
+++ b/scrbl/racket-webview-qt.scrbl
@@ -0,0 +1,447 @@
+#lang scribble/manual
+
+@title{Racket FFI Interface for @tt{rktwebview_qt}}
+@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
+
+@section{Overview}
+
+The module @tt{racket-webview-qt.rkt} provides a Racket FFI wrapper around the
+native @tt{rktwebview_qt} library. It loads the shared library, initializes the
+native runtime, and exposes Racket functions for creating and controlling
+webview windows.
+
+The wrapper translates the low-level C interface into a Racket-oriented API
+based on structures, callbacks, and ordinary Racket values.
+
+The module provides:
+
+@itemlist[#:style 'compact
+ @item{creation of HTTP(S) contexts}
+ @item{creation and management of webview windows}
+ @item{navigation and HTML loading}
+ @item{JavaScript execution}
+ @item{window geometry and visibility control}
+ @item{native dialogs}
+ @item{asynchronous event delivery}
+ @item{version and cleanup utilities}
+]
+
+@section{Requirements}
+
+The native backend requires Qt version @tt{6.10.2} or newer.
+
+The shared library @tt{rktwebview_qt} must therefore be built against Qt
+@tt{6.10.2} or a compatible later release.
+
+Earlier Qt versions are not supported.
+
+@section{Module Initialization}
+
+Loading the module performs several initialization steps automatically.
+
+@itemlist[#:style 'compact
+ @item{determines the operating system and architecture}
+ @item{sets Qt runtime environment variables}
+ @item{loads the @tt{rktwebview_qt} shared library}
+ @item{initializes the native runtime}
+ @item{starts a background thread that processes native events}
+]
+
+Currently the wrapper supports the following platforms:
+
+@itemlist[#:style 'compact
+ @item{@tt{'linux}}
+ @item{@tt{'windows}}
+]
+
+If the current system is unsupported, loading the module raises an error.
+
+@section{Data Model}
+
+@subsection{The @tt{rkt-wv} Structure}
+
+Each webview window is represented by a transparent Racket structure.
+
+@defstruct*[rkt-wv
+ ([win exact-integer?]
+ [evt-queue any/c]
+ [callback procedure?]
+ [valid boolean?]
+ [close-callback procedure?])]{
+
+Represents a webview instance managed by the Racket wrapper.
+
+Fields:
+
+@itemlist[#:style 'compact
+ @item{@tt{win}: the native integer window handle}
+ @item{@tt{evt-queue}: internal queue of pending event strings}
+ @item{@tt{callback}: user event callback}
+ @item{@tt{valid}: mutable flag indicating whether the wrapper considers the
+ window active}
+ @item{@tt{close-callback}: procedure invoked when the window is closed}
+]
+
+Although the structure is transparent, user code should normally treat it as
+an opaque handle.
+}
+
+@defproc[(rkt-wv-win [wv rkt-wv?]) exact-integer?]{
+Returns the native window handle associated with @racket[wv].
+}
+
+@section{HTTP(S) Contexts}
+
+A context represents the shared HTTP(S) environment used by webviews.
+
+Contexts correspond to native WebEngine profiles and determine properties such
+as:
+
+@itemlist[#:style 'compact
+ @item{cookies and cache}
+ @item{injected JavaScript}
+ @item{trusted certificates}
+]
+
+@defproc[(rkt-webview-new-context
+ [boilerplate-js string?]
+ [server-cert bytes?])
+ exact-integer?]{
+
+Creates a new HTTP(S) context and returns its native identifier.
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@racket[boilerplate-js]: JavaScript source injected into pages}
+ @item{@racket[server-cert]: optional certificate data}
+]
+
+The returned context identifier can be passed to
+@racket[rkt-webview-create] to create webviews within that context.
+}
+
+@section{Creating Webviews}
+
+@defproc[(rkt-webview-create
+ [context exact-integer?]
+ [parent (or/c #f rkt-wv?)]
+ [evt-callback (-> rkt-wv? string? any)]
+ [close-callback (-> any)])
+ rkt-wv?]{
+
+Creates a new webview window in the given HTTP(S) context.
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@racket[context]: context identifier returned by
+ @racket[rkt-webview-new-context]}
+ @item{@racket[parent]: optional parent webview}
+ @item{@racket[evt-callback]: procedure invoked for each event}
+ @item{@racket[close-callback]: procedure invoked when the window is closed}
+]
+
+The result is a new @racket[rkt-wv] structure.
+
+Events generated by the native layer are delivered asynchronously through
+@racket[evt-callback].
+}
+
+@section{Window Lifecycle}
+
+@defproc[(rkt-webview-close [wv rkt-wv?]) boolean?]{
+
+Requests that the webview window be closed.
+
+The wrapper forwards the request to the native backend and schedules cleanup of
+the event-processing loop.
+
+Returns @racket[#t].
+}
+
+@defproc[(rkt-webview-valid? [wv rkt-wv?]) boolean?]{
+
+Returns whether the webview still exists.
+
+The wrapper first checks its internal validity flag and then queries the native
+runtime.
+}
+
+@defproc[(rkt-webview-exit) void?]{
+
+Closes all webviews and stops the background event-processing thread.
+
+This function is also registered with the Racket plumber so that cleanup occurs
+automatically when the process exits.
+}
+
+@section{Window Configuration}
+
+@defproc[(rkt-webview-set-title! [wv rkt-wv?] [title string?])
+ symbol?]{
+
+Sets the native window title.
+
+Returns a result symbol such as:
+
+@itemlist[#:style 'compact
+ @item{@racket['oke]}
+ @item{@racket['failed]}
+]
+}
+
+@defproc[(rkt-webview-set-ou-token [wv rkt-wv?] [token string?])
+ boolean?]{
+
+Associates an Organizational Unit token with the window.
+
+This token may be used by the native layer when accepting certain
+self-signed certificates.
+}
+
+@section{Navigation}
+
+@defproc[(rkt-webview-set-url! [wv rkt-wv?] [url string?]) symbol?]{
+
+Navigates the webview to the given URL.
+
+Returns a result symbol such as:
+
+@itemlist[#:style 'compact
+ @item{@racket['oke]}
+ @item{@racket['set_navigate_failed]}
+]
+}
+
+@defproc[(rkt-webview-set-html! [wv rkt-wv?] [html string?]) symbol?]{
+
+Loads HTML directly into the webview.
+
+Returns a result symbol such as:
+
+@itemlist[#:style 'compact
+ @item{@racket['oke]}
+ @item{@racket['set_html_failed]}
+]
+}
+
+@section{JavaScript Execution}
+
+@defproc[(rkt-webview-run-js [wv rkt-wv?] [js string?]) symbol?]{
+
+Executes JavaScript without returning a value.
+
+Returns a result symbol such as:
+
+@itemlist[#:style 'compact
+ @item{@racket['oke]}
+ @item{@racket['eval_js_failed]}
+]
+}
+
+@defproc[(rkt-webview-call-js [wv rkt-wv?] [js string?])
+ (list/c symbol? string?)]{
+
+Executes JavaScript and returns:
+
+@racketblock[
+(list result value)
+]
+
+where:
+
+@itemlist[#:style 'compact
+ @item{@racket[result] is the native result code}
+ @item{@racket[value] is a JSON string containing the returned data}
+]
+
+The JSON structure is generated by the native backend.
+}
+
+@section{Window Geometry}
+
+@defproc[(rkt-webview-move [wv rkt-wv?] [x exact-integer?] [y exact-integer?])
+ symbol?]{
+
+Moves the webview window to the given screen coordinates.
+}
+
+@defproc[(rkt-webview-resize
+ [wv rkt-wv?]
+ [width exact-integer?]
+ [height exact-integer?])
+ symbol?]{
+
+Resizes the window.
+}
+
+@defproc[(rkt-webview-show [wv rkt-wv?]) symbol?]{
+Shows the window.
+}
+
+@defproc[(rkt-webview-hide [wv rkt-wv?]) symbol?]{
+Hides the window.
+}
+
+@defproc[(rkt-webview-show-normal [wv rkt-wv?]) symbol?]{
+Restores the window to its normal state.
+}
+
+@defproc[(rkt-webview-maximize [wv rkt-wv?]) symbol?]{
+Maximizes the window.
+}
+
+@defproc[(rkt-webview-minimize [wv rkt-wv?]) symbol?]{
+Minimizes the window.
+}
+
+@defproc[(rkt-webview-present [wv rkt-wv?]) symbol?]{
+Requests that the window be presented to the user.
+}
+
+@defproc[(rkt-webview-window-state [wv rkt-wv?]) symbol?]{
+
+Returns the current window state.
+
+Possible results include:
+
+@itemlist[#:style 'compact
+ @item{@racket['normal]}
+ @item{@racket['minimized]}
+ @item{@racket['maximized]}
+ @item{@racket['hidden]}
+]
+}
+
+@section{Developer Tools}
+
+@defproc[(rkt-webview-open-devtools [wv rkt-wv?]) symbol?]{
+Opens the browser developer tools window.
+}
+
+@section{Native Dialogs}
+
+Dialog functions return immediately with a status code.
+The user’s choice is delivered asynchronously through the event callback.
+
+@defproc[(rkt-webview-choose-dir
+ [wv rkt-wv?]
+ [title string?]
+ [base-dir string?])
+ symbol?]{
+
+Requests a directory-selection dialog.
+}
+
+@defproc[(rkt-webview-file-open
+ [wv rkt-wv?]
+ [title string?]
+ [base-dir string?]
+ [extensions string?])
+ symbol?]{
+
+Requests a file-open dialog.
+}
+
+@defproc[(rkt-webview-file-save
+ [wv rkt-wv?]
+ [title string?]
+ [base-dir string?]
+ [extensions string?])
+ symbol?]{
+
+Requests a file-save dialog.
+}
+
+@defproc[(rkt-webview-messagebox
+ [wv rkt-wv?]
+ [title string?]
+ [message string?]
+ [submessage string?]
+ [type symbol?])
+ symbol?]{
+
+Shows a native message box.
+}
+
+@section{Event Delivery}
+
+Each webview has an associated event callback.
+
+The callback has the form:
+
+@racketblock[
+(λ (wv event-json) ...)
+]
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@racket[wv]: the webview handle}
+ @item{@racket[event-json]: JSON event string from the native backend}
+]
+
+Typical event types include:
+
+@itemlist[#:style 'compact
+ @item{@tt{"show"}}
+ @item{@tt{"hide"}}
+ @item{@tt{"move"}}
+ @item{@tt{"resize"}}
+ @item{@tt{"closed"}}
+ @item{@tt{"page-loaded"}}
+ @item{@tt{"navigation-request"}}
+ @item{@tt{"js-evt"}}
+]
+
+The wrapper does not parse the JSON payload.
+
+@section{Version Information}
+
+@defproc[(rkt-webview-version)
+ (list/c list? list?)]{
+
+Returns version information for the native backend.
+
+Example result:
+
+@racketblock[
+(list
+ (list 'webview-c-api 1 0 0)
+ (list 'qt 6 10 2))
+]
+}
+
+@section{Example}
+
+@racketblock[
+(define ctx
+ (rkt-webview-new-context "" #""))
+
+(define wv
+ (rkt-webview-create
+ ctx
+ #f
+ (λ (wv evt)
+ (displayln evt))
+ (λ ()
+ (displayln "closed"))))
+
+(rkt-webview-set-title! wv "Example")
+(rkt-webview-set-url! wv "https://example.org")
+]
+
+@section{Summary}
+
+The FFI module provides a thin Racket interface to the native
+@tt{rktwebview_qt} backend.
+
+Key characteristics:
+
+@itemlist[#:style 'compact
+ @item{thin wrapper around the native C API}
+ @item{asynchronous event delivery}
+ @item{JSON-based event payloads}
+ @item{simple Racket structures for webviews}
+]
\ No newline at end of file
diff --git a/scrbl/racket-webview.scrbl b/scrbl/racket-webview.scrbl
new file mode 100644
index 0000000..7fca68f
--- /dev/null
+++ b/scrbl/racket-webview.scrbl
@@ -0,0 +1,797 @@
+#lang scribble/manual
+
+@title{High-Level Racket Interface for Webviews}
+@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
+
+@defmodule[racket-webview]
+
+@section{Overview}
+
+The module @tt{racket-webview.rkt} provides the high-level Racket interface to
+the native @tt{rktwebview_qt} backend.
+
+It is built on top of the low-level FFI layer and adds:
+
+@itemlist[#:style 'compact
+ @item{Racket contracts on exported procedures}
+ @item{HTTP(S) context management}
+ @item{an embedded local HTTPS server per context}
+ @item{automatic self-signed certificate generation}
+ @item{decoded event delivery as Racket hash tables}
+ @item{decoded JavaScript results as Racket values}
+ @item{DOM convenience functions for values, attributes, classes, and styles}
+]
+
+Applications are expected to use this module rather than the lower-level FFI
+module directly.
+
+@section{Architecture}
+
+This module sits above the low-level FFI wrapper and provides a more idiomatic
+Racket API.
+
+The stack is organized as follows:
+
+@itemlist[#:style 'compact
+ @item{the native Qt backend}
+ @item{the thin FFI wrapper in @tt{racket-webview-qt.rkt}}
+ @item{the higher-level Racket interface in @tt{racket-webview.rkt}}
+]
+
+This layer introduces two main abstractions:
+
+@itemlist[#:style 'compact
+ @item{@tt{wv-context}, representing an HTTP(S) context}
+ @item{@tt{wv-win}, representing a webview window}
+]
+
+@section{HTTP(S) Contexts}
+
+A context represents the HTTP(S) environment in which webviews operate.
+
+Unlike the lower-level FFI layer, this module does more than create a native
+WebEngine profile. It also starts a local HTTPS server bound to
+@tt{127.0.0.1} on an automatically chosen port.
+
+Each context therefore combines:
+
+@itemlist[#:style 'compact
+ @item{a native WebEngine context}
+ @item{a local HTTPS server}
+ @item{a self-signed certificate}
+ @item{a base URL}
+ @item{a file getter used to resolve incoming requests}
+]
+
+@defstruct*[wv-context
+ ([context exact-integer?]
+ [port exact-integer?]
+ [file-getter procedure?]
+ [webserver-thread thread?]
+ [base-url string?]
+ [request-count any/c]
+ [sec-token-cache any/c]
+ [cert-ou-token string?])]{
+Represents a webview HTTP(S) context.
+
+The structure is transparent, but it is intended to be treated as a context
+handle.
+}
+
+@defproc[(webview-new-context
+ [file-getter procedure?]
+ [#:boilerplate-js boilerplate-js string?
+ (webview-default-boilerplate-js)])
+ wv-context?]{
+
+Creates a new HTTP(S) context.
+
+The @racket[file-getter] procedure is used by the embedded web server to map
+request paths to files on disk.
+
+The function performs the following steps:
+
+@itemlist[#:style 'compact
+ @item{creates a fresh context structure}
+ @item{generates a self-signed certificate}
+ @item{starts a local HTTPS server on @tt{127.0.0.1}}
+ @item{creates the native context through the FFI layer}
+ @item{stores the resulting base URL}
+]
+
+The @racket[boilerplate-js] argument supplies JavaScript that is injected into
+pages loaded within the native context.
+
+The returned value is a @racket[wv-context] structure.
+}
+
+@defproc[(wv-context-base-url [ctx wv-context?]) string?]{
+Returns the base URL associated with the context.
+
+This is the local HTTPS URL served by the embedded web server, for example a
+URL of the form @tt{https://127.0.0.1:port}.
+}
+
+@section{Webview Windows}
+
+A webview window is represented by a @racket[wv-win] structure.
+
+@defstruct*[wv-win
+ ([handle any/c]
+ [context wv-context?]
+ [window-nr exact-integer?])]{
+Represents a webview window.
+
+Fields:
+
+@itemlist[#:style 'compact
+ @item{@tt{handle}: the low-level FFI handle}
+ @item{@tt{context}: the owning @racket[wv-context]}
+ @item{@tt{window-nr}: the native numeric window identifier}
+]
+}
+
+@defproc[(wv-win-window-nr [wv wv-win?]) exact-integer?]{
+Returns the native numeric window identifier of @racket[wv].
+}
+
+@defproc[(webview-create
+ [context wv-context?]
+ [url-path string?]
+ [event-callback procedure?]
+ [#:parent parent (or/c wv-win? #f) #f])
+ wv-win?]{
+
+Creates a new webview window inside the given context.
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@racket[context]: the HTTP(S) context}
+ @item{@racket[url-path]: initial URL path to load}
+ @item{@racket[event-callback]: callback receiving decoded events}
+ @item{@racket[parent]: optional parent window}
+]
+
+The function creates the native window, associates the context certificate
+OU token with it, and immediately loads the given path through
+@racket[webview-set-url!].
+
+The callback receives two arguments:
+
+@itemlist[#:style 'compact
+ @item{the created @racket[wv-win]}
+ @item{a decoded event hash}
+]
+
+Native JSON event strings are converted to Racket hashes before delivery.
+}
+
+@defproc[(webview-close [wv wv-win?]) symbol?]{
+Closes the webview.
+
+Returns @racket['oke].
+}
+
+@section{Navigation and HTML Loading}
+
+@defproc[(webview-set-url! [wv wv-win?] [url (or/c string? url?)]) symbol?]{
+
+Loads a URL into the webview.
+
+If the supplied URL is relative or missing scheme and host information, the
+function resolves it against the base URL of the window's context.
+
+This makes it possible to load application pages relative to the embedded local
+HTTPS server.
+}
+
+@defproc[(webview-navigate! [wv wv-win?] [place string?]) symbol?]{
+
+Navigates the current page by assigning to @tt{window.location} through
+JavaScript.
+
+This is distinct from @racket[webview-set-url!], which loads a URL through the
+native navigation interface.
+}
+
+@defproc[(webview-set-html! [wv wv-win?] [html (or/c string? xexpr?)]) symbol?]{
+
+Loads HTML directly into the webview.
+
+If @racket[html] is an x-expression, it is converted with
+@racket[xexpr->string] first.
+}
+
+@defproc[(webview-base-url [wv wv-win?]) url?]{
+Returns the base URL of the webview's context as a Racket URL value.
+}
+
+@section{Window Management}
+
+@defproc[(webview-devtools [wv wv-win?]) symbol?]{
+Opens the developer tools for the webview.
+}
+
+@defproc[(webview-move [wv wv-win?] [x number?] [y number?]) symbol?]{
+Moves the window.
+}
+
+@defproc[(webview-resize [wv wv-win?] [w number?] [h number?]) symbol?]{
+Resizes the window.
+}
+
+@defproc[(webview-show [wv wv-win?]) symbol?]{
+Shows the window.
+}
+
+@defproc[(webview-hide [wv wv-win?]) symbol?]{
+Hides the window.
+}
+
+@defproc[(webview-show-normal [wv wv-win?]) symbol?]{
+Restores the window to the normal state.
+}
+
+@defproc[(webview-maximize [wv wv-win?]) symbol?]{
+Maximizes the window.
+}
+
+@defproc[(webview-minimize [wv wv-win?]) symbol?]{
+Minimizes the window.
+}
+
+@defproc[(webview-present [wv wv-win?]) symbol?]{
+Presents the window to the user.
+}
+
+@defproc[(webview-window-state [wv wv-win?]) symbol?]{
+Returns the current window state.
+}
+
+@defproc[(webview-set-title! [wv wv-win?] [title string?]) symbol?]{
+Sets the native window title.
+}
+
+@section{Message Boxes and File Dialogs}
+
+@defproc[(webview-messagebox
+ [wv wv-win?]
+ [type symbol?]
+ [title string?]
+ [message string?]
+ [#:sub submessage string? ""])
+ symbol?]{
+
+Shows a native message box.
+
+The @racket[type] argument is passed through to the lower layer and is expected
+to be one of the supported message box kinds, such as
+@racket['info], @racket['error], @racket['warning], @racket['yes-no], or
+@racket['oke-cancel].
+}
+
+@defproc[(webview-choose-dir
+ [wv wv-win?]
+ [title string?]
+ [base-dir (or/c path? string?)])
+ symbol?]{
+
+Opens a directory chooser dialog.
+}
+
+@defproc[(webview-file-open
+ [wv wv-win?]
+ [title string?]
+ [base-dir (or/c path? string?)]
+ [permitted-exts (or/c wv-permitted-exts? wv-list-of-permitted-exts?)])
+ symbol?]{
+
+Opens a native file-open dialog.
+
+The @racket[permitted-exts] value is converted to a Qt file filter string.
+}
+
+@defproc[(webview-file-save
+ [wv wv-win?]
+ [title string?]
+ [base-dir (or/c path? string?)]
+ [permitted-exts (or/c wv-permitted-exts? wv-list-of-permitted-exts?)])
+ symbol?]{
+
+Opens a native file-save dialog.
+
+The @racket[permitted-exts] value is converted to a Qt file filter string.
+}
+
+@section{Permitted Extensions}
+
+The file dialog helpers use @racket[wv-permitted-exts] values to describe file
+type filters.
+
+@defstruct*[wv-permitted-exts
+ ([name string?]
+ [exts wv-permitted-exts-exts?])]{
+Represents a named file filter.
+
+The @racket[name] field is the human-readable filter name.
+
+The @racket[exts] field is a list of filename extensions represented as
+symbols.
+}
+
+@defproc[(wv-permitted-exts-exts? [v any/c]) boolean?]{
+Returns whether @racket[v] is a valid list of extension symbols.
+}
+
+@defproc[(wv-permitted-exts-name? [v any/c]) boolean?]{
+Returns whether @racket[v] is a valid filter name.
+}
+
+@defproc[(wv-list-of-permitted-exts? [v any/c]) boolean?]{
+Returns whether @racket[v] is a list of valid @racket[wv-permitted-exts]
+values.
+}
+
+@defproc[(webview-filter->exts [str string?])
+ (or/c wv-permitted-exts? #f)]{
+Parses a Qt-style filter string into a @racket[wv-permitted-exts] value.
+
+If the string does not match the expected filter format, the result is
+@racket[#f].
+}
+
+@section{Event Binding}
+
+The module provides helpers for binding DOM events from page elements back to
+the Racket side.
+
+@defproc[(webview-bind!
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [event (or/c symbol? list-of-symbol?)])
+ list?]{
+
+Binds one or more DOM events to elements selected by @racket[selector].
+
+If @racket[selector] is a symbol, it is interpreted as an element id and
+rewritten as a CSS id selector.
+
+If @racket[event] is a symbol, it is treated as a single event name.
+
+The result is a list describing the created bindings.
+}
+
+@defproc[(webview-unbind!
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [event (or/c symbol? list-of-symbol?)])
+ list?]{
+
+Removes previously established DOM event bindings.
+
+Its arguments and return value follow the same conventions as
+@racket[webview-bind!].
+}
+
+@section{JavaScript Execution}
+
+@defproc[(webview-run-js [wv wv-win?] [js string?]) symbol?]{
+Executes JavaScript for side effects.
+}
+
+@defproc[(webview-call-js-result? [x any/c]) boolean?]{
+Returns whether @racket[x] has the low-level two-element result shape used by
+the FFI function @racket[rkt-webview-call-js].
+
+This predicate is mainly a utility for internal result checking.
+}
+
+@defproc[(webview-call-js [wv wv-win?] [js string?])
+ (or/c string? list? boolean? hash?)]{
+
+Executes JavaScript and returns the decoded @tt{result} field of the underlying
+native JSON response.
+
+Unlike the lower-level FFI wrapper, this function does not return a
+@racket[(list result-symbol json-string)] pair. Instead:
+
+@itemlist[#:style 'compact
+ @item{the low-level result is checked}
+ @item{the JSON string is decoded}
+ @item{the @tt{result} field is extracted}
+ @item{the extracted value is returned directly}
+]
+
+If the JavaScript evaluation fails, the function raises an error.
+}
+
+@section{DOM Content Helpers}
+
+@defproc[(webview-set-innerHTML!
+ [wv wv-win?]
+ [id symbol?]
+ [html (or/c string? xexpr?)])
+ symbol?]{
+
+Sets the @tt{innerHTML} of the element with the given id.
+
+If @racket[html] is an x-expression, it is converted to HTML first.
+}
+
+@section{Form Value Helpers}
+
+@defproc[(webview-set-value!
+ [wv wv-win?]
+ [id symbol?]
+ [val (or/c symbol?
+ string?
+ number?
+ boolean?
+ g:date?
+ g:time?
+ g:datetime?
+ rgba?)])
+ symbol?]{
+
+Sets the value of the element with the given id.
+
+Checkboxes and radio buttons are handled through their @tt{checked} property.
+Other elements are handled through their @tt{value} property.
+
+Date, time, datetime, and color values are converted to strings before being
+sent to the browser.
+}
+
+@defproc[(webview-value [wv wv-win?] [id symbol?])
+ (or/c string? boolean?)]{
+
+Returns the current value of the element with the given id.
+
+For checkboxes and radio buttons, the returned value reflects the checked state.
+For other elements, the result is the string value.
+}
+
+@defproc[(webview-value/boolean [wv wv-win?] [id symbol?])
+ (or/c boolean? #f)]{
+Returns the current value converted to a boolean.
+}
+
+@defproc[(webview-value/symbol [wv wv-win?] [id symbol?])
+ (or/c symbol? #f)]{
+Returns the current value converted to a symbol.
+}
+
+@defproc[(webview-value/number [wv wv-win?] [id symbol?])
+ (or/c number? #f)]{
+Returns the current value converted to a number.
+}
+
+@defproc[(webview-value/date [wv wv-win?] [id symbol?])
+ (or/c g:date? #f)]{
+Returns the current value converted to a date.
+}
+
+@defproc[(webview-value/time [wv wv-win?] [id symbol?])
+ (or/c g:time? #f)]{
+Returns the current value converted to a time.
+}
+
+@defproc[(webview-value/datetime [wv wv-win?] [id symbol?])
+ (or/c g:datetime? #f)]{
+Returns the current value converted to a datetime.
+}
+
+@defproc[(webview-value/color [wv wv-win?] [id symbol?])
+ (or/c rgba? #f)]{
+Returns the current value converted to an RGBA color.
+}
+
+@section{Class Helpers}
+
+@defproc[(webview-add-class!
+ [wv wv-win?]
+ [id-or-selector (or/c symbol? string?)]
+ [class (or/c symbol? string? list?)])
+ hash?]{
+
+Adds one or more CSS classes to all elements selected by
+@racket[id-or-selector].
+
+A symbol selector is interpreted as an element id.
+}
+
+@defproc[(webview-remove-class!
+ [wv wv-win?]
+ [id-or-selector (or/c symbol? string?)]
+ [class (or/c symbol? string? list?)])
+ hash?]{
+
+Removes one or more CSS classes from all selected elements.
+}
+
+@section{Style Helpers}
+
+@defproc[(webview-set-style!
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [style-entries (or/c kv? list-of-kv?)])
+ hash?]{
+
+Sets one or more inline CSS style properties on the selected elements.
+
+The @racket[style-entries] argument is either a single key/value pair or a list
+of key/value pairs.
+}
+
+@defproc[(webview-get-style
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [styles (or/c symbol? list-of-symbol?)])
+ (or/c list? hash?)]{
+
+Reads computed style values from the selected elements.
+
+If @racket[selector] is a symbol, the result is the style hash for that single
+element.
+
+If @racket[selector] is a string selector, the result is a list of
+@racket[(cons id style-hash)] pairs.
+}
+
+@defproc[(webview-unset-style!
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [style-entries (or/c symbol? list-of-symbol?)])
+ hash?]{
+
+Clears one or more inline style properties from the selected elements.
+}
+
+@section{Attribute Helpers}
+
+@defproc[(webview-set-attr!
+ [wv wv-win?]
+ [selector (or/c symbol? string?)]
+ [attr-entries (or/c kv? list-of-kv?)])
+ hash?]{
+
+Sets one or more HTML attributes on the selected elements.
+
+Date, time, datetime, and color values are converted to strings before being
+sent to the browser.
+}
+
+@defproc[(webview-attr
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c string? boolean?)]{
+
+Returns the value of the named attribute of the element with the given id.
+
+If the attribute does not exist, the result is @racket[#f].
+}
+
+@defproc[(webview-attr/boolean
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c boolean? #f)]{
+Returns the attribute converted to a boolean.
+}
+
+@defproc[(webview-attr/symbol
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c symbol? #f)]{
+Returns the attribute converted to a symbol.
+}
+
+@defproc[(webview-attr/number
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c number? #f)]{
+Returns the attribute converted to a number.
+}
+
+@defproc[(webview-attr/date
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c g:date? #f)]{
+Returns the attribute converted to a date.
+}
+
+@defproc[(webview-attr/time
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c g:time? #f)]{
+Returns the attribute converted to a time.
+}
+
+@defproc[(webview-attr/datetime
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c g:datetime? #f)]{
+Returns the attribute converted to a datetime.
+}
+
+@defproc[(webview-attr/color
+ [wv wv-win?]
+ [id symbol?]
+ [attr (or/c symbol? string?)])
+ (or/c rgba? #f)]{
+Returns the attribute converted to an RGBA color.
+}
+
+@section{Utility Functions}
+
+@defproc[(webview-standard-file-getter
+ [base-path path-string?]
+ [#:not-exist on-not-exist procedure?
+ (λ (file base-path path) path)])
+ procedure?]{
+
+Creates a standard file getter for use with @racket[webview-new-context].
+
+The resulting procedure maps request paths to files under @racket[base-path].
+
+The special request path @tt{/} is mapped to @tt{index.html}.
+
+If the resolved file does not exist, the @racket[on-not-exist] procedure is
+used to determine the result path.
+}
+
+@defproc[(webview-default-boilerplate-js [custom-js procedure?] ...)
+ string?]{
+
+Returns the default boilerplate JavaScript used by the system.
+
+Any supplied procedures are called and their returned JavaScript strings are
+appended to the default boilerplate.
+}
+
+@defproc[(webview-version) pair?]{
+
+Returns version information for the high-level webview package and the native
+backend.
+
+The result is a pair whose first element describes the Racket webview package
+version and whose remaining data comes from @racket[rkt-webview-version].
+}
+
+@section{Event Reference}
+
+Events are delivered to @racket[webview-create]'s callback as decoded Racket
+hash tables.
+
+The helper @racket[util-parse-event] converts the incoming JSON and also
+normalizes the @tt{event} field from a string to a symbol.
+
+Each event therefore contains at least an @racket['event] entry.
+
+@subsection{Window Events}
+
+@subsubsection{@tt{show}}
+
+Example:
+
+@racketblock[
+'#hash((event . show))
+]
+
+@subsubsection{@tt{hide}}
+
+Example:
+
+@racketblock[
+'#hash((event . hide))
+]
+
+@subsubsection{@tt{move}}
+
+Typical fields:
+
+@itemlist[#:style 'compact
+ @item{@racket['x]}
+ @item{@racket['y]}
+]
+
+@subsubsection{@tt{resize}}
+
+Typical fields:
+
+@itemlist[#:style 'compact
+ @item{@racket['w]}
+ @item{@racket['h]}
+]
+
+@subsubsection{@tt{closed}}
+
+Indicates that the window was closed.
+
+@subsubsection{@tt{can-close?}}
+
+Generated when the user attempts to close the window.
+
+@subsection{Navigation and Loading Events}
+
+@subsubsection{@tt{page-loaded}}
+
+Contains an @racket['oke] field indicating whether loading succeeded.
+
+@subsubsection{@tt{navigation-request}}
+
+Typically contains:
+
+@itemlist[#:style 'compact
+ @item{@racket['url]}
+ @item{@racket['type]}
+]
+
+@subsection{JavaScript Events}
+
+@subsubsection{@tt{js-evt}}
+
+Represents a JavaScript-originated event sent through the injected browser
+bridge.
+
+The original JavaScript payload is available under the @racket['js-evt] key.
+
+@subsection{Dialog Events}
+
+Dialog completion is reported through ordinary events, including event names
+such as:
+
+@itemlist[#:style 'compact
+ @item{@tt{choose-dir-ok}}
+ @item{@tt{choose-dir-cancel}}
+ @item{@tt{file-open-ok}}
+ @item{@tt{file-open-cancel}}
+ @item{@tt{file-save-ok}}
+ @item{@tt{file-save-cancel}}
+ @item{@tt{msgbox-ok}}
+ @item{@tt{msgbox-cancel}}
+ @item{@tt{msgbox-yes}}
+ @item{@tt{msgbox-no}}
+]
+
+@section{Example}
+
+@racketblock[
+(define file-getter
+ (webview-standard-file-getter "htdocs"))
+
+(define ctx
+ (webview-new-context file-getter))
+
+(define wv
+ (webview-create
+ ctx
+ "index.html"
+ (λ (wv evt)
+ (displayln evt))))
+
+(webview-set-title! wv "Example")
+(webview-resize wv 800 600)
+]
+
+@section{Summary}
+
+The module @tt{racket-webview.rkt} provides the main Racket programming
+interface for the webview system.
+
+Compared to the lower-level FFI layer, it adds:
+
+@itemlist[#:style 'compact
+ @item{HTTP(S) contexts with local web serving}
+ @item{automatic certificate generation}
+ @item{decoded event delivery}
+ @item{decoded JavaScript results}
+ @item{DOM convenience helpers}
+]
\ No newline at end of file
diff --git a/scrbl/rktwebview-api.scrbl b/scrbl/rktwebview-api.scrbl
index 1a7b44b..1b348fe 100644
--- a/scrbl/rktwebview-api.scrbl
+++ b/scrbl/rktwebview-api.scrbl
@@ -1,348 +1,784 @@
#lang scribble/manual
-@title{Public API Specification for the Qt–Backed WebView Bridge}
+@title{API Reference for @tt{rktwebview_qt}}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
-
@italic{For use with Racket FFI}
-This document defines the public C-level API of the Qt-based WebView bridge.
-It targets Racket developers integrating the C library via the Racket FFI.
-It specifies the interface, result values, memory ownership rules, event formats,
-expected behavior, and recommended calling patterns. Internal architecture is documented separately.
+@section{Overview}
-@section{Calling Convention and Structure}
+@tt{rktwebview_qt} exposes a small C-compatible API for creating and controlling
+Qt-based webviews from Racket through the FFI.
-All functions are C functions with normal C linkage.
-Window handles are simple integers that uniquely identify native windows.
+The public interface is declared in @tt{rktwebview.h}. Its implementation in
+@tt{rktwebview.cpp} mainly forwards requests to the internal Qt runtime.
+
+The API consists of:
@itemlist[#:style 'compact
- @item{Header: @tt{rktwebview.h}}
- @item{ABI: plain C, no name mangling}
- @item{Handles remain valid from creation until the window is fully closed}
+ @item{initialization and event processing}
+ @item{version and memory-management helpers}
+ @item{browser-context management}
+ @item{window creation and lifecycle control}
+ @item{navigation and content loading}
+ @item{JavaScript execution}
+ @item{window-state manipulation}
+ @item{native dialogs}
]
+@section{Basic Types}
-@section{Opaque and Helper Types}
+@subsection{Window and Context Handles}
-@subsection{Window Handle}
-
-@itemlist[#:style 'compact
- @item{@tt{typedef int rktwebview_t}: Valid for any window created by @tt{rkt_webview_create} until closed.}
-]
-
-@subsection{Event Callback Types}
-
-@itemlist[#:style 'compact
- @item{@tt{typedef struct \{ rktwebview_t wv; char* event; \} rkt_event_t}: JSON event payload for the host (UTF‐8).}
- @item{@tt{typedef void (*event_cb_t)(rkt_event_t*)}: Host-supplied callback for event delivery.}
-]
-
-@subsection{JS Result Structure}
-
-@itemlist[#:style 'compact
- @item{@tt{typedef struct \{ result_t result; char* value; \} rkt_js_result_t}: @tt{result} is always @tt{oke} or @tt{eval_js_failed}; @tt{value} holds a UTF‐8 JSON string describing the JS outcome.}
-]
-
-
-@section{Enumerations}
-
-@subsection{result_t}
-
-@itemlist[#:style 'compact
- @item{@tt{oke = 0}}
- @item{@tt{set_html_failed = 1}}
- @item{@tt{set_navigate_failed = 2}}
- @item{@tt{eval_js_failed = 3}}
- @item{@tt{move_failed = 12}}
- @item{@tt{resize_failed = 13}}
- @item{@tt{choose_dir_failed = 14}}
- @item{@tt{open_file_failed = 15}}
- @item{@tt{save_file_failed = 16}}
- @item{@tt{failed = 17}}
-]
-
-@subsection{window_state_t}
-
-@itemlist[#:style 'compact
- @item{@tt{invalid = -1}}
- @item{@tt{normal = 0}}
- @item{@tt{minimized = 1}}
- @item{@tt{maximized = 2}}
- @item{@tt{hidden = 3}}
- @item{@tt{normal_active = 16}}
- @item{@tt{maximized_active = 18}}
-]
-
-
-@section{Threading Model}
-
-@itemlist[#:style 'compact
- @item{All GUI work occurs on the Qt GUI thread.}
- @item{Public calls block until the posted internal command has completed.}
- @item{The host is expected to drive Qt by calling @tt{rkt_webview_process_events}.}
-]
-
-
-@section{Memory Ownership}
-
-@itemlist[#:style 'compact
- @item{@tt{rkt_event_t*} must be freed using @tt{rkt_webview_destroy_event}.}
- @item{@tt{rkt_js_result_t*} must be freed using @tt{rkt_webview_destroy_js_result}.}
- @item{All returned strings are UTF‐8 and owned by the library until destroyed with the corresponding function.}
-]
-
-
-@section{Function Reference (C signatures)}
-
-The following are literal C signatures with behavioral descriptions.
-
-@subsection{Initialization and Event Pumping}
-
-@codeblock{
-void rkt_webview_init(void);
-}
-Initializes Qt and internal state. Safe to call multiple times (subsequent calls are no-ops).
-
-@codeblock{
-void rkt_webview_process_events(int for_ms);
-}
-Pumps the Qt event loop for approximately @tt{for_ms} milliseconds.
-
-Idea for implementation in racket:
-Use a @emph{cooperative thread} with @emph{time slicing}. A practical starting point:
-@itemlist[#:style 'compact
- @item{~3 ms Qt pumping per iteration: @tt{(rkt_webview_process_events 3)}}
- @item{~10 ms Racket time (scheduler): @tt{(sleep 0.01)}}
-]
-Tune these values for your application.
-
-Example:
+The API uses integer handles for windows and browser contexts.
@verbatim|{
-(define running? (box #t))
-
-(define (pump-loop)
- (let loop ()
- (when (unbox running?)
- (rkt_webview_process_events 3) ; ~3 ms Qt
- (sleep 0.01) ; ~10 ms for Racket scheduler
- (loop))))
-
-(define pump-thread (thread pump-loop))
-
-;; ... later on shutdown:
-(set-box! running? #f)
-(thread-wait pump-thread)
+typedef int rktwebview_t;
+typedef int rkt_wv_context_t;
}|
+A value of type @tt{rktwebview_t} identifies a single webview window.
+A value of type @tt{rkt_wv_context_t} identifies a browser context.
-@subsection{Window Lifecycle}
+@subsection{Returned Data Objects}
-@codeblock{
-rktwebview_t rkt_webview_create(rktwebview_t parent, event_cb_t cb);
-}
-Creates a window, optionally modal if @tt{parent} exists. Registers @tt{cb}.
-The window is shown before returning.
+Some API calls return a pointer to a heap-allocated @tt{rkt_data_t} value.
-@codeblock{
-void rkt_webview_close(rktwebview_t wv);
-}
-Requests closure of @tt{wv}.
+@verbatim|{
+typedef enum {
+ version = 1,
+ event = 2,
+ js_result = 3
+} rkt_data_kind_t;
+
+typedef struct {
+ rkt_data_kind_t kind;
+ union {
+ rkt_version_t version;
+ rkt_event_t event;
+ rkt_js_result_t js_result;
+ } data;
+} rkt_data_t;
+}|
+
+The @tt{kind} field determines which member of the union is valid.
+
+@subsubsection{Version Data}
+
+@verbatim|{
+typedef struct {
+ int qt_major;
+ int qt_minor;
+ int qt_patch;
+ int api_major;
+ int api_minor;
+ int api_patch;
+} rkt_version_t;
+}|
+
+Version data is returned by @tt{rkt_webview_version}.
+
+@subsubsection{Event Data}
+
+@verbatim|{
+typedef struct {
+ rktwebview_t wv;
+ char *event;
+} rkt_event_t;
+}|
+
+Event data is delivered to the registered callback when the backend emits an
+event for a window.
+
+The @tt{event} field is a UTF-8 JSON string allocated by the native side.
+
+@subsubsection{JavaScript Result Data}
+
+@verbatim|{
+typedef struct {
+ result_t result;
+ char *value;
+} rkt_js_result_t;
+}|
+
+JavaScript result data is returned by @tt{rkt_webview_call_js}.
+
+The @tt{value} field contains a UTF-8 JSON string describing the result of the
+JavaScript evaluation.
+
+@subsection{Result Codes}
+
+Many functions return a value of type @tt{result_t}.
+
+@verbatim|{
+typedef enum {
+ no_result_yet = -1,
+ oke = 0,
+ set_html_failed = 1,
+ set_navigate_failed = 2,
+ eval_js_failed = 3,
+ no_devtools_on_platform = 4,
+ no_delegate_for_context = 5,
+ webview_missing_dependency = 6,
+ webview_canceled = 7,
+ webview_invalid_state = 8,
+ webview_invalid_argument = 9,
+ webview_unspecified = 10,
+ webview_dispatch_failed = 11,
+ move_failed = 12,
+ resize_failed = 13,
+ choose_dir_failed = 14,
+ open_file_failed = 15,
+ save_file_failed = 16,
+ failed = 17
+} result_t;
+}|
+
+In the current implementation, the most commonly returned values are:
-Behavior:
@itemlist[#:style 'compact
- @item{If the user tries to close via the window manager (close button, Alt–F4, etc.), the library intercepts it and emits @tt{"can-close?"}. The window remains open.}
- @item{To actually close after receiving @tt{"can-close?"}, the host must call @tt{rkt_webview_close}.}
- @item{When the final close occurs, @tt{"closed"} is emitted and the handle becomes invalid.}
+ @item{@tt{oke} for success}
+ @item{@tt{set_html_failed} for @tt{rkt_webview_set_html}}
+ @item{@tt{set_navigate_failed} for @tt{rkt_webview_set_url}}
+ @item{@tt{eval_js_failed} for JavaScript execution failure}
+ @item{@tt{move_failed} for failed move requests}
+ @item{@tt{resize_failed} for failed resize requests}
+ @item{@tt{failed} for general window-operation failure}
]
-@codeblock{
-bool rkt_webview_valid(rktwebview_t wv);
-}
-Returns whether @tt{wv} refers to an active window.
+@subsection{Window States}
+Window state is represented by @tt{window_state_t}.
-@subsection{Navigation and Content}
+@verbatim|{
+typedef enum {
+ invalid = -1,
+ normal = 0,
+ minimized = 1,
+ maximized = 2,
+ hidden = 3,
+ normal_active = 16,
+ maximized_active = 18
+} window_state_t;
+}|
-@codeblock{
-result_t rkt_webview_set_url(rktwebview_t wv, const char* url);
-}
-Navigate to @tt{url}. Returns @tt{oke} or @tt{set_navigate_failed}.
+This type is returned by @tt{rkt_webview_window_state}.
-@codeblock{
-result_t rkt_webview_set_html(rktwebview_t wv, const char* html);
-}
-Set page content from @tt{html}. Returns @tt{oke} or @tt{set_html_failed}.
+@subsection{Message Box Types}
-@codeblock{
-result_t rkt_webview_set_title(rktwebview_t wv, const char* title);
-}
-Set the native window title. Returns @tt{oke} or @tt{failed}.
+Message boxes use the type @tt{rkt_messagetype_t}.
+@verbatim|{
+typedef enum {
+ info = 1,
+ error = 2,
+ warning = 3,
+ yes_no = 4,
+ oke_cancel = 5
+} rkt_messagetype_t;
+}|
-@subsection{JavaScript Execution}
+@subsection{Event Callback Type}
-@codeblock{
-result_t rkt_webview_run_js(rktwebview_t wv, const char* js);
-}
-Fire-and-forget JavaScript execution. Returns @tt{oke} or @tt{eval_js_failed}.
+A window is created with an event callback of the following type:
+
+@verbatim|{
+typedef void (*event_cb_t)(rkt_data_t *);
+}|
+
+The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is
+@tt{event}.
+
+The callback argument must eventually be released by calling
+@tt{rkt_webview_free_data}.
+
+@subsection{Memory Ownership}
+
+The following functions return heap-allocated @tt{rkt_data_t *} values:
-@codeblock{
-rkt_js_result_t* rkt_webview_call_js(rktwebview_t wv, const char* js);
-}
-Synchronous JS call. Guarantees:
@itemlist[#:style 'compact
- @item{@tt{result} is @tt{oke} or @tt{eval_js_failed}.}
- @item{@tt{value} is JSON: @tt{{"oke": , "result": , "exn": }}.}
+ @item{@tt{rkt_webview_version}}
+ @item{@tt{rkt_webview_call_js}}
+ @item{the event callback receives event values allocated by the native side}
]
-Must be freed with @tt{rkt_webview_destroy_js_result()}.
-@codeblock{
-result_t rkt_webview_destroy_js_result(rkt_js_result_t* r);
-}
-Frees JS result memory. Returns @tt{oke}.
+Such values must be released by calling:
+@verbatim|{
+void rkt_webview_free_data(rkt_data_t *d);
+}|
+
+This function frees both the outer structure and any string data owned by it.
+
+@section{Initialization and Event Processing}
+
+@subsection{@tt{rkt_webview_init}}
+
+@verbatim|{
+void rkt_webview_init();
+}|
+
+Initializes the global runtime if it has not already been initialized.
+
+This function is idempotent. The implementation checks whether the global
+runtime already exists before constructing it.
+
+Most public API functions call @tt{rkt_webview_init} automatically before doing
+their work, so explicit initialization is optional but harmless.
+
+@subsection{@tt{rkt_webview_process_events}}
+
+@verbatim|{
+void rkt_webview_process_events(int for_ms);
+}|
+
+Processes Qt events for approximately @tt{for_ms} milliseconds.
+
+The implementation repeatedly sleeps briefly and then calls the internal Qt
+event-processing routine. This function is important for keeping windows
+responsive and for delivering callbacks such as:
+
+@itemlist[#:style 'compact
+ @item{window lifecycle events}
+ @item{page-load events}
+ @item{JavaScript-originated events}
+ @item{dialog completion events}
+]
+
+Applications embedding this backend are expected to call this function
+regularly.
+
+@section{Version and Utility Functions}
+
+@subsection{@tt{rkt_webview_version}}
+
+@verbatim|{
+rkt_data_t *rkt_webview_version();
+}|
+
+Returns a newly allocated @tt{rkt_data_t} whose @tt{kind} is @tt{version}.
+
+The returned structure contains:
+
+@itemlist[#:style 'compact
+ @item{the Qt version used to build the backend}
+ @item{the API version declared by the backend}
+]
+
+The returned value must be released with @tt{rkt_webview_free_data}.
+
+@subsection{@tt{rkt_webview_free_data}}
+
+@verbatim|{
+void rkt_webview_free_data(rkt_data_t *d);
+}|
+
+Frees a @tt{rkt_data_t} that was allocated by the backend.
+
+The function inspects @tt{d->kind} and frees any owned string data before
+freeing the outer object.
+
+@subsection{@tt{rkt_webview_set_ou_token}}
+
+@verbatim|{
+void rkt_webview_set_ou_token(rktwebview_t wv, const char *token);
+}|
+
+Associates an Organizational Unit token with the window.
+
+During certificate-error handling, self-signed certificates whose
+Organizational Unit matches this token may be accepted automatically.
+
+This function does not return a status code.
@subsection{Developer Tools}
-@codeblock{
+@subsubsection{@tt{rkt_webview_open_devtools}}
+
+@verbatim|{
result_t rkt_webview_open_devtools(rktwebview_t wv);
-}
-Open devtools. Returns @tt{oke} or @tt{no_devtools_on_platform} (platform-dependent).
+}|
+
+Opens a DevTools window associated with the given webview.
+
+In the current implementation this function returns:
+
+@itemlist[#:style 'compact
+ @item{@tt{oke} on success}
+ @item{@tt{eval_js_failed} on failure}
+]
+
+Even though the @tt{result_t} enumeration contains
+@tt{no_devtools_on_platform}, the implementation currently maps failure to
+@tt{eval_js_failed}.
-@subsection{Window Geometry and Visibility}
+@section{HTTP(s) Contexts}
-@codeblock{
+@subsection{@tt{rkt_webview_new_context}}
+
+@verbatim|{
+rkt_wv_context_t rkt_webview_new_context(const char *boilerplate_js,
+ const char *optional_server_cert_pem);
+}|
+
+Creates a new http(s) context and returns its context handle.
+
+A context corresponds to a Qt WebEngine profile and may be shared by multiple
+windows.
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@tt{boilerplate_js}: JavaScript source code to inject into pages}
+ @item{@tt{optional_server_cert_pem}: optional PEM-encoded certificate to trust}
+]
+
+Behavior:
+
+@itemlist[#:style 'compact
+ @item{A fresh profile is created.}
+ @item{The backend injects built-in event-bridge JavaScript into the profile.}
+ @item{The supplied @tt{boilerplate_js} is also injected at document-ready time.}
+ @item{If a certificate string is supplied, it is added as an additional
+ trusted certificate.}
+]
+
+The returned context handle can be passed to @tt{rkt_webview_create}.
+
+@section{Window Creation and Lifecycle}
+
+@subsection{@tt{rkt_webview_create}}
+
+@verbatim|{
+int rkt_webview_create(rkt_wv_context_t context,
+ rktwebview_t parent,
+ event_cb_t js_event_cb);
+}|
+
+Creates a new window in the given context.
+
+Arguments:
+
+@itemlist[#:style 'compact
+ @item{@tt{context}: the context handle returned by
+ @tt{rkt_webview_new_context}}
+ @item{@tt{parent}: a parent window handle, or a 0 value when no parent
+ is intended}
+ @item{@tt{js_event_cb}: callback used for all events emitted by this window}
+]
+
+Returns a window handle on success.
+
+The window is shown on success. When the parent window exists, the window
+will be a modal window to the parent.
+
+The callback receives event notifications for that window, including page
+events, geometry changes, JavaScript-originated events, and dialog results.
+
+@subsection{@tt{rkt_webview_close}}
+
+@verbatim|{
+void rkt_webview_close(rktwebview_t wv);
+}|
+
+Requests the window to close.
+
+The implementation closes the window through the internal Qt command path.
+
+When a close completes, a @tt{"closed"} event is emitted to the callback.
+This should also be called in response to a 'can-close? event.
+
+@subsection{@tt{rkt_webview_valid}}
+
+@verbatim|{
+bool rkt_webview_valid(rktwebview_t wv);
+}|
+
+Returns @tt{#t} when the given window handle currently exists and is known to
+the runtime, and @tt{#f} otherwise.
+
+@section{Window Configuration}
+
+@subsection{@tt{rkt_webview_set_title}}
+
+@verbatim|{
+result_t rkt_webview_set_title(rktwebview_t wv, const char *title);
+}|
+
+Sets the native title of the window.
+
+Returns @tt{oke} on success and @tt{failed} on failure.
+
+
+@section{Navigation and Content Loading}
+
+@subsection{@tt{rkt_webview_set_url}}
+
+@verbatim|{
+result_t rkt_webview_set_url(rktwebview_t wv, const char *url);
+}|
+
+Navigates the window to the given URL.
+
+Returns:
+
+@itemlist[#:style 'compact
+ @item{@tt{oke} on success}
+ @item{@tt{set_navigate_failed} on failure}
+]
+
+A successful or failed load eventually also produces a @tt{"page-loaded"} event.
+
+@subsection{@tt{rkt_webview_set_html}}
+
+@verbatim|{
+result_t rkt_webview_set_html(rktwebview_t wv, const char *html);
+}|
+
+Loads the given HTML string directly into the window.
+
+Returns:
+
+@itemlist[#:style 'compact
+ @item{@tt{oke} on success}
+ @item{@tt{set_html_failed} on failure}
+]
+
+A successful or failed load eventually also produces a @tt{"page-loaded"} event.
+
+@section{JavaScript Execution}
+
+@subsection{@tt{rkt_webview_run_js}}
+
+@verbatim|{
+result_t rkt_webview_run_js(rktwebview_t wv, const char *js);
+}|
+
+Executes JavaScript in the page without returning a value to the caller.
+
+Returns:
+
+@itemlist[#:style 'compact
+ @item{@tt{oke} on success}
+ @item{@tt{eval_js_failed} on failure}
+]
+
+This function is suitable for side-effecting JavaScript.
+
+@subsection{@tt{rkt_webview_call_js}}
+
+@verbatim|{
+rkt_data_t *rkt_webview_call_js(rktwebview_t wv, const char *js);
+}|
+
+Executes JavaScript and returns a newly allocated @tt{rkt_data_t} whose
+@tt{kind} is @tt{js_result}.
+
+The returned @tt{js_result.value} field contains a compact JSON string.
+
+The implementation wraps the supplied code in a small JavaScript function and
+returns a JSON object of the form:
+
+@verbatim|{
+{"oke": true|false, "result": ..., "exn": ...}
+}|
+
+
+The outer native result code is stored in @tt{js_result.result}:
+
+@itemlist[#:style 'compact
+ @item{@tt{oke} when the wrapped JavaScript completed successfully, @tt{result} will contain the javascript result. }
+ @item{@tt{eval_js_failed} when the wrapped JavaScript reported failure, @tt{exn} will contain the javascript exception information.}
+]
+
+The returned value must be released with @tt{rkt_webview_free_data}.
+
+The javascript code is executed as part of an anonymous function and must always end with a @tt{return} statement to return the result.
+
+
+@section{Window Geometry and Visibility}
+
+@subsection{@tt{rkt_webview_move}}
+
+@verbatim|{
result_t rkt_webview_move(rktwebview_t wv, int x, int y);
-}
-Waits until move is acknowledged. Returns @tt{oke} or @tt{move_failed}.
+}|
-@codeblock{
-result_t rkt_webview_resize(rktwebview_t wv, int w, int h);
-}
-Waits until resize is acknowledged. Returns @tt{oke} or @tt{resize_failed}.
+Moves the window to the given screen position.
-@codeblock{
-result_t rkt_webview_hide(rktwebview_t wv);
-}
-@codeblock{
-result_t rkt_webview_show(rktwebview_t wv);
-}
-@codeblock{
-result_t rkt_webview_show_normal(rktwebview_t wv);
-}
-@codeblock{
-result_t rkt_webview_present(rktwebview_t wv);
-}
-@codeblock{
-result_t rkt_webview_maximize(rktwebview_t wv);
-}
-@codeblock{
-result_t rkt_webview_minimize(rktwebview_t wv);
-}
-All return @tt{oke} or @tt{failed} (when the operation cannot be applied).
-
-@codeblock{
-window_state_t rkt_webview_window_state(rktwebview_t wv);
-}
-Returns a simplified window state.
-
-
-@subsection{Event & Dialog Systems}
-
-@codeblock{
-result_t rkt_webview_destroy_event(rkt_event_t* e);
-}
-Frees event memory. Returns @tt{oke}.
-
-
-@subsection{Native Dialogs}
-
-@codeblock{
-rkt_js_result_t* rkt_webview_choose_dir(rktwebview_t wv, const char* title, const char* base_dir);
-}
-Returns JSON:
-@itemlist[#:style 'compact
- @item{@tt{"state":"choosen","dir":"..."}}
- @item{@tt{"state":"canceled","dir":"..."}}
-]
-Must be freed with @tt{rkt_webview_destroy_js_result}.
-
-@codeblock{
-rkt_js_result_t* rkt_webview_file_open(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts);
-}
-@codeblock{
-rkt_js_result_t* rkt_webview_file_save(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts);
-}
-Return JSON:
-@itemlist[#:style 'compact
- @item{@tt{"state":"choosen","file":"...","used-filter":"..."}}
- @item{@tt{"state":"canceled","file":"","used-filter":"..."}}
-]
-Must be freed with @tt{rkt_webview_destroy_js_result}.
-
-
-@subsection{Security}
-
-@codeblock{
-void rkt_webview_set_ou_token(rktwebview_t wv, const char* token);
-}
-
-This function configures a per-window "OU acceptance token" that affects how
-certificate errors are handled inside the internal @tt{QWebEnginePage}.
-
-When a TLS certificate error occurs, the internal page examines the certificate
-chain. If all the following conditions are met:
+Returns:
@itemlist[#:style 'compact
- @item{the certificate is self-signed,}
- @item{the certificate contains an Organizational Unit (OU) field,}
- @item{the OU value exactly matches the token previously set with @tt{rkt_webview_set_ou_token},}
+ @item{@tt{oke} on success}
+ @item{@tt{move_failed} on failure}
]
-then the certificate is programmatically accepted and the load is allowed to
-continue.
+@subsection{@tt{rkt_webview_resize}}
-This mechanism is intended for controlled environments (e.g., local development
-servers or private deployments) where a known self-signed certificate is used
-and its OU value is part of the trust boundary.
+@verbatim|{
+result_t rkt_webview_resize(rktwebview_t wv, int width, int height);
+}|
-Note: starting with Qt 6.10, @tt{QWebEnginePage} introduces an API for providing
-a specific certificate to be accepted directly by the page. However, this API is
-very new ("bleeding edge") and not yet used or relied upon in this WebView
-bridge.
+Resizes the window to the given dimensions.
-
-@section{Event JSON Format}
+Returns:
@itemlist[#:style 'compact
- @item{@tt{"event":"page-loaded","oke": }}
- @item{@tt{"event":"show"}}
- @item{@tt{"event":"hide"}}
- @item{@tt{"event":"move","x": ,"y": }}
- @item{@tt{"event":"resize","w": ,"h": }}
- @item{@tt{"event":"can-close?"}}
- @item{@tt{"event":"closed"}}
- @item{@tt{"event":"js-evt","js-evt":