mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Merge pull request #1022 from lightpanda-io/slot
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Start working on HTMLSlotElement
This commit is contained in:
@@ -560,6 +560,17 @@ pub const Element = struct {
|
||||
state.shadow_root = sr;
|
||||
parser.documentFragmentSetHost(sr.proto, @ptrCast(@alignCast(self)));
|
||||
|
||||
// Storing the ShadowRoot on the element makes sense, it's the ShadowRoot's
|
||||
// parent. When we render, we go top-down, so we'll have the element, get
|
||||
// its shadowroot, and go on. that's what the above code does.
|
||||
// But we sometimes need to go bottom-up, e.g when we have a slot element
|
||||
// and want to find the containing parent. Unforatunately , we don't have
|
||||
// that link, so we need to create it. In the DOM, the ShadowRoot is
|
||||
// represented by this DocumentFragment (it's the ShadowRoot's base prototype)
|
||||
// So we can also store the ShadowRoot in the DocumentFragment's state.
|
||||
const fragment_state = try page.getOrCreateNodeState(@ptrCast(@alignCast(fragment)));
|
||||
fragment_state.shadow_root = sr;
|
||||
|
||||
return sr;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const DocumentFragment = @import("document_fragment.zig").DocumentFragment;
|
||||
const HTMLCollection = @import("html_collection.zig").HTMLCollection;
|
||||
const HTMLAllCollection = @import("html_collection.zig").HTMLAllCollection;
|
||||
const HTMLCollectionIterator = @import("html_collection.zig").HTMLCollectionIterator;
|
||||
const ShadowRoot = @import("shadow_root.zig").ShadowRoot;
|
||||
const Walker = @import("walker.zig").WalkerDepthFirst;
|
||||
|
||||
// HTML
|
||||
@@ -354,11 +355,23 @@ pub const Node = struct {
|
||||
// - An Element inside a standard web page will return an HTMLDocument object representing the entire page (or <iframe>).
|
||||
// - An Element inside a shadow DOM will return the associated ShadowRoot.
|
||||
// - An Element that is not attached to a document or a shadow tree will return the root of the DOM tree it belongs to
|
||||
pub fn _getRootNode(self: *parser.Node, options: ?struct { composed: bool = false }) !Union {
|
||||
const GetRootNodeResult = union(enum) {
|
||||
shadow_root: *ShadowRoot,
|
||||
node: Union,
|
||||
};
|
||||
pub fn _getRootNode(self: *parser.Node, options: ?struct { composed: bool = false }, page: *Page) !GetRootNodeResult {
|
||||
if (options) |options_| if (options_.composed) {
|
||||
log.warn(.web_api, "not implemented", .{ .feature = "getRootNode composed" });
|
||||
};
|
||||
return try Node.toInterface(try parser.nodeGetRootNode(self));
|
||||
|
||||
const root = try parser.nodeGetRootNode(self);
|
||||
if (page.getNodeState(root)) |state| {
|
||||
if (state.shadow_root) |sr| {
|
||||
return .{ .shadow_root = sr };
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .node = try Node.toInterface(root) };
|
||||
}
|
||||
|
||||
pub fn _hasChildNodes(self: *parser.Node) !bool {
|
||||
|
||||
@@ -26,6 +26,7 @@ const Page = @import("../page.zig").Page;
|
||||
const urlStitch = @import("../../url.zig").URL.stitch;
|
||||
const URL = @import("../url/url.zig").URL;
|
||||
const Node = @import("../dom/node.zig").Node;
|
||||
const NodeUnion = @import("../dom/node.zig").Union;
|
||||
const Element = @import("../dom/element.zig").Element;
|
||||
const DataSet = @import("DataSet.zig");
|
||||
|
||||
@@ -85,6 +86,7 @@ pub const Interfaces = .{
|
||||
HTMLScriptElement,
|
||||
HTMLSourceElement,
|
||||
HTMLSpanElement,
|
||||
HTMLSlotElement,
|
||||
HTMLStyleElement,
|
||||
HTMLTableElement,
|
||||
HTMLTableCaptionElement,
|
||||
@@ -1008,6 +1010,101 @@ pub const HTMLSpanElement = struct {
|
||||
pub const subtype = .node;
|
||||
};
|
||||
|
||||
pub const HTMLSlotElement = struct {
|
||||
pub const Self = parser.Slot;
|
||||
pub const prototype = *HTMLElement;
|
||||
pub const subtype = .node;
|
||||
|
||||
pub fn get_name(self: *parser.Slot) !?[]const u8 {
|
||||
return (try parser.elementGetAttribute(@ptrCast(@alignCast(self)), "name")) orelse "";
|
||||
}
|
||||
|
||||
pub fn set_name(self: *parser.Slot, value: []const u8) !void {
|
||||
return parser.elementSetAttribute(@ptrCast(@alignCast(self)), "name", value);
|
||||
}
|
||||
|
||||
const AssignedNodesOpts = struct {
|
||||
flatten: bool = false,
|
||||
};
|
||||
pub fn _assignedNodes(self: *parser.Slot, opts_: ?AssignedNodesOpts, page: *Page) ![]NodeUnion {
|
||||
const opts = opts_ orelse AssignedNodesOpts{ .flatten = false };
|
||||
|
||||
if (try findAssignedSlotNodes(self, opts, page)) |nodes| {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
if (!opts.flatten) {
|
||||
return &.{};
|
||||
}
|
||||
|
||||
const node: *parser.Node = @ptrCast(@alignCast(self));
|
||||
const nl = try parser.nodeGetChildNodes(node);
|
||||
const len = try parser.nodeListLength(nl);
|
||||
if (len == 0) {
|
||||
return &.{};
|
||||
}
|
||||
|
||||
var assigned = try page.call_arena.alloc(NodeUnion, len);
|
||||
var i: usize = 0;
|
||||
while (true) : (i += 1) {
|
||||
const child = try parser.nodeListItem(nl, @intCast(i)) orelse break;
|
||||
assigned[i] = try Node.toInterface(child);
|
||||
}
|
||||
return assigned[0..i];
|
||||
}
|
||||
|
||||
fn findAssignedSlotNodes(self: *parser.Slot, opts: AssignedNodesOpts, page: *Page) !?[]NodeUnion {
|
||||
if (opts.flatten) {
|
||||
log.warn(.web_api, "not implemented", .{ .feature = "HTMLSlotElement flatten assignedNodes" });
|
||||
}
|
||||
|
||||
const slot_name = try parser.elementGetAttribute(@ptrCast(@alignCast(self)), "name");
|
||||
const node: *parser.Node = @ptrCast(@alignCast(self));
|
||||
var root = try parser.nodeGetRootNode(node);
|
||||
if (page.getNodeState(root)) |state| {
|
||||
if (state.shadow_root) |sr| {
|
||||
root = @ptrCast(@alignCast(sr.host));
|
||||
}
|
||||
}
|
||||
|
||||
var arr: std.ArrayList(NodeUnion) = .empty;
|
||||
const w = @import("../dom/walker.zig").WalkerChildren{};
|
||||
var next: ?*parser.Node = null;
|
||||
while (true) {
|
||||
next = try w.get_next(root, next) orelse break;
|
||||
if (try parser.nodeType(next.?) != .element) {
|
||||
if (slot_name == null) {
|
||||
// default slot (with no name), takes everything
|
||||
try arr.append(page.call_arena, try Node.toInterface(next.?));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const el: *parser.Element = @ptrCast(@alignCast(next.?));
|
||||
const element_slot = try parser.elementGetAttribute(el, "slot");
|
||||
|
||||
if (nullableStringsAreEqual(slot_name, element_slot)) {
|
||||
// either they're the same string or they are both null
|
||||
try arr.append(page.call_arena, try Node.toInterface(next.?));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return if (arr.items.len == 0) null else arr.items;
|
||||
}
|
||||
|
||||
fn nullableStringsAreEqual(a: ?[]const u8, b: ?[]const u8) bool {
|
||||
if (a == null and b == null) {
|
||||
return true;
|
||||
}
|
||||
if (a) |aa| {
|
||||
const bb = b orelse return false;
|
||||
return std.mem.eql(u8, aa, bb);
|
||||
}
|
||||
|
||||
// a is null, but b isn't (else the first guard clause would have hit)
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
pub const HTMLStyleElement = struct {
|
||||
pub const Self = parser.Style;
|
||||
pub const prototype = *HTMLElement;
|
||||
@@ -1168,6 +1265,7 @@ pub fn toInterfaceFromTag(comptime T: type, e: *parser.Element, tag: parser.Tag)
|
||||
.select => .{ .HTMLSelectElement = @as(*parser.Select, @ptrCast(e)) },
|
||||
.source => .{ .HTMLSourceElement = @as(*parser.Source, @ptrCast(e)) },
|
||||
.span => .{ .HTMLSpanElement = @as(*parser.Span, @ptrCast(e)) },
|
||||
.slot => .{ .HTMLSlotElement = @as(*parser.Slot, @ptrCast(e)) },
|
||||
.style => .{ .HTMLStyleElement = @as(*parser.Style, @ptrCast(e)) },
|
||||
.table => .{ .HTMLTableElement = @as(*parser.Table, @ptrCast(e)) },
|
||||
.caption => .{ .HTMLTableCaptionElement = @as(*parser.TableCaption, @ptrCast(e)) },
|
||||
@@ -1484,6 +1582,10 @@ test "Browser: HTML.HTMLScriptElement" {
|
||||
try testing.htmlRunner("html/script/inline_defer.html");
|
||||
}
|
||||
|
||||
test "Browser: HTML.HTMLSlotElement" {
|
||||
try testing.htmlRunner("html/html_slot_element.html");
|
||||
}
|
||||
|
||||
const Check = struct {
|
||||
input: []const u8,
|
||||
expected: ?[]const u8 = null, // Needed when input != expected
|
||||
|
||||
@@ -259,6 +259,7 @@ pub const Tag = enum(u8) {
|
||||
_var = c.DOM_HTML_ELEMENT_TYPE_VAR,
|
||||
video = c.DOM_HTML_ELEMENT_TYPE_VIDEO,
|
||||
wbr = c.DOM_HTML_ELEMENT_TYPE_WBR,
|
||||
slot = c.DOM_HTML_ELEMENT_TYPE_SLOT,
|
||||
undef = c.DOM_HTML_ELEMENT_TYPE__UNKNOWN,
|
||||
|
||||
pub fn all() []Tag {
|
||||
@@ -1973,6 +1974,7 @@ pub const Script = c.dom_html_script_element;
|
||||
pub const Select = c.dom_html_select_element;
|
||||
pub const Source = struct { base: *c.dom_html_element };
|
||||
pub const Span = struct { base: *c.dom_html_element };
|
||||
pub const Slot = c.dom_html_slot_element;
|
||||
pub const Style = c.dom_html_style_element;
|
||||
pub const Table = c.dom_html_table_element;
|
||||
pub const TableCaption = c.dom_html_table_caption_element;
|
||||
|
||||
66
src/tests/html/html_slot_element.html
Normal file
66
src/tests/html/html_slot_element.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<script src="../testing.js"></script>
|
||||
<script>
|
||||
class LightPanda extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
|
||||
const slot1 = document.createElement('slot');
|
||||
slot1.name = 'slot-1';
|
||||
shadow.appendChild(slot1);
|
||||
|
||||
switch (this.getAttribute('mode')) {
|
||||
case '1':
|
||||
slot1.innerHTML = 'hello';
|
||||
break;
|
||||
case '2':
|
||||
const slot2 = document.createElement('slot');
|
||||
shadow.appendChild(slot2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.customElements.define("lp-test", LightPanda);
|
||||
</script>
|
||||
|
||||
<lp-test id=lp1 mode=1></lp-test>
|
||||
<lp-test id=lp2 mode=0></lp-test>
|
||||
<lp-test id=lp3 mode=0>default</lp-test>
|
||||
<lp-test id=lp4 mode=1><p slot=other>default</p></lp-test>
|
||||
<lp-test id=lp5 mode=1><p slot=slot-1>default</p> xx <b slot=slot-1>other</b></lp-test>
|
||||
<lp-test id=lp6 mode=2>More <p slot=slot-1>default2</p> <span>!!</span></lp-test>
|
||||
|
||||
<script id=HTMLSlotElement>
|
||||
function assertNodes(expected, actual) {
|
||||
actual = actual.map((n) => n.id || n.textContent)
|
||||
testing.expectEqual(expected, actual);
|
||||
}
|
||||
|
||||
for (let idx of [1, 2, 3, 4]) {
|
||||
const lp = $(`#lp${idx}`);
|
||||
const slot = lp.shadowRoot.querySelector('slot');
|
||||
|
||||
assertNodes([], slot.assignedNodes());
|
||||
assertNodes([], slot.assignedNodes({}));
|
||||
assertNodes([], slot.assignedNodes({flatten: false}));
|
||||
if (lp.getAttribute('mode') === '1') {
|
||||
assertNodes(['hello'], slot.assignedNodes({flatten: true}));
|
||||
} else {
|
||||
assertNodes([], slot.assignedNodes({flatten: true}));
|
||||
}
|
||||
}
|
||||
|
||||
const lp5 = $('#lp5');
|
||||
const s5 = lp5.shadowRoot.querySelector('slot');
|
||||
assertNodes(['default', 'other'], s5.assignedNodes());
|
||||
|
||||
const lp6 = $('#lp6');
|
||||
const s6 = lp6.shadowRoot.querySelectorAll('slot');
|
||||
assertNodes(['default2'], s6[0].assignedNodes({}));
|
||||
assertNodes(['default2'], s6[0].assignedNodes({flatten: true}));
|
||||
assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({}));
|
||||
assertNodes(['More ', ' ', '!!'], s6[1].assignedNodes({flatten: true}));
|
||||
</script>
|
||||
Reference in New Issue
Block a user