From 41823f06c447d1e76ee9c3dd899c7fc8403bcace Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 16 Jul 2026 12:41:20 +0800 Subject: [PATCH] limit range collection to container range --- src/browser/Factory.zig | 11 ++--------- src/browser/webapi/Document.zig | 9 ++++----- src/browser/webapi/Node.zig | 3 +-- src/browser/webapi/Range.zig | 17 ++++++++++++----- src/browser/webapi/selector/Selector.zig | 15 +++++++++------ 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index 097fb2c1b..5065b7141 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -269,17 +269,10 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child return chain.get(1); } -pub fn abstractRange(self: *const Factory, arena: Allocator, child: anytype, frame: *Frame) !*@TypeOf(child) { - return self.abstractRangeIn(arena, child, frame.document.asNode(), frame); -} - -// `container` is the initial start/end container: per spec, createRange() -// initializes both boundary points to (document, 0) of the document it was -// called on, which is not necessarily the frame's main document. -pub fn abstractRangeIn(_: *const Factory, arena: Allocator, child: anytype, container: *Node, frame: *Frame) !*@TypeOf(child) { +pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, frame: *Frame) !*@TypeOf(child) { const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(arena); - const doc = container; + const doc = frame.document.asNode(); const abstract_range = chain.get(0); abstract_range.* = AbstractRange{ ._rc = .{}, diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index fd047c1d9..b2a621c01 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -162,15 +162,14 @@ pub fn setLocation(self: *Document, url: [:0]const u8) !void { return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = frame }); } -// Approximation of quirks mode: an HTML document without a doctype is in -// quirks mode. (Legacy doctypes that also trigger quirks are not detected.) +// Approximation of quirks mode: an HTML document without a doctype is in quirks mode. pub fn isQuirksMode(self: *const Document) bool { if (self._type != .html) { return false; } - var child = self._proto.firstChild(); - while (child) |c| : (child = c.nextSibling()) { - if (c._type == .document_type) { + var it = self._proto.childrenIterator(); + while (it.next()) |child| { + if (child._type == .document_type) { return false; } } diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index d79c115fa..41fd55c96 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -1402,8 +1402,7 @@ pub fn getElementsByClassName(self: *Node, class_name: []const u8, frame: *Frame try class_names.append(arena, try frame.dupeString(name)); } - const doc: ?*Document = if (self._type == .document) self._type.document else self.ownerDocument(frame); - const quirks = if (doc) |d| d.isQuirksMode() else false; + const quirks = if (self.ownerDocumentIncludingSelf(frame)) |doc| doc.isQuirksMode() else false; return collections.NodeLive(.class_name).init(self, .{ .names = class_names.items, .case_insensitive = quirks, diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index deefa9b47..4b8c251c4 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -38,11 +38,15 @@ pub fn init(frame: *Frame) !*Range { } // Both boundary points start at (container, 0); document.createRange() -// passes the document it was called on. +// passes the document it was called on, which is not necessarily the +// frame's main document. pub fn initIn(container: *Node, frame: *Frame) !*Range { const arena = try frame.getArena(.medium, "Range"); errdefer frame.releaseArena(arena); - return frame._factory.abstractRangeIn(arena, Range{ ._proto = undefined }, container, frame); + const range = try frame._factory.abstractRange(arena, Range{ ._proto = undefined }, frame); + range._proto._start_container = container; + range._proto._end_container = container; + return range; } pub fn asAbstractRange(self: *Range) *AbstractRange { @@ -452,11 +456,14 @@ pub fn deleteContents(self: *Range, frame: *Frame) !void { // range's boundary points. Appends the top-most contained nodes (those whose // parent isn't contained) under `node`, without descending into them. fn collectContained(self: *const Range, node: *Node, list: *std.ArrayList(*Node), arena: std.mem.Allocator) !void { - var child = node.firstChild(); - while (child) |c| : (child = c.nextSibling()) { + var it = node.childrenIterator(); + while (it.next()) |c| { if (self.nodeContained(c)) { try list.append(arena, c); - } else { + } else if (c.contains(self._proto._start_container) or c.contains(self._proto._end_container)) { + // A non-contained child either straddles a boundary point or lies + // entirely outside the range; only the former can have contained + // descendants, so don't descend into the rest. try self.collectContained(c, list, arena); } } diff --git a/src/browser/webapi/selector/Selector.zig b/src/browser/webapi/selector/Selector.zig index f6bdae851..71658419b 100644 --- a/src/browser/webapi/selector/Selector.zig +++ b/src/browser/webapi/selector/Selector.zig @@ -187,14 +187,17 @@ pub fn classAttributeContains(class_attr: []const u8, class_name: []const u8) bo } pub fn classAttributeContainsCase(class_attr: []const u8, class_name: []const u8, case_insensitive: bool) bool { - if (class_name.len == 0 or class_name.len > class_attr.len) return false; + if (class_name.len == 0 or class_name.len > class_attr.len) { + return false; + } var search = class_attr; - while (if (case_insensitive) - std.ascii.indexOfIgnoreCase(search, class_name) - else - std.mem.indexOf(u8, search, class_name)) |pos| - { + while (true) { + const pos = (if (case_insensitive) + std.ascii.indexOfIgnoreCase(search, class_name) + else + std.mem.indexOf(u8, search, class_name)) orelse break; + const is_start = pos == 0 or isClassWhitespace(search[pos - 1]); const end = pos + class_name.len; const is_end = end == search.len or isClassWhitespace(search[end]);