Skip to content

Commit d05bbff

Browse files
feat(webview): Windows vtable fix + full verification stack + 3-OS CI e2e
The Windows 'COM apartment' blocker was a misdiagnosis. get_CoreWebView2 lives at vtable slot 25, not 3 — slot 3 on the controller is get_IsVisible, which writes a BOOL into the out-pointer; reading that back as ICoreWebView2* yields 1+garbage, crashing on any vtable access. All vtable indices now verified against the official WebView2 SDK header (Microsoft.Web.WebView2 nupkg): - Windows backend: corrected indices; controller+CoreWebView2 AddRef'd and retained (post-open navigate works); title/url via get_DocumentTitle/get_Source; capture! via PrintWindow -> DIB -> BMP -> PowerShell PNG; put_Bounds sizes the webview to the client area; WM_CLOSE wired to on-close; HRESULT checks + stderr diagnostics throughout. - Linux backend: capture! via gdk_pixbuf (get_from_window + savev png). - open-window/open-webview #:devtools? (macOS setInspectable:, Windows OpenDevToolsWindow, Linux accepted-but-ignored). - scripts/webview-e2e.rkt: cross-platform real-window e2e (open, title commit, capture, navigate, close, on-close) with exit codes; ALL PASS locally on macOS. - CI: webview-e2e job on ubuntu (Xvfb + WebKitGTK + DMABUF workaround), windows, macOS. - Docs: status matrices updated (honest 'pending CI verification' for Windows/Linux), CHANGELOG, AGENTS.md with the misdiagnosis story.
1 parent f626af9 commit d05bbff

11 files changed

Lines changed: 519 additions & 94 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,48 @@ jobs:
3838
run: raco fmt --check glaze-lib/ glaze-cli/ glaze-test/
3939
continue-on-error: true
4040

41+
webview-e2e:
42+
# Real-window WebView end-to-end on each OS: open -> load (title
43+
# commits) -> capture -> navigate -> close -> on-close. Windows needs
44+
# nothing extra (WebView2 runtime is preinstalled); Linux runs under
45+
# Xvfb with the WebKitGTK packages.
46+
runs-on: ${{ matrix.os }}
47+
strategy:
48+
fail-fast: false
49+
matrix:
50+
os: [ubuntu-latest, windows-latest, macOS-latest]
51+
racket-version: ['8.12']
52+
53+
steps:
54+
- uses: actions/checkout@v4
55+
56+
- name: Install Racket
57+
uses: Bogdanp/setup-racket@v1.11
58+
with:
59+
version: ${{ matrix.racket-version }}
60+
61+
- name: Install Glaze packages
62+
run: |
63+
raco pkg install --no-docs --link ./glaze-lib
64+
raco pkg install --link ./glaze
65+
66+
- name: Install webview deps (Linux)
67+
if: runner.os == 'Linux'
68+
run: |
69+
sudo apt-get update
70+
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev xvfb
71+
72+
- name: Run webview e2e
73+
env:
74+
WEBKIT_DISABLE_DMABUF_RENDERER: '1'
75+
shell: bash
76+
run: |
77+
if [ "$RUNNER_OS" = "Linux" ]; then
78+
xvfb-run -a racket scripts/webview-e2e.rkt
79+
else
80+
racket scripts/webview-e2e.rkt
81+
fi
82+
4183
package:
4284
# Build a sample app with `raco glaze build --installer` on each platform
4385
# and upload the resulting distribution + installer as artifacts.

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Glaze 是 "Tauri-like framework for Racket"——Racket 写后端,Web 技术
1919

