mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 20:54:43 +00:00
Add getElementsByTagNameNS
Do what we can based on the ns and names that we currently have. Also also, allow element names with non-ascii characters. both changes are driven by WPT dom/nodes/case.html
This commit is contained in:
123
src/browser/tests/element/get_elements_by_tag_name_ns.html
Normal file
123
src/browser/tests/element/get_elements_by_tag_name_ns.html
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<div id="container" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<div id="div1">div1</div>
|
||||||
|
<p id="p1">p1</p>
|
||||||
|
<div id="div2">div2</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svg id="svgContainer" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||||
|
<circle id="circle1" cx="50" cy="50" r="40"/>
|
||||||
|
<rect id="rect1" x="10" y="10" width="30" height="30"/>
|
||||||
|
<circle id="circle2" cx="25" cy="25" r="10"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div id="mixed">
|
||||||
|
<div id="htmlDiv" xmlns="http://www.w3.org/1999/xhtml">HTML div</div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle id="svgCircle" cx="10" cy="10" r="5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id=basic>
|
||||||
|
{
|
||||||
|
const htmlNS = "http://www.w3.org/1999/xhtml";
|
||||||
|
const svgNS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
// Test HTML namespace
|
||||||
|
const htmlDivs = document.getElementsByTagNameNS(htmlNS, 'div');
|
||||||
|
testing.expectEqual(true, htmlDivs instanceof HTMLCollection);
|
||||||
|
testing.expectEqual(5, htmlDivs.length); // container, div1, div2, mixed, htmlDiv
|
||||||
|
|
||||||
|
const htmlPs = document.getElementsByTagNameNS(htmlNS, 'p');
|
||||||
|
testing.expectEqual(1, htmlPs.length);
|
||||||
|
testing.expectEqual('p1', htmlPs[0].id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=svgNamespace>
|
||||||
|
{
|
||||||
|
const svgNS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
const circles = document.getElementsByTagNameNS(svgNS, 'circle');
|
||||||
|
testing.expectEqual(3, circles.length); // circle1, circle2, svgCircle
|
||||||
|
testing.expectEqual('circle1', circles[0].id);
|
||||||
|
testing.expectEqual('circle2', circles[1].id);
|
||||||
|
testing.expectEqual('svgCircle', circles[2].id);
|
||||||
|
|
||||||
|
const rects = document.getElementsByTagNameNS(svgNS, 'rect');
|
||||||
|
testing.expectEqual(1, rects.length);
|
||||||
|
testing.expectEqual('rect1', rects[0].id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=nullNamespace>
|
||||||
|
{
|
||||||
|
// Null namespace should match elements with null namespace
|
||||||
|
const nullNsElements = document.getElementsByTagNameNS(null, 'div');
|
||||||
|
testing.expectEqual(0, nullNsElements.length); // Our divs are in HTML namespace
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=wildcardNamespace>
|
||||||
|
{
|
||||||
|
// Wildcard namespace "*" should match all namespaces
|
||||||
|
const allDivs = document.getElementsByTagNameNS('*', 'div');
|
||||||
|
testing.expectEqual(5, allDivs.length); // All divs regardless of namespace
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=wildcardLocalName>
|
||||||
|
{
|
||||||
|
const htmlNS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
// Wildcard local name should match all elements in that namespace
|
||||||
|
const allHtmlElements = document.getElementsByTagNameNS(htmlNS, '*');
|
||||||
|
testing.expectEqual(true, allHtmlElements.length > 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=caseSensitive>
|
||||||
|
{
|
||||||
|
const htmlNS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
// getElementsByTagNameNS is case-sensitive for local names
|
||||||
|
const lowerDivs = document.getElementsByTagNameNS(htmlNS, 'div');
|
||||||
|
const upperDivs = document.getElementsByTagNameNS(htmlNS, 'DIV');
|
||||||
|
|
||||||
|
testing.expectEqual(5, lowerDivs.length);
|
||||||
|
testing.expectEqual(0, upperDivs.length); // Should be 0 because it's case-sensitive
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=unknownNamespace>
|
||||||
|
{
|
||||||
|
// Unknown namespace should still work
|
||||||
|
const unknownNs = document.getElementsByTagNameNS('http://example.com/unknown', 'div');
|
||||||
|
testing.expectEqual(0, unknownNs.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=emptyResult>
|
||||||
|
{
|
||||||
|
const htmlNS = "http://www.w3.org/1999/xhtml";
|
||||||
|
const svgNS = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
testing.expectEqual(0, document.getElementsByTagNameNS(htmlNS, 'nonexistent').length);
|
||||||
|
testing.expectEqual(0, document.getElementsByTagNameNS(svgNS, 'nonexistent').length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=elementMethod>
|
||||||
|
{
|
||||||
|
const htmlNS = "http://www.w3.org/1999/xhtml";
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
|
||||||
|
// getElementsByTagNameNS on element should only search descendants
|
||||||
|
const divsInContainer = container.getElementsByTagNameNS(htmlNS, 'div');
|
||||||
|
testing.expectEqual(2, divsInContainer.length); // div1, div2 (not container itself)
|
||||||
|
testing.expectEqual('div1', divsInContainer[0].id);
|
||||||
|
testing.expectEqual('div2', divsInContainer[1].id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -249,6 +249,23 @@ pub fn getElementsByTagName(self: *Document, tag_name: []const u8, page: *Page)
|
|||||||
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
|
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getElementsByTagNameNS(self: *Document, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
|
||||||
|
if (local_name.len > 256) {
|
||||||
|
return error.InvalidTagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse namespace - "*" means wildcard (null), null means Element.Namespace.null
|
||||||
|
const ns: ?Element.Namespace = if (namespace) |ns_str|
|
||||||
|
if (std.mem.eql(u8, ns_str, "*")) null else Element.Namespace.parse(ns_str)
|
||||||
|
else
|
||||||
|
Element.Namespace.null;
|
||||||
|
|
||||||
|
return collections.NodeLive(.tag_name_ns).init(self.asNode(), .{
|
||||||
|
.namespace = ns,
|
||||||
|
.local_name = try String.init(page.arena, local_name, .{}),
|
||||||
|
}, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
||||||
const arena = page.arena;
|
const arena = page.arena;
|
||||||
|
|
||||||
@@ -914,7 +931,8 @@ fn validateElementName(name: []const u8) !void {
|
|||||||
const is_valid = (c >= 'a' and c <= 'z') or
|
const is_valid = (c >= 'a' and c <= 'z') or
|
||||||
(c >= 'A' and c <= 'Z') or
|
(c >= 'A' and c <= 'Z') or
|
||||||
(c >= '0' and c <= '9') or
|
(c >= '0' and c <= '9') or
|
||||||
c == '_' or c == '-' or c == '.' or c == ':';
|
c == '_' or c == '-' or c == '.' or c == ':' or
|
||||||
|
c >= 128; // Allow non-ASCII UTF-8
|
||||||
|
|
||||||
if (!is_valid) {
|
if (!is_valid) {
|
||||||
return error.InvalidCharacterError;
|
return error.InvalidCharacterError;
|
||||||
@@ -984,6 +1002,7 @@ pub const JsApi = struct {
|
|||||||
pub const querySelector = bridge.function(Document.querySelector, .{ .dom_exception = true });
|
pub const querySelector = bridge.function(Document.querySelector, .{ .dom_exception = true });
|
||||||
pub const querySelectorAll = bridge.function(Document.querySelectorAll, .{ .dom_exception = true });
|
pub const querySelectorAll = bridge.function(Document.querySelectorAll, .{ .dom_exception = true });
|
||||||
pub const getElementsByTagName = bridge.function(Document.getElementsByTagName, .{});
|
pub const getElementsByTagName = bridge.function(Document.getElementsByTagName, .{});
|
||||||
|
pub const getElementsByTagNameNS = bridge.function(Document.getElementsByTagNameNS, .{});
|
||||||
pub const getSelection = bridge.function(Document.getSelection, .{});
|
pub const getSelection = bridge.function(Document.getSelection, .{});
|
||||||
pub const getElementsByClassName = bridge.function(Document.getElementsByClassName, .{});
|
pub const getElementsByClassName = bridge.function(Document.getElementsByClassName, .{});
|
||||||
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
||||||
|
|||||||
@@ -1135,6 +1135,23 @@ pub fn getElementsByTagName(self: *Element, tag_name: []const u8, page: *Page) !
|
|||||||
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
|
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getElementsByTagNameNS(self: *Element, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
|
||||||
|
if (local_name.len > 256) {
|
||||||
|
return error.InvalidTagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse namespace - "*" means wildcard (null), null means Namespace.null
|
||||||
|
const ns: ?Namespace = if (namespace) |ns_str|
|
||||||
|
if (std.mem.eql(u8, ns_str, "*")) null else Namespace.parse(ns_str)
|
||||||
|
else
|
||||||
|
Namespace.null;
|
||||||
|
|
||||||
|
return collections.NodeLive(.tag_name_ns).init(self.asNode(), .{
|
||||||
|
.namespace = ns,
|
||||||
|
.local_name = try String.init(page.arena, local_name, .{}),
|
||||||
|
}, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
||||||
const arena = page.arena;
|
const arena = page.arena;
|
||||||
|
|
||||||
@@ -1531,6 +1548,7 @@ pub const JsApi = struct {
|
|||||||
pub const getClientRects = bridge.function(Element.getClientRects, .{});
|
pub const getClientRects = bridge.function(Element.getClientRects, .{});
|
||||||
pub const getBoundingClientRect = bridge.function(Element.getBoundingClientRect, .{});
|
pub const getBoundingClientRect = bridge.function(Element.getBoundingClientRect, .{});
|
||||||
pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{});
|
pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{});
|
||||||
|
pub const getElementsByTagNameNS = bridge.function(Element.getElementsByTagNameNS, .{});
|
||||||
pub const getElementsByClassName = bridge.function(Element.getElementsByClassName, .{});
|
pub const getElementsByClassName = bridge.function(Element.getElementsByClassName, .{});
|
||||||
pub const children = bridge.accessor(Element.getChildren, null, .{});
|
pub const children = bridge.accessor(Element.getChildren, null, .{});
|
||||||
pub const focus = bridge.function(Element.focus, .{});
|
pub const focus = bridge.function(Element.focus, .{});
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const NodeLive = @import("node_live.zig").NodeLive;
|
|||||||
const Mode = enum {
|
const Mode = enum {
|
||||||
tag,
|
tag,
|
||||||
tag_name,
|
tag_name,
|
||||||
|
tag_name_ns,
|
||||||
class_name,
|
class_name,
|
||||||
all_elements,
|
all_elements,
|
||||||
child_elements,
|
child_elements,
|
||||||
@@ -42,6 +43,7 @@ const HTMLCollection = @This();
|
|||||||
_data: union(Mode) {
|
_data: union(Mode) {
|
||||||
tag: NodeLive(.tag),
|
tag: NodeLive(.tag),
|
||||||
tag_name: NodeLive(.tag_name),
|
tag_name: NodeLive(.tag_name),
|
||||||
|
tag_name_ns: NodeLive(.tag_name_ns),
|
||||||
class_name: NodeLive(.class_name),
|
class_name: NodeLive(.class_name),
|
||||||
all_elements: NodeLive(.all_elements),
|
all_elements: NodeLive(.all_elements),
|
||||||
child_elements: NodeLive(.child_elements),
|
child_elements: NodeLive(.child_elements),
|
||||||
@@ -76,6 +78,7 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
|
|||||||
.tw = switch (self._data) {
|
.tw = switch (self._data) {
|
||||||
.tag => |*impl| .{ .tag = impl._tw.clone() },
|
.tag => |*impl| .{ .tag = impl._tw.clone() },
|
||||||
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
||||||
|
.tag_name_ns => |*impl| .{ .tag_name_ns = impl._tw.clone() },
|
||||||
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
||||||
.all_elements => |*impl| .{ .all_elements = impl._tw.clone() },
|
.all_elements => |*impl| .{ .all_elements = impl._tw.clone() },
|
||||||
.child_elements => |*impl| .{ .child_elements = impl._tw.clone() },
|
.child_elements => |*impl| .{ .child_elements = impl._tw.clone() },
|
||||||
@@ -94,6 +97,7 @@ pub const Iterator = GenericIterator(struct {
|
|||||||
tw: union(Mode) {
|
tw: union(Mode) {
|
||||||
tag: TreeWalker.FullExcludeSelf,
|
tag: TreeWalker.FullExcludeSelf,
|
||||||
tag_name: TreeWalker.FullExcludeSelf,
|
tag_name: TreeWalker.FullExcludeSelf,
|
||||||
|
tag_name_ns: TreeWalker.FullExcludeSelf,
|
||||||
class_name: TreeWalker.FullExcludeSelf,
|
class_name: TreeWalker.FullExcludeSelf,
|
||||||
all_elements: TreeWalker.FullExcludeSelf,
|
all_elements: TreeWalker.FullExcludeSelf,
|
||||||
child_elements: TreeWalker.Children,
|
child_elements: TreeWalker.Children,
|
||||||
@@ -108,6 +112,7 @@ pub const Iterator = GenericIterator(struct {
|
|||||||
return switch (self.list._data) {
|
return switch (self.list._data) {
|
||||||
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
||||||
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
||||||
|
.tag_name_ns => |*impl| impl.nextTw(&self.tw.tag_name_ns),
|
||||||
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
||||||
.all_elements => |*impl| impl.nextTw(&self.tw.all_elements),
|
.all_elements => |*impl| impl.nextTw(&self.tw.all_elements),
|
||||||
.child_elements => |*impl| impl.nextTw(&self.tw.child_elements),
|
.child_elements => |*impl| impl.nextTw(&self.tw.child_elements),
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const Form = @import("../element/html/Form.zig");
|
|||||||
const Mode = enum {
|
const Mode = enum {
|
||||||
tag,
|
tag,
|
||||||
tag_name,
|
tag_name,
|
||||||
|
tag_name_ns,
|
||||||
class_name,
|
class_name,
|
||||||
name,
|
name,
|
||||||
all_elements,
|
all_elements,
|
||||||
@@ -44,9 +45,15 @@ const Mode = enum {
|
|||||||
form,
|
form,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const TagNameNsFilter = struct {
|
||||||
|
namespace: ?Element.Namespace, // null means wildcard "*"
|
||||||
|
local_name: String,
|
||||||
|
};
|
||||||
|
|
||||||
const Filters = union(Mode) {
|
const Filters = union(Mode) {
|
||||||
tag: Element.Tag,
|
tag: Element.Tag,
|
||||||
tag_name: String,
|
tag_name: String,
|
||||||
|
tag_name_ns: TagNameNsFilter,
|
||||||
class_name: [][]const u8,
|
class_name: [][]const u8,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
all_elements,
|
all_elements,
|
||||||
@@ -83,7 +90,7 @@ const Filters = union(Mode) {
|
|||||||
pub fn NodeLive(comptime mode: Mode) type {
|
pub fn NodeLive(comptime mode: Mode) type {
|
||||||
const Filter = Filters.TypeOf(mode);
|
const Filter = Filters.TypeOf(mode);
|
||||||
const TW = switch (mode) {
|
const TW = switch (mode) {
|
||||||
.tag, .tag_name, .class_name, .name, .all_elements, .links, .anchors, .form => TreeWalker.FullExcludeSelf,
|
.tag, .tag_name, .tag_name_ns, .class_name, .name, .all_elements, .links, .anchors, .form => TreeWalker.FullExcludeSelf,
|
||||||
.child_elements, .child_tag, .selected_options => TreeWalker.Children,
|
.child_elements, .child_tag, .selected_options => TreeWalker.Children,
|
||||||
};
|
};
|
||||||
return struct {
|
return struct {
|
||||||
@@ -222,6 +229,18 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
const element_tag = el.getTagNameLower();
|
const element_tag = el.getTagNameLower();
|
||||||
return std.mem.eql(u8, element_tag, self._filter.str());
|
return std.mem.eql(u8, element_tag, self._filter.str());
|
||||||
},
|
},
|
||||||
|
.tag_name_ns => {
|
||||||
|
const el = node.is(Element) orelse return false;
|
||||||
|
if (self._filter.namespace) |ns| {
|
||||||
|
if (el._namespace != ns) return false;
|
||||||
|
}
|
||||||
|
// ok, namespace matches, check local name
|
||||||
|
if (self._filter.local_name.eql(comptime .wrap("*"))) {
|
||||||
|
// wildcard, match-all
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return self._filter.local_name.eqlSlice(el.getLocalName());
|
||||||
|
},
|
||||||
.class_name => {
|
.class_name => {
|
||||||
if (self._filter.len == 0) {
|
if (self._filter.len == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -328,6 +347,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
.name => return page._factory.create(NodeList{ .data = .{ .name = self } }),
|
.name => return page._factory.create(NodeList{ .data = .{ .name = self } }),
|
||||||
.tag => HTMLCollection{ ._data = .{ .tag = self } },
|
.tag => HTMLCollection{ ._data = .{ .tag = self } },
|
||||||
.tag_name => HTMLCollection{ ._data = .{ .tag_name = self } },
|
.tag_name => HTMLCollection{ ._data = .{ .tag_name = self } },
|
||||||
|
.tag_name_ns => HTMLCollection{ ._data = .{ .tag_name_ns = self } },
|
||||||
.class_name => HTMLCollection{ ._data = .{ .class_name = self } },
|
.class_name => HTMLCollection{ ._data = .{ .class_name = self } },
|
||||||
.all_elements => HTMLCollection{ ._data = .{ .all_elements = self } },
|
.all_elements => HTMLCollection{ ._data = .{ .all_elements = self } },
|
||||||
.child_elements => HTMLCollection{ ._data = .{ .child_elements = self } },
|
.child_elements => HTMLCollection{ ._data = .{ .child_elements = self } },
|
||||||
|
|||||||
Reference in New Issue
Block a user