mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #71 from Browsercore/domdoc-getelementsbyclassname
document.getElementsByClassName
This commit is contained in:
@@ -7,7 +7,8 @@ const Case = jsruntime.test_utils.Case;
|
|||||||
const checkCases = jsruntime.test_utils.checkCases;
|
const checkCases = jsruntime.test_utils.checkCases;
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
const HTMLCollection = @import("html_collection.zig").HTMLCollection;
|
|
||||||
|
const collection = @import("html_collection.zig");
|
||||||
|
|
||||||
const Element = @import("element.zig").Element;
|
const Element = @import("element.zig").Element;
|
||||||
const ElementUnion = @import("element.zig").Union;
|
const ElementUnion = @import("element.zig").Union;
|
||||||
@@ -49,12 +50,14 @@ pub const Document = struct {
|
|||||||
// the spec changed to return an HTMLCollection instead.
|
// the spec changed to return an HTMLCollection instead.
|
||||||
// That's why we reimplemented getElementsByTagName by using an
|
// That's why we reimplemented getElementsByTagName by using an
|
||||||
// HTMLCollection in zig here.
|
// HTMLCollection in zig here.
|
||||||
pub fn _getElementsByTagName(self: *parser.Document, tag_name: []const u8) HTMLCollection {
|
pub fn _getElementsByTagName(self: *parser.Document, tag_name: []const u8) collection.HTMLCollection {
|
||||||
const root = parser.documentGetDocumentElement(self);
|
const root = parser.documentGetDocumentElement(self);
|
||||||
return HTMLCollection{
|
return collection.HTMLCollectionByTagName(parser.elementToNode(root), tag_name);
|
||||||
.root = parser.elementToNode(root),
|
}
|
||||||
.match = tag_name,
|
|
||||||
};
|
pub fn _getElementsByClassName(self: *parser.Document, classNames: []const u8) collection.HTMLCollection {
|
||||||
|
const root = parser.documentGetDocumentElement(self);
|
||||||
|
return collection.HTMLCollectionByClassName(parser.elementToNode(root), classNames);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,6 +96,16 @@ pub fn testExecFn(
|
|||||||
};
|
};
|
||||||
try checkCases(js_env, &getElementsByTagName);
|
try checkCases(js_env, &getElementsByTagName);
|
||||||
|
|
||||||
|
var getElementsByClassName = [_]Case{
|
||||||
|
.{ .src = "let ok = document.getElementsByClassName('ok')", .ex = "undefined" },
|
||||||
|
.{ .src = "ok.length", .ex = "2" },
|
||||||
|
.{ .src = "let empty = document.getElementsByClassName('empty')", .ex = "undefined" },
|
||||||
|
.{ .src = "empty.length", .ex = "1" },
|
||||||
|
.{ .src = "let emptyok = document.getElementsByClassName('empty ok')", .ex = "undefined" },
|
||||||
|
.{ .src = "emptyok.length", .ex = "1" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &getElementsByClassName);
|
||||||
|
|
||||||
const tags = comptime parser.Tag.all();
|
const tags = comptime parser.Tag.all();
|
||||||
comptime var createElements: [(tags.len) * 2]Case = undefined;
|
comptime var createElements: [(tags.len) * 2]Case = undefined;
|
||||||
inline for (tags, 0..) |tag, i| {
|
inline for (tags, 0..) |tag, i| {
|
||||||
|
|||||||
@@ -5,11 +5,81 @@ const parser = @import("../netsurf.zig");
|
|||||||
const jsruntime = @import("jsruntime");
|
const jsruntime = @import("jsruntime");
|
||||||
const Case = jsruntime.test_utils.Case;
|
const Case = jsruntime.test_utils.Case;
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
const checkCases = jsruntime.test_utils.checkCases;
|
||||||
|
const generate = @import("../generate.zig");
|
||||||
|
|
||||||
const utils = @import("utils.z");
|
const utils = @import("utils.z");
|
||||||
const Element = @import("element.zig").Element;
|
const Element = @import("element.zig").Element;
|
||||||
const Union = @import("element.zig").Union;
|
const Union = @import("element.zig").Union;
|
||||||
|
|
||||||
|
const Matcher = union(enum) {
|
||||||
|
matchByTagName: MatchByTagName,
|
||||||
|
matchByClassName: MatchByClassName,
|
||||||
|
|
||||||
|
pub fn match(self: Matcher, node: *parser.Node) bool {
|
||||||
|
switch (self) {
|
||||||
|
inline else => |case| return case.match(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MatchByTagName = struct {
|
||||||
|
// tag is used to select node against their name.
|
||||||
|
// tag comparison is case insensitive.
|
||||||
|
tag: []const u8,
|
||||||
|
is_wildcard: bool,
|
||||||
|
|
||||||
|
fn init(tag_name: []const u8) MatchByTagName {
|
||||||
|
return MatchByTagName{
|
||||||
|
.tag = tag_name,
|
||||||
|
.is_wildcard = std.mem.eql(u8, tag_name, "*"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match(self: MatchByTagName, node: *parser.Node) bool {
|
||||||
|
return self.is_wildcard or std.ascii.eqlIgnoreCase(self.tag, parser.nodeName(node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn HTMLCollectionByTagName(root: *parser.Node, tag_name: []const u8) HTMLCollection {
|
||||||
|
return HTMLCollection{
|
||||||
|
.root = root,
|
||||||
|
.matcher = Matcher{
|
||||||
|
.matchByTagName = MatchByTagName.init(tag_name),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MatchByClassName = struct {
|
||||||
|
classNames: []const u8,
|
||||||
|
|
||||||
|
fn init(classNames: []const u8) MatchByClassName {
|
||||||
|
return MatchByClassName{
|
||||||
|
.classNames = classNames,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match(self: MatchByClassName, node: *parser.Node) bool {
|
||||||
|
var it = std.mem.splitAny(u8, self.classNames, " ");
|
||||||
|
const e = parser.nodeToElement(node);
|
||||||
|
while (it.next()) |c| {
|
||||||
|
if (!parser.elementHasClass(e, c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn HTMLCollectionByClassName(root: *parser.Node, classNames: []const u8) HTMLCollection {
|
||||||
|
return HTMLCollection{
|
||||||
|
.root = root,
|
||||||
|
.matcher = Matcher{
|
||||||
|
.matchByClassName = MatchByClassName.init(classNames),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#htmlcollection
|
// WEB IDL https://dom.spec.whatwg.org/#htmlcollection
|
||||||
// HTMLCollection is re implemented in zig here because libdom
|
// HTMLCollection is re implemented in zig here because libdom
|
||||||
// dom_html_collection expects a comparison function callback as arguement.
|
// dom_html_collection expects a comparison function callback as arguement.
|
||||||
@@ -17,10 +87,9 @@ const Union = @import("element.zig").Union;
|
|||||||
pub const HTMLCollection = struct {
|
pub const HTMLCollection = struct {
|
||||||
pub const mem_guarantied = true;
|
pub const mem_guarantied = true;
|
||||||
|
|
||||||
|
matcher: Matcher,
|
||||||
|
|
||||||
root: *parser.Node,
|
root: *parser.Node,
|
||||||
// match is used to select node against their name.
|
|
||||||
// match comparison is case insensitive.
|
|
||||||
match: []const u8,
|
|
||||||
|
|
||||||
// save a state for the collection to improve the _item speed.
|
// save a state for the collection to improve the _item speed.
|
||||||
cur_idx: ?u32 = undefined,
|
cur_idx: ?u32 = undefined,
|
||||||
@@ -76,20 +145,15 @@ pub const HTMLCollection = struct {
|
|||||||
/// get_length computes the collection's length dynamically according to
|
/// get_length computes the collection's length dynamically according to
|
||||||
/// the current root structure.
|
/// the current root structure.
|
||||||
// TODO: nodes retrieved must be de-referenced.
|
// TODO: nodes retrieved must be de-referenced.
|
||||||
pub fn get_length(self: *HTMLCollection, allocator: std.mem.Allocator) !u32 {
|
pub fn get_length(self: *HTMLCollection) u32 {
|
||||||
var len: u32 = 0;
|
var len: u32 = 0;
|
||||||
var node: *parser.Node = self.root;
|
var node: *parser.Node = self.root;
|
||||||
var ntype: parser.NodeType = undefined;
|
var ntype: parser.NodeType = undefined;
|
||||||
|
|
||||||
const imatch = try std.ascii.allocUpperString(allocator, self.match);
|
|
||||||
defer allocator.free(imatch);
|
|
||||||
|
|
||||||
const is_wildcard = std.mem.eql(u8, self.match, "*");
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ntype = parser.nodeType(node);
|
ntype = parser.nodeType(node);
|
||||||
if (ntype == .element) {
|
if (ntype == .element) {
|
||||||
if (is_wildcard or std.mem.eql(u8, imatch, parser.nodeName(node))) {
|
if (self.matcher.match(node)) {
|
||||||
len += 1;
|
len += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,26 +164,21 @@ pub const HTMLCollection = struct {
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _item(self: *HTMLCollection, allocator: std.mem.Allocator, index: u32) !?Union {
|
pub fn _item(self: *HTMLCollection, index: u32) ?Union {
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
var node: *parser.Node = self.root;
|
var node: *parser.Node = self.root;
|
||||||
var ntype: parser.NodeType = undefined;
|
var ntype: parser.NodeType = undefined;
|
||||||
|
|
||||||
const is_wildcard = std.mem.eql(u8, self.match, "*");
|
|
||||||
|
|
||||||
// Use the current state to improve speed if possible.
|
// Use the current state to improve speed if possible.
|
||||||
if (self.cur_idx != null and index >= self.cur_idx.?) {
|
if (self.cur_idx != null and index >= self.cur_idx.?) {
|
||||||
i = self.cur_idx.?;
|
i = self.cur_idx.?;
|
||||||
node = self.cur_node.?;
|
node = self.cur_node.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imatch = try std.ascii.allocUpperString(allocator, self.match);
|
|
||||||
defer allocator.free(imatch);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ntype = parser.nodeType(node);
|
ntype = parser.nodeType(node);
|
||||||
if (ntype == .element) {
|
if (ntype == .element) {
|
||||||
if (is_wildcard or std.mem.eql(u8, imatch, parser.nodeName(node))) {
|
if (self.matcher.match(node)) {
|
||||||
// check if we found the searched element.
|
// check if we found the searched element.
|
||||||
if (i == index) {
|
if (i == index) {
|
||||||
// save the current state
|
// save the current state
|
||||||
@@ -140,7 +199,7 @@ pub const HTMLCollection = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _namedItem(self: *HTMLCollection, allocator: std.mem.Allocator, name: []const u8) !?Union {
|
pub fn _namedItem(self: *HTMLCollection, name: []const u8) ?Union {
|
||||||
if (name.len == 0) {
|
if (name.len == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -148,15 +207,10 @@ pub const HTMLCollection = struct {
|
|||||||
var node: *parser.Node = self.root;
|
var node: *parser.Node = self.root;
|
||||||
var ntype: parser.NodeType = undefined;
|
var ntype: parser.NodeType = undefined;
|
||||||
|
|
||||||
const is_wildcard = std.mem.eql(u8, self.match, "*");
|
|
||||||
|
|
||||||
const imatch = try std.ascii.allocUpperString(allocator, self.match);
|
|
||||||
defer allocator.free(imatch);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ntype = parser.nodeType(node);
|
ntype = parser.nodeType(node);
|
||||||
if (ntype == .element) {
|
if (ntype == .element) {
|
||||||
if (is_wildcard or std.mem.eql(u8, imatch, parser.nodeName(node))) {
|
if (self.matcher.match(node)) {
|
||||||
const elem = @as(*parser.Element, @ptrCast(node));
|
const elem = @as(*parser.Element, @ptrCast(node));
|
||||||
|
|
||||||
var attr = parser.elementGetAttribute(elem, "id");
|
var attr = parser.elementGetAttribute(elem, "id");
|
||||||
|
|||||||
@@ -65,6 +65,18 @@ inline fn stringFromData(data: []const u8) *String {
|
|||||||
return s.?;
|
return s.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LWCString = c.lwc_string;
|
||||||
|
|
||||||
|
// TODO implement lwcStringToData
|
||||||
|
// inline fn lwcStringToData(s: *LWCString) []const u8 {
|
||||||
|
// }
|
||||||
|
|
||||||
|
inline fn lwcStringFromData(data: []const u8) *LWCString {
|
||||||
|
var s: ?*LWCString = undefined;
|
||||||
|
_ = c.lwc_intern_string(data.ptr, data.len, &s);
|
||||||
|
return s.?;
|
||||||
|
}
|
||||||
|
|
||||||
// Tag
|
// Tag
|
||||||
|
|
||||||
pub const Tag = enum(u8) {
|
pub const Tag = enum(u8) {
|
||||||
@@ -522,6 +534,11 @@ pub fn nodeReplaceChild(node: *Node, new_child: *Node, old_child: *Node) *Node {
|
|||||||
return res.?;
|
return res.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodeToElement is an helper to convert a node to an element.
|
||||||
|
pub inline fn nodeToElement(node: *Node) *Element {
|
||||||
|
return @as(*Element, @ptrCast(node));
|
||||||
|
}
|
||||||
|
|
||||||
// CharacterData
|
// CharacterData
|
||||||
pub const CharacterData = c.dom_characterdata;
|
pub const CharacterData = c.dom_characterdata;
|
||||||
|
|
||||||
@@ -621,6 +638,12 @@ pub fn elementGetAttribute(elem: *Element, name: []const u8) ?[]const u8 {
|
|||||||
return stringToData(s.?);
|
return stringToData(s.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn elementHasClass(elem: *Element, class: []const u8) bool {
|
||||||
|
var res: bool = undefined;
|
||||||
|
_ = elementVtable(elem).dom_element_has_class.?(elem, lwcStringFromData(class), &res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
// elementToNode is an helper to convert an element to a node.
|
// elementToNode is an helper to convert an element to a node.
|
||||||
pub inline fn elementToNode(e: *Element) *Node {
|
pub inline fn elementToNode(e: *Element) *Node {
|
||||||
return @as(*Node, @ptrCast(e));
|
return @as(*Node, @ptrCast(e));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div id='content'>
|
<div id='content'>
|
||||||
<a id='link' href='foo'>OK</a>
|
<a id='link' href='foo' class='ok'>OK</a>
|
||||||
<p id='para-empty'>
|
<p id='para-empty' class='ok empty'>
|
||||||
<span id='para-empty-child'></span>
|
<span id='para-empty-child'></span>
|
||||||
</p>
|
</p>
|
||||||
<p id='para'> And</p>
|
<p id='para'> And</p>
|
||||||
|
|||||||
26
tests/wpt/dom/Document-getElementsByClassName.html
Normal file
26
tests/wpt/dom/Document-getElementsByClassName.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<title>Document.getElementsByClassName</title>
|
||||||
|
<link rel="author" title="Intel" href="http://www.intel.com">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script>
|
||||||
|
test(function() {
|
||||||
|
var a = document.createElement("a"),
|
||||||
|
b = document.createElement("b");
|
||||||
|
a.className = "foo";
|
||||||
|
this.add_cleanup(function() {document.body.removeChild(a);});
|
||||||
|
document.body.appendChild(a);
|
||||||
|
|
||||||
|
var l = document.getElementsByClassName("foo");
|
||||||
|
assert_true(l instanceof HTMLCollection);
|
||||||
|
assert_equals(l.length, 1);
|
||||||
|
|
||||||
|
b.className = "foo";
|
||||||
|
document.body.appendChild(b);
|
||||||
|
assert_equals(l.length, 2);
|
||||||
|
|
||||||
|
document.body.removeChild(b);
|
||||||
|
assert_equals(l.length, 1);
|
||||||
|
}, "getElementsByClassName() should be a live collection");
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user