diff --git a/src/browser/Runner.zig b/src/browser/Runner.zig index 111fe152e..1f08018d6 100644 --- a/src/browser/Runner.zig +++ b/src/browser/Runner.zig @@ -301,13 +301,10 @@ fn _tick(self: *Runner, comptime is_cdp: bool, timeout_ms: u32, conditions: []Wa return .done; } -pub fn waitForSelector(self: *Runner, frame_id: u32, selector: [:0]const u8, timeout_ms: u32) !*Node.Element { +pub fn waitForSelector(self: *Runner, frame_id: u32, input: [:0]const u8, timeout_ms: u32) !*Node.Element { const session = self.session; - const arena = try session.getArena(.small, "Runner.waitForSelector"); - defer session.releaseArena(arena); - var timer = try std.time.Timer.start(); - const parsed_selector = try Selector.parseLeaky(arena, selector); + const selector = try Selector.cachedParse(session.browser, input); while (true) { if (session.isCancelled()) { @@ -319,7 +316,7 @@ pub fn waitForSelector(self: *Runner, frame_id: u32, selector: [:0]const u8, tim }; const frame = &page.frame; - if (try parsed_selector.query(frame.document.asNode(), frame)) |el| { + if (try Selector.query(selector, frame.document.asNode(), frame)) |el| { return el; } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 0ec8a5d9c..effb8def1 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -1143,11 +1143,13 @@ pub fn animate(_: *Element, _: ?js.Object, _: ?js.Object, frame: *Frame) !*Anima return Animation.init(frame); } -pub fn closest(self: *Element, selector: []const u8, frame: *Frame) !?*Element { - if (selector.len == 0) { +pub fn closest(self: *Element, input: []const u8, frame: *Frame) !?*Element { + if (input.len == 0) { return error.SyntaxError; } + const selector = try Selector.cachedParse(frame._session.browser, input); + var current: ?*Element = self; while (current) |el| { if (try Selector.matchesWithScope(el, selector, self, frame)) { diff --git a/src/browser/webapi/selector/Selector.zig b/src/browser/webapi/selector/Selector.zig index 96963bec4..ecdc82251 100644 --- a/src/browser/webapi/selector/Selector.zig +++ b/src/browser/webapi/selector/Selector.zig @@ -21,6 +21,7 @@ const lp = @import("lightpanda"); const Node = @import("../Node.zig"); const Frame = @import("../../Frame.zig"); +const Browser = @import("../../Browser.zig"); const Parser = @import("Parser.zig"); pub const List = @import("List.zig"); @@ -44,16 +45,9 @@ pub fn mapErrorToDOM(err: anyerror) anyerror { }; } -pub fn parseLeaky(arena: Allocator, input: []const u8) !Parsed { - if (input.len == 0) { - return error.SyntaxError; - } - return .{ .selectors = try Parser.parseList(arena, input) }; -} - /// One-off synthesized selectors use the `*Uncached` variants instead. -fn cachedParse(frame: *Frame, input: []const u8) ![]const Selector { - return frame._session.browser.selector_cache.parse(input); +pub fn cachedParse(browser: *Browser, input: []const u8) ![]const Selector { + return browser.selector_cache.parse(input); } /// On the Browser because a parsed selector references no Frame/Context, so @@ -136,24 +130,23 @@ fn matchesAny(selectors: []const Selector, el: *Node.Element, scope: *Node, fram } pub fn querySelector(root: *Node, input: []const u8, frame: *Frame) !?*Node.Element { - const parsed = Parsed{ .selectors = try cachedParse(frame, input) }; - return parsed.query(root, frame); + return query(try cachedParse(frame._session.browser, input), root, frame); } pub fn querySelectorAll(root: *Node, input: []const u8, frame: *Frame) !*List { const arena = try frame.getArena(.small, "querySelectorAll"); errdefer frame.releaseArena(arena); - return collectAll(arena, try cachedParse(frame, input), root, frame); + return collectAll(arena, try cachedParse(frame._session.browser, input), root, frame); } pub fn matches(el: *Node.Element, input: []const u8, frame: *Frame) !bool { - return matchesAny(try cachedParse(frame, input), el, el.asNode(), frame); + return matchesAny(try cachedParse(frame._session.browser, input), el, el.asNode(), frame); } // Like matches, but allows the caller to specify a scope node distinct from el. // Used by closest() so that :scope always refers to the original context element. -pub fn matchesWithScope(el: *Node.Element, input: []const u8, scope: *Node.Element, frame: *Frame) !bool { - return matchesAny(try cachedParse(frame, input), el, scope.asNode(), frame); +pub fn matchesWithScope(el: *Node.Element, selector: []const Selector, scope: *Node.Element, frame: *Frame) !bool { + return matchesAny(selector, el, scope.asNode(), frame); } /// Uncached counterparts for one-off selectors (SelectorPath): parse into @@ -163,8 +156,7 @@ pub fn querySelectorUncached(arena: Allocator, root: *Node, input: []const u8, f if (input.len == 0) { return error.SyntaxError; } - const parsed = Parsed{ .selectors = try Parser.parseList(arena, input) }; - return parsed.query(root, frame); + return query(try Parser.parseList(arena, input), root, frame); } pub fn querySelectorAllUncached(root: *Node, input: []const u8, frame: *Frame) !*List { @@ -363,31 +355,27 @@ pub const Selector = struct { } }; -pub const Parsed = struct { - selectors: []const Selector, - - pub fn query(self: Parsed, root: *Node, frame: *Frame) !?*Node.Element { - for (self.selectors) |selector| { - // Fast path: single compound with only an ID selector - if (selector.segments.len == 0 and selector.first.parts.len == 1) { - const first = selector.first.parts[0]; - if (first == .id) { - const el = frame.getElementByIdFromNode(root, first.id) orelse continue; - // Check if the element is within the root subtree - const node = el.asNode(); - if (node != root and root.contains(node)) { - return el; - } - continue; - } - } - - if (List.initOne(root, selector, frame)) |node| { - if (node.is(Node.Element)) |el| { +pub fn query(selectors: []const Selector, root: *Node, frame: *Frame) !?*Node.Element { + for (selectors) |selector| { + // Fast path: single compound with only an ID selector + if (selector.segments.len == 0 and selector.first.parts.len == 1) { + const first = selector.first.parts[0]; + if (first == .id) { + const el = frame.getElementByIdFromNode(root, first.id) orelse continue; + // Check if the element is within the root subtree + const node = el.asNode(); + if (node != root and root.contains(node)) { return el; } + continue; + } + } + + if (List.initOne(root, selector, frame)) |node| { + if (node.is(Node.Element)) |el| { + return el; } } - return null; } -}; + return null; +}