2020
| 平台 | 状态 | 说明 |
2121
|------|------|------|
22-
| macOS | ✅ 端到端可用 | NSWindow + WKWebView(objc FFI),含验证 API |
23-
| Windows | ⚠️ experimental | COM 初始化链已通,`Navigate` 卡在 STA 生命周期问题,见 `webview-windows.rkt` 头注释 |
24-
| Linux | 🔲 骨架 | GtkWindow + WebKitGTK 绑定未接线,见 `webview-linux.rkt` |
22+
| macOS | ✅ 端到端可用 | NSWindow + WKWebView(objc FFI),含验证 API + devtools |
23+
| Windows | 🔄 修复待 CI 验证 | 历史上卡在"COM apartment"——真相是 `get_CoreWebView2` vtable 索引错(25 被写成 3,实际调了 `get_IsVisible`)。vtable 顺序已对官方 SDK 头文件逐一核对,详见 `webview-windows.rkt` 头注释 |
24+
| Linux | 🔄 待 CI 验证 | 泵结构 + destroy 回调 + title/url/capture 已接线,Xvfb CI e2e |
2525

2626
## 快速命令
2727

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
77

88
## [0.3.0] - Unreleased
99

10+
### Fixed (Phase 3, in progress)
11+
- **Windows WebView2 root cause found and fixed**: the long-standing
12+
"COM apartment" diagnosis was wrong — `get_CoreWebView2` was being called
13+
at vtable slot 3 (actually `get_IsVisible`, which writes a BOOL into the
14+
out-pointer), yielding a garbage pointer that crashed on any vtable
15+
access. All vtable indices are now verified against the official
16+
Microsoft.Web.WebView2 SDK header. The backend additionally: AddRefs and
17+
retains the controller/CoreWebView2 for post-open navigate, implements
18+
title/url (get_DocumentTitle / get_Source) and capture! (PrintWindow +
19+
PowerShell BMP->PNG), sizes the WebView to the client area, and wires
20+
WM_CLOSE to on-close. Runtime verification pending the new CI e2e job.
21+
- Linux backend: capture! via gdk_pixbuf; CI e2e job added (Xvfb +
22+
WebKitGTK) alongside macOS/Windows real-window e2e.
23+
- `open-window`/`open-webview` accept `#:devtools?` (macOS:
24+
setInspectable:; Windows: OpenDevToolsWindow; Linux: not yet).
25+
1026
### Added (Phase 3, in progress)
1127
- **JavaScript bridge** (`glaze/api`): real JSON routing over the frontend
1228
server — `GET`/`POST`/`PUT`/`DELETE` route values with `:param` capture,

‎README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ You get:
2828
| Agent-friendly UI verification (`title`/`url`/screenshot) | **built-in** | via WebDriver | via CDP | limited |
2929
| WebView backends | WebView2 / WKWebView / WebKitGTK | same | bundled Chromium | WebView2/WKWebView |
3030

31-
The honest gaps today: Windows webview is experimental (see status below), no devtools wiring, no typed IPC layer — plain JSON.
31+
The honest gaps today: Windows/Linux webview runtime verification is pending CI (see status below); no typed IPC layer — plain JSON.
3232

3333
## Platform status
3434

@@ -37,9 +37,10 @@ The honest gaps today: Windows webview is experimental (see status below), no de
3737
| HTTP server + browser | ✅ | ✅ | ✅ |
3838
| System tray | ✅ | ✅ | ✅ (CI-verified) |
3939
| JSON API bridge | ✅ | ✅ | ✅ |
40-
| Native webview window | ✅ verified end-to-end | ⚠️ experimental (WebView2 init works; `Navigate` pending a COM fix) | 🔲 structural, not runtime-verified |
41-
| `webview-title` / `webview-url` | ✅ | partial (url) | partial (url) |
42-
| `webview-capture!` (screenshot) | ✅ | 🔲 | 🔲 |
40+
| Native webview window | ✅ verified end-to-end | 🔄 fixed against official SDK header; CI e2e pending | 🔄 structural; CI e2e (Xvfb) pending |
41+
| `webview-title` / `webview-url` | ✅ | 🔄 implemented (`get_DocumentTitle`/`get_Source`); CI pending | 🔄 implemented; CI pending |
42+
| `webview-capture!` (screenshot) | ✅ | 🔄 implemented (PrintWindow + PowerShell PNG); CI pending | 🔄 implemented (gdk_pixbuf); CI pending |
43+
| `#:devtools?` | ✅ (inspectable, macOS 13+) | ✅ (`OpenDevToolsWindow`) | 🔲 |
4344

