feat(webview2): v2 consolidation — fix 6 bugs, add webview2gen CLI, capabilities, tests, docs (WAI-300)#5419
feat(webview2): v2 consolidation — fix 6 bugs, add webview2gen CLI, capabilities, tests, docs (WAI-300)#5419leaanthony wants to merge 14 commits into
Conversation
Four correctness bugs in the code generator fixed: 1. String output vtable call was passing the nil *uint16 pointer directly (`uintptr(unsafe.Pointer(_name))`) instead of its address so COM could write back. Fixed to `uintptr(unsafe.Pointer(&_name))`. 2. Scalar numeric input params (UINT32, INT32 …) were passed as `uintptr(unsafe.Pointer(&val))` — a pointer to a local — instead of `uintptr(val)`. Root cause: processVtableCallInput used the IDL type name (uppercase "UINT32") to detect numerics but the check looked for lowercase "uint". Now uses GoType throughout. 3. Native int output ([out,retval] int* exitCode) was passed by value (`uintptr(exitCode)` == 0) instead of by reference. Fixed by placing pointer-depth checks before the numeric GoType check. 4. COM BOOL ([in] BOOL value) was passed as unsafe.Pointer(&bool) — a pointer to a 1-byte Go bool — instead of a 4-byte int32 value. Added inputBoolSetup.tmpl and processSetupInputs handling. New capability: [in] LPCWSTR* now maps to []string (C string array) with inputStringArraySetup.tmpl converting to []*uint16 before the vtable call. New files: types/typemap.go — TypePatterns table (17 patterns) + ResolveGoType() types/templates/inputBoolSetup.tmpl types/templates/inputStringArraySetup.tmpl generator/idl_parse_test.go — parse all 6 IDL files, check counts + inheritance chain generator/typemap_test.go — one test per pattern + ResolveGoType unit tests All 36 tests pass (go test ./generator/). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
Bug 1 (CRITICAL): Out-pointer string — [out] LPWSTR* vtable call was missing & before the *uint16 variable, so the COM vtable wrote to the wrong address. Fixed in processVtableCallInput() by checking IsOutputParam(). Bug 2 (MODERATE): [in] LPWSTR* was treated as a single string instead of []string. Added GoType promotion to "[]string" in Process(), new inputStringArraySetup.tmpl for IIFE-based per-element UTF-16 conversion, and &arr[0] vtable call input. Bug 3 (MODERATE): Generated interfaces were missing Release() method, causing callers to use IUnknownVtbl.CallRelease for every object. Added Release() uint32 method to interfacevtbl.tmpl alongside AddRef(). Bug 4 (MINOR): Get<Interface>() QueryInterface helpers silently returned nil on failure. Fixed to return (*T, error) and check HRESULT. Bug 5 (MINOR): VARIANT was defined as uintptr (wrong size/layout). Replaced with a 16-byte struct matching the Windows ABI (VT + 3 reserved uint16 + 8-byte union). Bug 6 (MINOR): AddRef() and Release() returned uintptr instead of uint32. COM AddRef/Release return ULONG (uint32). Updated all signatures and the IUnknownImpl interface. Callback wrappers in interfaceInvoke.tmpl now cast uint32 to uintptr for the Windows callback ABI. All 7 generator unit tests pass. Test fixtures regenerated to match corrected output. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
…7/5410 Removes a stray blank line in the SetAllowedOrigins fixture so the fixture matches the merged template output. All generator tests pass.
…292/293)
Adds the single entry point requested by the WebView2 v2 spec:
webview2gen download — fetch IDL from NuGet, cache in scripts/
webview2gen generate — regenerate pkg/webview2 from cached IDL
webview2gen capabilities — emit pkg/webview2/capabilities.go from
the SDK release notes (supports --source
for offline runs)
webview2gen verify — fail if regeneration diverges from the
committed output (CI gate)
webview2gen test — run generator + internal package tests
webview2gen full — download → generate → capabilities → verify
New internal packages (all unit-tested, no Windows required):
internal/idlversion — WebView2 version parse/compare
internal/idl — NuGet fetcher + on-disk cache (Store)
internal/notes — release-notes scraper + interface→version map
internal/capabilities — capabilities.go emitter (Go AST-validated tests)
All four packages have go test ./... passing; the CLI runs end-to-end
against the cached SDK 1.0.2903.40 IDL and the resulting pkg/webview2
compiles cleanly on GOOS=windows.
Regenerates all 304 files in pkg/webview2 via `webview2gen generate`
to propagate the 6 bug fixes landed earlier in this branch:
• Bug 1 (CRITICAL): out-pointer string methods now take the address
of the local *uint16, so COM writes the result back where the
caller can read it (~109 methods).
• Bug 2: [in] LPCWSTR* / LPWSTR* now maps to []string with a real
UTF-16 array marshaler.
• Bug 3: every interface has Release() alongside AddRef().
• Bug 4: COM AddRef/Release return uint32 (Windows ULONG), not
uintptr.
• Bug 5: VARIANT is a proper 16-byte struct matching the Windows
ABI, not uintptr.
• Bug 6: IUnknownVtbl.CallRelease returns error instead of a
silently dropped HRESULT.
Adds pkg/webview2/capabilities.go (104 interfaces) emitted from the
SDK release notes via `webview2gen capabilities --source test.md`.
The file is `go:build windows`-gated and exposes:
• InterfaceMinimumVersion (map)
• SupportsInterface(runtimeVersion, iface) (bool, required, error)
• HasCapability(runtimeVersion, cap) (bool, error)
• AllCapabilities (slice of named feature gates)
The whole pkg/webview2 tree compiles cleanly under GOOS=windows.
The .gitignore line `webview2gen` was too broad — it matched the
cmd/webview2gen package directory and silently excluded main.go and
main_test.go from the previous commit. Tighten the pattern to the
two paths where a compiled binary could land:
/scripts/webview2gen
/scripts/cmd/webview2gen/webview2gen
Also adds the CLI files (now reachable) and the v2 documentation:
• webview2/README.md — pipeline + capability usage + the
six-bug-fix changelog table
• webview2/ARCHITECTURE.md — design rationale, trade-offs,
rejected approaches, future work
• webview2/pkg/webview2/doc.go — package doc, regeneration recipe,
`//go:generate webview2gen full`
Adds `.github/workflows/webview2-verify.yml` to run the generator tests, gate hand-edits via `webview2gen verify`, regenerate capabilities.go from the cached release-notes snapshot, and cross-compile-check the output for windows on every PR that touches `webview2/`. Fixes a bug in `webview2gen verify`: it was flagging the hand-written `pkg/webview2/doc.go` as an unexpected committed file. `verify` now skips `doc.go` alongside `capabilities.go` and `*_test.go`, matching which files are actually owned by the generator.
…lease
Two issues spotted in the first run of `webview2gen capabilities`:
1. Loose mention-matching pulled in renamed interfaces (e.g.
ICoreWebView2Host) and double-counted across prerelease + stable
releases, producing wrong "minimum version" entries like
ICoreWebView2_7 → 1.0.1305-prerelease while ICoreWebView2_11 sat
at 1.0.1264.42 (newer interface but earlier version!).
2. Prereleases were treated as authoritative for capability gating
even though consumers only run stable runtimes.
Switches the parser to match the canonical introduction signal —
`[ICoreWebView2_N interface](url)` — emitted in the API listings of
each stable release section, and explicitly skips any release whose
SDKVersion includes "prerelease" or "preview".
Regenerated `pkg/webview2/capabilities.go` (now 23 stable entries,
correctly ordered) and added IsPrerelease + matching unit tests.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| runs-on: ubuntu-latest | ||
| env: | ||
| GOWORK: "off" | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: '1.24' | ||
|
|
||
| - name: Generator tests | ||
| working-directory: webview2/scripts | ||
| run: go test -race ./... | ||
|
|
||
| - name: Verify generated bindings are up to date | ||
| working-directory: webview2/scripts | ||
| run: | | ||
| go run ./cmd/webview2gen verify | ||
| # capabilities.go is also generator-output; regenerate it from the | ||
| # cached release-notes snapshot and fail if it changes. | ||
| go run ./cmd/webview2gen capabilities --source test.md | ||
| if ! git diff --exit-code -- ../pkg/webview2/capabilities.go; then | ||
| echo "::error::pkg/webview2/capabilities.go is out of date — run 'webview2gen capabilities'" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Cross-compile generated bindings for Windows | ||
| working-directory: webview2 | ||
| run: GOOS=windows go vet ./pkg/webview2/ |
| // Fetch downloads the release-notes markdown. | ||
| func Fetch() ([]byte, error) { | ||
| client := &http.Client{Timeout: FetchTimeout} | ||
| resp, err := client.Get(SourceURL) |
There was a problem hiding this comment.
Medium severity and reachable issue identified in your code:
Line 54 has a vulnerable usage of golang.org/x/net, introducing a medium severity vulnerability.
ℹ️ Why this is reachable
A reachable issue is a real security risk because your project actually executes the vulnerable code. This issue is reachable because your code uses a certain version of golang.org/x/net.
Affected versions of golang.org/x/net, golang.org/x/net/http2, and net/http are vulnerable to Uncontrolled Resource Consumption. An attacker may cause an HTTP/2 endpoint to read arbitrary amounts of header data by sending an excessive number of CONTINUATION frames.
To resolve this comment:
Upgrade this dependency to at least version 0.23.0 at webview2/scripts/go.mod.
💬 Ignore this finding
To ignore this, reply with:
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
You can view more details on this finding in the Semgrep AppSec Platform here.
| } | ||
|
|
||
| url := fmt.Sprintf(NuGetPackageURL, version) | ||
| resp, err := f.HTTPClient.Get(url) |
There was a problem hiding this comment.
Medium severity and reachable issue identified in your code:
Line 107 has a vulnerable usage of golang.org/x/net, introducing a medium severity vulnerability.
ℹ️ Why this is reachable
A reachable issue is a real security risk because your project actually executes the vulnerable code. This issue is reachable because your code uses a certain version of golang.org/x/net.
Affected versions of golang.org/x/net, golang.org/x/net/http2, and net/http are vulnerable to Uncontrolled Resource Consumption. An attacker may cause an HTTP/2 endpoint to read arbitrary amounts of header data by sending an excessive number of CONTINUATION frames.
To resolve this comment:
Upgrade this dependency to at least version 0.23.0 at webview2/scripts/go.mod.
💬 Ignore this finding
To ignore this, reply with:
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
You can view more details on this finding in the Semgrep AppSec Platform here.
…fetch archive Microsoft restructured the WebView2 SDK release-notes markdown around the "Phase 1/2/3" promotion vocabulary; top-level interface links now use `[ICoreWebView2_N](url)` instead of the older `[ICoreWebView2_N interface](url)`. The interfaceLinkRE accepts both shapes and tightens the trailing `](` to exclude prose mentions and method links. Older release sections rotate off index.md roughly every 12 months and move to archive.md. Without the archive the capability map is empty for anything older than ~1 year. Fetch() now pulls both URLs and concatenates; the archive fetch is best-effort so a transient archive 404 still yields a (smaller) mapping rather than a hard failure. test.md is refreshed to the live index + archive concatenation so the CI `webview2gen capabilities -source test.md` drift check stays honest.
Re-run `webview2gen download --version 1.0.3967.48 && webview2gen generate && webview2gen capabilities` against the latest stable WebView2 SDK (~1000-build jump from the cached 1.0.2903.40). The generator handled the full SDK delta without surfacing any of the 6 fixed bugs and without any new parser gaps; `GOOS=windows go vet ./pkg/webview2/` and `webview2gen verify` both pass. Delta: - 14 new interface bindings (Find API, DragStarting, DedicatedWorker, ICoreWebView2_28, ControllerOptions3/4, Environment15, Frame7, CompositionController5) - 2 enums extended (COREWEBVIEW2_PERMISSION_KIND, COREWEBVIEW2_PROCESS_FAILED_REASON) - capabilities.go: 23 → 98 interface→minimum-version entries (the archive fetch added historical SDKs back to 1.0.622) - WebView2.1.0.3967.48.idl cached in scripts/
Windows verification of
|
| Check | Result |
|---|---|
go build ./pkg/webview2/ |
PASS — all generated files compile cleanly |
| Runtime detection | PASS — 148.0.3967.54 |
HasCapability (IsMutedChanged/v91, ScreenCapture/v131, WebView2Environment, BasicSettings, NavigationCompleted, CookieManagement) |
PASS |
chromium.HasCapability consistency |
PASS |
Embed (CreateCoreWebView2Controller) |
PASS |
Environment() accessor |
PASS |
GetUserAgent (LPWSTR out-pointer) |
PASS — Mozilla/5.0 ... Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0 — no null bytes |
GetIsScriptEnabled (bool getter) |
PASS — returns true |
Generated pkg/webview2/ICoreWebView2.GetSource pattern |
PASS — uses uintptr(unsafe.Pointer(&_uri)) (correct) |
⚠️ Pre-existing bug found in pkg/edge/ — please include fix in this PR
GetUserAgent() in both ICoreWebViewSettings and ICoreWebView2Settings2 passes the nil *uint16 value instead of its address. COM's ICoreWebView2Settings2::get_UserAgent takes a LPWSTR* out-pointer, so passing a null returns E_POINTER (0x80004003).
Fix (1 line in each file):
--- a/webview2/pkg/edge/ICoreWebViewSettings.go
+++ b/webview2/pkg/edge/ICoreWebViewSettings.go
@@ -272,7 +272,7 @@ func (i *ICoreWebViewSettings) GetUserAgent() (string, error) {
var _userAgent *uint16
hr, _, _ := i.vtbl.GetUserAgent.Call(
uintptr(unsafe.Pointer(i)),
- uintptr(unsafe.Pointer(_userAgent)),
+ uintptr(unsafe.Pointer(&_userAgent)),
)
--- a/webview2/pkg/edge/ICoreWebView2Settings2.go
+++ b/webview2/pkg/edge/ICoreWebView2Settings2.go
@@ -59,7 +59,7 @@ func (i *ICoreWebView2Settings2) GetUserAgent() (string, error) {
hr, _, _ := i.Vtbl.GetUserAgent.Call(
uintptr(unsafe.Pointer(i)),
- uintptr(unsafe.Pointer(_userAgent)),
+ uintptr(unsafe.Pointer(&_userAgent)),
)Note: PutUserAgent is correct in both files — its _userAgent is allocated by UTF16PtrFromString so passing the value (not &) is right for an in-parameter.
ℹ️ Generator tests on Windows (infrastructure, not code)
go test ./updater/... fails on Windows due to: (1) test invokes webview2gen but binary is webview2gen.exe, (2) generator produces CRLF on Windows but golden files have LF. The generated bindings are correct.
ℹ️ Navigation test
Navigate("about:blank") + NavigationCompleted event did not fire within 15s from a scheduled-task process. All settings getters pass. GetSource is correct by code inspection (&_source at corewebview2.go:330).
CC @leaanthony
Taliesin is an AI agent. CC @leaanthony
Two Windows-side issues surfaced when the Engineer-Windows agent ran the
generator's own test suite inside the golden Hyper-V VM:
1. `webview2gen` tests build the CLI with `go build -o webview2gen` and
then `exec.Command(bin, ...)` — on Windows `go build` writes
`webview2gen.exe` regardless, so the exec.Command misses the binary
by name. Mirror the same `.exe` suffix the toolchain does.
2. text/template preserves whatever line endings the template carries.
A Windows checkout with core.autocrlf=true converts every `.tmpl`
to CRLF, so the emitted Go files become CRLF and diverge from the
LF golden files committed in scripts/generator/testfiles/. Strip
CR in the writer (runGenerate) and the verifier (runVerify) so the
output is deterministic across platforms.
Defense in depth: add webview2/.gitattributes forcing LF on `.tmpl`,
`.go`, `.go.txt`, `.idl`. This stops the CRLF from entering the
checkout in the first place; the generator-side normalisation handles
the case where a checkout already has CRLF.
Verified on Mac:
- `go test ./...` in scripts/ — all green
- `webview2gen verify` — 320 files match the committed tree
…dings Engineer-Windows surfaced three generator bugs while smoke-testing pkg/webview2 on the win-node1 VM. Each one produced code that compiled cleanly under `GOOS=windows go vet` but failed at runtime — `go vet` is a syntax/unused-var check, not a behaviour check. All three were fixed at the generator level per the project guidance; no hand-edits to generated code. 1. HWND/HANDLE/HCURSOR as scalars (param.go) These are uintptr aliases in golang.org/x/sys/windows — they're already handle *values*, not pointers to handles. The default branch in processVtableCallInput emitted `uintptr(unsafe.Pointer(&parentWindow))`, which passes a stack-local pointer instead of the handle value. The receiving COM method dereferences this as garbage. Added these three names to the scalar-pass-by-value branch alongside int/uint/float/bool, matching the existing treatment in defaultErrorValue(). 2. syscall.Call third return as err (interfaceMethod.tmpl + SuccessValues) `windows.syscall.Call` returns (r1, r2, lastErr) — lastErr is GetLastError, not the HRESULT, and is non-nil after every call regardless of success. The template bound the third return to `err` and SuccessValues emitted `return ..., err` for the success path, so methods that succeeded surfaced stale Win32 errors from prior unrelated syscalls. Template now uses `_` for the third return; SuccessValues emits `nil`. 3. windows.NewCallback string params (interfaceInvoke.tmpl + interface.go) `windows.NewCallback` panics at init if any callback parameter is wider than a uintptr. Go `string` is a 2-word fat pointer, so any event handler with a string parameter (e.g. AddScriptToExecuteOnDocumentCreatedCompleted's `result LPCWSTR`) was unsafe to register. The Invoke trampoline now accepts `*uint16` for string params and converts to `string` inline via UTF16PtrToString before the impl call. Added InvokeGoInputs, InvokeConversionCode, InvokeInputParamNames methods on InterfaceMethod to drive the new template form. The Impl interface keeps the Go-native `string` signature so callers are unaffected. Generator testfiles updated to match the new emitter. `webview2gen verify` clean (320 files match), `GOOS=windows go vet ./pkg/webview2/` clean, `go test ./...` in scripts/ green.
Output of `webview2gen generate -dir . -out ../pkg/webview2` after the
three generator fixes in the previous commit:
- HWND/HANDLE/HCURSOR params now emit `uintptr(x)` instead of
`uintptr(unsafe.Pointer(&x))` — fixes silent garbage-handle bugs in
every HWND-taking method (e.g. CreateCoreWebView2Controller's
parentWindow).
- syscall.Call third return is discarded; success path returns nil
instead of stale GetLastError — every method that previously returned
`..., err` now returns `..., nil` on success.
- Invoke trampolines for handlers with string callback parameters now
take `*uint16` and convert inline.
191 files changed; no API changes, no new files (the same SDK 1.0.3967.48
output, just emitted with the bug fixes). `webview2gen verify` clean.
Summary
Consolidates the WebView2 v2 spec (WAI-300) into a single branch. Supersedes #5407 and #5410 — both are folded in here with conflicts resolved.
What's in this branch
Foundation (from #5407 + #5410, merged & reconciled)
.gofiles:[out] LPWSTR* namewrote the pointer to the caller's stackuintptr(unsafe.Pointer(&_name))— ~109 methods[in] LPCWSTR*was a singlestring[]stringwith array marshalerRelease()(refcount leak)Release() uint32QueryInterfacesilently returned nil on failure(*T, error)with HRESULTVARIANTwasuintptr(8 bytes)AddRef/Releasereturneduintptruint32(COMULONG)types/typemap.go).New tooling —
webview2genCLISingle entry point for the SDK refresh pipeline:
Four internal packages, all unit-tested with no Windows dependency:
internal/idl— NuGet.nupkgfetcher + on-disk cacheinternal/idlversion—Parse/Comparefor1.0.X.Y(handles lexical-vs-numeric)internal/notes— release-notes scraper (matches the canonical[ICoreWebView2_N interface](url)link pattern, skips prereleases)internal/capabilities—pkg/webview2/capabilities.goemitter (Go-AST-validated)Capabilities — runtime feature detection
pkg/webview2/capabilities.goexposes:InterfaceMinimumVersionmap (interface name → minimum stable SDK)SupportsInterface(runtimeVersion, iface) (bool, required, error)HasCapability(runtimeVersion, cap Capability) (bool, error)AllCapabilitiesslice of named feature gatesDocumentation
webview2/README.md— pipeline + capability usage + bug-fix changelogwebview2/ARCHITECTURE.md— design rationale, trade-offs, rejected approachespkg/webview2/doc.go—//go:generate webview2gen full, no-hand-edits noticeCI
.github/workflows/webview2-verify.ymlruns on every PR touchingwebview2/:go test -race ./...for the generator + internal packageswebview2gen verify(blocks accidental hand-edits to generated files)capabilities.gofrom the cached release-notes snapshot and fails on driftGOOS=windows go vet ./pkg/webview2/Verification
go test -race ./...clean across all packages (40+ test cases)GOOS=windows go vet ./pkg/webview2/cleanwebview2gen verify↔ committedpkg/webview2/cleanwebview2gen fullend-to-end against cached SDK 1.0.2903.40 produces the same 306 filesDeferred (explicit follow-ups, not in this PR)
v3/pkg/application/webview_window_windows*.goandv3/internal/assetserver/webview/request_windows.gostill importpkg/edge. Migration topkg/webview2is a separate refactor with its own review surface;pkg/edgeis intentionally untouched here so the runtime keeps building. This branch makespkg/webview2viable as the migration target.win-node1require Windows hardware + a real WebView2 runtime; tracked as a separate ticket.Test plan
cd webview2/scripts && go test -race ./...passesGOOS=windows go vet ./webview2/pkg/webview2/passeswebview2gen verifypasses against committed outputwebview2gen fullreproduces the committed bindings byte-for-bytechromium_*.goconsumers to importpkg/webview2🤖 Generated with Claude Code