Documentation and some other stuff.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
516
scrbl/rktwebview-shared-memory-diagram-simple.svg
Normal file
516
scrbl/rktwebview-shared-memory-diagram-simple.svg
Normal file
@@ -0,0 +1,516 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="1400"
|
||||
height="820"
|
||||
viewBox="0 0 1400 820"
|
||||
version="1.1"
|
||||
id="svg34"
|
||||
sodipodi:docname="rktwebview-shared-memory-diagram-simple.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs34">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker49"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path49" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker48"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path48" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker45"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path45" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="Triangle"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path135" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker40"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path40" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker38"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path38" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview34"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.78374771"
|
||||
inkscape:cx="729.82669"
|
||||
inkscape:cy="468.90089"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1129"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg34" />
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="1400"
|
||||
height="820"
|
||||
fill="#ffffff"
|
||||
id="rect1" />
|
||||
<!-- Title -->
|
||||
<rect
|
||||
x="292.14935"
|
||||
y="50.869148"
|
||||
width="807.46985"
|
||||
height="69.175186"
|
||||
fill="#4b7fda"
|
||||
stroke="#000000"
|
||||
stroke-width="2.82481"
|
||||
id="rect2"
|
||||
ry="14.206594"
|
||||
rx="26.788099" />
|
||||
<text
|
||||
x="693.3324"
|
||||
y="95.456741"
|
||||
font-family="Arial"
|
||||
font-size="30px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text2">Shared Memory</text>
|
||||
<!-- Shared memory block -->
|
||||
<rect
|
||||
x="298.4361"
|
||||
y="138.76608"
|
||||
width="800"
|
||||
height="600"
|
||||
fill="#efefef"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect3" />
|
||||
<!-- Allocator -->
|
||||
<g
|
||||
id="g36"
|
||||
transform="translate(-1.5639013,-1.2339131)">
|
||||
<g
|
||||
id="g34"
|
||||
transform="translate(-24.328594,-25.434439)">
|
||||
<rect
|
||||
x="350"
|
||||
y="185"
|
||||
width="240"
|
||||
height="60"
|
||||
fill="#d8d8d8"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect4"
|
||||
ry="11.058452"
|
||||
rx="12.164297" />
|
||||
</g>
|
||||
<text
|
||||
x="445.67142"
|
||||
y="197.56557"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
id="text4">Allocator</text>
|
||||
</g>
|
||||
<text
|
||||
x="311.94321"
|
||||
y="237.80243"
|
||||
font-family="Arial"
|
||||
font-size="16px"
|
||||
id="text5">free list, slot table, allocation headers</text>
|
||||
<!-- Queues -->
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="183.76608"
|
||||
width="395"
|
||||
height="54"
|
||||
fill="#f0bc23"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect5" />
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="237.76608"
|
||||
width="395"
|
||||
height="50"
|
||||
fill="#fbf6df"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect6" />
|
||||
<text
|
||||
x="830.4361"
|
||||
y="218.76608"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
id="text6">Command Queue</text>
|
||||
<text
|
||||
x="830.4361"
|
||||
y="269.76608"
|
||||
font-family="Arial"
|
||||
font-size="18px"
|
||||
text-anchor="middle"
|
||||
id="text7">command payloads</text>
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="348.76608"
|
||||
width="395"
|
||||
height="54"
|
||||
fill="#71b53b"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect7" />
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="402.76608"
|
||||
width="395"
|
||||
height="50"
|
||||
fill="#edf5e0"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect8" />
|
||||
<text
|
||||
x="830.4361"
|
||||
y="383.76608"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text8">Result Queue</text>
|
||||
<text
|
||||
x="830.4361"
|
||||
y="434.76608"
|
||||
font-family="Arial"
|
||||
font-size="18px"
|
||||
text-anchor="middle"
|
||||
id="text9">result payloads</text>
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="513.76611"
|
||||
width="395"
|
||||
height="54"
|
||||
fill="#e56a0b"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect9" />
|
||||
<rect
|
||||
x="633.4361"
|
||||
y="567.76611"
|
||||
width="395"
|
||||
height="50"
|
||||
fill="#fae9dd"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect10" />
|
||||
<text
|
||||
x="830.4361"
|
||||
y="548.76611"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text10">Event Queue</text>
|
||||
<text
|
||||
x="830.4361"
|
||||
y="599.76611"
|
||||
font-family="Arial"
|
||||
font-size="18px"
|
||||
text-anchor="middle"
|
||||
id="text11">event payloads</text>
|
||||
<!-- Payload label -->
|
||||
<text
|
||||
x="698.4361"
|
||||
y="673.76611"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#555555"
|
||||
id="text12">Payloads in Shared Memory</text>
|
||||
<line
|
||||
x1="388.4361"
|
||||
y1="686.76611"
|
||||
x2="1008.4361"
|
||||
y2="686.76611"
|
||||
stroke="#888888"
|
||||
stroke-width="2"
|
||||
id="line12" />
|
||||
<!-- Left process -->
|
||||
<rect
|
||||
x="43.653984"
|
||||
y="361.71698"
|
||||
width="274.56424"
|
||||
height="326.83121"
|
||||
fill="#ffffff"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect12" />
|
||||
<g
|
||||
id="g37"
|
||||
transform="translate(-1.7755917,-110.71258)">
|
||||
<rect
|
||||
x="45"
|
||||
y="470"
|
||||
width="275"
|
||||
height="90"
|
||||
fill="#e26a10"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect13" />
|
||||
<text
|
||||
x="182"
|
||||
y="525"
|
||||
font-family="Arial"
|
||||
font-size="28px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text13">Racket Process</text>
|
||||
</g>
|
||||
<text
|
||||
x="101.61144"
|
||||
y="514.19263"
|
||||
font-family="Arial"
|
||||
font-size="22px"
|
||||
font-weight="bold"
|
||||
id="text14">• Call API (FFI)</text>
|
||||
<text
|
||||
x="117.09328"
|
||||
y="543.13422"
|
||||
font-family="Arial"
|
||||
font-size="22px"
|
||||
font-weight="bold"
|
||||
id="text15">• Receive Result</text>
|
||||
<text
|
||||
x="96.082222"
|
||||
y="589.76929"
|
||||
font-family="Arial"
|
||||
font-size="22px"
|
||||
font-weight="bold"
|
||||
id="text16">• Poll Events</text>
|
||||
<!-- Right process -->
|
||||
<g
|
||||
id="g44"
|
||||
transform="translate(15.023069,-161.99993)">
|
||||
<rect
|
||||
x="1120"
|
||||
y="460"
|
||||
width="245"
|
||||
height="240"
|
||||
fill="#f7f7f8"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect16" />
|
||||
<rect
|
||||
x="1120"
|
||||
y="460"
|
||||
width="245"
|
||||
height="105"
|
||||
fill="#676e7a"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect17" />
|
||||
<text
|
||||
x="1242"
|
||||
y="515"
|
||||
font-family="Arial"
|
||||
font-size="24px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text17">rktwebview_prg</text>
|
||||
<text
|
||||
x="1242"
|
||||
y="548"
|
||||
font-family="Arial"
|
||||
font-size="18px"
|
||||
text-anchor="middle"
|
||||
fill="#ffffff"
|
||||
id="text18">(Qt / Chromium)</text>
|
||||
<g
|
||||
id="g41"
|
||||
transform="translate(0,-11.058452)">
|
||||
<rect
|
||||
x="1152"
|
||||
y="590"
|
||||
width="180"
|
||||
height="48"
|
||||
fill="#efdf9b"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect18" />
|
||||
<text
|
||||
x="1242"
|
||||
y="621"
|
||||
font-family="Arial"
|
||||
font-size="20px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
id="text19">Command Thread</text>
|
||||
</g>
|
||||
<g
|
||||
id="g42">
|
||||
<g
|
||||
id="g43"
|
||||
transform="translate(0,-10.947309)">
|
||||
<rect
|
||||
x="1152"
|
||||
y="650"
|
||||
width="180"
|
||||
height="48"
|
||||
fill="#efdf9b"
|
||||
stroke="#000000"
|
||||
stroke-width="2"
|
||||
id="rect19" />
|
||||
<text
|
||||
x="1242"
|
||||
y="681"
|
||||
font-family="Arial"
|
||||
font-size="20px"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
id="text20">Qt GUI Thread</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<!-- Command arrows -->
|
||||
<!-- Result arrows right to left -->
|
||||
<!-- Event arrows right to left -->
|
||||
<!-- Footer -->
|
||||
<text
|
||||
x="698.4361"
|
||||
y="783.76611"
|
||||
font-family="Arial"
|
||||
font-size="16px"
|
||||
text-anchor="middle"
|
||||
fill="#555555"
|
||||
id="text34">Racket calls the C API through FFI; the helper process consumes commands and produces results and events.</text>
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-width:4.2;stroke-dasharray:none;marker-end:url(#marker38)"
|
||||
d="m 638.72046,571.59389 -321.80095,1.10585"
|
||||
id="path37"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:4.1534;stroke-dasharray:none;marker-end:url(#marker40)"
|
||||
d="m 635.3126,404.52149 -55.08407,1.10725 -2.16016,128.43983 -260.29922,2.21448"
|
||||
id="path39" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:4.2;stroke-dasharray:none;marker-end:url(#Triangle)"
|
||||
d="M 306.96691,505.24318 H 543.61777 V 261.95724 h 87.36177"
|
||||
id="path41" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:3.89804;stroke-dasharray:none;marker-end:url(#marker45)"
|
||||
d="m 1165.3148,499.58917 h -86.7061 v 77.63074 h -36.8501"
|
||||
id="path44" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:3.9801;stroke-dasharray:none;marker-end:url(#marker49)"
|
||||
d="m 1030.6475,241.07822 h 31.4718 v 189.62297 h 95.6253"
|
||||
id="path46" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:4.2;stroke-dasharray:none;marker-end:url(#marker48)"
|
||||
d="m 1162.0759,452.99389 h -112.2811 v -53.58867 l -18.8137,5.04111"
|
||||
id="path47" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -1,346 +1,256 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@title{Structure and behavior of @tt{rktwebview_qt}}
|
||||
@title{Qt WebView Backend Architecture}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@section{Introduction}
|
||||
|
||||
It would, of course, be preferable to place everything within a single process,
|
||||
under a unified structure. This would be elegant. It is not how things are.
|
||||
|
||||
@centered{
|
||||
@image[#:scale 0.45]{rktwebview-shared-memory-diagram-simple.svg}
|
||||
}
|
||||
|
||||
Qt WebEngine establishes its own order: threads, event loops, internal state.
|
||||
Once set in motion, it does not easily yield. It persists, and it expects its
|
||||
environment to adapt accordingly. These conditions are accepted.
|
||||
|
||||
The Racket process, however, is of a different nature. It is light, precise,
|
||||
capable of starting and stopping without residue. It must remain so.
|
||||
|
||||
So a boundary is drawn.
|
||||
|
||||
On one side, Qt: a large, immovable instrument—something like an organ. Once it
|
||||
begins to sound, it fills the space, and it is not easily silenced. On the other,
|
||||
Racket: a violin, agile and expressive, able to begin and end a phrase at will.
|
||||
|
||||
They do not become the same instrument. They are allowed to play together.
|
||||
Communication is arranged accordingly. A shared memory region, containing three
|
||||
queues: commands, results, and events. A command is issued. It crosses the boundary. It is taken up and executed. A result returns.
|
||||
Events also arise, independently, and must be handled when they appear.
|
||||
|
||||
Within this structure, the violin may move freely—provided it does not attempt to
|
||||
reconfigure the organ. No attempt is made to unify the instruments. Such efforts would not improve the music. Instead, the composition is written so that each plays its part.
|
||||
|
||||
From the outside, one hears only a simple exchange: a call, a response. Internally, the balance is carefully maintained. For now, this is sufficient. And it holds.
|
||||
|
||||
@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.
|
||||
This backend provides a webview implementation by delegating all GUI and browser
|
||||
functionality to a separate Qt process.
|
||||
|
||||
The backend provides:
|
||||
The embedding Racket process does not manipulate Qt widgets directly. Instead,
|
||||
it communicates with a helper process that owns the Qt event loop and all
|
||||
@tt{QWebEngine} objects.
|
||||
|
||||
@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}
|
||||
]
|
||||
This design exists to work around limitations of Qt WebEngine in combination with
|
||||
the lifecycle model of the DrRacket environment.
|
||||
|
||||
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{Execution Model}
|
||||
|
||||
@section{Requirements}
|
||||
The runtime consists of two processes: the embedding Racket process and a helper
|
||||
process running Qt and Qt WebEngine.
|
||||
|
||||
The @tt{rktwebview_qt} backend requires Qt version @tt{6.10.2} or newer.
|
||||
All GUI state lives in the helper process. The embedding side holds no direct
|
||||
references to Qt objects. Communication is explicit and happens through shared
|
||||
memory.
|
||||
|
||||
The backend is built against Qt WebEngine and depends on the runtime
|
||||
components provided by Qt. Earlier Qt versions are not supported.
|
||||
@section{Shared Memory and Queues}
|
||||
|
||||
Applications using the backend must therefore ensure that a compatible
|
||||
Qt runtime (≥ @tt{6.10.2}) is available.
|
||||
A shared memory region is created during initialization. Inside that region,
|
||||
three FIFO queues are established: a command queue, a result queue, and an event
|
||||
queue.
|
||||
|
||||
In packaged builds the required Qt runtime is normally bundled together
|
||||
with the backend library.
|
||||
Each message consists of a numeric code and a payload, typically JSON:
|
||||
|
||||
@subsection{Compatibility}
|
||||
@centerline{@tt{(code, payload)}}
|
||||
|
||||
The backend requires Qt version @tt{6.10.2} or newer.
|
||||
The queues have distinct roles. The @italic{command queue} carries requests from the embedding process to the Qt process, for example creating a window, loading a URL, or executing JavaScript. The @italic{result queue} carries direct replies to those commands. A synchronous call on the embedding side blocks until a corresponding result is available. The @italic{event queue} carries asynchronous notifications generated by the Qt side, such as page load completion, navigation requests, window movement, or events
|
||||
originating from JavaScript.
|
||||
|
||||
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.
|
||||
@section{Command Execution}
|
||||
|
||||
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.
|
||||
A function call on the embedding side is translated into a command and written to
|
||||
the command queue.
|
||||
|
||||
Packaged builds typically include the required Qt runtime components.
|
||||
From there the flow is fixed: (1) the command is read by a worker thread in the
|
||||
helper process, (2) it is reposted onto the Qt GUI thread, (3) the GUI thread
|
||||
executes the operation, and (4) the result is written back to the result queue.
|
||||
|
||||
@section{Layers}
|
||||
The worker thread never manipulates Qt objects. All GUI work happens on the GUI
|
||||
thread.
|
||||
|
||||
The system consists of several layers.
|
||||
From the caller’s perspective, a synchronous call returns only after the GUI
|
||||
thread has completed the action.
|
||||
|
||||
@subsection{C ABI Layer}
|
||||
@section{Event Delivery}
|
||||
|
||||
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.
|
||||
Many relevant events are not tied to a specific command. Page loading, navigation
|
||||
attempts, window movement, and JavaScript-originated events are delivered through
|
||||
the event queue.
|
||||
|
||||
Important characteristics of the ABI include:
|
||||
Events are retrieved explicitly by polling.
|
||||
|
||||
@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}
|
||||
]
|
||||
Each event contains a name, an identifier, and optional fields depending on its
|
||||
type. Events are delivered in FIFO order.
|
||||
|
||||
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{Contexts and Webviews}
|
||||
|
||||
@section{Runtime Controller}
|
||||
The backend uses a two-level model consisting of contexts and webviews.
|
||||
|
||||
The class @tt{Rktwebview_qt} acts as the central runtime controller.
|
||||
A context represents a browser environment and corresponds to a
|
||||
@tt{QWebEngineProfile}. It defines how pages run, including injected scripts and optional trust configuration using explicitly trusted self-signed certificates.
|
||||
|
||||
Its responsibilities include:
|
||||
Each context is identified by an integer handle.
|
||||
|
||||
@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}
|
||||
]
|
||||
Within a context, one or more webviews can be created. A webview represents a
|
||||
window containing a browser view. Webviews are also identified by integer
|
||||
handles.
|
||||
|
||||
Internally, it maintains several registries:
|
||||
A webview always belongs to exactly one context. When creating a webview, the
|
||||
context handle must be provided.
|
||||
|
||||
@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}
|
||||
]
|
||||
Webviews may optionally have a parent webview. If a parent is specified, the
|
||||
resulting window is created as a modal child of that parent; otherwise it is
|
||||
created as a top-level window.
|
||||
|
||||
Each API call that manipulates a window or browser state is forwarded through
|
||||
this controller.
|
||||
From the Racket side, this means that a context must be created first. That
|
||||
context handle is then used to create webviews, which are subsequently addressed
|
||||
through their own handles.
|
||||
|
||||
@section{HTTP(s) Contexts}
|
||||
All Qt objects remain internal to the helper process; only these integer handles
|
||||
cross the process boundary.
|
||||
|
||||
A http(s) context represents a shared WebEngine profile.
|
||||
@section{JavaScript Bridge}
|
||||
|
||||
Contexts are created using @tt{rkt_webview_new_context}. Each context contains:
|
||||
Each context installs a small JavaScript bridge into every page, allowing
|
||||
JavaScript code to send structured data to the host via:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{a @tt{QWebEngineProfile}}
|
||||
@item{optional injected JavaScript code}
|
||||
@item{optional trusted certificates}
|
||||
]
|
||||
@centerline{@tt{window.rkt_send_event(obj)}}
|
||||
|
||||
Multiple windows normally share the same context. This allows cookies,
|
||||
configuration, and injected scripts to be reused.
|
||||
The objects are collected and forwarded to the event queue.
|
||||
|
||||
A context normally represents a https server that can be reached on a certain (local) Url.
|
||||
@section{Navigation and Window Behavior}
|
||||
|
||||
@section{Window and View Classes}
|
||||
User actions are not always executed immediately; navigation initiated by the
|
||||
user may result in a @tt{"navigation-request"} event instead of being followed
|
||||
automatically, and closing a window may result in a @tt{"can-close?"} event. The
|
||||
Racket side is expected to decide how to handle these situations.
|
||||
|
||||
Two Qt classes implement the actual browser window.
|
||||
@section{Design Considerations}
|
||||
|
||||
@subsection{@tt{WebviewWindow}}
|
||||
@bold{Qt WebEngine lifecycle.}
|
||||
Qt WebEngine cannot be safely reinitialized within a single process.
|
||||
|
||||
@tt{WebviewWindow} derives from @tt{QMainWindow}. It represents the native
|
||||
top-level window.
|
||||
In practice, once a @tt{QApplication} using WebEngine has been started and shut
|
||||
down, the WebEngine runtime cannot be started again. This is a known and
|
||||
documented limitation (see for example QTBUG-70519, QTBUG-87460,
|
||||
QTBUG-145033). The underlying cause is that WebEngine starts internal threads
|
||||
and resources that are not fully released, even after application shutdown.
|
||||
|
||||
Responsibilities include:
|
||||
Attempts to reinitialize WebEngine in the same process result in undefined
|
||||
behavior, including crashes, hangs, or inconsistent state.
|
||||
|
||||
@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}
|
||||
]
|
||||
In the DrRacket environment, where components may be restarted under a
|
||||
custodian, this makes an in-process design fundamentally unsuitable. Even when
|
||||
libraries are loaded and unloaded using @tt{#:custodian}, the WebEngine runtime
|
||||
cannot be reset to a clean state.
|
||||
|
||||
Window geometry changes are slightly delayed through timers so that the host
|
||||
application receives fewer intermediate events.
|
||||
By moving Qt and WebEngine into a separate process, this limitation is avoided
|
||||
entirely: each start of the backend creates a fresh runtime, and terminating the
|
||||
helper process guarantees that all associated threads and resources are released
|
||||
by the operating system.
|
||||
|
||||
@subsection{@tt{WebViewQt}}
|
||||
@bold{Event loop and threading.}
|
||||
Qt requires that GUI operations are performed on the Qt GUI thread.
|
||||
|
||||
@tt{WebViewQt} derives from @tt{QWebEngineView}. This class mainly associates
|
||||
the Qt widget with a numeric handle used by the external API.
|
||||
Instead of attempting to integrate Qt’s event loop with Racket, the design
|
||||
isolates Qt completely and runs it in its own process.
|
||||
|
||||
@section{Command Dispatch}
|
||||
Within that process, a worker thread receives commands and forwards them to the
|
||||
GUI thread using Qt’s event mechanism (via @tt{postEvent}). The Racket side never
|
||||
interacts with Qt objects directly.
|
||||
|
||||
Many API functions are implemented using a small command-dispatch mechanism.
|
||||
@bold{Failure isolation.}
|
||||
Qt WebEngine is a large subsystem with its own internal processes (including the
|
||||
Chromium-based @tt{QtWebEngineProcess}) and is generally stable in practice.
|
||||
|
||||
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.
|
||||
Running the Qt side in a separate process provides isolation: if the helper
|
||||
process terminates, the embedding Racket process remains unaffected and can
|
||||
decide how to recover.
|
||||
|
||||
This design ensures that GUI operations occur inside the Qt event loop while
|
||||
the external API remains synchronous.
|
||||
@bold{Shared memory communication.}
|
||||
The communication pattern consists of commands, results, and events, mapped onto
|
||||
shared memory FIFO queues, keeping the model simple and explicit.
|
||||
|
||||
@section{Event Processing}
|
||||
@bold{JSON encoding.}
|
||||
All payloads are encoded as JSON, providing a natural bridge between JavaScript,
|
||||
Qt/C++, and Racket: JavaScript produces JSON natively, Qt maps it to variant
|
||||
types, and Racket can decode it easily.
|
||||
|
||||
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.
|
||||
For control commands, payload sizes are small and infrequent, so serialization
|
||||
cost is negligible compared to GUI-thread execution and WebEngine processing; for
|
||||
dynamic data such as JavaScript results and custom events, JSON is the
|
||||
appropriate representation. A binary protocol would reduce overhead but increase
|
||||
complexity and reduce inspectability.
|
||||
|
||||
@section{Navigation}
|
||||
@section{Shared Memory Architecture}
|
||||
|
||||
Two main navigation operations are provided.
|
||||
Communication between the Racket process and @tt{rktwebview_prg} is implemented
|
||||
using a shared memory region. This region serves three purposes at once: it
|
||||
stores shared data structures, it provides a simple allocator, and it hosts the
|
||||
FIFO queues used for message passing.
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@tt{rkt_webview_set_url} loads a URL}
|
||||
@item{@tt{rkt_webview_set_html} loads raw HTML}
|
||||
]
|
||||
At the start of the shared memory block, a small administration area is stored,
|
||||
including a pointer to the current end of allocated memory, a list of active
|
||||
allocations, a free list, and a fixed slot table. The slot table acts as a
|
||||
directory of shared objects; queues are created once, stored in slots, and can be
|
||||
retrieved by both processes using only the slot number.
|
||||
|
||||
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.
|
||||
Memory allocation inside the shared block is intentionally simple. Each
|
||||
allocation is preceded by a small header containing size information and links
|
||||
for a double-linked list. Allocation first attempts to reuse a block from the
|
||||
free list; if no suitable block is available, memory is taken from the unused
|
||||
tail of the region. Freed blocks are returned to the free list and may later be
|
||||
reused. Blocks are not compacted or coalesced. This is not a general-purpose
|
||||
heap; it is a small, predictable allocator for queue items and payload strings.
|
||||
|
||||
Shared objects are referenced primarily through offsets (via @tt{ShmPlace})
|
||||
rather than raw pointers. This makes the layout independent of the virtual
|
||||
address at which the shared memory is mapped in each process.
|
||||
|
||||
Queues are built directly on top of this allocator. Each queue consists of a
|
||||
small header containing the first item, the last item, and a count, followed by a
|
||||
linked list of queue items. Each item stores a numeric command or event code, a
|
||||
pointer to its payload in shared memory, and links to neighboring items.
|
||||
|
||||
Synchronization is split into two layers. A shared lock protects allocator and
|
||||
queue metadata, while each queue has its own semaphore indicating whether items
|
||||
are available. One mechanism protects the structure; the other tells you whether
|
||||
there is anything worth reading.
|
||||
|
||||
On POSIX systems such as Linux, shared memory is implemented using
|
||||
@tt{shm_open}, @tt{ftruncate}, and @tt{mmap}, with synchronization via named
|
||||
POSIX semaphores created using @tt{sem_open}. The owner process initializes these
|
||||
objects and removes them again using @tt{shm_unlink} and @tt{sem_unlink}.
|
||||
|
||||
On Windows, the same model is implemented using @tt{CreateFileMappingA} and
|
||||
@tt{MapViewOfFile} for shared memory, and @tt{CreateSemaphoreA} or
|
||||
@tt{OpenSemaphoreA} for synchronization. The design is identical, but the kernel
|
||||
objects follow the Windows lifetime model and are released when the last handle
|
||||
is closed.
|
||||
|
||||
The shared memory region has a fixed size (currently 10MB) and is not resized at
|
||||
runtime. Although the use of @tt{ShmPlace} offsets would in principle allow
|
||||
relocation, resizing would require coordinated remapping in both processes while
|
||||
all activity is paused. The current design therefore treats the region as
|
||||
fixed-size and relies on reuse of freed blocks.
|
||||
|
||||
This implies that the communication channel is bounded. Payloads such as
|
||||
@tt{set_html} or large JavaScript results must fit within the available free
|
||||
space in the shared memory block. In practice, the usable limit is somewhat below
|
||||
the nominal 10MB due to allocator overhead, queue administration, and concurrent
|
||||
messages.
|
||||
|
||||
This is a message transport, not an infinite sack of HTML.
|
||||
|
||||
@@ -1,80 +1,107 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require scribble/racket
|
||||
scribble/example
|
||||
@(require racket/base
|
||||
racket/class
|
||||
scribble/core
|
||||
(for-label racket/base
|
||||
racket/string
|
||||
racket/class
|
||||
racket/path
|
||||
"../private/wv-settings.rkt"
|
||||
"../private/racket-webview.rkt")
|
||||
)
|
||||
racket/file))
|
||||
|
||||
@defmodule[racket-webview]
|
||||
@; "../private/wv-context.rkt")
|
||||
|
||||
@title{Object-Oriented Interface: @racket[wv-context%]}
|
||||
@title{wv-context}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[wv-context]
|
||||
|
||||
@section{Overview}
|
||||
|
||||
The library is organized around two main concepts: contexts and windows.
|
||||
|
||||
A context represents the shared runtime environment for one or more webview
|
||||
windows. It owns the underlying webview context, provides the base URL used by
|
||||
those windows, and gives access to persistent settings through
|
||||
@racketmodname[wv-settings].
|
||||
|
||||
This module exports the @racket[wv-context%] class.
|
||||
|
||||
@defclass[wv-context% object% ()]{
|
||||
|
||||
An OO wrapper around a webview context together with its associated settings.
|
||||
A @racket[wv-context%] object creates and owns a webview context by calling
|
||||
@racket[webview-new-context]. It also creates a corresponding @racket[wv-settings%]
|
||||
instance for access to global and per-section settings. This class manages
|
||||
construction of a webview context using a file getter and boilerplate JavaScript,
|
||||
exposure of the created low-level context object via @racket[context],
|
||||
access to the base URL of the created context via @racket[base-url] and
|
||||
access to settings views via @racket[settings].
|
||||
Represents a webview context.
|
||||
|
||||
A @racket[wv-context%] object is a thin class-based wrapper around the
|
||||
higher-level context support provided by @racketmodname[racket-webview]. It
|
||||
creates and stores a @racket[wv-context] value internally and provides methods
|
||||
to access that value, its base URL, and a settings object.
|
||||
|
||||
@defconstructor[([base-path path-string?]
|
||||
[file-getter procedure? (webview-standard-file-getter base-path)]
|
||||
[context-js procedure? (λ () "")]
|
||||
[boilerplate-js procedure? (webview-default-boilerplate-js context-js)]
|
||||
[ini object?])]{
|
||||
Creates a new @racket[wv-context%] object.
|
||||
[file-getter procedure?
|
||||
(webview-standard-file-getter base-path)]
|
||||
[context-js procedure?
|
||||
(λ () "")]
|
||||
[boilerplate-js string?
|
||||
(webview-default-boilerplate-js context-js)]
|
||||
[ini any/c
|
||||
(error "You need to provide a 'ini' file settings interface for settings, e.g. simple-ini/class")])]{
|
||||
|
||||
The @racket[base-path] argument is used to construct the default
|
||||
@racket[file-getter].
|
||||
Creates a new context object.
|
||||
|
||||
The @racket[file-getter] argument is passed to @racket[webview-new-context]
|
||||
and is responsible for resolving files served by the context.
|
||||
|
||||
The @racket[context-js] argument must be a procedure producing the JavaScript
|
||||
snippet that should be injected into the context.
|
||||
|
||||
The @racket[boilerplate-js] argument must be a procedure producing the final
|
||||
boilerplate JavaScript used when constructing the context. By default it is
|
||||
built from @racket[context-js] using @racket[webview-default-boilerplate-js].
|
||||
|
||||
The @racket[ini] argument is mandatory and supplies the settings backend used
|
||||
for construction of the internal @racket[wv-settings%] object. If omitted, the
|
||||
constructor raises an error.
|
||||
|
||||
During initialization the class:
|
||||
The constructor accepts the following initialization fields.
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{creates a new low-level context with @racket[webview-new-context];}
|
||||
@item{creates a @racket[wv-settings%] object using @racket[ini] and the
|
||||
global context identifier @racket['global].}
|
||||
@item{@racket[base-path] is the base path used by the default file getter.}
|
||||
@item{@racket[file-getter] is the procedure used to resolve files for the
|
||||
local web server. Its default value is
|
||||
@racket[(webview-standard-file-getter base-path)].}
|
||||
@item{@racket[context-js] is a procedure producing additional JavaScript for
|
||||
the context. Its default value is @racket[(λ () "")].}
|
||||
@item{@racket[boilerplate-js] is the JavaScript boilerplate installed into the
|
||||
underlying webview context. Its default value is
|
||||
@racket[(webview-default-boilerplate-js context-js)].}
|
||||
@item{@racket[ini] is the settings backend used to construct the associated
|
||||
@racket[wv-settings%] object. No default backend is provided; omitting it
|
||||
raises an error.}]
|
||||
|
||||
After @racket[super-new], the constructor creates the underlying
|
||||
@racket[wv-context] value using @racket[webview-new-context], and then creates a
|
||||
settings object using:
|
||||
|
||||
@racketblock[
|
||||
(new wv-settings% [ini ini] [wv-context 'global])
|
||||
]
|
||||
|
||||
The settings object is stored internally and used by the
|
||||
@racket[settings] method.
|
||||
}
|
||||
|
||||
@defmethod*[([(context) wv-context?])]{
|
||||
Returns the underlying webview context (struct) created during
|
||||
initialization.
|
||||
@defmethod[(context) wv-context?]{
|
||||
|
||||
Returns the internal context value created by @racket[webview-new-context].
|
||||
|
||||
This is the underlying @racketmodname[racket-webview] context object used by
|
||||
the rest of the library.
|
||||
}
|
||||
|
||||
@defmethod*[([(settings [section symbol?]) (is-a?/c wv-settings%)])] {
|
||||
Returns a @tt{wv-settings%} object for @racket[section].
|
||||
This method delegates to the internally stored @racket[wv-settings%] object by
|
||||
calling @racket[(send settings-obj clone section)]. The resulting object shares
|
||||
the same @racket[ini] backend as the global settings object, but addresses the
|
||||
supplied section.
|
||||
@defmethod[(settings [section symbol?]) wv-settings%]{
|
||||
|
||||
Returns a cloned settings object for @racket[section].
|
||||
|
||||
The method delegates to the internal @racket[wv-settings%] instance as:
|
||||
|
||||
@racketblock[
|
||||
(send settings-obj clone section)
|
||||
]
|
||||
|
||||
This allows per-section settings access while preserving the original settings
|
||||
object stored by the context.
|
||||
}
|
||||
|
||||
@defmethod*[([(base-url) any/c])] {
|
||||
Returns the base URL of the underlying webview context.
|
||||
This method delegates to @racket[wv-context-base-url].
|
||||
}
|
||||
@defmethod[(base-url) string?]{
|
||||
|
||||
Returns the base URL of the underlying context.
|
||||
|
||||
The method delegates to @racket[wv-context-base-url] applied to the internal
|
||||
context value.
|
||||
}
|
||||
}
|
||||
283
scrbl/wv-element.scrbl
Normal file
283
scrbl/wv-element.scrbl
Normal file
@@ -0,0 +1,283 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require racket/base
|
||||
racket/class
|
||||
scribble/core
|
||||
(for-label racket/base
|
||||
racket/string
|
||||
racket/class
|
||||
"../private/wv-window.rkt"
|
||||
"../private/racket-webview.rkt"))
|
||||
|
||||
@title{wv-element}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[wv-element]
|
||||
|
||||
DOM element wrapper used by the window layer.
|
||||
|
||||
This module exports the @racket[wv-element%] class. Instances of this class
|
||||
represent one DOM element within a @racket[wv-window%] and provide a small
|
||||
object-oriented interface for event dispatch, content replacement, CSS class
|
||||
manipulation, style access, and attribute access.
|
||||
|
||||
@section{Overview}
|
||||
|
||||
A @racket[wv-element%] object is associated with:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{one window}
|
||||
@item{one DOM element identifier}]
|
||||
|
||||
The class delegates DOM operations to the JavaScript-based API from
|
||||
@racketmodname[racket-webview]. The accepted argument shapes and result values
|
||||
of its methods therefore follow the contracts of those lower-level functions.
|
||||
|
||||
The class also stores per-element event callbacks used by @racket[wv-window%]
|
||||
when dispatching JavaScript events received from the browser.
|
||||
|
||||
@section{Class: wv-element%}
|
||||
|
||||
@defclass[wv-element% object% ()]{
|
||||
|
||||
Represents one DOM element identified by its id.
|
||||
|
||||
The class stores the owning window and the element id supplied at construction
|
||||
time. It also maintains an internal hash table mapping event symbols to
|
||||
callbacks.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates an element wrapper.
|
||||
|
||||
@racket[window] is the owning window object. @racket[element-id] is the DOM
|
||||
element identifier used in calls to the lower-level DOM manipulation functions.
|
||||
}
|
||||
|
||||
@defmethod[(id) symbol?]{
|
||||
|
||||
Returns the element id.
|
||||
}
|
||||
|
||||
@defmethod[(add-event-callback! [evt symbol?] [cb procedure?]) void?]{
|
||||
|
||||
Associates @racket[cb] with @racket[evt].
|
||||
|
||||
If a callback was already stored for @racket[evt], it is replaced.
|
||||
}
|
||||
|
||||
@defmethod[(remove-event-callback! [evt symbol?]) void?]{
|
||||
|
||||
Removes the callback associated with @racket[evt], if any.
|
||||
}
|
||||
|
||||
@defmethod[(event-callback-count) exact-integer?]{
|
||||
|
||||
Returns the number of registered event callbacks.
|
||||
}
|
||||
|
||||
@defmethod[(dispatch-event [evt symbol?] [data any/c])
|
||||
(or/c 'wv-unhandled-js-event 'wv-handled-js-event)]{
|
||||
|
||||
Dispatches an event to the callback registered for @racket[evt].
|
||||
|
||||
If no callback is registered, the method returns
|
||||
@racket['wv-unhandled-js-event].
|
||||
|
||||
If a callback is found, it is invoked as:
|
||||
|
||||
@racketblock[
|
||||
(cb this evt data)
|
||||
]
|
||||
|
||||
and the method returns @racket['wv-handled-js-event].
|
||||
}
|
||||
|
||||
@defmethod[(set-innerHTML! [html (or/c string? xexpr?)])
|
||||
(is-a?/c wv-element%)]{
|
||||
|
||||
Sets the @tt{innerHTML} of this element using JavaScript and returns this
|
||||
element.
|
||||
|
||||
The operation delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-innerHTML! wv element-id html)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(add-class! [cl (or/c symbol? string? list?)]) any/c]{
|
||||
|
||||
Adds one or more CSS classes to this element.
|
||||
|
||||
The accepted values follow the contract of @racket[webview-add-class!]. The
|
||||
operation delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-add-class! wv element-id cl)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(remove-class! [cl (or/c symbol? string? list?)]) any/c]{
|
||||
|
||||
Removes one or more CSS classes from this element.
|
||||
|
||||
The accepted values follow the contract of @racket[webview-remove-class!]. The
|
||||
operation delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-remove-class! wv element-id cl)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(display [d (or/c symbol? string?)] ...) any/c]{
|
||||
|
||||
Gets or sets the CSS @tt{display} property of this element.
|
||||
|
||||
If no argument is supplied, the current value is returned by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-get-style wv element-id 'display)
|
||||
]
|
||||
|
||||
If an argument is supplied, only the first argument is used. The value may be a
|
||||
symbol or string and is converted internally before being passed to
|
||||
@racket[webview-set-style!].
|
||||
|
||||
The resulting style value is then read back and returned.
|
||||
}
|
||||
|
||||
@defmethod[(visibility [v (or/c symbol? string?)] ...) any/c]{
|
||||
|
||||
Gets or sets the CSS @tt{visibility} property of this element.
|
||||
|
||||
If no argument is supplied, the current value is returned by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-get-style wv element-id 'visibility)
|
||||
]
|
||||
|
||||
If an argument is supplied, only the first argument is used. The value may be a
|
||||
symbol or string and is converted internally before being passed to
|
||||
@racket[webview-set-style!].
|
||||
|
||||
The resulting style value is then read back and returned.
|
||||
}
|
||||
|
||||
@defmethod[(set-style! [styles (or/c kv? list-of-kv?)]) any/c]{
|
||||
|
||||
Sets one or more inline style properties on this element.
|
||||
|
||||
The accepted values follow the contract of @racket[webview-set-style!]. The
|
||||
method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-style! wv element-id styles)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(unset-style! [styles (or/c symbol? list-of-symbol?)]) any/c]{
|
||||
|
||||
Clears one or more inline style properties on this element.
|
||||
|
||||
The accepted values follow the contract of @racket[webview-unset-style!]. The
|
||||
method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-unset-style! wv element-id styles)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set-attr! [attr-entries (or/c kv? list-of-kv?)]) any/c]{
|
||||
|
||||
Sets one or more attributes on this element.
|
||||
|
||||
The accepted values follow the contract of @racket[webview-set-attr!]. The
|
||||
method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-attr! wv element-id attr-entries)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr [attr (or/c symbol? string?)]) (or/c string? boolean?)]{
|
||||
|
||||
Returns the value of the attribute identified by @racket[attr].
|
||||
|
||||
The result follows the contract of @racket[webview-attr]. If the underlying
|
||||
JavaScript result is @tt{null}, the lower layer returns @racket[#f].
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/number [attr (or/c symbol? string?)]) (or/c number? #f)]{
|
||||
|
||||
Returns the attribute value converted to a number.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/number wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/symbol [attr (or/c symbol? string?)]) (or/c symbol? #f)]{
|
||||
|
||||
Returns the attribute value converted to a symbol.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/symbol wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/boolean [attr (or/c symbol? string?)]) (or/c boolean? #f)]{
|
||||
|
||||
Returns the attribute value converted to a boolean.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/boolean wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/date [attr (or/c symbol? string?)]) any/c]{
|
||||
|
||||
Returns the attribute value converted to a date.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/date wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/time [attr (or/c symbol? string?)]) any/c]{
|
||||
|
||||
Returns the attribute value converted to a time.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/time wv element-id attr)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(attr/datetime [attr (or/c symbol? string?)]) any/c]{
|
||||
|
||||
Returns the attribute value converted to a datetime.
|
||||
|
||||
The method delegates to:
|
||||
|
||||
@racketblock[
|
||||
(webview-attr/datetime wv element-id attr)
|
||||
]
|
||||
}
|
||||
}
|
||||
386
scrbl/wv-input.scrbl
Normal file
386
scrbl/wv-input.scrbl
Normal file
@@ -0,0 +1,386 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require racket/base
|
||||
racket/class
|
||||
scribble/core
|
||||
(for-label racket/base
|
||||
racket/class
|
||||
"../private/racket-webview.rkt"
|
||||
"../private/wv-element.rkt"
|
||||
"../private/wv-window.rkt"
|
||||
"../private/rgba.rkt"))
|
||||
|
||||
@title{wv-input}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[wv-input]
|
||||
|
||||
Typed input-element wrappers used by the window layer.
|
||||
|
||||
This module exports a family of classes derived from @racket[wv-element%]. Each
|
||||
class represents one DOM input element and provides a typed @racket[get] method
|
||||
together with a @racket[set!] method.
|
||||
|
||||
@section{Overview}
|
||||
|
||||
All classes in this module inherit from @racket[wv-element%].
|
||||
|
||||
Each input wrapper:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{is associated with one @racket[wv-window%]}
|
||||
@item{is associated with one DOM element id}
|
||||
@item{uses @racket[webview-set-value!] to write values}
|
||||
@item{uses a type-specific @racket[webview-value*] function to read values}]
|
||||
|
||||
The classes do not add their own storage. They delegate directly to the
|
||||
lower-level DOM/value API from @racketmodname[racket-webview]. Their accepted
|
||||
argument shapes and result values therefore follow the contracts of those
|
||||
lower-level functions.
|
||||
|
||||
@section{Common Structure}
|
||||
|
||||
All input wrapper classes have the same constructor shape:
|
||||
|
||||
@racketblock[
|
||||
(new some-input% [window some-window] [element-id 'some-id])
|
||||
]
|
||||
|
||||
where @racket[window] is the owning @racket[wv-window%] and
|
||||
@racket[element-id] is the DOM id of the corresponding element.
|
||||
|
||||
Each class provides two public methods:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[get], returning the current typed value}
|
||||
@item{@racket[set!], writing a new value through @racket[webview-set-value!]}]
|
||||
|
||||
@section{Class: wv-input/text%}
|
||||
|
||||
@defclass[wv-input/text% wv-element% ()]{
|
||||
|
||||
Wrapper for a text input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a text-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c string? boolean?)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/number%}
|
||||
|
||||
@defclass[wv-input/number% wv-element% ()]{
|
||||
|
||||
Wrapper for a numeric input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a numeric-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c number? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/number wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/boolean%}
|
||||
|
||||
@defclass[wv-input/boolean% wv-element% ()]{
|
||||
|
||||
Wrapper for an input whose value is interpreted as a boolean.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a boolean-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c boolean? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/boolean wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/date%}
|
||||
|
||||
@defclass[wv-input/date% wv-element% ()]{
|
||||
|
||||
Wrapper for a date input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a date-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c g:date? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/date wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/time%}
|
||||
|
||||
@defclass[wv-input/time% wv-element% ()]{
|
||||
|
||||
Wrapper for a time input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a time-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c g:time? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/time wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/datetime%}
|
||||
|
||||
@defclass[wv-input/datetime% wv-element% ()]{
|
||||
|
||||
Wrapper for a datetime input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a datetime-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c g:datetime? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/datetime wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/range%}
|
||||
|
||||
@defclass[wv-input/range% wv-element% ()]{
|
||||
|
||||
Wrapper for a range input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a range-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c number? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/number wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/check%}
|
||||
|
||||
@defclass[wv-input/check% wv-element% ()]{
|
||||
|
||||
Wrapper for a checkbox input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a checkbox wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c boolean? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/boolean wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/radio%}
|
||||
|
||||
@defclass[wv-input/radio% wv-element% ()]{
|
||||
|
||||
Wrapper for a radio input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a radio-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c boolean? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/boolean wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@section{Class: wv-input/color%}
|
||||
|
||||
@defclass[wv-input/color% wv-element% ()]{
|
||||
|
||||
Wrapper for a color input element.
|
||||
|
||||
@defconstructor[([window (is-a?/c wv-window%)]
|
||||
[element-id symbol?])]{
|
||||
|
||||
Creates a color-input wrapper.
|
||||
}
|
||||
|
||||
@defmethod[(get) (or/c rgba? #f)]{
|
||||
|
||||
Returns the current value by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-value/color wv element-id)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod[(set! [v (or/c symbol? string? number? boolean?
|
||||
g:date? g:time? g:datetime? rgba?)])
|
||||
any/c]{
|
||||
|
||||
Writes @racket[v] by delegating to:
|
||||
|
||||
@racketblock[
|
||||
(webview-set-value! wv element-id v)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,117 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require scribble/racket
|
||||
scribble/example
|
||||
@(require racket/base
|
||||
racket/class
|
||||
scribble/core
|
||||
(for-label racket/base
|
||||
racket/string
|
||||
racket/class
|
||||
racket/file)
|
||||
)
|
||||
racket/file))
|
||||
|
||||
@defmodule[racket-webview]
|
||||
|
||||
@title{Object-Oriented Interface: @racket[wv-settings%]}
|
||||
@title{wv-settings}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[wv-settings]
|
||||
|
||||
Settings wrapper used by the webview library.
|
||||
|
||||
This module exports the @racket[wv-settings%] class, which provides a small
|
||||
object-oriented interface over an @racket[ini] settings backend. Settings are
|
||||
accessed relative to a context.
|
||||
|
||||
@section{Overview}
|
||||
|
||||
A @racket[wv-settings%] object combines:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{an @racket[ini] settings object}
|
||||
@item{a @racket[symbol] identifying the settings context}]
|
||||
|
||||
Keys used in this class are symbols.
|
||||
|
||||
@section{Class: wv-settings%}
|
||||
|
||||
@defclass[wv-settings% object% ()]{
|
||||
|
||||
An OO wrapper around a settings backend implementing, e.g. @racket[simple-ini/class].
|
||||
A @racket[wv-settings%] object represents a view on one settings section.
|
||||
The supplied @racket[wv-context] value is used as the section identifier for
|
||||
context-local settings. The class delegates all actual storage operations to the supplied
|
||||
@racket[ini] object.
|
||||
Wraps an @racket[ini] settings object for one context.
|
||||
|
||||
@defconstructor[([ini object?]
|
||||
The class delegates all reads and writes to the supplied @racket[ini] object.
|
||||
|
||||
@defconstructor[([ini (is-a?/c ini%)]
|
||||
[wv-context symbol?])]{
|
||||
Creates a new settings object. The @racket[ini] argument is the backing settings object.
|
||||
The @racket[wv-context] argument is used as the section identifier for
|
||||
context-local settings. The class assumes that @racket[ini] supports methods compatible with:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[(send ini get section key)]}
|
||||
@item{@racket[(send ini get section key default)]}
|
||||
@item{@racket[(send ini set! section key value)]}
|
||||
Creates a settings wrapper.
|
||||
|
||||
@racket[ini] must provide compatible @racket[get] and @racket[set!] methods.
|
||||
@racket[wv-context] is the context symbol used for contextual settings access.
|
||||
}
|
||||
|
||||
@defmethod[(get [key symbol?] [default-value any/c] ...) any/c]{
|
||||
|
||||
Returns the value associated with @racket[key] in the current context.
|
||||
|
||||
If no default value is supplied, the call delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini get wv-context key)
|
||||
]
|
||||
|
||||
If a default value is supplied, only the first extra argument is used, and the
|
||||
call delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini get wv-context key default)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod*[([(get [key symbol?]) any/c]
|
||||
[(get [key symbol?] [default-value any/c]) any/c])]{
|
||||
Returns the value associated with @racket[key] in the current section.
|
||||
Returns @tt{default-value} (when supplied) if the key is not found.
|
||||
@defmethod[(set! [key symbol?] [value any/c]) any/c]{
|
||||
|
||||
Stores @racket[value] under @racket[key] in the current context.
|
||||
|
||||
Delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini set! wv-context key value)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod*[([(set! [key symbol?] [value any/c]) this])]{
|
||||
Stores @racket[value] under @racket[key] in the current section.
|
||||
@defmethod[(get/global [key symbol?] [default-value any/c] ...) any/c]{
|
||||
|
||||
Returns the value associated with @racket[key] in the global settings section.
|
||||
|
||||
If no default value is supplied, the call delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini get 'global key)
|
||||
]
|
||||
|
||||
If a default value is supplied, only the first extra argument is used, and the
|
||||
call delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini get 'global key default)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod*[([(get/global [key symbol?]) any/c]
|
||||
[(get/global [key symbol?] [default-value any/c]) any/c])]{
|
||||
Returns the value associated with @racket[key] in the global section.
|
||||
This method delegates to @tt{(send ini get 'global key default-value)}.
|
||||
@defmethod[(set/global! [key symbol?] [value any/c]) any/c]{
|
||||
|
||||
Stores @racket[value] under @racket[key] in the global settings section.
|
||||
|
||||
Delegates to:
|
||||
|
||||
@racketblock[
|
||||
(send ini set! 'global key value)
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod*[([(set/global! [key symbol?] [value any/c]) this])]{
|
||||
Stores @racket[value] under @racket[key] in the global section.
|
||||
Delegates to @tt{(send ini set! 'global key value)}.
|
||||
@defmethod[(clone [context symbol?]) wv-settings%]{
|
||||
|
||||
Creates a new @racket[wv-settings%] object sharing the same @racket[ini]
|
||||
backend but using @racket[context] as its context.
|
||||
}
|
||||
|
||||
@defmethod*[([(clone [context any/c]) (is-a?/c wv-settings%)])]{
|
||||
Creates a new @racket[wv-settings%] object using the same
|
||||
@racket[ini] backend but with another section identifier.
|
||||
It is intended to support cheap creation of per-section settings views.
|
||||
}
|
||||
@defmethod[(context) symbol?]{
|
||||
|
||||
@defmethod*[([(context) symbol?])]{
|
||||
Returns the section identifier associated with this settings object.
|
||||
Returns the current context symbol.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,256 +1,457 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require scribble/racket
|
||||
scribble/example
|
||||
@(require racket/base
|
||||
racket/class
|
||||
scribble/core
|
||||
(for-label racket/base
|
||||
racket/string
|
||||
racket/class
|
||||
racket/path
|
||||
racket/file
|
||||
net/url
|
||||
"../private/wv-context.rkt"
|
||||
"../private/wv-settings.rkt"))
|
||||
"../private/wv-settings.rkt"
|
||||
"../private/wv-element.rkt"
|
||||
"../private/wv-input.rkt"
|
||||
"../private/rgba.rkt"))
|
||||
|
||||
@title{wv-window}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[wv-window]
|
||||
|
||||
@title{Object-Oriented Interface: @racket[wv-window%]}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
Window abstraction built on top of @racketmodname[racket-webview].
|
||||
|
||||
This module exports the @racket[wv-window%] class and re-exports the APIs from
|
||||
@racketmodname[wv-element], @racketmodname[wv-input], @racketmodname[rgba], and
|
||||
@racketmodname[wv-settings].
|
||||
|
||||
@section{Overview}
|
||||
|
||||
A @racket[wv-window%] object represents one webview window.
|
||||
|
||||
A window belongs to a context, loads one HTML path within that context, manages
|
||||
its own title and geometry, dispatches incoming events, and provides access to
|
||||
element wrappers through @racket[element].
|
||||
|
||||
The class also provides synchronous wrappers around the asynchronous dialog
|
||||
interface by using continuations internally.
|
||||
|
||||
@section{Class: wv-window%}
|
||||
|
||||
@defclass[wv-window% object% ()]{
|
||||
|
||||
An OO wrapper around a single webview window.
|
||||
A @racket[wv-window%] object owns one low-level window created with
|
||||
@racket[webview-create], keeps a per-window settings view, and offers methods for
|
||||
window management, DOM element access, event binding, file dialogs, and message boxes.
|
||||
Represents one window in a webview context.
|
||||
|
||||
The class is designed so that subclasses can override event methods such as
|
||||
@racket[moved], @racket[resized], @racket[page-loaded], @racket[js-event],
|
||||
@racket[navigation-request], @racket[can-close?], and @racket[closed].
|
||||
The class wraps a lower-level window created through
|
||||
@racketmodname[racket-webview] and connects it to the surrounding class-based
|
||||
API.
|
||||
|
||||
@defconstructor[([parent (or/c #f (is-a?/c wv-window%)) #f]
|
||||
[wv-context (is-a?/c wv-context%) (if (eq? parent #f)
|
||||
(error "wv context is mandatory")
|
||||
(get-field wv-context parent))]
|
||||
[html-path path-string? (error "html path is mandatory")]
|
||||
[settings (is-a?/c wv-settings%)
|
||||
(send wv-context settings
|
||||
[wv-context (if (eq? parent #f)
|
||||
(error "wv context is mandatory")
|
||||
(get-field wv-context parent))]
|
||||
[html-path path-string?]
|
||||
[settings (send wv-context settings
|
||||
(string->symbol (format "~a" html-path)))]
|
||||
[title string? "Racket Webview Window"]
|
||||
[width (or/c #f exact-integer?) #f]
|
||||
[height (or/c #f exact-integer?) #f]
|
||||
[x (or/c #f exact-integer?) #f]
|
||||
[y (or/c #f exact-integer?) #f]
|
||||
[wv any/c #f])]{
|
||||
Creates a new window.
|
||||
[width (or/c exact-integer? #f) #f]
|
||||
[height (or/c exact-integer? #f) #f]
|
||||
[x (or/c exact-integer? #f) #f]
|
||||
[y (or/c exact-integer? #f) #f])]{
|
||||
|
||||
The @racket[wv-context] argument supplies the underlying webview context.
|
||||
If @racket[parent] is given, the default value of @racket[wv-context] is taken from it.
|
||||
The @racket[html-path] argument identifies the HTML resource to open.
|
||||
The default @racket[settings] object is derived from @racket[wv-context] using
|
||||
@racket[html-path] as per-window settings key.
|
||||
Creates a window.
|
||||
|
||||
The constructor creates the low-level window using @racket[webview-create] and then
|
||||
calls @racket[init-size] to restore position and size from settings, falling back to
|
||||
constructor arguments or internal defaults.
|
||||
The constructor requires @racket[html-path]. If @racket[parent] is @racket[#f],
|
||||
@racket[wv-context] is mandatory. If a parent window is supplied,
|
||||
@racket[wv-context] defaults to the context of that parent.
|
||||
|
||||
The default value of @racket[settings] is obtained as:
|
||||
|
||||
@racketblock[
|
||||
(send wv-context settings (string->symbol (format "~a" html-path)))
|
||||
]
|
||||
|
||||
After construction, the class:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{creates the underlying webview window}
|
||||
@item{installs the internal event handler}
|
||||
@item{sets the title}
|
||||
@item{initializes position and size from settings or defaults}]
|
||||
|
||||
If a parent is supplied, the underlying lower-level parent window is passed to
|
||||
@racket[webview-create].
|
||||
}
|
||||
|
||||
@defmethod*[([(context) (is-a?/c wv-context%)])]{
|
||||
Returns the window context object associated with this window.
|
||||
@defmethod[(context) any/c]{
|
||||
|
||||
Returns the window context object supplied at construction.
|
||||
}
|
||||
|
||||
@defmethod*[([(win-context) symbol?])]{
|
||||
Returns the symbolic context identifier derived from @racket[html-path].
|
||||
This value is typically used as the settings section for the window.
|
||||
@defmethod[(win-context) symbol?]{
|
||||
|
||||
Returns the settings context key for this window.
|
||||
|
||||
The value is computed as:
|
||||
|
||||
@racketblock[
|
||||
(string->symbol (format "~a" html-path))
|
||||
]
|
||||
}
|
||||
|
||||
@defmethod*[([(info) hash?])]{
|
||||
Returns a hash table with administrative information about the window.
|
||||
The hash contains entries for at least @racket['wv-context], @racket['wv],
|
||||
@defmethod[(info) hash?]{
|
||||
|
||||
Returns administrative information for the window as a hash table.
|
||||
|
||||
The returned hash contains the keys @racket['wv-context], @racket['wv],
|
||||
@racket['html-path], and @racket['elements].
|
||||
}
|
||||
|
||||
@defmethod*[([(element [id symbol?]) any/c]
|
||||
[(element [id symbol?] [type symbol?]) any/c])]{
|
||||
Returns the wrapper object for the DOM element with identifier @racket[id].
|
||||
@defmethod[(element [id symbol?] [type symbol?] ...) any/c]{
|
||||
|
||||
If the element has not been wrapped before, a new wrapper object is created and cached.
|
||||
When @racket[type] is omitted, the method attempts to determine the HTML input type via
|
||||
JavaScript. Depending on the resolved type, it creates an instance of a more specific
|
||||
wrapper class such as @racket[wv-input/text%], @racket[wv-input/date%],
|
||||
@racket[wv-input/number%], or falls back to @racket[wv-element%].
|
||||
Returns the wrapper object associated with the DOM element identified by
|
||||
@racket[id].
|
||||
|
||||
Element wrappers are cached in an internal weak hash table. If no wrapper exists
|
||||
yet, one is created and cached.
|
||||
|
||||
If no explicit @racket[type] is supplied, the method asks the browser for the
|
||||
element's @tt{type} attribute. Depending on the resulting type, one of the
|
||||
following classes is instantiated:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket[wv-input/text%] for @racket['text]}
|
||||
@item{@racket[wv-input/date%] for @racket['date]}
|
||||
@item{@racket[wv-input/time%] for @racket['time]}
|
||||
@item{@racket[wv-input/datetime%] for @racket['datetime-local]}
|
||||
@item{@racket[wv-input/range%] for @racket['range]}
|
||||
@item{@racket[wv-input/number%] for @racket['number]}
|
||||
@item{@racket[wv-input/check%] for @racket['checkbox]}
|
||||
@item{@racket[wv-input/radio%] for @racket['radio]}
|
||||
@item{@racket[wv-input/color%] for @racket['color]}
|
||||
@item{@racket[wv-element%] otherwise}]
|
||||
|
||||
Newly created wrappers receive this window through the @racket[window] init
|
||||
argument and the element id through @racket[element-id].
|
||||
}
|
||||
|
||||
@defmethod*[([(moved [x exact-integer?] [y exact-integer?]) void?])]{
|
||||
Event hook called when the window has moved.
|
||||
The default implementation stores the new position in the settings object.
|
||||
Subclasses may override this method.
|
||||
@defmethod[(moved [xx exact-integer?] [yy exact-integer?]) any/c]{
|
||||
|
||||
Updates the internal position and stores the new coordinates in
|
||||
@racket[settings] under @racket['x] and @racket['y].
|
||||
}
|
||||
|
||||
@defmethod*[([(resized [width exact-integer?] [height exact-integer?]) void?])]{
|
||||
Event hook called when the window has been resized.
|
||||
The default implementation stores the new size in the settings object.
|
||||
Subclasses may override this method.
|
||||
@defmethod[(resized [w exact-integer?] [h exact-integer?]) any/c]{
|
||||
|
||||
Updates the internal size and stores the new dimensions in
|
||||
@racket[settings] under @racket['width] and @racket['height].
|
||||
}
|
||||
|
||||
@defmethod*[([(window-state-changed [state any/c]) any/c])]{
|
||||
Event hook called when the native window state changes, for example after show,
|
||||
hide, or maximize notifications. The default implementation returns @racket[#t].
|
||||
}
|
||||
@defmethod[(window-state-changed [st symbol?]) any/c]{
|
||||
|
||||
@defmethod*[([(page-loaded [ok? any/c]) any/c])]{
|
||||
Event hook called after the page load completes. The argument indicates whether the
|
||||
load succeeded. The default implementation returns @racket[#t].
|
||||
}
|
||||
Called when the underlying window state changes.
|
||||
|
||||
@defmethod*[([(can-close?) any/c])]{
|
||||
Event hook queried before closing the window.
|
||||
If this method returns a true value, the default event handler closes the window.
|
||||
The default implementation returns @racket[#t].
|
||||
}
|
||||
|
||||
@defmethod*[([(closed) any/c])]{
|
||||
Event hook called after the window has been closed.
|
||||
@defmethod[(page-loaded [oke any/c]) any/c]{
|
||||
|
||||
Called when a @racket['page-loaded] event is received.
|
||||
|
||||
The default implementation returns @racket[#t].
|
||||
}
|
||||
|
||||
@defmethod*[([(js-event [js-event hash?]) any/c])]{
|
||||
Handles a JavaScript-originated event.
|
||||
@defmethod[(can-close?) any/c]{
|
||||
|
||||
The default implementation looks up the wrapped element referenced by the event and
|
||||
forwards the event to that element's dispatcher. If no matching element is known,
|
||||
it returns @racket['wv-unhandled-js-event]. Subclasses may override this for
|
||||
application-specific dispatch.
|
||||
Called when a @racket['can-close?] event is received.
|
||||
|
||||
If this method returns a true value, the window closes itself by calling
|
||||
@racket[close].
|
||||
|
||||
The default implementation returns @racket[#t].
|
||||
}
|
||||
|
||||
@defmethod*[([(navigation-request [type symbol?] [url url?]) (or/c 'internal 'external)])]{
|
||||
Handles a navigation request.
|
||||
@defmethod[(closed) any/c]{
|
||||
|
||||
If the requested URL starts with the context base URL, the window navigates internally
|
||||
using @racket[webview-set-url!] and returns @racket['internal]. Otherwise the URL is
|
||||
opened externally using @racket[send-url] and the method returns @racket['external].
|
||||
Called when a @racket['closed] event is received.
|
||||
|
||||
The default implementation returns @racket[#t].
|
||||
}
|
||||
|
||||
@defmethod*[([(add-class! [selector-or-id string?] [class string?]) this])]{
|
||||
Adds the CSS class @racket[class] to the elements selected by @racket[selector-or-id].
|
||||
Delegates to @racket[webview-add-class!].
|
||||
@defmethod[(js-event [js-event hash?]) any/c]{
|
||||
|
||||
Dispatches a JavaScript event received from the browser.
|
||||
|
||||
The incoming hash is expected to contain at least the keys @racket['evt],
|
||||
@racket['id], and optionally @racket['data]. If the referenced element exists in
|
||||
the internal element cache, the event is dispatched to that element by calling
|
||||
its @racket[dispatch-event] method.
|
||||
|
||||
If no matching element wrapper exists, the method returns
|
||||
@racket['wv-unhandled-js-event].
|
||||
}
|
||||
|
||||
@defmethod*[([(remove-class! [selector-or-id string?] [class string?]) this])]{
|
||||
Removes the CSS class @racket[class] from the elements selected by @racket[selector-or-id].
|
||||
Delegates to @racket[webview-remove-class!].
|
||||
@defmethod[(navigation-request [type symbol?] [url url?]) (or/c 'internal 'external)]{
|
||||
|
||||
Handles a navigation request event.
|
||||
|
||||
If the requested URL starts with the context base URL, the request is treated as
|
||||
internal, loaded into the current window, and @racket['internal] is returned.
|
||||
|
||||
Otherwise the URL is opened externally using @racket[send-url], and
|
||||
@racket['external] is returned.
|
||||
}
|
||||
|
||||
@defmethod*[([(devtools) this])]{
|
||||
Opens or activates the developer tools for the underlying webview.
|
||||
@defmethod[(add-class! [selector-or-id (or/c symbol? string?)]
|
||||
[cl (or/c symbol? string? list?)])
|
||||
(is-a?/c wv-window%)]{
|
||||
|
||||
Adds one or more CSS classes to the selected elements and returns this window.
|
||||
}
|
||||
|
||||
@defmethod*[([(move [x exact-integer?] [y exact-integer?]) this])]{
|
||||
Moves the native window to the given screen coordinates.
|
||||
@defmethod[(remove-class! [selector-or-id (or/c symbol? string?)]
|
||||
[cl (or/c symbol? string? list?)])
|
||||
(is-a?/c wv-window%)]{
|
||||
|
||||
Removes one or more CSS classes from the selected elements and returns this
|
||||
window.
|
||||
}
|
||||
|
||||
@defmethod*[([(resize [width exact-integer?] [height exact-integer?]) this])]{
|
||||
Resizes the native window to the given dimensions.
|
||||
@defmethod[(devtools) (is-a?/c wv-window%)]{
|
||||
|
||||
Opens the developer tools and returns this window.
|
||||
}
|
||||
|
||||
@defmethod*[([(close) this])]{
|
||||
Closes the native window.
|
||||
@defmethod[(move [x exact-integer?] [y exact-integer?]) (is-a?/c wv-window%)]{
|
||||
|
||||
Moves the window and returns this window.
|
||||
}
|
||||
|
||||
@defmethod*[([(bind! [selector string?]
|
||||
[events (or/c symbol? (listof symbol?))]
|
||||
[callback procedure?]) (listof any/c)])]{
|
||||
Binds one or more DOM events to all elements matched by @racket[selector].
|
||||
@defmethod[(resize [w exact-integer?] [h exact-integer?]) (is-a?/c wv-window%)]{
|
||||
|
||||
This method delegates the JavaScript side of the binding to @racket[webview-bind!],
|
||||
creates or reuses wrapper objects for the matched elements, and installs
|
||||
@racket[callback] as event callback on each wrapper. The return value is the list of
|
||||
matched element wrapper objects.
|
||||
Resizes the window and returns this window.
|
||||
}
|
||||
|
||||
@defmethod*[([(unbind! [selector string?]
|
||||
[events (or/c symbol? (listof symbol?))]) any/c])]{
|
||||
Removes previously installed bindings for the selected elements and events.
|
||||
This method delegates to @racket[webview-unbind!] and removes callbacks from the
|
||||
corresponding cached wrapper objects.
|
||||
@defmethod[(close) (is-a?/c wv-window%)]{
|
||||
|
||||
Closes the window and returns this window.
|
||||
}
|
||||
|
||||
@defmethod*[([(file-dialog-done [flag symbol?]
|
||||
[file any/c]
|
||||
[dir any/c]
|
||||
[filter any/c]) void?])]{
|
||||
Continuation hook used internally to resume a pending file or directory dialog.
|
||||
Applications normally do not call this method directly.
|
||||
@defmethod[(bind! [selector (or/c symbol? string?)]
|
||||
[events (or/c symbol? list?)]
|
||||
[callback procedure?])
|
||||
list?]{
|
||||
|
||||
Binds one or more DOM events.
|
||||
|
||||
The method first delegates to @racket[webview-bind!]. For each returned binding,
|
||||
it connects the corresponding element wrapper to @racket[callback].
|
||||
|
||||
If @racket[events] is a symbol, it is treated as a single-element list.
|
||||
|
||||
The result is the list produced by mapping over the binding items. In practice,
|
||||
this yields the corresponding cached element wrapper objects.
|
||||
}
|
||||
|
||||
@defmethod*[([(choose-dir [title string?] [base-dir (or/c #f path-string?)])
|
||||
(or/c 'showing path-string? #f)])]{
|
||||
Shows a directory chooser.
|
||||
@defmethod[(unbind! [selector (or/c symbol? string?)]
|
||||
[events (or/c symbol? list?)])
|
||||
list?]{
|
||||
|
||||
If the chooser is successfully shown, the method initially returns @racket['showing]
|
||||
and later resumes with the selected directory path or @racket[#f] when the dialog is
|
||||
canceled. If the dialog cannot be shown, it returns @racket[#f].
|
||||
Only one file or directory dialog can be active at a time.
|
||||
Removes one or more DOM event bindings.
|
||||
|
||||
The method first delegates to @racket[webview-unbind!], then disconnects the
|
||||
corresponding callbacks from cached element wrappers.
|
||||
|
||||
If an element wrapper no longer has any event callbacks, it is removed from the
|
||||
internal cache.
|
||||
|
||||
The returned value is the list produced by @racket[webview-unbind!].
|
||||
}
|
||||
|
||||
@defmethod*[([(file-open [title string?]
|
||||
[base-dir (or/c #f path-string?)]
|
||||
[filters any/c])
|
||||
(or/c 'showing list? #f)])]{
|
||||
Shows a file-open dialog.
|
||||
@defmethod[(set-title! [title string?]) any/c]{
|
||||
|
||||
If the dialog is accepted, the resumed result is a list whose contents are derived from
|
||||
the low-level file dialog result. If the dialog is canceled or cannot be shown, the
|
||||
result is @racket[#f]. Only one file or directory dialog can be active at a time.
|
||||
Sets the window title.
|
||||
}
|
||||
|
||||
@defmethod*[([(file-save [title string?]
|
||||
[base-dir (or/c #f path-string?)]
|
||||
[filters any/c])
|
||||
(or/c 'showing list? #f)])]{
|
||||
Shows a file-save dialog.
|
||||
@defmethod[(file-dialog-done [flag symbol?]
|
||||
[file any/c]
|
||||
[dir any/c]
|
||||
[filter any/c])
|
||||
any/c]{
|
||||
|
||||
If the dialog is accepted, the resumed result is a list whose contents are derived from
|
||||
the low-level file dialog result. If the dialog is canceled or cannot be shown, the
|
||||
result is @racket[#f]. Only one file or directory dialog can be active at a time.
|
||||
Internal continuation callback used by file and directory dialogs.
|
||||
|
||||
The default implementation resumes the pending continuation stored by the window.
|
||||
}
|
||||
|
||||
@defmethod*[([(message-done [event symbol?]) void?])]{
|
||||
Continuation hook used internally to resume a pending message box.
|
||||
Applications normally do not call this method directly.
|
||||
@defmethod[(choose-dir [title string?] [base-dir (or/c path-string? #f)])
|
||||
(or/c 'showing path-string? #f)]{
|
||||
|
||||
Shows a directory chooser dialog.
|
||||
|
||||
At most one file or directory dialog can be active at a time. Attempting to open
|
||||
another one raises an exception.
|
||||
|
||||
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
||||
|
||||
If the underlying dialog could not be started, the method returns @racket[#f].
|
||||
If the dialog is shown successfully, the method returns @racket['showing] first
|
||||
and later resumes through the internal continuation mechanism.
|
||||
|
||||
When resumed after completion:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{the selected directory is returned on success}
|
||||
@item{@racket[#f] is returned if the dialog was canceled}]
|
||||
}
|
||||
|
||||
@defmethod*[([(message [type symbol?]
|
||||
[title string?]
|
||||
[message string?]
|
||||
[#:sub submessage string? ""])
|
||||
(or/c 'showing symbol? #f)])]{
|
||||
Shows a native message box.
|
||||
@defmethod[(file-open [title string?]
|
||||
[base-dir (or/c path-string? #f)]
|
||||
[filters any/c])
|
||||
(or/c 'showing list? #f)]{
|
||||
|
||||
If the message box is shown successfully, the method initially returns @racket['showing]
|
||||
and later resumes with the button result, such as @racket['msgbox-ok],
|
||||
@racket['msgbox-cancel], @racket['msgbox-yes], or @racket['msgbox-no].
|
||||
If the message box cannot be shown, the method returns @racket[#f].
|
||||
Only one message box can be active at a time.
|
||||
Shows an open-file dialog.
|
||||
|
||||
At most one file or directory dialog can be active at a time. Attempting to open
|
||||
another one raises an exception.
|
||||
|
||||
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
||||
|
||||
If the underlying dialog could not be started, the method returns @racket[#f].
|
||||
If the dialog is shown successfully, the method returns @racket['showing] first
|
||||
and later resumes through the internal continuation mechanism.
|
||||
|
||||
When resumed after completion:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
|
||||
@item{on cancellation, the method returns @racket[#f]}]
|
||||
|
||||
This matches the current source code exactly.
|
||||
}
|
||||
|
||||
@defmethod*[([(init-size) void?])]{
|
||||
Initializes the window position and size from the settings object.
|
||||
If no persisted values are available, constructor arguments or module defaults are used.
|
||||
@defmethod[(file-save [title string?]
|
||||
[base-dir (or/c path-string? #f)]
|
||||
[filters any/c])
|
||||
(or/c 'showing list? #f)]{
|
||||
|
||||
Shows a save-file dialog.
|
||||
|
||||
At most one file or directory dialog can be active at a time. Attempting to open
|
||||
another one raises an exception.
|
||||
|
||||
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
||||
|
||||
If the underlying dialog could not be started, the method returns @racket[#f].
|
||||
If the dialog is shown successfully, the method returns @racket['showing] first
|
||||
and later resumes through the internal continuation mechanism.
|
||||
|
||||
When resumed after completion:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
|
||||
@item{on cancellation, the method returns @racket[#f]}]
|
||||
|
||||
This matches the current source code exactly.
|
||||
}
|
||||
|
||||
@defmethod[(message-done [evt symbol?]) any/c]{
|
||||
|
||||
Internal continuation callback used by message boxes.
|
||||
|
||||
The default implementation resumes the pending message-box continuation stored by
|
||||
the window.
|
||||
}
|
||||
|
||||
@defproc[(root-file-not-found-handler [standard-file path-string?]
|
||||
[not-found-handler procedure?])
|
||||
@defmethod[(message [type symbol?]
|
||||
[title string?]
|
||||
[message string?]
|
||||
[#:sub submessage string? ""])
|
||||
(or/c 'showing symbol? #f)]{
|
||||
|
||||
Shows a message box.
|
||||
|
||||
At most one message box can be active at a time. Attempting to show another one
|
||||
raises an exception.
|
||||
|
||||
If the underlying dialog could not be started, the method returns @racket[#f].
|
||||
If the dialog is shown successfully, the method returns @racket['showing] first
|
||||
and later resumes through the internal continuation mechanism.
|
||||
|
||||
When resumed after completion, the result is the event symbol returned by the
|
||||
lower layer, such as the symbols corresponding to @tt{ok}, @tt{cancel},
|
||||
@tt{yes}, or @tt{no}.
|
||||
}
|
||||
|
||||
@defmethod[(init-size) any/c]{
|
||||
|
||||
Initializes the window position and size.
|
||||
|
||||
The method reads @racket['x], @racket['y], @racket['width], and
|
||||
@racket['height] from @racket[settings]. If a value is missing, the constructor
|
||||
argument is used if present; otherwise a default is chosen.
|
||||
|
||||
The resulting geometry is then applied through @racket[move] and @racket[resize].
|
||||
}
|
||||
}
|
||||
|
||||
@section{Events}
|
||||
|
||||
The window installs an internal event handler when it is created.
|
||||
|
||||
This handler reacts to the following event kinds produced by the lower layer:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racket['resize], forwarded to @racket[resized]}
|
||||
@item{@racket['move], forwarded to @racket[moved]}
|
||||
@item{@racket['page-loaded], forwarded to @racket[page-loaded]}
|
||||
@item{@racket['show], @racket['hide], and @racket['maximize], forwarded to
|
||||
@racket[window-state-changed]}
|
||||
@item{@racket['can-close?], causing @racket[can-close?] to be queried}
|
||||
@item{@racket['closed], forwarded to @racket[closed]}
|
||||
@item{@racket['js-evt], decoded and forwarded to @racket[js-event]}
|
||||
@item{@racket['navigation-request], forwarded to @racket[navigation-request]}
|
||||
@item{file-dialog completion events, forwarded to @racket[file-dialog-done]}
|
||||
@item{message-box completion events, forwarded to @racket[message-done]}]
|
||||
|
||||
Unhandled events are printed to the console by the current implementation.
|
||||
|
||||
@section{Function: root-file-not-found-handler}
|
||||
|
||||
@defproc[(root-file-not-found-handler [standard-file string?]
|
||||
[not-found-handler procedure?] ...)
|
||||
procedure?]{
|
||||
Creates a file-resolution procedure that can be used as a fallback handler for a webview
|
||||
context.
|
||||
|
||||
The returned procedure redirects requests for @tt{"/"} and, when needed,
|
||||
@tt{"/index.html"} to @racket[standard-file]. For other missing files it either returns
|
||||
the unresolved path unchanged or delegates to @racket[not-found-handler] when one is
|
||||
supplied.
|
||||
Creates a file-not-found handler for use with file serving.
|
||||
|
||||
The returned procedure has the shape:
|
||||
|
||||
@racketblock[
|
||||
(lambda (file base-path path) ...)
|
||||
]
|
||||
|
||||
Its behavior is as follows:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{if @racket[file] is @tt{"/"}, it retries using @racket[standard-file]}
|
||||
@item{if @racket[file] is @tt{"/index.html"}, it falls back to
|
||||
@racket[standard-file] when the target does not exist}
|
||||
@item{if the requested path exists, it is returned}
|
||||
@item{otherwise, if an extra @racket[not-found-handler] was supplied, that
|
||||
handler is called}
|
||||
@item{if no extra handler was supplied, the original path is returned}]
|
||||
|
||||
Only the first optional @racket[not-found-handler] is used.
|
||||
}
|
||||
|
||||
@defthing[webview-version any/c]{
|
||||
The version identifier exported by the underlying webview binding.
|
||||
}
|
||||
@section{Re-exports}
|
||||
|
||||
This module also re-exports the public APIs from:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{@racketmodname[wv-element]}
|
||||
@item{@racketmodname[wv-input]}
|
||||
@item{@racketmodname[rgba]}
|
||||
@item{@racketmodname[wv-settings]}]
|
||||
|
||||
It also re-exports @racket[webview-version].
|
||||
Reference in New Issue
Block a user