4445
Without a native backend, `run-app` / `open-window` automatically fall back to the system browser — the app still works everywhere.
4546

‎README.zh-CN.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Racket 自带的 `racket/gui` 可以用,但很难做出现代化的产品级 U
2828
| Agent 友好的 UI 验证(title/url/截图) | **内置** | 需 WebDriver | 需 CDP | 有限 |
2929
| WebView 后端 | WebView2 / WKWebView / WebKitGTK | 相同 | 自带 Chromium | WebView2/WKWebView |
3030

31-
当前的诚实差距:Windows WebView 尚在实验阶段(见下表状态)、未接 devtools、IPC 为纯 JSON 无类型层。
31+
当前的诚实差距:Windows/Linux WebView 的真机验证待 CI 跑通(见下表状态)、IPC 为纯 JSON 无类型层。
3232

3333
## 平台支持状态
3434

@@ -37,9 +37,10 @@ Racket 自带的 `racket/gui` 可以用,但很难做出现代化的产品级 U
3737
| HTTP 服务器 + 浏览器 | ✅ | ✅ | ✅ |
3838
| 系统托盘 | ✅ | ✅ | ✅(CI 验证) |
3939
| JSON API 桥接 | ✅ | ✅ | ✅ |
40-
| 原生 WebView 窗口 | ✅ 端到端验证 | ⚠️ 实验性(WebView2 初始化已通;`Navigate` 待修 COM 问题) | 🔲 结构完整,未真机验证 |
41-
| `webview-title` / `webview-url` | ✅ | 部分(url) | 部分(url) |
42-
| `webview-capture!`(截图) | ✅ | 🔲 | 🔲 |
40+
| 原生 WebView 窗口 | ✅ 端到端验证 | 🔄 已按官方 SDK 头文件修正,待 CI 验证 | 🔄 结构完整,待 CI(Xvfb)验证 |
41+
| `webview-title` / `webview-url` | ✅ | 🔄 已实现(`get_DocumentTitle`/`get_Source`),待 CI | 🔄 已实现,待 CI |
42+
| `webview-capture!`(截图) | ✅ | 🔄 已实现(PrintWindow + PowerShell 转 PNG),待 CI | 🔄 已实现(gdk_pixbuf),待 CI |
43+
| `#:devtools?` | ✅(inspectable,macOS 13+) | ✅(`OpenDevToolsWindow`) | 🔲 |
4344

4445
原生后端不可用时,`run-app` / `open-window` 自动回退系统浏览器 —— 应用在所有平台都能跑。
4546

