mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
support element.relList and improve TreeWalker
This commit is contained in:
@@ -93,6 +93,7 @@ _attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attri
|
||||
_element_styles: Element.StyleLookup = .{},
|
||||
_element_datasets: Element.DatasetLookup = .{},
|
||||
_element_class_lists: Element.ClassListLookup = .{},
|
||||
_element_rel_lists: Element.RelListLookup = .{},
|
||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||
|
||||
@@ -263,6 +264,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
||||
self._element_styles = .{};
|
||||
self._element_datasets = .{};
|
||||
self._element_class_lists = .{};
|
||||
self._element_rel_lists = .{};
|
||||
self._element_shadow_roots = .{};
|
||||
self._element_assigned_slots = .{};
|
||||
self._notified_network_idle = .init;
|
||||
|
||||
@@ -41,6 +41,14 @@ pub fn isArray(self: Value) bool {
|
||||
return self.js_val.isArray();
|
||||
}
|
||||
|
||||
pub fn isNull(self: Value) bool {
|
||||
return self.js_val.isNull();
|
||||
}
|
||||
|
||||
pub fn isUndefined(self: Value) bool {
|
||||
return self.js_val.isUndefined();
|
||||
}
|
||||
|
||||
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
|
||||
return self.context.valueToString(self.js_val, .{ .allocator = allocator });
|
||||
}
|
||||
@@ -61,6 +69,10 @@ pub fn persist(self: Value) !Value {
|
||||
return Value{ .context = context, .js_val = persisted.toValue() };
|
||||
}
|
||||
|
||||
pub fn toZig(self: Value, comptime T: type) !T {
|
||||
return self.context.jsValueToZig(T, self.js_val);
|
||||
}
|
||||
|
||||
pub fn toObject(self: Value) js.Object {
|
||||
return .{
|
||||
.context = self.context,
|
||||
|
||||
@@ -79,25 +79,81 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
||||
|
||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
||||
var node = self._current.firstChild();
|
||||
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||
const filter_result = try self.acceptNode(n);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
node = self.nextSiblingOrNull(n);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_SKIP) {
|
||||
// Descend into children of this skipped node
|
||||
if (n.firstChild()) |child| {
|
||||
node = child;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// REJECT or SKIP with no children - find next sibling, walking up if necessary
|
||||
var current_node = n;
|
||||
while (true) {
|
||||
if (current_node.nextSibling()) |sibling| {
|
||||
node = sibling;
|
||||
break;
|
||||
}
|
||||
|
||||
// No sibling, go up to parent
|
||||
const parent = current_node._parent orelse return null;
|
||||
if (parent == self._current) {
|
||||
// We've exhausted all children of self._current
|
||||
return null;
|
||||
}
|
||||
current_node = parent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
||||
var node = self._current.lastChild();
|
||||
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||
const filter_result = try self.acceptNode(n);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
node = self.previousSiblingOrNull(n);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_SKIP) {
|
||||
// Descend into children of this skipped node
|
||||
if (n.lastChild()) |child| {
|
||||
node = child;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// REJECT or SKIP with no children - find previous sibling, walking up if necessary
|
||||
var current_node = n;
|
||||
while (true) {
|
||||
if (current_node.previousSibling()) |sibling| {
|
||||
node = sibling;
|
||||
break;
|
||||
}
|
||||
|
||||
// No sibling, go up to parent
|
||||
const parent = current_node._parent orelse return null;
|
||||
if (parent == self._current) {
|
||||
// We've exhausted all children of self._current
|
||||
return null;
|
||||
}
|
||||
current_node = parent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -131,15 +187,39 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
var sibling = self.previousSiblingOrNull(node);
|
||||
while (sibling) |sib| {
|
||||
node = sib;
|
||||
|
||||
// Check if this sibling is rejected before descending into it
|
||||
const sib_result = try self.acceptNode(node);
|
||||
if (sib_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this sibling and its descendants entirely
|
||||
sibling = self.previousSiblingOrNull(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Descend to the deepest last child, but respect FILTER_REJECT
|
||||
while (true) {
|
||||
var child = self.lastChildOrNull(node);
|
||||
|
||||
// Find the rightmost non-rejected child
|
||||
while (child) |c| {
|
||||
if (self.isInSubtree(c)) {
|
||||
node = c;
|
||||
child = self.lastChildOrNull(node);
|
||||
if (!self.isInSubtree(c)) break;
|
||||
|
||||
const filter_result = try self.acceptNode(c);
|
||||
if (filter_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this child and try its previous sibling
|
||||
child = self.previousSiblingOrNull(c);
|
||||
} else {
|
||||
// ACCEPT or SKIP - use this child
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (child == null) break; // No acceptable children
|
||||
|
||||
// Descend into this child
|
||||
node = child.?;
|
||||
}
|
||||
|
||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
|
||||
@@ -301,14 +301,26 @@ pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@i
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
||||
return DOMTreeWalker.init(root, show, filter, page);
|
||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||
return DOMTreeWalker.init(root, try whatToShow(what_to_show), filter, page);
|
||||
}
|
||||
|
||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
||||
return DOMNodeIterator.init(root, show, filter, page);
|
||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||
return DOMNodeIterator.init(root, try whatToShow(what_to_show), filter, page);
|
||||
}
|
||||
|
||||
fn whatToShow(value_: ?js.Value) !u32 {
|
||||
const value = value_ orelse return 4294967295; // show all when undefined
|
||||
if (value.isUndefined()) {
|
||||
// undefined explicitly passed
|
||||
return 4294967295;
|
||||
}
|
||||
|
||||
if (value.isNull()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value.toZig(u32);
|
||||
}
|
||||
|
||||
pub fn getReadyState(self: *const Document) []const u8 {
|
||||
|
||||
@@ -44,6 +44,7 @@ const Element = @This();
|
||||
pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
||||
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
||||
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
||||
pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
||||
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
||||
|
||||
@@ -550,6 +551,17 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||
const gop = try page._element_rel_lists.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
||||
._element = self,
|
||||
._attribute_name = "rel",
|
||||
});
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn getDataset(self: *Element, page: *Page) !*DOMStringMap {
|
||||
const gop = try page._element_datasets.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
|
||||
@@ -220,7 +220,18 @@ pub const JsApi = struct {
|
||||
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
|
||||
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
|
||||
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{});
|
||||
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });
|
||||
pub const toString = bridge.function(Anchor.getHref, .{});
|
||||
|
||||
fn _getRelList(self: *Anchor, page: *Page) !?*@import("../../collections.zig").DOMTokenList {
|
||||
const element = self.asElement();
|
||||
// relList is only valid for HTML and SVG <a> elements
|
||||
const namespace = element._namespace;
|
||||
if (namespace != .html and namespace != .svg) {
|
||||
return null;
|
||||
}
|
||||
return element.getRelList(page);
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
|
||||
@@ -68,6 +68,16 @@ pub const JsApi = struct {
|
||||
|
||||
pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{});
|
||||
pub const href = bridge.accessor(Link.getHref, Link.setHref, .{});
|
||||
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });
|
||||
|
||||
fn _getRelList(self: *Link, page: *Page) !?*@import("../../collections.zig").DOMTokenList {
|
||||
const element = self.asElement();
|
||||
// relList is only valid for HTML <link> elements, not SVG or MathML
|
||||
if (element._namespace != .html) {
|
||||
return null;
|
||||
}
|
||||
return element.getRelList(page);
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
|
||||
Reference in New Issue
Block a user