mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Merge pull request #1325 from lightpanda-io/tokenlist_treewalker
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_styles: Element.StyleLookup = .{},
|
||||||
_element_datasets: Element.DatasetLookup = .{},
|
_element_datasets: Element.DatasetLookup = .{},
|
||||||
_element_class_lists: Element.ClassListLookup = .{},
|
_element_class_lists: Element.ClassListLookup = .{},
|
||||||
|
_element_rel_lists: Element.RelListLookup = .{},
|
||||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||||
|
|
||||||
@@ -263,6 +264,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._element_styles = .{};
|
self._element_styles = .{};
|
||||||
self._element_datasets = .{};
|
self._element_datasets = .{};
|
||||||
self._element_class_lists = .{};
|
self._element_class_lists = .{};
|
||||||
|
self._element_rel_lists = .{};
|
||||||
self._element_shadow_roots = .{};
|
self._element_shadow_roots = .{};
|
||||||
self._element_assigned_slots = .{};
|
self._element_assigned_slots = .{};
|
||||||
self._notified_network_idle = .init;
|
self._notified_network_idle = .init;
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ pub fn isArray(self: Value) bool {
|
|||||||
return self.js_val.isArray();
|
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 {
|
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
|
||||||
return self.context.valueToString(self.js_val, .{ .allocator = allocator });
|
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() };
|
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 {
|
pub fn toObject(self: Value) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.context = self.context,
|
.context = self.context,
|
||||||
|
|||||||
@@ -79,25 +79,81 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
|
|
||||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
||||||
var node = self._current.firstChild();
|
var node = self._current.firstChild();
|
||||||
|
|
||||||
while (node) |n| {
|
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;
|
self._current = n;
|
||||||
return 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
||||||
var node = self._current.lastChild();
|
var node = self._current.lastChild();
|
||||||
|
|
||||||
while (node) |n| {
|
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;
|
self._current = n;
|
||||||
return 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,15 +187,39 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
var sibling = self.previousSiblingOrNull(node);
|
var sibling = self.previousSiblingOrNull(node);
|
||||||
while (sibling) |sib| {
|
while (sibling) |sib| {
|
||||||
node = 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);
|
var child = self.lastChildOrNull(node);
|
||||||
|
|
||||||
|
// Find the rightmost non-rejected child
|
||||||
while (child) |c| {
|
while (child) |c| {
|
||||||
if (self.isInSubtree(c)) {
|
if (!self.isInSubtree(c)) break;
|
||||||
node = c;
|
|
||||||
child = self.lastChildOrNull(node);
|
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 {
|
} else {
|
||||||
|
// ACCEPT or SKIP - use this child
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (child == null) break; // No acceptable children
|
||||||
|
|
||||||
|
// Descend into this child
|
||||||
|
node = child.?;
|
||||||
|
}
|
||||||
|
|
||||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
|
|||||||
@@ -301,14 +301,26 @@ pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@i
|
|||||||
return error.NotSupported;
|
return error.NotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||||
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
return DOMTreeWalker.init(root, try whatToShow(what_to_show), filter, page);
|
||||||
return DOMTreeWalker.init(root, show, filter, page);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||||
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
return DOMNodeIterator.init(root, try whatToShow(what_to_show), filter, page);
|
||||||
return DOMNodeIterator.init(root, 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 {
|
pub fn getReadyState(self: *const Document) []const u8 {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const Element = @This();
|
|||||||
pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
||||||
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
||||||
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
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 ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||||
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
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.*;
|
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 {
|
pub fn getDataset(self: *Element, page: *Page) !*DOMStringMap {
|
||||||
const gop = try page._element_datasets.getOrPut(page.arena, self);
|
const gop = try page._element_datasets.getOrPut(page.arena, self);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
|
|||||||
@@ -220,7 +220,18 @@ pub const JsApi = struct {
|
|||||||
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
|
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
|
||||||
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
|
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
|
||||||
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{});
|
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, .{});
|
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");
|
const testing = @import("../../../../testing.zig");
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{});
|
pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{});
|
||||||
pub const href = bridge.accessor(Link.getHref, Link.setHref, .{});
|
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");
|
const testing = @import("../../../../testing.zig");
|
||||||
|
|||||||
Reference in New Issue
Block a user