diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index f875d2e6..b05ddeea 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -576,7 +576,7 @@ pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !vo }; const parse_arena = try self.getArena(.medium, "Frame.parseBlob"); defer self.releaseArena(parse_arena); - var parser = Parser.init(parse_arena, self.document.asNode(), self); + var parser = Parser.init(parse_arena, self.document.asNode(), self, .{ .allow_declarative_shadow = true }); parser.parse(blob._slice); } else { self.document.injectBlank(self) catch |err| { @@ -1187,7 +1187,7 @@ fn frameDoneCallback(ctx: *anyopaque) !void { const parse_arena = try self.getArena(.medium, "Frame.parse"); defer self.releaseArena(parse_arena); - var parser = Parser.init(parse_arena, self.document.asNode(), self); + var parser = Parser.init(parse_arena, self.document.asNode(), self, .{ .allow_declarative_shadow = true }); switch (self._parse_state) { .html => |*html| { @@ -3563,11 +3563,20 @@ pub fn updateRangesForNodeRemoval(self: *Frame, parent: *Node, child: *Node, chi // TODO: optimize and cleanup, this is called a lot (e.g., innerHTML = '') pub fn parseHtmlAsChildren(self: *Frame, node: *Node, html: []const u8) !void { + return self.parseHtmlAsChildrenInner(node, html, false); +} + +// setHTMLUnsafe variant: parse a fragment that may contain declarative shadow node +pub fn parseHtmlUnsafeAsChildren(self: *Frame, node: *Node, html: []const u8) !void { + return self.parseHtmlAsChildrenInner(node, html, true); +} + +fn parseHtmlAsChildrenInner(self: *Frame, node: *Node, html: []const u8, allow_declarative_shadow: bool) !void { const previous_parse_mode = self._parse_mode; self._parse_mode = .fragment; defer self._parse_mode = previous_parse_mode; - var parser = Parser.init(self.call_arena, node, self); + var parser = Parser.init(self.call_arena, node, self, .{ .allow_declarative_shadow = allow_declarative_shadow }); parser.parseFragment(html); // html5ever wraps fragment output in an element; unwrap so its diff --git a/src/browser/markdown.zig b/src/browser/markdown.zig index 898608c8..66a1f89d 100644 --- a/src/browser/markdown.zig +++ b/src/browser/markdown.zig @@ -775,7 +775,7 @@ fn testMarkdownShadow(light: []const u8, shadow: []const u8, expected: []const u try frame.parseHtmlAsChildren(host.asNode(), light); } - const sr = try host.attachShadow("open", frame); + const sr = try host.attachShadow(comptime .wrap("open"), frame); try frame.parseHtmlAsChildren(sr.asNode(), shadow); var aw: std.Io.Writer.Allocating = .init(testing.allocator); @@ -814,3 +814,24 @@ test "browser.markdown: slot fallback content when nothing assigned" { \\Default text , "Default text\n"); } + +// End-to-end: a declarative shadow root (parsed via setHTMLUnsafe) is attached +// as a real shadow tree, and markdown's composed-tree piercing then renders it. +test "browser.markdown: declarative shadow DOM renders through piercing" { + const testing = @import("../testing.zig"); + const frame = try testing.test_session.createPage(); + defer testing.test_session.removePage(); + frame.url = "http://localhost/"; + + const doc = frame.window._document; + const host = try doc.createElement("div", null, frame); + try host.setHTMLUnsafe( + \\
+ , frame); + + var aw: std.Io.Writer.Allocating = .init(testing.allocator); + defer aw.deinit(); + try dump(host.asNode(), .{}, &aw.writer, frame); + + try testing.expectString("\nshadow content\n", aw.written()); +} diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index 5dcb8284..65d66e4a 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -71,7 +71,16 @@ pending_text: ?PendingText, // second chunk of a run, so the common case stays at one copy. buf: std.ArrayList(u8), -pub fn init(arena: Allocator, node: *Node, frame: *Frame) Parser { +// Whether `