Skip to content

Commit 9093dee

Browse files
committed
Clean up web targets and docs
1 parent 00724d6 commit 9093dee

4 files changed

Lines changed: 110 additions & 75 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ For example runs (`zig build run -Drun-mode=...`):
4444

4545
- `webview`: native webview first
4646
- `browser`: external browser app-window
47-
- `web-tab` (or `web`): browser tab
47+
- `web-tab`: browser tab
4848
- `web-url`: serve URL only; do not auto-open browser
4949
- Ordered combinations are supported, e.g. `webview,browser,web-url`
5050

@@ -54,7 +54,6 @@ Examples:
5454
zig build run -Dexample=minimal -Drun-mode=webview
5555
zig build run -Dexample=minimal -Drun-mode=browser
5656
zig build run -Dexample=minimal -Drun-mode=web-tab
57-
zig build run -Dexample=minimal -Drun-mode=web
5857
zig build run -Dexample=minimal -Drun-mode=webview,browser,web-url
5958
```
6059

build.zig

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ pub fn build(b: *Build) void {
145145
const minify_embedded_js = b.option(bool, "minify-embedded-js", "Minify embedded JS helpers with pure Zig asset processing (default: true)") orelse true;
146146
const minify_written_js = b.option(bool, "minify-written-js", "Minify written JS helper assets with pure Zig asset processing (default: false)") orelse false;
147147
const selected_example = b.option(ExampleChoice, "example", "Example to run with `zig build run` (default: all)") orelse .all;
148-
const run_mode = b.option([]const u8, "run-mode", "Runtime launch order for examples. Presets: `webview`, `browser` (app-window), `web-tab`, `web` (alias for web-tab), `web-url`. Or ordered tokens (`webview,browser,web-url`, `browser,webview`, etc). Default: webview,browser,web-url") orelse "webview,browser,web-url";
148+
const run_mode = b.option([]const u8, "run-mode", "Runtime launch order for examples. Presets: `webview`, `browser` (app-window), `web-tab`, `web-url`. Or ordered tokens (`webview,browser,web-url`, `browser,webview`, etc). Default: webview,browser,web-url") orelse "webview,browser,web-url";
149149
const linux_webview_target = b.option([]const u8, "linux-webview-target", "Linux native webview runtime target for examples: `webview` (WebKit2GTK 4.1/4.0, default) or `webkitgtk-6`") orelse "webview";
150150
if (!isValidRunMode(run_mode)) {
151-
@panic("invalid -Drun-mode value: use `webview`, `browser`, `web-tab`, `web`, `web-url`, or an ordered comma-separated combination");
151+
@panic("invalid -Drun-mode value: use `webview`, `browser`, `web-tab`, `web-url`, or an ordered comma-separated combination");
152152
}
153153
if (!isValidLinuxWebviewTarget(linux_webview_target)) {
154154
@panic("invalid -Dlinux-webview-target value: use `webview` or `webkitgtk-6`");
@@ -549,7 +549,6 @@ fn isValidRunMode(mode: []const u8) bool {
549549
if (std.mem.eql(u8, mode, "webview") or std.mem.eql(u8, mode, "browser") or std.mem.eql(u8, mode, "web-tab") or std.mem.eql(u8, mode, "web-url")) {
550550
return true;
551551
}
552-
if (std.mem.eql(u8, mode, "web")) return true;
553552

554553
var token_count: usize = 0;
555554
var seen_webview = false;
@@ -568,7 +567,7 @@ fn isValidRunMode(mode: []const u8) bool {
568567
seen_webview = true;
569568
continue;
570569
}
571-
if (std.mem.eql(u8, token, "browser") or std.mem.eql(u8, token, "web-tab") or std.mem.eql(u8, token, "web")) {
570+
if (std.mem.eql(u8, token, "browser") or std.mem.eql(u8, token, "web-tab")) {
572571
if (seen_browser_surface) return false;
573572
seen_browser_surface = true;
574573
continue;

examples/shared/demo_runner.zig

Lines changed: 78 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -58,45 +58,75 @@ pub const rpc_methods = struct {
5858
}
5959
};
6060

61+
const BrowserLaunchPreference = enum {
62+
auto,
63+
app_window,
64+
web_tab,
65+
};
66+
67+
const RunModeSpec = struct {
68+
launch_policy: webui.LaunchPolicy,
69+
browser_launch_preference: BrowserLaunchPreference,
70+
};
71+
6172
fn parseSurfaceToken(token: []const u8) ?webui.LaunchSurface {
6273
if (std.mem.eql(u8, token, "webview")) return .native_webview;
6374
if (std.mem.eql(u8, token, "browser")) return .browser_window;
64-
if (std.mem.eql(u8, token, "web-tab")) return .web_url;
65-
if (std.mem.eql(u8, token, "web")) return .web_url;
66-
if (std.mem.eql(u8, token, "web-url")) return .web_url;
75+
if (std.mem.eql(u8, token, "web-tab") or std.mem.eql(u8, token, "web-url")) return .web_url;
6776
return null;
6877
}
6978

70-
fn launchPolicyFromRunModeValue(mode: []const u8) webui.LaunchPolicy {
71-
if (std.mem.eql(u8, mode, "webview")) return .{
72-
.first = .native_webview,
73-
.second = null,
74-
.third = null,
75-
.allow_dual_surface = false,
76-
.app_mode_required = true,
77-
};
78-
if (std.mem.eql(u8, mode, "browser")) return .{
79-
.first = .browser_window,
80-
.second = null,
81-
.third = null,
82-
.allow_dual_surface = false,
83-
.app_mode_required = true,
79+
fn launchPolicyForSingleSurface(surface: webui.LaunchSurface) webui.LaunchPolicy {
80+
return switch (surface) {
81+
.native_webview => .{
82+
.first = .native_webview,
83+
.second = null,
84+
.third = null,
85+
.allow_dual_surface = false,
86+
.app_mode_required = true,
87+
},
88+
.browser_window => .{
89+
.first = .browser_window,
90+
.second = null,
91+
.third = null,
92+
.allow_dual_surface = false,
93+
.app_mode_required = true,
94+
},
95+
.web_url => webui.LaunchPolicy.webUrlOnly(),
8496
};
85-
if (std.mem.eql(u8, mode, "web-tab")) return webui.LaunchPolicy.webUrlOnly();
86-
if (std.mem.eql(u8, mode, "web")) return webui.LaunchPolicy.webUrlOnly();
87-
if (std.mem.eql(u8, mode, "web-url")) {
88-
return webui.LaunchPolicy.webUrlOnly();
97+
}
98+
99+
fn parseRunModeSpec(mode: []const u8) RunModeSpec {
100+
if (parseSurfaceToken(mode)) |single_surface| {
101+
return .{
102+
.launch_policy = launchPolicyForSingleSurface(single_surface),
103+
.browser_launch_preference = if (std.mem.eql(u8, mode, "browser"))
104+
.app_window
105+
else if (std.mem.eql(u8, mode, "web-tab"))
106+
.web_tab
107+
else
108+
.auto,
109+
};
89110
}
90111

91112
var surfaces: [3]?webui.LaunchSurface = .{ null, null, null };
92113
var count: usize = 0;
114+
var launch_pref: BrowserLaunchPreference = .auto;
93115

94116
var it = std.mem.tokenizeAny(u8, mode, ",> ");
95117
while (it.next()) |raw_token| {
96118
const token = std.mem.trim(u8, raw_token, " \t\r\n");
97119
if (token.len == 0) continue;
98120
const parsed = parseSurfaceToken(token) orelse continue;
99121

122+
if (launch_pref == .auto) {
123+
if (std.mem.eql(u8, token, "browser")) {
124+
launch_pref = .app_window;
125+
} else if (std.mem.eql(u8, token, "web-tab")) {
126+
launch_pref = .web_tab;
127+
}
128+
}
129+
100130
var exists = false;
101131
for (surfaces) |candidate| {
102132
if (candidate != null and candidate.? == parsed) {
@@ -110,7 +140,12 @@ fn launchPolicyFromRunModeValue(mode: []const u8) webui.LaunchPolicy {
110140
count += 1;
111141
}
112142

113-
if (count == 0) return webui.LaunchPolicy.webviewFirst();
143+
if (count == 0) {
144+
return .{
145+
.launch_policy = webui.LaunchPolicy.webviewFirst(),
146+
.browser_launch_preference = .auto,
147+
};
148+
}
114149

115150
var policy = webui.LaunchPolicy{
116151
.first = surfaces[0].?,
@@ -122,31 +157,12 @@ fn launchPolicyFromRunModeValue(mode: []const u8) webui.LaunchPolicy {
122157
const has_native = policy.first == .native_webview or
123158
(policy.second != null and policy.second.? == .native_webview) or
124159
(policy.third != null and policy.third.? == .native_webview);
125-
if (!has_native) {
126-
policy.app_mode_required = false;
127-
}
128-
return policy;
129-
}
160+
if (!has_native) policy.app_mode_required = false;
130161

131-
const BrowserLaunchPreference = enum {
132-
auto,
133-
app_window,
134-
web_tab,
135-
};
136-
137-
fn browserLaunchPreferenceFromRunMode(mode: []const u8) BrowserLaunchPreference {
138-
if (std.mem.eql(u8, mode, "browser")) return .app_window;
139-
if (std.mem.eql(u8, mode, "web-tab")) return .web_tab;
140-
if (std.mem.eql(u8, mode, "web")) return .web_tab;
141-
142-
var it = std.mem.tokenizeAny(u8, mode, ",> ");
143-
while (it.next()) |raw_token| {
144-
const token = std.mem.trim(u8, raw_token, " \t\r\n");
145-
if (token.len == 0) continue;
146-
if (std.mem.eql(u8, token, "browser")) return .app_window;
147-
if (std.mem.eql(u8, token, "web-tab") or std.mem.eql(u8, token, "web")) return .web_tab;
148-
}
149-
return .auto;
162+
return .{
163+
.launch_policy = policy,
164+
.browser_launch_preference = launch_pref,
165+
};
150166
}
151167

152168
fn surfaceName(surface: webui.LaunchSurface) []const u8 {
@@ -299,8 +315,9 @@ fn tagFor(comptime kind: ExampleKind) []const u8 {
299315

300316
fn appOptionsFor(comptime kind: ExampleKind) webui.AppOptions {
301317
const run_mode = webui.BuildFlags.run_mode;
302-
const launch_policy = launchPolicyFromRunModeValue(run_mode);
303-
const launch_pref = browserLaunchPreferenceFromRunMode(run_mode);
318+
const run_mode_spec = parseRunModeSpec(run_mode);
319+
const launch_policy = run_mode_spec.launch_policy;
320+
const launch_pref = run_mode_spec.browser_launch_preference;
304321
const native_first = launch_policy.first == .native_webview;
305322
var surface_mode: webui.BrowserSurfaceMode = if (native_first) .native_webview_host else .tab;
306323
var fallback_mode: webui.BrowserFallbackMode = if (native_first) .strict else .allow_system;
@@ -340,45 +357,43 @@ fn linuxWebViewTargetFromBuildFlag() webui.LinuxWebViewTarget {
340357
}
341358

342359
test "run-mode browser launch preference parser supports web-tab" {
343-
try std.testing.expectEqual(BrowserLaunchPreference.app_window, browserLaunchPreferenceFromRunMode("browser"));
344-
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, browserLaunchPreferenceFromRunMode("web-tab"));
345-
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, browserLaunchPreferenceFromRunMode("web"));
346-
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, browserLaunchPreferenceFromRunMode("webview,web-tab,web-url"));
347-
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, browserLaunchPreferenceFromRunMode("webview,web,web-url"));
348-
try std.testing.expectEqual(BrowserLaunchPreference.auto, browserLaunchPreferenceFromRunMode("web-url"));
360+
try std.testing.expectEqual(BrowserLaunchPreference.app_window, parseRunModeSpec("browser").browser_launch_preference);
361+
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, parseRunModeSpec("web-tab").browser_launch_preference);
362+
try std.testing.expectEqual(BrowserLaunchPreference.web_tab, parseRunModeSpec("webview,web-tab,web-url").browser_launch_preference);
363+
try std.testing.expectEqual(BrowserLaunchPreference.auto, parseRunModeSpec("web-url").browser_launch_preference);
349364
}
350365

351366
test "run-mode browser maps to browser-first launch policy" {
352-
const policy = launchPolicyFromRunModeValue("browser");
367+
const policy = parseRunModeSpec("browser").launch_policy;
353368
try std.testing.expectEqual(@as(webui.LaunchSurface, .browser_window), policy.first);
354369
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), policy.second);
355370
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), policy.third);
356371
try std.testing.expect(policy.app_mode_required);
357372
}
358373

359-
test "run-mode web alias maps to web-url only launch policy" {
360-
const policy = launchPolicyFromRunModeValue("web");
374+
test "run-mode web-url maps to web-url only launch policy" {
375+
const policy = parseRunModeSpec("web-url").launch_policy;
361376
try std.testing.expectEqual(@as(webui.LaunchSurface, .web_url), policy.first);
362377
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), policy.second);
363378
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), policy.third);
364379
try std.testing.expect(!policy.app_mode_required);
365380
}
366381

367382
test "single-token run-mode presets are strict with no fallback surfaces" {
368-
const webview_policy = launchPolicyFromRunModeValue("webview");
383+
const webview_policy = parseRunModeSpec("webview").launch_policy;
369384
try std.testing.expectEqual(@as(webui.LaunchSurface, .native_webview), webview_policy.first);
370385
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), webview_policy.second);
371386
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), webview_policy.third);
372387

373-
const browser_policy = launchPolicyFromRunModeValue("browser");
388+
const browser_policy = parseRunModeSpec("browser").launch_policy;
374389
try std.testing.expectEqual(@as(webui.LaunchSurface, .browser_window), browser_policy.first);
375390
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), browser_policy.second);
376391
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), browser_policy.third);
377392

378-
const web_policy = launchPolicyFromRunModeValue("web");
379-
try std.testing.expectEqual(@as(webui.LaunchSurface, .web_url), web_policy.first);
380-
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), web_policy.second);
381-
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), web_policy.third);
393+
const web_url_policy = parseRunModeSpec("web-url").launch_policy;
394+
try std.testing.expectEqual(@as(webui.LaunchSurface, .web_url), web_url_policy.first);
395+
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), web_url_policy.second);
396+
try std.testing.expectEqual(@as(?webui.LaunchSurface, null), web_url_policy.third);
382397
}
383398

384399
fn styleFor(comptime kind: ExampleKind) webui.WindowStyle {

src/backends/linux_webview_host.zig

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub const Host = struct {
5555
startup_done: bool = false,
5656
startup_error: ?anyerror = null,
5757
ui_ready: bool = false,
58+
drain_scheduled: bool = false,
5859
closed: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
5960
shutdown_requested: bool = false,
6061

@@ -184,9 +185,12 @@ pub const Host = struct {
184185
}
185186

186187
fn scheduleDrainLocked(self: *Host) void {
187-
if (!self.ui_ready) return;
188+
if (!self.ui_ready or self.drain_scheduled or self.queue.items.len == 0) return;
188189
if (self.symbols) |symbols| {
189-
_ = symbols.g_idle_add(&onIdleDrain, self);
190+
self.drain_scheduled = true;
191+
if (symbols.g_idle_add(&onIdleDrain, self) == 0) {
192+
self.drain_scheduled = false;
193+
}
190194
}
191195
}
192196
};
@@ -308,6 +312,7 @@ fn failStartup(host: *Host, err: anyerror) void {
308312
host.symbols = null;
309313
}
310314

315+
host.drain_scheduled = false;
311316
host.startup_error = err;
312317
host.startup_done = true;
313318
host.closed.store(true, .release);
@@ -320,6 +325,7 @@ fn finishThreadShutdown(host: *Host) void {
320325

321326
host.closed.store(true, .release);
322327
host.ui_ready = false;
328+
host.drain_scheduled = false;
323329
host.window_widget = null;
324330
host.content_widget = null;
325331
host.webview = null;
@@ -345,6 +351,12 @@ fn onIdleDrain(data: ?*anyopaque) callconv(.c) c_int {
345351
const host = data orelse return 0;
346352
const typed: *Host = @ptrCast(@alignCast(host));
347353
drainCommandsUiThread(typed);
354+
355+
typed.mutex.lock();
356+
typed.drain_scheduled = false;
357+
typed.scheduleDrainLocked();
358+
typed.mutex.unlock();
359+
348360
return 0;
349361
}
350362

@@ -399,18 +411,28 @@ fn onSizeAllocate(widget: ?*anyopaque, allocation: ?*anyopaque, data: ?*anyopaqu
399411
}
400412

401413
fn drainCommandsUiThread(host: *Host) void {
402-
// Drain one command at a time so command execution does not hold queue lock.
414+
// Swap queued work into a local batch so we keep lock hold-time short and
415+
// avoid O(n^2) ordered-removal when command bursts arrive.
416+
var batch = Queue.init(host.allocator);
417+
defer batch.deinit();
418+
403419
while (true) {
404420
host.mutex.lock();
405421
if (host.queue.items.len == 0) {
422+
if (host.queue.capacity == 0 and batch.capacity > 0) {
423+
std.mem.swap(Queue, &host.queue, &batch);
424+
}
406425
host.mutex.unlock();
407426
break;
408427
}
409-
var cmd = host.queue.orderedRemove(0);
428+
std.mem.swap(Queue, &host.queue, &batch);
410429
host.mutex.unlock();
411430

412-
executeUiCommand(host, &cmd);
413-
cmd.deinit(host.allocator);
431+
for (batch.items) |*cmd| {
432+
executeUiCommand(host, cmd);
433+
cmd.deinit(host.allocator);
434+
}
435+
batch.clearRetainingCapacity();
414436
}
415437
}
416438

0 commit comments

Comments
 (0)