Skip to content

Commit 49aa5e7

Browse files
ctateWhiteHades
andauthored
Native-only Linux host: compile out WebKitGTK when no web intent is declared (#110)
* Compile the GTK host without WebKitGTK for native-only apps - NATIVE_SDK_ALLOW_WEBKITGTK_STUB mirrors the Windows WebView2 seam: the define wins over header visibility, compiles out every WebKit-touching path (opaque never-non-NULL web-view pointers keep the GTK-only bookkeeping shape), and stubs the exported webview entry points - Both Linux build graphs (build/app.zig and the generated template) compile gtk_host.c with the stub and drop the webkitgtk-6.0 link when the web layer is excluded; the native scaffold's generated CI stops installing libwebkitgtk-6.0-dev Co-authored-by: WhiteHades <44260523+WhiteHades@users.noreply.github.com> * Audit the web layer in Linux ELF executables - tools/audit_web_layer.zig auto-detects PE vs ELF and hand-rolls an ELF64 reader over the section headers: DT_NEEDED entries naming libwebkitgtk/libjavascriptcoregtk and webkit_/jsc_ dynamic symbols are the evidence, refusal (never a pass) for anything it cannot parse - native package grows the ELF twin of the PE guard: a WebKitGTK-linking binary packaged under a native-only decision is refused with the same teaching message, pinned by synthetic-ELF tests covering both evidence channels Co-authored-by: WhiteHades <44260523+WhiteHades@users.noreply.github.com> * Prove the Linux seam in CI from both directions - linux-webkitgtk gains test-linux-web-layer-audit (seam under webkit-PRESENT conditions: native-only ELF scans clean, web ELF keeps its references) and linux-canvas-smoke drops libwebkitgtk-6.0-dev so the build itself is the native-only link test, with the ELF audit run on the real binary - vendor pins keep the stub define wired through gtk_host.c and both build graphs; the macOS gpu-dashboard smoke asserts a native-only session spawns zero new WebKit helper processes Co-authored-by: WhiteHades <44260523+WhiteHades@users.noreply.github.com> * Pin native-only behavior: automation sessions, record/replay, docs - An automation-driven session on a native-only canvas app proves normal command dispatch plus the WebViewLayerNotBuilt teaching error on a webview attempt, with zero webviews reaching the platform host - The session record/replay reference journal round-trips identically under web_layer=false; the capabilities and app.zon pages note the user-visible Linux consequence (no WebKitGTK to build or run) Co-authored-by: WhiteHades <44260523+WhiteHades@users.noreply.github.com> * Forward the web engine to native package from the SDK build graph - A Chromium exe packaged through zig build package shipped no CEF runtime because the CLI defaults to the system engine; forward --web-engine, --cef-dir, and --cef-auto-install exactly as the generated build graph already does --------- Co-authored-by: WhiteHades <44260523+WhiteHades@users.noreply.github.com>
1 parent 9b4f62d commit 49aa5e7

13 files changed

Lines changed: 873 additions & 54 deletions

File tree

.github/scripts/linux-canvas-smoke.sh

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
#!/usr/bin/env bash
2-
# Linux canvas smoke under Xvfb.
2+
# Linux canvas smoke under Xvfb — run on a machine with NO WebKitGTK dev
3+
# package, which makes the build itself the native-only link test.
34
#
4-
# Exercises the Linux gpu_surface software path against system WebKitGTK
5-
# without a display server: builds examples/ui-inbox with -Dplatform=linux
6-
# -Dweb-engine=system -Dautomation=true, runs it under Xvfb, and asserts
7-
# against the automation snapshot:
5+
# Exercises the Linux gpu_surface software path without a display server:
6+
# builds examples/ui-inbox (a native-only app, so its GTK host compiles
7+
# with the WebKitGTK stub seam and never links webkitgtk-6.0) with
8+
# -Dplatform=linux -Dweb-engine=system -Dautomation=true, runs it under
9+
# Xvfb, and asserts against the automation snapshot:
810
#
9-
# 1. snapshot ready=true (app booted, automation server live)
10-
# 2. gpu_backend=software (the software present path is active)
11-
# 3. gpu_nonblank=true (real pixels were presented)
12-
# 4. widget-click "Add task" -> '4 open' (automation input mutates state)
13-
# 5. automate screenshot renders a non-empty PNG
14-
# 6. ZERO WebKit helper processes for the whole run (the main WebView is
15-
# lazy; a canvas app must never boot the WebKit stack)
11+
# 1. the built ELF carries no WebKitGTK reference (no libwebkitgtk
12+
# DT_NEEDED entry, no webkit_/jsc_ dynamic symbol), audited on the
13+
# real binary by tools/audit_web_layer.zig
14+
# 2. snapshot ready=true (app booted, automation server live)
15+
# 3. gpu_backend=software (the software present path is active)
16+
# 4. gpu_nonblank=true (real pixels were presented)
17+
# 5. widget-click "Add task" -> '4 open' (automation input mutates state)
18+
# 6. automate screenshot renders a non-empty PNG
19+
# 7. ZERO WebKit helper processes for the whole run (a native-only app
20+
# has no web layer to boot WebKit with)
1621
#
1722
# Deliberately NOT `set -e` (same as windows-canvas-smoke.sh): grep exits 1
1823
# on zero matches, and under `set -e` an assignment like `x=$(grep ...)` or
@@ -21,10 +26,11 @@
2126
# goes through fail(), which dumps the snapshot and the app log.
2227
set -u
2328

24-
# No WebKit sandbox workaround: a canvas app's main WebView is created
25-
# lazily and never materializes, so no WebKit helper processes start and
26-
# the runner's user-namespace restrictions never come into play. The
27-
# zero-WebKit assertion below keeps it that way.
29+
# No WebKit sandbox workaround: a native-only app's host is compiled
30+
# without the web layer entirely (and even in web builds the main
31+
# WebView is lazy), so no WebKit helper processes start and the runner's
32+
# user-namespace restrictions never come into play. The zero-WebKit
33+
# assertion below keeps it that way.
2834

2935
# GTK_A11Y=none: under Xvfb there is no session bus providing org.a11y.Bus,
3036
# and GTK4's a11y init blocks ~25 s on the GDBus name lookup before warning
@@ -94,22 +100,27 @@ assert_no_webkit() {
94100
# ---- build ----------------------------------------------------------------
95101
(cd "$repo_root" && zig build) || fail "root zig build (CLI) failed"
96102
(cd "$app_dir" && zig build -Dplatform=linux -Dweb-engine=system -Dautomation=true) \
97-
|| fail "ui-inbox Linux build failed"
103+
|| fail "ui-inbox Linux build failed (a native-only app must build without the WebKitGTK dev package)"
104+
105+
# ---- 1: the native-only ELF carries no WebKitGTK reference -----------------
106+
(cd "$repo_root" && zig run tools/audit_web_layer.zig -- "$app_dir/zig-out/bin/ui-inbox" absent) \
107+
|| fail "native-only ELF audit failed (the binary references WebKitGTK)"
108+
echo "== native-only ELF audit ok"
98109

99110
# ---- launch ---------------------------------------------------------------
100111
cd "$app_dir" || fail "missing $app_dir"
101112
rm -rf .zig-cache/native-sdk-automation
102113
xvfb-run -a "$app_dir/zig-out/bin/ui-inbox" > "$app_log" 2>&1 &
103114
app_pid=$!
104115

105-
# ---- 1: automation snapshot becomes ready ---------------------------------
116+
# ---- 2: automation snapshot becomes ready ---------------------------------
106117
# `automate assert` self-reports on timeout (missing patterns + snapshot
107118
# tail) and prints the measured latency on success, so green logs carry
108119
# the readiness margin.
109120
"$cli" automate assert --timeout-ms "$ready_timeout_ms" 'ready=true' \
110121
|| fail "snapshot never became ready"
111122

112-
# ---- 2 + 3: software backend presented non-blank pixels --------------------
123+
# ---- 3 + 4: software backend presented non-blank pixels --------------------
113124
"$cli" automate assert --timeout-ms 30000 'gpu_nonblank=true' \
114125
|| fail "gpu_nonblank never became true"
115126
grep -q 'gpu_backend=software' "$snap" || fail "gpu_backend is not software"
@@ -118,7 +129,7 @@ echo "== canvas: $(grep -o 'gpu_backend=[a-z]*' "$snap" | head -1)" \
118129
assert_no_webkit "after first presented frame"
119130
echo "== zero WebKit processes after first presented frame"
120131

121-
# ---- 4: automation widget-click mutates the model --------------------------
132+
# ---- 5: automation widget-click mutates the model --------------------------
122133
echo "== open before click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
123134
add_id=$(grep -o 'widget @w1/inbox-canvas#[0-9]* role=button name="Add task"' "$snap" \
124135
| grep -o '#[0-9]*' | tr -d '#')
@@ -128,12 +139,12 @@ add_id=$(grep -o 'widget @w1/inbox-canvas#[0-9]* role=button name="Add task"' "$
128139
|| fail "widget-click did not reach '4 open'"
129140
echo "== open after click: $(grep -oE '[0-9]+ open' "$snap" | head -1)"
130141

131-
# ---- 5: screenshot renders a non-empty PNG ---------------------------------
142+
# ---- 6: screenshot renders a non-empty PNG ---------------------------------
132143
"$cli" automate screenshot inbox-canvas || fail "CLI screenshot failed"
133144
test -s .zig-cache/native-sdk-automation/screenshot-inbox-canvas.png \
134145
|| fail "screenshot PNG missing or empty"
135146

136-
# ---- 6: still zero WebKit processes at the end of the run -------------------
147+
# ---- 7: still zero WebKit processes at the end of the run -------------------
137148
assert_no_webkit "at end of run"
138149
echo "== zero WebKit processes at end of run"
139150

.github/workflows/ci.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ jobs:
8181
- name: Install WebKitGTK dependencies
8282
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
8383
- run: zig build test-webview-system-link -Dplatform=linux
84+
# Declare-to-use, proven on real Linux executables WITH the
85+
# WebKitGTK dev package installed — the seam, not the environment:
86+
# the native-only ui-inbox binary must carry no libwebkitgtk
87+
# DT_NEEDED entry and no webkit_/jsc_ dynamic symbol even though
88+
# the headers were right there, while the webview example must
89+
# keep them. (The environment half — building native-only on a
90+
# runner with no WebKitGTK dev package at all — is the
91+
# linux-canvas-smoke job.)
92+
- run: zig build test-linux-web-layer-audit
8493

8594
windows-webview:
8695
name: Windows WebView
@@ -144,15 +153,22 @@ jobs:
144153
- uses: mlugg/setup-zig@v2
145154
with:
146155
version: 0.16.0
156+
# Deliberately NO libwebkitgtk-6.0-dev: ui-inbox declares no web
157+
# use, so its host compiles with the WebKitGTK stub seam and this
158+
# job doubles as the native-only Linux LINK test — the build can
159+
# only succeed if nothing in a native-only app needs the WebKitGTK
160+
# headers or pkg-config entry. (The seam under webkit-PRESENT
161+
# conditions is the linux-webkitgtk job's ELF cross-audit.)
147162
- name: Install GTK and Xvfb
148-
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev xvfb
163+
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev xvfb
149164
# Drives the gpu_surface software path under Xvfb: snapshot ready,
150165
# gpu_backend=software, gpu_nonblank=true, automation widget-click,
151-
# a rendered screenshot, and ZERO WebKit helper processes (the main
152-
# WebView is lazy; canvas apps never boot WebKit). A11y env, the
153-
# widened cold-start readiness budget (shared runners stall ~27 s
154-
# before the first runtime event), and failure forensics (dump
155-
# snapshot + app log) all live in the script.
166+
# a rendered screenshot, an ELF audit that the built binary carries
167+
# no WebKitGTK reference, and ZERO WebKit helper processes (canvas
168+
# apps never boot WebKit). A11y env, the widened cold-start
169+
# readiness budget (shared runners stall ~27 s before the first
170+
# runtime event), and failure forensics (dump snapshot + app log)
171+
# all live in the script.
156172
- name: Build and drive ui-inbox headless
157173
run: .github/scripts/linux-canvas-smoke.sh
158174

build.zig

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,19 @@ pub fn build(b: *std.Build) void {
510510
.{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/include" },
511511
.{ .path = "src/tooling/templates.zig", .pattern = "third_party/webview2/x64/WebView2Loader.dll" },
512512
});
513+
// The Linux mirror of the Windows seam: the stub define wins over
514+
// header visibility (a native-only build never reintroduces the
515+
// libwebkitgtk link even where the dev package is installed), the
516+
// header is required for web builds, and both build graphs compile
517+
// gtk_host.c with the stub and drop the webkitgtk-6.0 link when the
518+
// web layer is excluded.
519+
addFileContainsCheckStep(b, file_contains_checker, test_step, "test-linux-webkitgtk-seam", "Verify the WebKitGTK compile seam stays wired through the GTK host and both Linux build graphs", &.{
520+
.{ .path = "src/platform/linux/gtk_host.c", .pattern = "#if defined(NATIVE_SDK_ALLOW_WEBKITGTK_STUB)" },
521+
.{ .path = "src/platform/linux/gtk_host.c", .pattern = "#elif __has_include(<webkit/webkit.h>)" },
522+
.{ .path = "src/platform/linux/gtk_host.c", .pattern = "#error \"webkit/webkit.h not found" },
523+
.{ .path = "build/app.zig", .pattern = "\"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB\"" },
524+
.{ .path = "src/tooling/templates.zig", .pattern = "\"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB\"" },
525+
});
513526
addLayoutCheckStep(b, test_step, "test-windows-webview2-loader-layout", "Verify the vendored WebView2 loader binaries are present", &.{
514527
"third_party/webview2/x64/WebView2Loader.dll",
515528
"third_party/webview2/arm64/WebView2Loader.dll",
@@ -838,7 +851,8 @@ pub fn build(b: *std.Build) void {
838851
audit_webview_exe.addArgs(&.{ "examples/webview/zig-out/bin/webview.exe", "present" });
839852
audit_webview_exe.has_side_effects = true;
840853
audit_webview_exe.step.dependOn(&build_webview_windows.step);
841-
const audit_native_only_loader = b.addSystemCommand(&.{ "sh", "-c",
854+
const audit_native_only_loader = b.addSystemCommand(&.{
855+
"sh", "-c",
842856
\\test ! -f examples/ui-inbox/zig-out/bin/WebView2Loader.dll || {
843857
\\ echo "web-layer audit FAILED: the native-only build installed WebView2Loader.dll" >&2
844858
\\ exit 1
@@ -850,6 +864,33 @@ pub fn build(b: *std.Build) void {
850864
web_layer_audit_step.dependOn(&audit_webview_exe.step);
851865
web_layer_audit_step.dependOn(&audit_native_only_loader.step);
852866

867+
// Linux web-layer ELF cross-audit: the same declare-to-use proof on
868+
// real Linux executables. Unlike the Windows step this cannot
869+
// cross-compile from any host — the GTK host needs the system GTK4
870+
// (and, for the web example, WebKitGTK) development headers — so the
871+
// step runs on Linux with both packages installed and proves the
872+
// SEAM, not the environment: the native-only exe must carry no
873+
// libwebkitgtk DT_NEEDED entry and no webkit_/jsc_ dynamic symbol
874+
// even though the dev package was right there, while the web example
875+
// must keep them. The environment half (a native-only app building
876+
// on a runner with no WebKitGTK dev package at all) is proven by the
877+
// linux-canvas-smoke CI job.
878+
const build_native_only_linux = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=linux" });
879+
build_native_only_linux.setCwd(b.path("examples/ui-inbox"));
880+
const build_webview_linux = b.addSystemCommand(&.{ "zig", "build", "-Dplatform=linux", "-Dweb-engine=system" });
881+
build_webview_linux.setCwd(b.path("examples/webview"));
882+
const audit_native_only_elf = b.addRunArtifact(web_layer_auditor);
883+
audit_native_only_elf.addArgs(&.{ "examples/ui-inbox/zig-out/bin/ui-inbox", "absent" });
884+
audit_native_only_elf.has_side_effects = true;
885+
audit_native_only_elf.step.dependOn(&build_native_only_linux.step);
886+
const audit_webview_elf = b.addRunArtifact(web_layer_auditor);
887+
audit_webview_elf.addArgs(&.{ "examples/webview/zig-out/bin/webview", "present" });
888+
audit_webview_elf.has_side_effects = true;
889+
audit_webview_elf.step.dependOn(&build_webview_linux.step);
890+
const linux_web_layer_audit_step = b.step("test-linux-web-layer-audit", "Build a native-only and a web example for Linux (Linux host with GTK4 + WebKitGTK dev packages) and audit the web layer in each ELF");
891+
linux_web_layer_audit_step.dependOn(&audit_native_only_elf.step);
892+
linux_web_layer_audit_step.dependOn(&audit_webview_elf.step);
893+
853894
const frontend_examples_step = b.step("test-examples-frontends", "Run frontend example tests");
854895
addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-next", "Run Next example tests", "examples/next", .owned);
855896
addExampleTestStep(b, host_cli_exe, frontend_examples_step, "test-example-react", "Run React example tests", "examples/react", .owned);
@@ -1459,6 +1500,16 @@ pub fn build(b: *std.Build) void {
14591500
\\ready_budget_ms="$smoke_budget_ms"
14601501
\\if [ "$ready_budget_ms" -lt 500 ]; then ready_budget_ms=500; fi
14611502
\\ready_budget_ns=$((ready_budget_ms * 1000000))
1503+
\\# gpu-dashboard is a native-only app (nothing in app.zon declares web
1504+
\\# use), so its macOS host must never boot the WebKit stack: WKWebView
1505+
\\# instantiation spawns com.apple.WebKit.* XPC helpers. They are
1506+
\\# launchd-parented (not app children), so the honest cheap probe is
1507+
\\# the machine-wide helper set before launch vs at the end of the
1508+
\\# session — a NEW helper during this headless smoke window means the
1509+
\\# lazy main-WebView path regressed (the failure lists the processes
1510+
\\# so a coincidental web app launched mid-smoke is tellable apart).
1511+
\\webkit_helpers() { pgrep -f 'com\.apple\.WebKit\.(WebContent|Networking|GPU)' 2>/dev/null | tr '\n' ' '; }
1512+
\\webkit_before="$(webkit_helpers)"
14621513
\\pid=""
14631514
\\trap 'status=$?; kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true; if [ "$status" -ne 0 ]; then echo "---- app log (.zig-cache/native-sdk-gpu-dashboard-smoke.log) ----" >&2; cat .zig-cache/native-sdk-gpu-dashboard-smoke.log >&2 2>/dev/null || true; fi' EXIT
14641515
\\stop_app() {
@@ -1625,6 +1676,19 @@ pub fn build(b: *std.Build) void {
16251676
\\if ! grep -q "gpu incremental verify view=dashboard-canvas checks=" "$verify_log" 2>/dev/null; then echo "dashboard verify mode recorded no incremental checks" >&2; exit 1; fi
16261677
\\if grep -q "verify MISMATCH" "$verify_log" 2>/dev/null; then echo "dashboard incremental present diverged from a full redraw:" >&2; grep "verify MISMATCH" "$verify_log" >&2; exit 1; fi
16271678
\\if grep "gpu incremental verify view=dashboard-canvas checks=" "$verify_log" | grep -qv "mismatches=0"; then echo "dashboard incremental verify reported mismatches" >&2; exit 1; fi
1679+
\\# The whole session ran (two launches, clicks, resizes): a native-only
1680+
\\# macOS app must have spawned ZERO new WebKit helper processes.
1681+
\\webkit_after="$(webkit_helpers)"
1682+
\\new_webkit=""
1683+
\\for helper_pid in $webkit_after; do
1684+
\\ case " $webkit_before " in *" $helper_pid "*) ;; *) new_webkit="$new_webkit $helper_pid" ;; esac
1685+
\\done
1686+
\\if [ -n "$new_webkit" ]; then
1687+
\\ echo "native-only gpu-dashboard session spawned WebKit helper processes:$new_webkit" >&2
1688+
\\ for helper_pid in $new_webkit; do ps -p "$helper_pid" -o pid,ppid,command >&2 || true; done
1689+
\\ exit 1
1690+
\\fi
1691+
\\echo "zero new WebKit helper processes during the native-only session"
16281692
\\echo "gpu-dashboard smoke ok"
16291693
,
16301694
"sh",

build/app.zig

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
347347
// without its loader. Handing over the decision itself makes
348348
// exe/package agreement structural.
349349
package_run.addArgs(&.{ "--web-layer", if (web_layer) "include" else "exclude" });
350+
// Same reasoning for the web engine: the CLI defaults to system, so
351+
// a Chromium exe packaged without these flags would ship no CEF
352+
// runtime (the generated build graph already forwards them).
353+
package_run.addArgs(&.{ "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir });
354+
if (cef_auto_install) package_run.addArg("--cef-auto-install");
350355
package_run.has_side_effects = true;
351356
const package_step = b.step("package", "Create a distributable package via the native CLI");
352357
package_step.dependOn(&package_run.step);
@@ -537,11 +542,23 @@ fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Res
537542
if (web_engine == .chromium) app_mod.linkSystemLibrary("c++", .{});
538543
} else if (platform == .linux) {
539544
switch (web_engine) {
540-
.system => {
545+
.system => if (web_layer) {
541546
app_mod.addCSourceFile(.{ .file = dep.path("src/platform/linux/gtk_host.c"), .flags = &.{} });
542547
app_mod.linkSystemLibrary("gtk4", .{});
543548
app_mod.linkSystemLibrary("webkitgtk-6.0", .{});
544549
app_mod.linkSystemLibrary("dl", .{});
550+
} else {
551+
// Native-only app (nothing in app.zon declares web use):
552+
// compile the GTK host without the embedded web layer.
553+
// The stub define excludes the layer outright — the host
554+
// honors it before probing for the WebKitGTK header, so
555+
// the layer stays out even on machines where the
556+
// development package is installed — libwebkitgtk is
557+
// neither linked nor required at runtime, and the
558+
// executable carries no WebKit reference at all.
559+
app_mod.addCSourceFile(.{ .file = dep.path("src/platform/linux/gtk_host.c"), .flags = &.{"-DNATIVE_SDK_ALLOW_WEBKITGTK_STUB"} });
560+
app_mod.linkSystemLibrary("gtk4", .{});
561+
app_mod.linkSystemLibrary("dl", .{});
545562
},
546563
.chromium => {
547564
const cef_check = addCefCheck(b, target, cef_dir);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feature: **Native-only host builds (Linux)**: the WebKitGTK compile seam mirrors the Windows one — an app whose app.zon declares no web use compiles its GTK host with `NATIVE_SDK_ALLOW_WEBKITGTK_STUB`, so the executable neither links `webkitgtk-6.0` nor references any `webkit_`/`jsc_` symbol, building needs no WebKitGTK development package, and users need no `libwebkitgtk` at runtime. The web-layer auditor (`tools/audit_web_layer.zig`) grew a hand-rolled ELF reader (DT_NEEDED entries + dynamic symbols) that CI runs both ways: the native-only fixture must scan clean even with the dev package installed, and the Linux canvas smoke now builds on a runner without WebKitGTK at all. `native package` refuses to package a WebKitGTK-linking binary under a native-only decision, record→replay and automation-driven sessions are pinned on native-only apps, and the macOS GPU dashboard smoke asserts a native-only app spawns zero WebKit helper processes.

0 commit comments

Comments
 (0)