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 `` is parsed into a real shadow root.
+// True for document navigation, document.write, and setHTMLUnsafe; false for
+// innerHTML and DOMParser (per spec). Set from Options at init.
+allow_declarative_shadow: bool = false,
+
+pub const Options = struct {
+ allow_declarative_shadow: bool = false,
+};
+
+pub fn init(arena: Allocator, node: *Node, frame: *Frame, opts: Options) Parser {
return .{
.err = null,
.frame = frame,
@@ -83,6 +92,7 @@ pub fn init(arena: Allocator, node: *Node, frame: *Frame) Parser {
},
.pending_text = null,
.buf = .empty,
+ .allow_declarative_shadow = opts.allow_declarative_shadow,
};
}
@@ -157,6 +167,7 @@ const Error = struct {
reparent_children,
append_before_sibling,
append_based_on_parent_node,
+ attach_declarative_shadow,
};
};
@@ -180,6 +191,8 @@ pub fn parse(self: *Parser, html: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
+ attachDeclarativeShadowCallback,
+ self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -209,6 +222,8 @@ pub fn parseWithEncoding(self: *Parser, html: []const u8, charset: []const u8) v
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
+ attachDeclarativeShadowCallback,
+ self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -235,6 +250,8 @@ pub fn parseXML(self: *Parser, xml: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
+ attachDeclarativeShadowCallback,
+ false,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -261,6 +278,8 @@ pub fn parseFragment(self: *Parser, html: []const u8) void {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
+ attachDeclarativeShadowCallback,
+ self.allow_declarative_shadow,
);
self.flushPendingText() catch |err| {
if (self.err == null) self.err = .{ .err = err, .source = .append };
@@ -271,10 +290,10 @@ pub const Streaming = struct {
parser: Parser,
handle: ?*anyopaque,
- pub fn init(arena: Allocator, node: *Node, frame: *Frame) Streaming {
+ pub fn init(arena: Allocator, node: *Node, frame: *Frame, opts: Options) Streaming {
return .{
.handle = null,
- .parser = Parser.init(arena, node, frame),
+ .parser = Parser.init(arena, node, frame, opts),
};
}
@@ -304,6 +323,8 @@ pub const Streaming = struct {
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
+ attachDeclarativeShadowCallback,
+ self.parser.allow_declarative_shadow,
) orelse return error.ParserCreationFailed;
}
@@ -499,6 +520,34 @@ fn _getTemplateContentsCallback(self: *Parser, node: *Node) !*anyopaque {
return pn;
}
+// Called for `` when declarative shadow roots are
+// allowed. Attaches a shadow root to `host` and redirects the (stack-only)
+// template's contents into it, so html5ever parses the template's children
+// straight into the shadow root. Returns 1 on success, 0 to tell html5ever to
+// fall back to inserting the template as a normal light-DOM element.
+fn attachDeclarativeShadowCallback(ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8 {
+ const self: *Parser = @ptrCast(@alignCast(ctx));
+ return self._attachDeclarativeShadowCallback(getNode(host_ref), getNode(template_ref), mode_is_open != 0) catch |err| {
+ self.err = .{ .err = err, .source = .attach_declarative_shadow };
+ return 0;
+ };
+}
+
+fn _attachDeclarativeShadowCallback(self: *Parser, host_node: *Node, template_node: *Node, mode_is_open: bool) !u8 {
+ // guaranteed by html5ever
+ const host = host_node.as(Element);
+ const mode: lp.String = if (mode_is_open) comptime .wrap("open") else comptime .wrap("closed");
+ const shadow = host.attachShadow(mode, self.frame) catch |err| switch (err) {
+ // Expected per-spec fall-backs (host can't host a shadow, or already
+ // has one): keep the in the light DOM instead.
+ error.NotSupported => return 0,
+ else => return err,
+ };
+ const template = template_node.as(Element).is(Element.Html.Template) orelse return 0;
+ template._content = shadow.asDocumentFragment();
+ return 1;
+}
+
fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque {
const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
// For non-elements, data is null. But, we expect this to only ever
diff --git a/src/browser/parser/html5ever.zig b/src/browser/parser/html5ever.zig
index 829ac429..135e6c96 100644
--- a/src/browser/parser/html5ever.zig
+++ b/src/browser/parser/html5ever.zig
@@ -37,6 +37,8 @@ pub extern "c" fn html5ever_parse_document(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
+ attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
+ allow_declarative_shadow: bool,
) void;
/// Parse HTML document with encoding conversion. Converts from charset to UTF-8 before parsing.
@@ -61,6 +63,8 @@ pub extern "c" fn html5ever_parse_document_with_encoding(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
+ attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
+ allow_declarative_shadow: bool,
) void;
pub extern "c" fn html5ever_parse_fragment(
@@ -82,6 +86,8 @@ pub extern "c" fn html5ever_parse_fragment(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
+ attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
+ allow_declarative_shadow: bool,
) void;
pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute);
@@ -112,6 +118,8 @@ pub extern "c" fn html5ever_streaming_parser_create(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
+ attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
+ allow_declarative_shadow: bool,
) ?*anyopaque;
pub extern "c" fn html5ever_streaming_parser_feed(
@@ -215,6 +223,8 @@ pub extern "c" fn xml5ever_parse_document(
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
+ attachDeclarativeShadowCallback: *const fn (ctx: *anyopaque, host_ref: *anyopaque, template_ref: *anyopaque, mode_is_open: u8) callconv(.c) u8,
+ allow_declarative_shadow: bool,
) void;
// General encoding api
diff --git a/src/browser/tests/document/write.html b/src/browser/tests/document/write.html
index 7322882f..e079c92c 100644
--- a/src/browser/tests/document/write.html
+++ b/src/browser/tests/document/write.html
@@ -142,6 +142,23 @@
}
+
+
+
+
+
diff --git a/src/browser/tests/shadowroot/declarative.html b/src/browser/tests/shadowroot/declarative.html
new file mode 100644
index 00000000..91371a17
--- /dev/null
+++ b/src/browser/tests/shadowroot/declarative.html
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/CustomElementDefinition.zig b/src/browser/webapi/CustomElementDefinition.zig
index 2e60a8ac..6b3eab48 100644
--- a/src/browser/webapi/CustomElementDefinition.zig
+++ b/src/browser/webapi/CustomElementDefinition.zig
@@ -36,6 +36,9 @@ observed_attributes: std.StringHashMapUnmanaged(void) = .{},
// For autonomous custom elements, this is null
extends: ?Element.Tag = null,
+// when disabledFeatures = ["shadow"], we'll throw if attachShadow is called
+disable_shadow: bool = false,
+
pub fn isAttributeObserved(self: *const CustomElementDefinition, name: String) bool {
return self.observed_attributes.contains(name.str());
}
diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig
index 4cfbca3f..da15b369 100644
--- a/src/browser/webapi/CustomElementRegistry.zig
+++ b/src/browser/webapi/CustomElementRegistry.zig
@@ -81,6 +81,20 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
}
}
+ // Read disabledFeatures static property: ["shadow"] makes attachShadow throw.
+ if (constructor.getPropertyValue("disabledFeatures") catch null) |disabled| {
+ if (disabled.isArray()) {
+ var js_arr = disabled.toArray();
+ for (0..js_arr.len()) |i| {
+ const val = js_arr.get(@intCast(i)) catch continue;
+ const feature = val.toSSO(false) catch continue;
+ if (feature.eql(comptime .wrap("shadow"))) {
+ definition.disable_shadow = true;
+ }
+ }
+ }
+ }
+
gop.key_ptr.* = owned_name;
gop.value_ptr.* = definition;
diff --git a/src/browser/webapi/DOMParser.zig b/src/browser/webapi/DOMParser.zig
index 8ab2fb9d..c85b497d 100644
--- a/src/browser/webapi/DOMParser.zig
+++ b/src/browser/webapi/DOMParser.zig
@@ -77,7 +77,7 @@ pub fn parseFromString(
}
// Parse HTML into the document
- var parser = Parser.init(arena, doc.asNode(), frame);
+ var parser = Parser.init(arena, doc.asNode(), frame, .{});
parser.parse(normalized);
if (parser.err) |pe| {
@@ -94,13 +94,13 @@ pub fn parseFromString(
// Parse XML into XMLDocument.
const doc_node = doc.asNode();
- var parser = Parser.init(arena, doc_node, frame);
+ var parser = Parser.init(arena, doc_node, frame, .{});
parser.parseXML(html);
if (parser.err != null or doc_node.firstChild() == null) {
// Return a document with a element per spec.
const err_doc = try frame._factory.document(XMLDocument{ ._proto = undefined });
- var err_parser = Parser.init(arena, err_doc.asNode(), frame);
+ var err_parser = Parser.init(arena, err_doc.asNode(), frame, .{});
err_parser.parseXML("error");
return err_doc.asDocument();
}
diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig
index 0c9ab938..eff8a70b 100644
--- a/src/browser/webapi/Document.zig
+++ b/src/browser/webapi/Document.zig
@@ -786,7 +786,7 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
const arena = try frame.getArena(.medium, "Document.write");
defer frame.releaseArena(arena);
- var parser = Parser.init(arena, fragment_node, frame);
+ var parser = Parser.init(arena, fragment_node, frame, .{ .allow_declarative_shadow = true });
parser.parseFragment(html);
// Extract children from wrapper HTML element (html5ever wraps fragments)
@@ -873,7 +873,7 @@ pub fn open(self: *Document, call_frame: *Frame) !*Document {
self._implementation = null;
self._ready_state = .loading;
- self._script_created_parser = Parser.Streaming.init(frame.arena, doc_node, frame);
+ self._script_created_parser = Parser.Streaming.init(frame.arena, doc_node, frame, .{ .allow_declarative_shadow = true });
try self._script_created_parser.?.start();
frame._parse_mode = .document;
diff --git a/src/browser/webapi/DocumentFragment.zig b/src/browser/webapi/DocumentFragment.zig
index cc692649..324af261 100644
--- a/src/browser/webapi/DocumentFragment.zig
+++ b/src/browser/webapi/DocumentFragment.zig
@@ -153,18 +153,13 @@ pub fn getInnerHTML(self: *DocumentFragment, writer: *std.Io.Writer, frame: *Fra
pub fn setInnerHTML(self: *DocumentFragment, html: []const u8, frame: *Frame) !void {
const parent = self.asNode();
+ return parent.setHTML(html, false, frame);
+}
- frame.domChanged();
- var it = parent.childrenIterator();
- while (it.next()) |child| {
- frame.removeNode(parent, child, .{ .will_be_reconnected = false });
- }
-
- if (html.len == 0) {
- return;
- }
-
- try frame.parseHtmlAsChildren(parent, html);
+/// allows declarative shadow dom
+pub fn setHTMLUnsafe(self: *DocumentFragment, html: []const u8, frame: *Frame) !void {
+ const parent = self.asNode();
+ return parent.setHTML(html, true, frame);
}
pub fn cloneFragment(self: *DocumentFragment, deep: bool, frame: *Frame) !*Node {
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index 98eef93a..3a26798e 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -484,18 +484,13 @@ pub fn getInnerHTML(self: *Element, writer: *std.Io.Writer, frame: *Frame) !void
pub fn setInnerHTML(self: *Element, html: []const u8, frame: *Frame) !void {
const parent = self.asNode();
+ return parent.setHTML(html, false, frame);
+}
- frame.domChanged();
- var it = parent.childrenIterator();
- while (it.next()) |child| {
- frame.removeNode(parent, child, .{ .will_be_reconnected = false });
- }
-
- if (html.len == 0) {
- return;
- }
-
- try frame.parseHtmlAsChildren(parent, html);
+/// allows declarative shadow dom
+pub fn setHTMLUnsafe(self: *Element, html: []const u8, frame: *Frame) !void {
+ const parent = self.asNode();
+ return parent.setHTML(html, true, frame);
}
pub fn getId(self: *const Element) []const u8 {
@@ -726,11 +721,41 @@ pub fn getAssignedSlot(self: *Element, frame: *Frame) ?*Html.Slot {
return frame._element_assigned_slots.get(self);
}
-pub fn attachShadow(self: *Element, mode_str: []const u8, frame: *Frame) !*ShadowRoot {
- if (frame._element_shadow_roots.get(self)) |_| {
- return error.AlreadyHasShadowRoot;
+// Whether this element may host a shadow root
+fn isValidShadowHost(self: *const Element) bool {
+ if (self._namespace != .html) {
+ return false;
}
- const mode = try ShadowRoot.Mode.fromString(mode_str);
+
+ return switch (self.getTag()) {
+ .article, .aside, .blockquote, .body, .div, .footer, .header, .main, .nav, .p, .section, .span, .h1, .h2, .h3, .h4, .h5, .h6, .custom => true,
+ else => false,
+ };
+}
+
+pub fn attachShadow(self: *Element, mode_str: String, frame: *Frame) !*ShadowRoot {
+ if (frame._element_shadow_roots.get(self)) |_| {
+ return error.NotSupported;
+ }
+ if (!self.isValidShadowHost()) {
+ return error.NotSupported;
+ }
+
+ // A custom element whose definition lists "shadow" in disabledFeatures
+ // cannot host a shadow root (imperative or declarative).
+ if (self.is(Html.Custom)) |custom| {
+ if (frame.window._custom_elements._definitions.get(custom._tag_name.str())) |def| {
+ if (def.disable_shadow) {
+ return error.NotSupported;
+ }
+ }
+ }
+ const mode: ShadowRoot.Mode = blk: {
+ if (mode_str.eql(comptime .wrap("open"))) break :blk .open;
+ if (mode_str.eql(comptime .wrap("closed"))) break :blk .closed;
+ return error.InvalidArgument;
+ };
+
const shadow_root = try ShadowRoot.init(self, mode, frame);
try frame._element_shadow_roots.put(frame.arena, self, shadow_root);
return shadow_root;
@@ -1792,11 +1817,12 @@ pub const JsApi = struct {
pub const assignedSlot = bridge.accessor(Element.getAssignedSlot, null, .{});
pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true });
pub const insertAdjacentHTML = bridge.function(Element.insertAdjacentHTML, .{ .dom_exception = true, .ce_reactions = true });
+ pub const setHTMLUnsafe = bridge.function(Element.setHTMLUnsafe, .{ .dom_exception = true, .ce_reactions = true });
pub const insertAdjacentElement = bridge.function(Element.insertAdjacentElement, .{ .dom_exception = true, .ce_reactions = true });
pub const insertAdjacentText = bridge.function(Element.insertAdjacentText, .{ .dom_exception = true, .ce_reactions = true });
const ShadowRootInit = struct {
- mode: []const u8,
+ mode: String,
};
fn _attachShadow(self: *Element, init: ShadowRootInit, frame: *Frame) !*ShadowRoot {
return self.attachShadow(init.mode, frame);
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index 00779af0..c57913aa 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -1076,6 +1076,25 @@ pub fn replaceChildren(self: *Node, nodes: []const NodeOrText, frame: *Frame) !v
}
}
+/// Shared implementation in Element and DocumentFragment
+pub fn setHTML(self: *Node, html: []const u8, allow_declarative_shadow: bool, frame: *Frame) !void {
+ frame.domChanged();
+ var it = self.childrenIterator();
+ while (it.next()) |child| {
+ frame.removeNode(self, child, .{ .will_be_reconnected = false });
+ }
+
+ if (html.len == 0) {
+ return;
+ }
+
+ if (allow_declarative_shadow) {
+ try frame.parseHtmlUnsafeAsChildren(self, html);
+ } else {
+ try frame.parseHtmlAsChildren(self, html);
+ }
+}
+
// Writes a JSON representation of the node and its children
pub fn jsonStringify(self: *const Node, writer: *std.json.Stringify) !void {
// stupid json api requires this to be const,
diff --git a/src/browser/webapi/ShadowRoot.zig b/src/browser/webapi/ShadowRoot.zig
index 3af7ccfe..53e19289 100644
--- a/src/browser/webapi/ShadowRoot.zig
+++ b/src/browser/webapi/ShadowRoot.zig
@@ -29,10 +29,6 @@ const ShadowRoot = @This();
pub const Mode = enum {
open,
closed,
-
- pub fn fromString(str: []const u8) !Mode {
- return std.meta.stringToEnum(Mode, str) orelse error.InvalidMode;
- }
};
_proto: *DocumentFragment,
@@ -70,6 +66,10 @@ pub fn getHost(self: *const ShadowRoot) *Element {
return self._host;
}
+pub fn setHTMLUnsafe(self: *ShadowRoot, html: []const u8, frame: *Frame) !void {
+ return self.asDocumentFragment().setHTMLUnsafe(html, frame);
+}
+
pub fn getElementById(self: *ShadowRoot, id: []const u8, frame: *Frame) ?*Element {
if (id.len == 0) {
return null;
@@ -137,6 +137,7 @@ pub const JsApi = struct {
return self.getElementById(try value.toZig([]const u8), frame);
}
pub const adoptedStyleSheets = bridge.accessor(ShadowRoot.getAdoptedStyleSheets, ShadowRoot.setAdoptedStyleSheets, .{});
+ pub const setHTMLUnsafe = bridge.function(ShadowRoot.setHTMLUnsafe, .{ .dom_exception = true, .ce_reactions = true });
};
const testing = @import("../../testing.zig");
diff --git a/src/html5ever/lib.rs b/src/html5ever/lib.rs
index 63216a1d..ca8e74da 100644
--- a/src/html5ever/lib.rs
+++ b/src/html5ever/lib.rs
@@ -52,6 +52,8 @@ pub extern "C" fn html5ever_parse_document(
reparent_children_callback: ReparentChildrenCallback,
append_before_sibling_callback: AppendBeforeSiblingCallback,
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ allow_declarative_shadow: bool,
) -> () {
if html.is_null() || len == 0 {
return ();
@@ -78,6 +80,8 @@ pub extern "C" fn html5ever_parse_document(
reparent_children_callback: reparent_children_callback,
append_before_sibling_callback: append_before_sibling_callback,
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
+ attach_declarative_shadow_callback: attach_declarative_shadow_callback,
+ allow_declarative_shadow: allow_declarative_shadow,
};
let bytes = unsafe { std::slice::from_raw_parts(html, len) };
@@ -111,6 +115,8 @@ pub extern "C" fn html5ever_parse_document_with_encoding(
reparent_children_callback: ReparentChildrenCallback,
append_before_sibling_callback: AppendBeforeSiblingCallback,
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ allow_declarative_shadow: bool,
) -> () {
if html.is_null() || len == 0 {
return ();
@@ -148,6 +154,8 @@ pub extern "C" fn html5ever_parse_document_with_encoding(
reparent_children_callback: reparent_children_callback,
append_before_sibling_callback: append_before_sibling_callback,
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
+ attach_declarative_shadow_callback: attach_declarative_shadow_callback,
+ allow_declarative_shadow: allow_declarative_shadow,
};
// Parse directly from decoded string
@@ -472,6 +480,8 @@ pub extern "C" fn html5ever_parse_fragment(
reparent_children_callback: ReparentChildrenCallback,
append_before_sibling_callback: AppendBeforeSiblingCallback,
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ allow_declarative_shadow: bool,
) -> () {
if html.is_null() || len == 0 {
return ();
@@ -498,6 +508,8 @@ pub extern "C" fn html5ever_parse_fragment(
reparent_children_callback: reparent_children_callback,
append_before_sibling_callback: append_before_sibling_callback,
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
+ attach_declarative_shadow_callback: attach_declarative_shadow_callback,
+ allow_declarative_shadow: allow_declarative_shadow,
};
let bytes = unsafe { std::slice::from_raw_parts(html, len) };
@@ -587,6 +599,8 @@ pub extern "C" fn html5ever_streaming_parser_create(
reparent_children_callback: ReparentChildrenCallback,
append_before_sibling_callback: AppendBeforeSiblingCallback,
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ allow_declarative_shadow: bool,
) -> *mut c_void {
let arena = Box::new(typed_arena::Arena::new());
@@ -615,6 +629,8 @@ pub extern "C" fn html5ever_streaming_parser_create(
reparent_children_callback: reparent_children_callback,
append_before_sibling_callback: append_before_sibling_callback,
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
+ attach_declarative_shadow_callback: attach_declarative_shadow_callback,
+ allow_declarative_shadow: allow_declarative_shadow,
};
// Create a parser which implements TendrilSink for streaming parsing
@@ -716,6 +732,8 @@ pub extern "C" fn xml5ever_parse_document(
reparent_children_callback: ReparentChildrenCallback,
append_before_sibling_callback: AppendBeforeSiblingCallback,
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ allow_declarative_shadow: bool,
) -> () {
if xml.is_null() || len == 0 {
return ();
@@ -742,6 +760,8 @@ pub extern "C" fn xml5ever_parse_document(
reparent_children_callback: reparent_children_callback,
append_before_sibling_callback: append_before_sibling_callback,
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
+ attach_declarative_shadow_callback: attach_declarative_shadow_callback,
+ allow_declarative_shadow: allow_declarative_shadow,
};
let bytes = unsafe { std::slice::from_raw_parts(xml, len) };
diff --git a/src/html5ever/sink.rs b/src/html5ever/sink.rs
index 371fd260..b47d8d96 100644
--- a/src/html5ever/sink.rs
+++ b/src/html5ever/sink.rs
@@ -62,6 +62,8 @@ pub struct Sink<'arena> {
pub reparent_children_callback: ReparentChildrenCallback,
pub append_before_sibling_callback: AppendBeforeSiblingCallback,
pub append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
+ pub attach_declarative_shadow_callback: AttachDeclarativeShadowCallback,
+ pub allow_declarative_shadow: bool,
}
impl<'arena> TreeSink for Sink<'arena> {
@@ -286,4 +288,26 @@ impl<'arena> TreeSink for Sink<'arena> {
(self.reparent_children_callback)(self.ctx, *node, *new_parent);
}
}
+
+ fn allow_declarative_shadow_roots(&self, _intended_parent: &Ref) -> bool {
+ self.allow_declarative_shadow
+ }
+
+ fn attach_declarative_shadow(&self, location: &Ref, template: &Ref, attrs: &[Attribute]) -> bool {
+ // html5ever only calls this when shadowrootmode is "open" or "closed",
+ // so anything other than "open" is treated as "closed".
+ let mode_is_open = attrs
+ .iter()
+ .find(|a| a.name.local.as_ref() == "shadowrootmode")
+ .map(|a| a.value.as_ref() == "open")
+ .unwrap_or(true);
+ unsafe {
+ (self.attach_declarative_shadow_callback)(
+ self.ctx,
+ *location,
+ *template,
+ if mode_is_open { 1 } else { 0 },
+ ) != 0
+ }
+ }
}
diff --git a/src/html5ever/types.rs b/src/html5ever/types.rs
index 48ed9c3e..13eb76a4 100644
--- a/src/html5ever/types.rs
+++ b/src/html5ever/types.rs
@@ -65,6 +65,13 @@ pub type AddAttrsIfMissingCallback = unsafe extern "C" fn(
pub type GetTemplateContentsCallback = unsafe extern "C" fn(ctx: Ref, target: Ref) -> Ref;
+pub type AttachDeclarativeShadowCallback = unsafe extern "C" fn(
+ ctx: Ref,
+ host: Ref,
+ template: Ref,
+ mode_is_open: u8,
+) -> u8;
+
pub type RemoveFromParentCallback = unsafe extern "C" fn(ctx: Ref, target: Ref) -> ();
pub type ReparentChildrenCallback = unsafe extern "C" fn(ctx: Ref, node: Ref, new_parent: Ref) -> ();