diff --git a/src/browser/css/libdom.zig b/src/browser/css/libdom.zig index 60689e95..ea28fb5c 100644 --- a/src/browser/css/libdom.zig +++ b/src/browser/css/libdom.zig @@ -81,6 +81,14 @@ pub const Node = struct { return t == .text; } + pub fn text(n: Node) !?[]const u8 { + const data = try parser.nodeTextContent(n.node); + if (data == null) return null; + if (data.?.len == 0) return null; + + return std.mem.trim(u8, data.?, &std.ascii.whitespace); + } + pub fn isEmptyText(n: Node) !bool { const data = try parser.nodeTextContent(n.node); if (data == null) return true; @@ -216,13 +224,14 @@ test "Browser.CSS.Libdom: matchFirst" { .{ .q = "li, p", .html = "

", .exp = 1 }, .{ .q = "p +/*This is a comment*/ p", .html = "

", .exp = 1 }, // .{ .q = "p:contains(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, - // .{ .q = "p:containsOwn(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, - // .{ .q = ":containsOwn(\"inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, - // .{ .q = "p:containsOwn(\"block\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, + .{ .q = "p:containsOwn(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, + .{ .q = ":containsOwn(\"inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, + .{ .q = ":containsOwn(\"Inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, + .{ .q = "p:containsOwn(\"block\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, // .{ .q = "div:has(#p1)", .html = "

text content

", .exp = 1 }, - // .{ .q = "div:has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, - // .{ .q = "body :has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, - // .{ .q = "body :haschild(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, + .{ .q = "div:has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, + .{ .q = "body :has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, + .{ .q = "body :haschild(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, // .{ .q = "p:matches([\\d])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 1 }, // .{ .q = "p:matches([a-z])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 1 }, // .{ .q = "p:matches([a-zA-Z])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 1 }, @@ -360,13 +369,14 @@ test "Browser.CSS.Libdom: matchAll" { .{ .q = "li, p", .html = "

", .exp = 3 }, .{ .q = "p +/*This is a comment*/ p", .html = "

", .exp = 1 }, // .{ .q = "p:contains(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, - // .{ .q = "p:containsOwn(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, - // .{ .q = ":containsOwn(\"inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, - // .{ .q = "p:containsOwn(\"block\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, + .{ .q = "p:containsOwn(\"that wraps\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, + .{ .q = ":containsOwn(\"inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, + .{ .q = ":containsOwn(\"Inner\")", .html = "

Text block that wraps inner text and continues

", .exp = 0 }, + .{ .q = "p:containsOwn(\"block\")", .html = "

Text block that wraps inner text and continues

", .exp = 1 }, .{ .q = "div:has(#p1)", .html = "

text content

", .exp = 1 }, - // .{ .q = "div:has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, - // .{ .q = "body :has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 2 }, - // .{ .q = "body :haschild(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, + .{ .q = "div:has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, + .{ .q = "body :has(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 2 }, + .{ .q = "body :haschild(:containsOwn(\"2\"))", .html = "

contents 1

contents 2

", .exp = 1 }, // .{ .q = "p:matches([\\d])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 2 }, // .{ .q = "p:matches([a-z])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 1 }, // .{ .q = "p:matches([a-zA-Z])", .html = "

0123456789

abcdef

0123ABCD

", .exp = 2 }, diff --git a/src/browser/css/parser.zig b/src/browser/css/parser.zig index 7b814ebc..63b863d8 100644 --- a/src/browser/css/parser.zig +++ b/src/browser/css/parser.zig @@ -557,8 +557,6 @@ pub const Parser = struct { const val = try buf.toOwnedSlice(allocator); errdefer allocator.free(val); - lowerstr(val); - return .{ .pseudo_class_contains = .{ .own = pseudo_class == .containsown, .val = val } }; }, .matches, .matchesown => { diff --git a/src/browser/css/selector.zig b/src/browser/css/selector.zig index ff9cf3dd..00ef1558 100644 --- a/src/browser/css/selector.zig +++ b/src/browser/css/selector.zig @@ -434,7 +434,25 @@ pub const Selector = union(enum) { else => Error.UnsupportedRelativePseudoClass, }; }, - .pseudo_class_contains => return Error.UnsupportedContainsPseudoClass, // TODO, need mem allocation. + .pseudo_class_contains => |v| { + // Only containsOwn is implemented. + if (v.own == false) return Error.UnsupportedContainsPseudoClass; + + var c = try n.firstChild(); + while (c != null) { + if (c.?.isText()) { + const text = try c.?.text(); + if (text) |_text| { + if (contains(_text, v.val, false)) { // we are case sensitive. Is this correct behavior? + return true; + } + } + } + + c = try c.?.nextSibling(); + } + return false; + }, .pseudo_class_regexp => return Error.UnsupportedRegexpPseudoClass, // TODO need mem allocation. .pseudo_class_nth => |v| { if (v.a == 0) {