‎glaze-lib/webview/main.rkt‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,22 @@
6666
#:title [title "Glaze"]
6767
#:width [width 1024]
6868
#:height [height 768]
69+
#:devtools? [devtools? #f]
6970
#:on-close [on-close (lambda () (void))]
7071
#:fallback-browser? [fallback? #f])
7172
(open-webview url
7273
#:title title
7374
#:width width
7475
#:height height
76+
#:devtools? devtools?
7577
#:on-close on-close
7678
#:fallback-browser? fallback?))
7779

7880
(define (open-webview url
7981
#:title [title "Glaze"]
8082
#:width [width 1024]
8183
#:height [height 768]
84+
#:devtools? [devtools? #f]
8285
#:on-close [on-close (lambda () (void))]
8386
#:fallback-browser? [fallback? #f])
8487
(define h
@@ -88,7 +91,12 @@
8891
(exn-message e))
8992
(displayln "use open-browser as fallback." (current-error-port))
9093
#f)])
91-
((ref 'open-webview) url #:title title #:width width #:height height #:on-close on-close)))
94+
((ref 'open-webview) url
95+
#:title title
96+
#:width width
97+
#:height height
98+
#:devtools? devtools?
99+
#:on-close on-close)))
92100
(cond
93101
[h (webview (detected-backend) h)]
94102
[fallback? (open-browser url) #f]

‎glaze-lib/webview/webview-linux.rkt‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
;; and kept alive forever (GTK stores the raw pointer).
2626

2727
(require ffi/unsafe
28+
racket/file
2829
racket/path
2930
racket/string)
3031

@@ -128,6 +129,7 @@
128129
#:title [title "Glaze"]
129130
#:width [width 1024]
130131
#:height [height 768]
132+
#:devtools? [devtools? #f]
131133
#:on-close [on-close (lambda () (void))])
132134
(unless (supported?)
133135
(error 'open-webview "Linux WebView backend not available"))
@@ -167,5 +169,45 @@
167169
(define u (webkit_web_view_get_uri (lin:webview-webview wv)))
168170
(and (non-empty-string? u) u)))
169171

172+
;; Window capture via GDK: gdk_pixbuf_get_from_window on the window's
173+
;; GdkWindow, then gdk_pixbuf_savev to PNG ("png" handler ships with GTK).
174+
(define gdk-pixbuf-lib
175+
(with-handlers ([exn:fail? (lambda (e) #f)])
176+
(ffi-lib "gdk_pixbuf-2.0" '("0" #f))))
177+
(define gtk_widget_get_window
178+
(maybe-bind gtk-lib "gtk_widget_get_window" (_fun _pointer -> _pointer)))
179+
(define gdk_pixbuf_get_from_window
180+
(and gdk-pixbuf-lib
181+
(get-ffi-obj "gdk_pixbuf_get_from_window"
182+
gdk-pixbuf-lib
183+
(_fun _pointer _int _int _int _int -> _pointer)
184+
(lambda () #f))))
185+
(define gdk_pixbuf_savev
186+
(and gdk-pixbuf-lib
187+
(get-ffi-obj "gdk_pixbuf_savev"
188+
gdk-pixbuf-lib
189+
(_fun _pointer _string _string _pointer _pointer _pointer -> _bool)
190+
(lambda () #f))))
191+
170192
(define (capture! wv [dest #f])
171-
#f)
193+
(and (not (unbox (lin:webview-closed?-box wv)))
194+
gtk_widget_get_window
195+
gdk_pixbuf_get_from_window
196+
gdk_pixbuf_savev
197+
(let ()
198+
(define gdkwin (gtk_widget_get_window (lin:webview-window wv)))
199+
(and gdkwin
200+
(let ()
201+
(define pixbuf (gdk_pixbuf_get_from_window gdkwin 0 0 -1 -1))
202+
(and pixbuf
203+
(let ()
204+
(define path
205+
(if dest
206+
(if (string? dest) (string->path dest) dest)
207+
(make-temporary-file "glaze-capture-~a.png")))
208+
(define ok?
209+
(gdk_pixbuf_savev pixbuf
210+
(path->string path)
211+
"png"
212+
#f #f #f))
213+
(and ok? path))))))))

‎glaze-lib/webview/webview-macos.rkt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@
200200
#:title [title "Glaze"]
201201
#:width [width 1024]
202202
#:height [height 768]
203+
#:devtools? [devtools? #f]
203204
#:on-close [on-close (lambda () (void))])
204205
(unless (supported?)
205206
(error 'open-webview "macOS WebView backend unavailable (WebKit failed to load)"))
@@ -240,6 +241,11 @@
240241
#:type _uint
241242
(bitwise-ior NSViewWidthSizable NSViewHeightSizable))
242243
(tellv window setContentView: #:type _id webview)
244+
;; #:devtools? makes WKWebView inspectable (macOS 13+); on older systems
245+
;; web inspectors need a bundle-local override — ignored here.
246+
(when (and devtools?
247+
(tell webview respondsToSelector: #:type _SEL (selector setInspectable:)))
248+
(tellv webview setInspectable: #:type _bool #t))
243249

244250
;; Delegate forwards windowWillClose: to the on-close thunk and flags the
245251
;; closed?-box so the pump loop exits. Registry key: the window's pointer.

‎glaze-lib/webview/webview-stub.rkt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#:title [title "Glaze"]
2020
#:width [width 1024]
2121
#:height [height 768]
22+
#:devtools? [devtools? #f]
2223
#:on-close [on-close (lambda () (void))])
2324
#f)
2425

0 commit comments

Comments
 (0)