Files
racket-webview/scrbl/rktwebview-api.scrbl
2026-03-09 09:28:46 +01:00

348 lines
9.9 KiB
Racket
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#lang scribble/manual
@title{Public API Specification for the QtBacked WebView Bridge}
@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{Calling Convention and Structure}
All functions are C functions with normal C linkage.
Window handles are simple integers that uniquely identify native windows.
@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}
]
@section{Opaque and Helper Types}
@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 (UTF8).}
@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 UTF8 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 UTF8 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:
@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)
}|
@subsection{Window Lifecycle}
@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.
@codeblock{
void rkt_webview_close(rktwebview_t wv);
}
Requests closure of @tt{wv}.
Behavior:
@itemlist[#:style 'compact
@item{If the user tries to close via the window manager (close button, AltF4, 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.}
]
@codeblock{
bool rkt_webview_valid(rktwebview_t wv);
}
Returns whether @tt{wv} refers to an active window.
@subsection{Navigation and Content}
@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}.
@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}.
@codeblock{
result_t rkt_webview_set_title(rktwebview_t wv, const char* title);
}
Set the native window title. Returns @tt{oke} or @tt{failed}.
@subsection{JavaScript Execution}
@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}.
@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": <bool>, "result": <value-or-false>, "exn": <string-or-false>}}.}
]
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}.
@subsection{Developer Tools}
@codeblock{
result_t rkt_webview_open_devtools(rktwebview_t wv);
}
Open devtools. Returns @tt{oke} or @tt{no_devtools_on_platform} (platform-dependent).
@subsection{Window Geometry and Visibility}
@codeblock{
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}.
@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:
@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},}
]
then the certificate is programmatically accepted and the load is allowed to
continue.
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.
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.
@section{Event JSON Format}
@itemlist[#:style 'compact
@item{@tt{"event":"page-loaded","oke": <bool>}}
@item{@tt{"event":"show"}}
@item{@tt{"event":"hide"}}
@item{@tt{"event":"move","x": <int>,"y": <int>}}
@item{@tt{"event":"resize","w": <int>,"h": <int>}}
@item{@tt{"event":"can-close?"}}
@item{@tt{"event":"closed"}}
@item{@tt{"event":"js-evt","js-evt": <object>}}
]
@section{Typical Usage Pattern}
@itemlist[#:style 'compact
@item{Initialize once.}
@item{Create windows.}
@item{Run a cooperative time-sliced pump loop (e.g., ~3 ms Qt, ~10 ms Racket).}
@item{Invoke JS run/call functions.}
@item{Handle and free events.}
@item{Close windows explicitly.}
]
@section{Summary}
This API provides a stable, simple interface for embedding a Qt WebEngine
webview inside a Racket program via FFI. The API is synchronous, uses compact
JSON for results, and defines clear memory ownership rules.
``