mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
MediaError and :scope pseudoclass
This commit is contained in:
@@ -565,6 +565,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/event/ProgressEvent.zig"),
|
||||
@import("../webapi/MessageChannel.zig"),
|
||||
@import("../webapi/MessagePort.zig"),
|
||||
@import("../webapi/media/MediaError.zig"),
|
||||
@import("../webapi/media/TextTrackCue.zig"),
|
||||
@import("../webapi/media/VTTCue.zig"),
|
||||
@import("../webapi/EventTarget.zig"),
|
||||
|
||||
127
src/browser/tests/element/query_selector_scope.html
Normal file
127
src/browser/tests/element/query_selector_scope.html
Normal file
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<div id="container">
|
||||
<div id="child1" class="item">
|
||||
<span id="grandchild1">Grandchild 1</span>
|
||||
<span id="grandchild2">Grandchild 2</span>
|
||||
</div>
|
||||
<div id="child2" class="item">
|
||||
<span id="grandchild3">Grandchild 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="scopeBasic">
|
||||
{
|
||||
// :scope refers to the reference element but querySelector only returns descendants
|
||||
const container = $('#container');
|
||||
|
||||
// :scope alone doesn't match anything because querySelector only returns descendants
|
||||
const scopeMatch = container.querySelector(':scope');
|
||||
testing.expectEqual(null, scopeMatch);
|
||||
|
||||
// :scope in querySelectorAll should also return empty
|
||||
const scopeMatches = container.querySelectorAll(':scope');
|
||||
testing.expectEqual(0, scopeMatches.length);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="scopeWithCombinators">
|
||||
{
|
||||
const container = $('#container');
|
||||
|
||||
// :scope > child - direct children of scope
|
||||
const directChildren = container.querySelectorAll(':scope > div');
|
||||
testing.expectEqual(2, directChildren.length);
|
||||
testing.expectEqual($('#child1'), directChildren[0]);
|
||||
testing.expectEqual($('#child2'), directChildren[1]);
|
||||
|
||||
// :scope > .item - direct children with class
|
||||
const itemChildren = container.querySelectorAll(':scope > .item');
|
||||
testing.expectEqual(2, itemChildren.length);
|
||||
|
||||
// :scope span - descendant spans of scope
|
||||
const spans = container.querySelectorAll(':scope span');
|
||||
testing.expectEqual(3, spans.length);
|
||||
|
||||
// :scope > div > span - grandchildren via specific path
|
||||
const grandchildren = container.querySelectorAll(':scope > div > span');
|
||||
testing.expectEqual(3, grandchildren.length);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="nested-container">
|
||||
<div class="outer">
|
||||
<div class="inner" id="target">
|
||||
<span class="text">Inner text</span>
|
||||
</div>
|
||||
<div class="inner">
|
||||
<span class="text">Other text</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="scopeNested">
|
||||
{
|
||||
const target = $('#target');
|
||||
|
||||
// :scope refers to target but querySelector only returns descendants
|
||||
const scopeMatch = target.querySelector(':scope');
|
||||
testing.expectEqual(null, scopeMatch);
|
||||
|
||||
// :scope > span should find direct children of target
|
||||
const directSpan = target.querySelector(':scope > span');
|
||||
testing.expectEqual('Inner text', directSpan.textContent);
|
||||
|
||||
// When querySelector is called on target, :scope refers to target
|
||||
const scopeChildren = target.querySelectorAll(':scope > .text');
|
||||
testing.expectEqual(1, scopeChildren.length);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="compound-test">
|
||||
<div class="box" id="box1">Box 1</div>
|
||||
<div class="box" id="box2">Box 2</div>
|
||||
<span class="box" id="box3">Box 3</span>
|
||||
</div>
|
||||
|
||||
<script id="scopeCompound">
|
||||
{
|
||||
const compound = $('#compound-test');
|
||||
|
||||
// Compound selector with :scope
|
||||
const divBoxes = compound.querySelectorAll(':scope > div.box');
|
||||
testing.expectEqual(2, divBoxes.length);
|
||||
testing.expectEqual($('#box1'), divBoxes[0]);
|
||||
testing.expectEqual($('#box2'), divBoxes[1]);
|
||||
|
||||
// :scope with multiple parts
|
||||
const spanBox = compound.querySelector(':scope > span.box');
|
||||
testing.expectEqual($('#box3'), spanBox);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="pseudo-container">
|
||||
<div class="parent" id="p1">
|
||||
<div class="child">Child 1</div>
|
||||
<div class="child">Child 2</div>
|
||||
</div>
|
||||
<div class="parent" id="p2">
|
||||
<div class="child">Child 3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="scopeWithOtherPseudos">
|
||||
{
|
||||
const container = $('#pseudo-container');
|
||||
|
||||
// :scope with :not()
|
||||
const notP1Children = container.querySelectorAll(':scope > .parent:not(#p1) > .child');
|
||||
testing.expectEqual(1, notP1Children.length);
|
||||
testing.expectEqual('Child 3', notP1Children[0].textContent);
|
||||
|
||||
// :scope with :first-child
|
||||
const firstParent = container.querySelector(':scope > .parent:first-child');
|
||||
testing.expectEqual($('#p1'), firstParent);
|
||||
}
|
||||
</script>
|
||||
12
src/browser/tests/media/mediaerror.html
Normal file
12
src/browser/tests/media/mediaerror.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="..//testing.js"></script>
|
||||
|
||||
<script id=constants>
|
||||
{
|
||||
// Test that MediaError constants exist
|
||||
testing.expectEqual(1, MediaError.MEDIA_ERR_ABORTED);
|
||||
testing.expectEqual(2, MediaError.MEDIA_ERR_NETWORK);
|
||||
testing.expectEqual(3, MediaError.MEDIA_ERR_DECODE);
|
||||
testing.expectEqual(4, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
|
||||
}
|
||||
</script>
|
||||
64
src/browser/webapi/media/MediaError.zig
Normal file
64
src/browser/webapi/media/MediaError.zig
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const MediaError = @This();
|
||||
|
||||
_code: u16,
|
||||
_message: []const u8 = "",
|
||||
|
||||
pub fn init(code: u16, message: []const u8, page: *Page) !*MediaError {
|
||||
return page.arena.create(MediaError{
|
||||
._code = code,
|
||||
._message = try page.dupeString(message),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getCode(self: *const MediaError) u16 {
|
||||
return self._code;
|
||||
}
|
||||
|
||||
pub fn getMessage(self: *const MediaError) []const u8 {
|
||||
return self._message;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(MediaError);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "MediaError";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
// Error code constants
|
||||
pub const MEDIA_ERR_ABORTED = bridge.property(1);
|
||||
pub const MEDIA_ERR_NETWORK = bridge.property(2);
|
||||
pub const MEDIA_ERR_DECODE = bridge.property(3);
|
||||
pub const MEDIA_ERR_SRC_NOT_SUPPORTED = bridge.property(4);
|
||||
|
||||
pub const code = bridge.accessor(MediaError.getCode, null, .{});
|
||||
pub const message = bridge.accessor(MediaError.getMessage, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: MediaError" {
|
||||
try testing.htmlRunner("media/mediaerror.html", .{});
|
||||
}
|
||||
@@ -54,7 +54,7 @@ pub fn collect(
|
||||
}
|
||||
|
||||
while (tw.next()) |node| {
|
||||
if (matches(node, result.selector, page)) {
|
||||
if (matches(node, result.selector, root, page)) {
|
||||
try nodes.put(allocator, node, {});
|
||||
}
|
||||
}
|
||||
@@ -66,12 +66,11 @@ pub fn initOne(root: *Node, selector: Selector.Selector, page: *Page) ?*Node {
|
||||
const result = optimizeSelector(root, &selector, page) orelse return null;
|
||||
|
||||
var tw = TreeWalker.init(result.root, .{});
|
||||
const optimized_selector = result.selector;
|
||||
if (result.exclude_root) {
|
||||
_ = tw.next();
|
||||
}
|
||||
while (tw.next()) |node| {
|
||||
if (matches(node, optimized_selector, page)) {
|
||||
if (matches(node, result.selector, root, page)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@@ -89,10 +88,12 @@ const OptimizeResult = struct {
|
||||
exclude_root: bool,
|
||||
selector: Selector.Selector,
|
||||
};
|
||||
|
||||
fn optimizeSelector(root: *Node, selector: *const Selector.Selector, page: *Page) ?OptimizeResult {
|
||||
const anchor = findIdSelector(selector) orelse return .{
|
||||
.root = root,
|
||||
.selector = selector.*,
|
||||
// Always exclude root - querySelector only returns descendants
|
||||
.exclude_root = true,
|
||||
};
|
||||
|
||||
@@ -173,7 +174,7 @@ fn optimizeSelector(root: *Node, selector: *const Selector.Selector, page: *Page
|
||||
.segments = selector.segments[0 .. seg_idx + 1],
|
||||
};
|
||||
|
||||
if (!matches(id_node, prefix_selector, page)) {
|
||||
if (!matches(id_node, prefix_selector, id_node, page)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -248,23 +249,23 @@ fn findIdSelector(selector: *const Selector.Selector) ?IdAnchor {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn matches(node: *Node, selector: Selector.Selector, page: *Page) bool {
|
||||
pub fn matches(node: *Node, selector: Selector.Selector, scope: *Node, page: *Page) bool {
|
||||
const el = node.is(Node.Element) orelse return false;
|
||||
|
||||
if (selector.segments.len == 0) {
|
||||
return matchesCompound(el, selector.first, page);
|
||||
return matchesCompound(el, selector.first, scope, page);
|
||||
}
|
||||
|
||||
const last_segment = selector.segments[selector.segments.len - 1];
|
||||
if (!matchesCompound(el, last_segment.compound, page)) {
|
||||
if (!matchesCompound(el, last_segment.compound, scope, page)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return matchSegments(node, selector, selector.segments.len - 1, null, page);
|
||||
return matchSegments(node, selector, selector.segments.len - 1, null, scope, page);
|
||||
}
|
||||
|
||||
// Match segments backward, with support for backtracking on subsequent_sibling
|
||||
fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize, root: ?*Node, page: *Page) bool {
|
||||
fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize, root: ?*Node, scope: *Node, page: *Page) bool {
|
||||
const segment = selector.segments[segment_index];
|
||||
const target_compound = if (segment_index == 0)
|
||||
selector.first
|
||||
@@ -272,9 +273,9 @@ fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize,
|
||||
selector.segments[segment_index - 1].compound;
|
||||
|
||||
const matched: ?*Node = switch (segment.combinator) {
|
||||
.descendant => matchDescendant(node, target_compound, root, page),
|
||||
.child => matchChild(node, target_compound, root, page),
|
||||
.next_sibling => matchNextSibling(node, target_compound, page),
|
||||
.descendant => matchDescendant(node, target_compound, root, scope, page),
|
||||
.child => matchChild(node, target_compound, root, scope, page),
|
||||
.next_sibling => matchNextSibling(node, target_compound, scope, page),
|
||||
.subsequent_sibling => {
|
||||
// For subsequent_sibling, try all matching siblings with backtracking
|
||||
var sibling = node.previousSibling();
|
||||
@@ -284,13 +285,13 @@ fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize,
|
||||
continue;
|
||||
};
|
||||
|
||||
if (matchesCompound(sibling_el, target_compound, page)) {
|
||||
if (matchesCompound(sibling_el, target_compound, scope, page)) {
|
||||
// If we're at the first segment, we found a match
|
||||
if (segment_index == 0) {
|
||||
return true;
|
||||
}
|
||||
// Try to match remaining segments from this sibling
|
||||
if (matchSegments(s, selector, segment_index - 1, root, page)) {
|
||||
if (matchSegments(s, selector, segment_index - 1, root, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
// This sibling didn't work, try the next one
|
||||
@@ -307,7 +308,7 @@ fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize,
|
||||
if (segment_index == 0) {
|
||||
return true;
|
||||
}
|
||||
return matchSegments(current, selector, segment_index - 1, root, page);
|
||||
return matchSegments(current, selector, segment_index - 1, root, scope, page);
|
||||
}
|
||||
|
||||
// subsequent_sibling already handled its recursion above
|
||||
@@ -315,12 +316,12 @@ fn matchSegments(node: *Node, selector: Selector.Selector, segment_index: usize,
|
||||
}
|
||||
|
||||
// Find an ancestor that matches the compound (any distance up the tree)
|
||||
fn matchDescendant(node: *Node, compound: Selector.Compound, root: ?*Node, page: *Page) ?*Node {
|
||||
fn matchDescendant(node: *Node, compound: Selector.Compound, root: ?*Node, scope: *Node, page: *Page) ?*Node {
|
||||
var current = node._parent;
|
||||
|
||||
while (current) |ancestor| {
|
||||
if (ancestor.is(Node.Element)) |ancestor_el| {
|
||||
if (matchesCompound(ancestor_el, compound, page)) {
|
||||
if (matchesCompound(ancestor_el, compound, scope, page)) {
|
||||
return ancestor;
|
||||
}
|
||||
}
|
||||
@@ -339,7 +340,7 @@ fn matchDescendant(node: *Node, compound: Selector.Compound, root: ?*Node, page:
|
||||
}
|
||||
|
||||
// Find the direct parent if it matches the compound
|
||||
fn matchChild(node: *Node, compound: Selector.Compound, root: ?*Node, page: *Page) ?*Node {
|
||||
fn matchChild(node: *Node, compound: Selector.Compound, root: ?*Node, scope: *Node, page: *Page) ?*Node {
|
||||
const parent = node._parent orelse return null;
|
||||
|
||||
// Don't match beyond the root boundary
|
||||
@@ -352,7 +353,7 @@ fn matchChild(node: *Node, compound: Selector.Compound, root: ?*Node, page: *Pag
|
||||
|
||||
const parent_el = parent.is(Node.Element) orelse return null;
|
||||
|
||||
if (matchesCompound(parent_el, compound, page)) {
|
||||
if (matchesCompound(parent_el, compound, scope, page)) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@@ -360,7 +361,7 @@ fn matchChild(node: *Node, compound: Selector.Compound, root: ?*Node, page: *Pag
|
||||
}
|
||||
|
||||
// Find the immediately preceding sibling if it matches the compound
|
||||
fn matchNextSibling(node: *Node, compound: Selector.Compound, page: *Page) ?*Node {
|
||||
fn matchNextSibling(node: *Node, compound: Selector.Compound, scope: *Node, page: *Page) ?*Node {
|
||||
var sibling = node.previousSibling();
|
||||
|
||||
// For next_sibling (+), we need the immediately preceding element sibling
|
||||
@@ -372,7 +373,7 @@ fn matchNextSibling(node: *Node, compound: Selector.Compound, page: *Page) ?*Nod
|
||||
};
|
||||
|
||||
// Found an element - check if it matches
|
||||
if (matchesCompound(sibling_el, compound, page)) {
|
||||
if (matchesCompound(sibling_el, compound, scope, page)) {
|
||||
return s;
|
||||
}
|
||||
// we found an element, it wasn't a match, we're done
|
||||
@@ -383,7 +384,7 @@ fn matchNextSibling(node: *Node, compound: Selector.Compound, page: *Page) ?*Nod
|
||||
}
|
||||
|
||||
// Find any preceding sibling that matches the compound
|
||||
fn matchSubsequentSibling(node: *Node, compound: Selector.Compound, page: *Page) ?*Node {
|
||||
fn matchSubsequentSibling(node: *Node, compound: Selector.Compound, scope: *Node, page: *Page) ?*Node {
|
||||
var sibling = node.previousSibling();
|
||||
|
||||
// For subsequent_sibling (~), check all preceding element siblings
|
||||
@@ -394,7 +395,7 @@ fn matchSubsequentSibling(node: *Node, compound: Selector.Compound, page: *Page)
|
||||
continue;
|
||||
};
|
||||
|
||||
if (matchesCompound(sibling_el, compound, page)) {
|
||||
if (matchesCompound(sibling_el, compound, scope, page)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -404,17 +405,17 @@ fn matchSubsequentSibling(node: *Node, compound: Selector.Compound, page: *Page)
|
||||
return null;
|
||||
}
|
||||
|
||||
fn matchesCompound(el: *Node.Element, compound: Selector.Compound, page: *Page) bool {
|
||||
fn matchesCompound(el: *Node.Element, compound: Selector.Compound, scope: *Node, page: *Page) bool {
|
||||
// For compound selectors, ALL parts must match
|
||||
for (compound.parts) |part| {
|
||||
if (!matchesPart(el, part, page)) {
|
||||
if (!matchesPart(el, part, scope, page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn matchesPart(el: *Node.Element, part: Part, page: *Page) bool {
|
||||
fn matchesPart(el: *Node.Element, part: Part, scope: *Node, page: *Page) bool {
|
||||
switch (part) {
|
||||
.id => |id| {
|
||||
const element_id = el.getAttributeSafe("id") orelse return false;
|
||||
@@ -435,7 +436,7 @@ fn matchesPart(el: *Node.Element, part: Part, page: *Page) bool {
|
||||
return std.mem.eql(u8, element_tag, tag_name);
|
||||
},
|
||||
.universal => return true,
|
||||
.pseudo_class => |pseudo| return matchesPseudoClass(el, pseudo, page),
|
||||
.pseudo_class => |pseudo| return matchesPseudoClass(el, pseudo, scope, page),
|
||||
.attribute => |attr| return matchesAttribute(el, attr),
|
||||
}
|
||||
}
|
||||
@@ -495,7 +496,7 @@ fn attributeContainsWord(value: []const u8, word: []const u8) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Page) bool {
|
||||
fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *Node, page: *Page) bool {
|
||||
const node = el.asNode();
|
||||
switch (pseudo) {
|
||||
// State pseudo-classes
|
||||
@@ -565,6 +566,10 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
const parent = node.parentNode() orelse return false;
|
||||
return parent._type == .document;
|
||||
},
|
||||
.scope => {
|
||||
// :scope matches the reference element (querySelector root)
|
||||
return node == scope;
|
||||
},
|
||||
.empty => {
|
||||
return node.firstChild() == null;
|
||||
},
|
||||
@@ -591,7 +596,7 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
.lang => return false,
|
||||
.not => |selectors| {
|
||||
for (selectors) |selector| {
|
||||
if (matches(node, selector, page)) {
|
||||
if (matches(node, selector, scope, page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -599,7 +604,7 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
},
|
||||
.is => |selectors| {
|
||||
for (selectors) |selector| {
|
||||
if (matches(node, selector, page)) {
|
||||
if (matches(node, selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -607,7 +612,7 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
},
|
||||
.where => |selectors| {
|
||||
for (selectors) |selector| {
|
||||
if (matches(node, selector, page)) {
|
||||
if (matches(node, selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -622,11 +627,11 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
continue;
|
||||
};
|
||||
|
||||
if (matches(child_el.asNode(), selector, page)) {
|
||||
if (matches(child_el.asNode(), selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchesHasDescendant(child_el, selector, page)) {
|
||||
if (matchesHasDescendant(child_el, selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -638,7 +643,7 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, page: *Pa
|
||||
}
|
||||
}
|
||||
|
||||
fn matchesHasDescendant(el: *Node.Element, selector: Selector.Selector, page: *Page) bool {
|
||||
fn matchesHasDescendant(el: *Node.Element, selector: Selector.Selector, scope: *Node, page: *Page) bool {
|
||||
var child = el.asNode().firstChild();
|
||||
while (child) |c| {
|
||||
const child_el = c.is(Node.Element) orelse {
|
||||
@@ -646,11 +651,11 @@ fn matchesHasDescendant(el: *Node.Element, selector: Selector.Selector, page: *P
|
||||
continue;
|
||||
};
|
||||
|
||||
if (matches(child_el.asNode(), selector, page)) {
|
||||
if (matches(child_el.asNode(), selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchesHasDescendant(child_el, selector, page)) {
|
||||
if (matchesHasDescendant(child_el, selector, scope, page)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -504,6 +504,7 @@ fn pseudoClass(self: *Parser, arena: Allocator, page: *Page) !Selector.PseudoCla
|
||||
if (fastEql(name, "modal")) return .modal;
|
||||
if (fastEql(name, "hover")) return .hover;
|
||||
if (fastEql(name, "focus")) return .focus;
|
||||
if (fastEql(name, "scope")) return .scope;
|
||||
if (fastEql(name, "empty")) return .empty;
|
||||
if (fastEql(name, "valid")) return .valid;
|
||||
},
|
||||
|
||||
@@ -82,7 +82,7 @@ pub fn matches(el: *Node.Element, input: []const u8, page: *Page) !bool {
|
||||
const selectors = try Parser.parseList(arena, input, page);
|
||||
|
||||
for (selectors) |selector| {
|
||||
if (List.matches(el.asNode(), selector, page)) {
|
||||
if (List.matches(el.asNode(), selector, el.asNode(), page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,7 @@ pub const PseudoClass = union(enum) {
|
||||
|
||||
// Tree structural
|
||||
root,
|
||||
scope,
|
||||
empty,
|
||||
first_child,
|
||||
last_child,
|
||||
|
||||
Reference in New Issue
Block a user