diff --git a/src/browser/tests/range.html b/src/browser/tests/range.html
index 22d79bdf..5e9afbdc 100644
--- a/src/browser/tests/range.html
+++ b/src/browser/tests/range.html
@@ -7,7 +7,7 @@
Span content
-
+
-
diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig
index c94c88a4..758f81e7 100644
--- a/src/browser/webapi/Range.zig
+++ b/src/browser/webapi/Range.zig
@@ -549,23 +549,93 @@ pub fn toString(self: *const Range, page: *Page) ![]const u8 {
}
fn writeTextContent(self: *const Range, writer: *std.Io.Writer) !void {
- if (self._proto.getCollapsed()) {
- return;
+ if (self._proto.getCollapsed()) return;
+
+ const start_node = self._proto._start_container;
+ const end_node = self._proto._end_container;
+ const start_offset = self._proto._start_offset;
+ const end_offset = self._proto._end_offset;
+
+ // Same text node — just substring
+ if (start_node == end_node) {
+ if (start_node.is(Node.CData)) |cdata| {
+ if (!isCommentOrPI(cdata)) {
+ const data = cdata.getData();
+ const s = @min(start_offset, data.len);
+ const e = @min(end_offset, data.len);
+ try writer.writeAll(data[s..e]);
+ }
+ return;
+ }
}
- if (self._proto._start_container == self._proto._end_container) {
- if (self._proto._start_container.is(Node.CData)) |cdata| {
+ const root = self._proto.getCommonAncestorContainer();
+
+ // Partial start: if start container is a text node, write from offset to end
+ if (start_node.is(Node.CData)) |cdata| {
+ if (!isCommentOrPI(cdata)) {
const data = cdata.getData();
- if (self._proto._start_offset < data.len and self._proto._end_offset <= data.len) {
- try writer.writeAll(data[self._proto._start_offset..self._proto._end_offset]);
+ const s = @min(start_offset, data.len);
+ try writer.writeAll(data[s..]);
+ }
+ }
+
+ // Walk fully-contained text nodes between the boundaries.
+ // For text containers, the walk starts after that node.
+ // For element containers, the walk starts at the child at offset.
+ const walk_start: ?*Node = if (start_node.is(Node.CData) != null)
+ nextInTreeOrder(start_node, root)
+ else
+ start_node.getChildAt(start_offset) orelse nextAfterSubtree(start_node, root);
+
+ const walk_end: ?*Node = if (end_node.is(Node.CData) != null)
+ end_node
+ else
+ end_node.getChildAt(end_offset) orelse nextAfterSubtree(end_node, root);
+
+ if (walk_start) |start| {
+ var current: ?*Node = start;
+ while (current) |n| {
+ if (walk_end) |we| {
+ if (n == we) break;
+ }
+ if (n.is(Node.CData)) |cdata| {
+ if (!isCommentOrPI(cdata)) {
+ try writer.writeAll(cdata.getData());
+ }
+ }
+ current = nextInTreeOrder(n, root);
+ }
+ }
+
+ // Partial end: if end container is a different text node, write from start to offset
+ if (start_node != end_node) {
+ if (end_node.is(Node.CData)) |cdata| {
+ if (!isCommentOrPI(cdata)) {
+ const data = cdata.getData();
+ const e = @min(end_offset, data.len);
+ try writer.writeAll(data[0..e]);
}
}
- // For elements, would need to iterate children
- return;
}
+}
- // Complex case: different containers - would need proper tree walking
- // For now, just return empty
+fn isCommentOrPI(cdata: *Node.CData) bool {
+ return cdata.is(Node.CData.Comment) != null or cdata.is(Node.CData.ProcessingInstruction) != null;
+}
+
+fn nextInTreeOrder(node: *Node, root: *Node) ?*Node {
+ if (node.firstChild()) |child| return child;
+ return nextAfterSubtree(node, root);
+}
+
+fn nextAfterSubtree(node: *Node, root: *Node) ?*Node {
+ var current = node;
+ while (current != root) {
+ if (current.nextSibling()) |sibling| return sibling;
+ current = current.parentNode() orelse return null;
+ }
+ return null;
}
pub const JsApi = struct {