From a59ddeb3600390395a5be07a34ff4ca7ef50cc30 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 14 May 2026 19:57:56 +0800 Subject: [PATCH 1/5] Dump using the latest Frame to prevent segfault during on frame change Fixes: https://github.com/lightpanda-io/browser/issues/2446 --- src/lightpanda.zig | 95 +++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/lightpanda.zig b/src/lightpanda.zig index ea1f8992..835be762 100644 --- a/src/lightpanda.zig +++ b/src/lightpanda.zig @@ -80,52 +80,55 @@ pub fn fetch(app: *App, browser: *Browser, url: [:0]const u8, opts: FetchOpts) ! // Stash scripts user want to inject. session.inject_scripts = opts.inject_script.items; - const frame = try session.createPage(); + { + const frame = try session.createPage(); + // frame isn't safe to use after navigate, it can be swapped out - // // Comment this out to get a profile of the JS code in v8/profile.json. - // // You can open this in Chrome's profiler. - // // I've seen it generate invalid JSON, but I'm not sure why. It - // // happens rarely, and I manually fix the file. - // frame.js.startCpuProfiler(); - // defer { - // if (frame.js.stopCpuProfiler()) |profile| { - // std.fs.cwd().writeFile(.{ - // .sub_path = ".lp-cache/cpu_profile.json", - // .data = profile, - // }) catch |err| { - // log.err(.app, "profile write error", .{ .err = err }); - // }; - // } else |err| { - // log.err(.app, "profile error", .{ .err = err }); - // } - // } + // // Comment this out to get a profile of the JS code in v8/profile.json. + // // You can open this in Chrome's profiler. + // // I've seen it generate invalid JSON, but I'm not sure why. It + // // happens rarely, and I manually fix the file. + // frame.js.startCpuProfiler(); + // defer { + // if (frame.js.stopCpuProfiler()) |profile| { + // std.fs.cwd().writeFile(.{ + // .sub_path = ".lp-cache/cpu_profile.json", + // .data = profile, + // }) catch |err| { + // log.err(.app, "profile write error", .{ .err = err }); + // }; + // } else |err| { + // log.err(.app, "profile error", .{ .err = err }); + // } + // } - // // Comment this out to get a heap V8 heap profil - // frame.js.startHeapProfiler(); - // defer { - // if (frame.js.stopHeapProfiler()) |profile| { - // std.fs.cwd().writeFile(.{ - // .sub_path = ".lp-cache/allocating.heapprofile", - // .data = profile.@"0", - // }) catch |err| { - // log.err(.app, "allocating write error", .{ .err = err }); - // }; - // std.fs.cwd().writeFile(.{ - // .sub_path = ".lp-cache/snapshot.heapsnapshot", - // .data = profile.@"1", - // }) catch |err| { - // log.err(.app, "heapsnapshot write error", .{ .err = err }); - // }; - // } else |err| { - // log.err(.app, "profile error", .{ .err = err }); - // } - // } + // // Comment this out to get a heap V8 heap profil + // frame.js.startHeapProfiler(); + // defer { + // if (frame.js.stopHeapProfiler()) |profile| { + // std.fs.cwd().writeFile(.{ + // .sub_path = ".lp-cache/allocating.heapprofile", + // .data = profile.@"0", + // }) catch |err| { + // log.err(.app, "allocating write error", .{ .err = err }); + // }; + // std.fs.cwd().writeFile(.{ + // .sub_path = ".lp-cache/snapshot.heapsnapshot", + // .data = profile.@"1", + // }) catch |err| { + // log.err(.app, "heapsnapshot write error", .{ .err = err }); + // }; + // } else |err| { + // log.err(.app, "profile error", .{ .err = err }); + // } + // } - const encoded_url = try URL.ensureEncoded(frame.call_arena, url, "UTF-8"); - _ = try frame.navigate(encoded_url, .{ - .reason = .address_bar, - .kind = .{ .push = null }, - }); + const encoded_url = try URL.ensureEncoded(frame.call_arena, url, "UTF-8"); + _ = try frame.navigate(encoded_url, .{ + .reason = .address_bar, + .kind = .{ .push = null }, + }); + } var runner = try session.runner(.{}); var timer = try std.time.Timer.start(); @@ -154,7 +157,11 @@ pub fn fetch(app: *App, browser: *Browser, url: [:0]const u8, opts: FetchOpts) ! } const writer = opts.writer orelse return; - if (opts.dump_mode) |mode| { + if (opts.dump_mode) |mode| blk: { + const frame = session.currentFrame() orelse { + try writer.writeAll("Frame closed. Please open a bug report including the URL\n"); + break :blk; + }; switch (mode) { .html => try dump.root(frame.window._document, opts.dump, writer, frame), .markdown => try markdown.dump(frame.window._document.asNode(), .{}, writer, frame), From 940976b6a78dd144c0bae8e8f33a06ca013c425a Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 13 May 2026 22:09:15 -0700 Subject: [PATCH 2/5] properly disable cache on Network.setCacheDisabled --- src/cdp/domains/network.zig | 52 +++++++++++++++++++++++++++++++- src/network/layer/CacheLayer.zig | 3 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index 62b170a4..8a36141e 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -55,7 +55,7 @@ pub fn processMessage(cmd: *CDP.Command) !void { switch (action) { .enable => return enable(cmd), .disable => return disable(cmd), - .setCacheDisabled => return cmd.sendResult(null, .{}), + .setCacheDisabled => return setCacheDisabled(cmd), .setUserAgentOverride => return @import("emulation.zig").setUserAgentOverride(cmd), .setExtraHTTPHeaders => return setExtraHTTPHeaders(cmd), .deleteCookies => return deleteCookies(cmd), @@ -82,6 +82,17 @@ fn disable(cmd: *CDP.Command) !void { return cmd.sendResult(null, .{}); } +fn setCacheDisabled(cmd: *CDP.Command) !void { + const params = (try cmd.params(struct { + cacheDisabled: bool, + })) orelse return error.InvalidParams; + + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + const client = &bc.cdp.browser.http_client; + client.cache_layer.disabled = params.cacheDisabled; + return cmd.sendResult(null, .{}); +} + fn setExtraHTTPHeaders(cmd: *CDP.Command) !void { const params = (try cmd.params(struct { headers: std.json.ArrayHashMap([]const u8), @@ -734,3 +745,42 @@ test "cdp.Network: canClearBrowserCache" { // Cache is disabled in standard tests for now. try ctx.expectSentResult(.{ .result = false }, .{ .id = 1 }); } + +test "cdp.Network: setCacheDisabled disables cache" { + var ctx = try testing.context(); + defer ctx.deinit(); + _ = try ctx.loadBrowserContext(.{ .id = "BID-CD1" }); + + try ctx.processMessage(.{ + .id = 1, + .method = "Network.setCacheDisabled", + .params = .{ .cacheDisabled = true }, + }); + try ctx.expectSentResult(null, .{ .id = 1 }); + + const client = ctx.cdp().browser.http_client; + try testing.expectEqual(true, client.cache_layer.disabled); +} + +test "cdp.Network: setCacheDisabled re-enables cache" { + var ctx = try testing.context(); + defer ctx.deinit(); + _ = try ctx.loadBrowserContext(.{ .id = "BID-CD2" }); + + try ctx.processMessage(.{ + .id = 1, + .method = "Network.setCacheDisabled", + .params = .{ .cacheDisabled = true }, + }); + try ctx.expectSentResult(null, .{ .id = 1 }); + + try ctx.processMessage(.{ + .id = 2, + .method = "Network.setCacheDisabled", + .params = .{ .cacheDisabled = false }, + }); + try ctx.expectSentResult(null, .{ .id = 2 }); + + const client = ctx.cdp().browser.http_client; + try testing.expectEqual(false, client.cache_layer.disabled); +} diff --git a/src/network/layer/CacheLayer.zig b/src/network/layer/CacheLayer.zig index e7e50e05..a79ca331 100644 --- a/src/network/layer/CacheLayer.zig +++ b/src/network/layer/CacheLayer.zig @@ -36,6 +36,7 @@ const log = lp.log; const CacheLayer = @This(); next: Layer = undefined, +disabled: bool = false, pub fn layer(self: *CacheLayer) Layer { return .{ @@ -50,7 +51,7 @@ fn request(ptr: *anyopaque, transfer: *Transfer) anyerror!void { const self: *CacheLayer = @ptrCast(@alignCast(ptr)); const req = &transfer.req; - if (req.params.method != .GET) { + if (self.disabled or req.params.method != .GET) { return self.next.request(transfer); } From 6d1740b40f5d4821b454269f6f6e60eb79f06a1b Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Thu, 14 May 2026 16:19:36 -0400 Subject: [PATCH 3/5] Surface at-rules through insertRule and replaceSync (fixes #2459) CSSStyleSheet.insertRule previously detected at-rules in the parser (has_skipped_at_rule) and returned the requested index without inserting anything. The original change (PR #1972) did this to keep at-rule input from killing module evaluation in apps like Expo Web -- correct, but the silent-success contract has a second-order effect: CSS-in-JS libraries (emotion, styled-components, Stitches, Mantine, Linaria) that round-trip through cssRules to deduplicate their stylesheets see the rule as missing after insertion, conclude the stylesheet is empty, and fall back to direct