Improve Range, adding missing functions and more validation

This commit is contained in:
Karl Seguin
2026-01-06 20:27:00 +08:00
parent d65025b3cb
commit 15358c1928
3 changed files with 685 additions and 21 deletions

View File

@@ -376,3 +376,447 @@
testing.expectEqual('Bold', fragment.childNodes[0].textContent); testing.expectEqual('Bold', fragment.childNodes[0].textContent);
} }
</script> </script>
<script id=offset_validation_setStart>
{
const range = document.createRange();
const p1 = $('#p1');
// Test setStart with offset beyond node length
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.setStart(p1, 999);
});
// Test with negative offset (wraps to large u32)
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.setStart(p1.firstChild, -1);
});
}
</script>
<script id=offset_validation_setEnd>
{
const range = document.createRange();
const p1 = $('#p1');
// Test setEnd with offset beyond node length
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.setEnd(p1, 999);
});
// Test with text node
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.setEnd(p1.firstChild, 9999);
});
}
</script>
<script id=comparePoint_basic>
{
// Create fresh elements to avoid DOM pollution from other tests
const div = document.createElement('div');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
p1.textContent = 'First paragraph text';
p2.textContent = 'Second paragraph text';
div.appendChild(p1);
div.appendChild(p2);
const range = document.createRange();
range.setStart(p1.firstChild, 0);
range.setEnd(p2.firstChild, 5);
// Point before range
testing.expectEqual(-1, range.comparePoint(div, 0));
// Point at start boundary
testing.expectEqual(0, range.comparePoint(p1.firstChild, 0));
// Point inside range (in p1)
testing.expectEqual(0, range.comparePoint(p1.firstChild, 3));
// Point inside range (in p2)
testing.expectEqual(0, range.comparePoint(p2.firstChild, 2));
// Point at end boundary
testing.expectEqual(0, range.comparePoint(p2.firstChild, 5));
// Point after range
testing.expectEqual(1, range.comparePoint(p2.firstChild, 10));
}
</script>
<script id=comparePoint_validation>
{
// Create fresh element
const p1 = document.createElement('p');
p1.textContent = 'Test content';
const range = document.createRange();
range.setStart(p1, 0);
range.setEnd(p1, 1);
// Test comparePoint with invalid offset
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.comparePoint(p1, 20);
});
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.comparePoint(p1.firstChild, -1);
});
}
</script>
<script id=different_document_collapse>
{
// Create fresh element in current document
const p1 = document.createElement('p');
p1.textContent = 'Local content';
const range = document.createRange();
// Create a foreign document
const foreignDoc = document.implementation.createHTMLDocument('');
const foreignP = foreignDoc.createElement('p');
foreignP.textContent = 'Foreign';
foreignDoc.body.appendChild(foreignP);
// Set up range in current document
range.setStart(p1, 0);
range.setEnd(p1, 1);
testing.expectEqual(false, range.collapsed);
// Setting start to foreign document should collapse to that point
range.setStart(foreignP, 0);
testing.expectEqual(true, range.collapsed);
testing.expectEqual(foreignP, range.startContainer);
testing.expectEqual(foreignP, range.endContainer);
}
</script>
<script id=detached_node_collapse>
{
// Create fresh element
const p1 = document.createElement('p');
p1.textContent = 'Attached content';
const range = document.createRange();
// Create a detached element
const detached = document.createElement('div');
detached.textContent = 'Detached';
// Set up range in document
range.setStart(p1, 0);
range.setEnd(p1, 1);
testing.expectEqual(false, range.collapsed);
// Setting end to detached node should collapse
range.setEnd(detached.firstChild, 0);
testing.expectEqual(true, range.collapsed);
testing.expectEqual(detached.firstChild, range.startContainer);
testing.expectEqual(detached.firstChild, range.endContainer);
}
</script>
<script id=isPointInRange_basic>
{
// Create fresh elements
const div = document.createElement('div');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
p1.textContent = 'First paragraph';
p2.textContent = 'Second paragraph';
div.appendChild(p1);
div.appendChild(p2);
const range = document.createRange();
range.setStart(p1.firstChild, 5);
range.setEnd(p2.firstChild, 6);
// Point before range
testing.expectEqual(false, range.isPointInRange(p1.firstChild, 0));
// Point at start boundary
testing.expectEqual(true, range.isPointInRange(p1.firstChild, 5));
// Point inside range
testing.expectEqual(true, range.isPointInRange(p1.firstChild, 7));
testing.expectEqual(true, range.isPointInRange(p2.firstChild, 3));
// Point at end boundary
testing.expectEqual(true, range.isPointInRange(p2.firstChild, 6));
// Point after range
testing.expectEqual(false, range.isPointInRange(p2.firstChild, 10));
}
</script>
<script id=isPointInRange_different_root>
{
// Create element in current document
const p1 = document.createElement('p');
p1.textContent = 'Local content';
const range = document.createRange();
range.setStart(p1, 0);
range.setEnd(p1, 1);
// Create element in different document
const foreignDoc = document.implementation.createHTMLDocument('');
const foreignP = foreignDoc.createElement('p');
foreignP.textContent = 'Foreign';
// Point in different root should return false (not throw)
testing.expectEqual(false, range.isPointInRange(foreignP, 0));
}
</script>
<script id=isPointInRange_validation>
{
const p1 = document.createElement('p');
p1.textContent = 'Test content';
const range = document.createRange();
range.setStart(p1, 0);
range.setEnd(p1, 1);
// Invalid offset should throw IndexSizeError
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.isPointInRange(p1, 999);
});
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
range.isPointInRange(p1.firstChild, 9999);
});
}
</script>
<script id=intersectsNode_basic>
{
// Create fresh elements
const div = document.createElement('div');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
const p3 = document.createElement('p');
p1.textContent = 'First';
p2.textContent = 'Second';
p3.textContent = 'Third';
div.appendChild(p1);
div.appendChild(p2);
div.appendChild(p3);
const range = document.createRange();
range.setStart(p1.firstChild, 2);
range.setEnd(p2.firstChild, 3);
// Node that intersects (p1 contains the start)
testing.expectEqual(true, range.intersectsNode(p1));
// Node that intersects (p2 contains the end)
testing.expectEqual(true, range.intersectsNode(p2));
// Node that doesn't intersect (p3 is after the range)
testing.expectEqual(false, range.intersectsNode(p3));
// Container intersects
testing.expectEqual(true, range.intersectsNode(div));
}
</script>
<script id=intersectsNode_detached>
{
const div = document.createElement('div');
const p1 = document.createElement('p');
p1.textContent = 'Content';
div.appendChild(p1);
const range = document.createRange();
range.setStart(p1, 0);
range.setEnd(p1, 1);
// The root node (div) should return true when it has no parent
// (Note: div is detached, so it's in the same tree as the range)
testing.expectEqual(true, range.intersectsNode(div));
}
</script>
<script id=intersectsNode_different_root>
{
const p1 = document.createElement('p');
p1.textContent = 'Local';
const range = document.createRange();
range.setStart(p1, 0);
range.setEnd(p1, 1);
// Node in different document should return false
const foreignDoc = document.implementation.createHTMLDocument('');
const foreignP = foreignDoc.createElement('p');
testing.expectEqual(false, range.intersectsNode(foreignP));
}
</script>
<script id=commonAncestorContainer_same_node>
{
const p = document.createElement('p');
p.textContent = 'Content';
const range = document.createRange();
range.setStart(p.firstChild, 0);
range.setEnd(p.firstChild, 3);
// Both boundaries in same text node, so that's the common ancestor
testing.expectEqual(p.firstChild, range.commonAncestorContainer);
}
</script>
<script id=commonAncestorContainer_siblings>
{
const div = document.createElement('div');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
p1.textContent = 'First';
p2.textContent = 'Second';
div.appendChild(p1);
div.appendChild(p2);
const range = document.createRange();
range.setStart(p1.firstChild, 0);
range.setEnd(p2.firstChild, 3);
// Start and end in different siblings, common ancestor is the parent div
testing.expectEqual(div, range.commonAncestorContainer);
}
</script>
<script id=commonAncestorContainer_nested>
{
const div = document.createElement('div');
const section = document.createElement('section');
const p = document.createElement('p');
const span = document.createElement('span');
p.textContent = 'Text';
span.textContent = 'Span';
div.appendChild(section);
section.appendChild(p);
div.appendChild(span);
const range = document.createRange();
range.setStart(p.firstChild, 0);
range.setEnd(span.firstChild, 2);
// Common ancestor of deeply nested p and sibling span is div
testing.expectEqual(div, range.commonAncestorContainer);
}
</script>
<script id=compareBoundaryPoints_constants>
{
// Test that the constants are defined
testing.expectEqual(0, Range.START_TO_START);
testing.expectEqual(1, Range.START_TO_END);
testing.expectEqual(2, Range.END_TO_END);
testing.expectEqual(3, Range.END_TO_START);
}
</script>
<script id=compareBoundaryPoints_basic>
{
const div = document.createElement('div');
const p1 = document.createElement('p');
const p2 = document.createElement('p');
p1.textContent = 'First paragraph';
p2.textContent = 'Second paragraph';
div.appendChild(p1);
div.appendChild(p2);
const range1 = document.createRange();
range1.setStart(p1.firstChild, 0);
range1.setEnd(p1.firstChild, 5);
const range2 = document.createRange();
range2.setStart(p1.firstChild, 3);
range2.setEnd(p2.firstChild, 5);
// range1 start is before range2 start
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.START_TO_START, range2));
// range1 start is before range2 end
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.START_TO_END, range2));
// range1 end is after range2 start
testing.expectEqual(1, range1.compareBoundaryPoints(Range.END_TO_START, range2));
// range1 end is before range2 end
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.END_TO_END, range2));
}
</script>
<script id=compareBoundaryPoints_same_range>
{
const p = document.createElement('p');
p.textContent = 'Content';
const range = document.createRange();
range.setStart(p.firstChild, 2);
range.setEnd(p.firstChild, 5);
// Comparing a range to itself should return 0
testing.expectEqual(0, range.compareBoundaryPoints(Range.START_TO_START, range));
testing.expectEqual(0, range.compareBoundaryPoints(Range.END_TO_END, range));
// Start is before end
testing.expectEqual(-1, range.compareBoundaryPoints(Range.START_TO_END, range));
// End is after start
testing.expectEqual(1, range.compareBoundaryPoints(Range.END_TO_START, range));
}
</script>
<script id=compareBoundaryPoints_invalid_how>
{
const p = document.createElement('p');
p.textContent = 'Test';
const range1 = document.createRange();
const range2 = document.createRange();
range1.setStart(p, 0);
range2.setStart(p, 0);
// Invalid how parameter should throw NotSupportedError
testing.expectError('NotSupportedError: Not Supported', () => {
range1.compareBoundaryPoints(4, range2);
});
testing.expectError('NotSupportedError: Not Supported', () => {
range1.compareBoundaryPoints(99, range2);
});
}
</script>
<script id=compareBoundaryPoints_different_root>
{
const p1 = document.createElement('p');
p1.textContent = 'Local';
const range1 = document.createRange();
range1.setStart(p1, 0);
range1.setEnd(p1, 1);
// Create range in different document
const foreignDoc = document.implementation.createHTMLDocument('');
const foreignP = foreignDoc.createElement('p');
foreignP.textContent = 'Foreign';
const range2 = foreignDoc.createRange();
range2.setStart(foreignP, 0);
range2.setEnd(foreignP, 1);
// Comparing ranges in different documents should throw WrongDocumentError
testing.expectError('WrongDocumentError: wrong_document_error', () => {
range1.compareBoundaryPoints(Range.START_TO_START, range2);
});
}
</script>

View File

@@ -69,6 +69,19 @@ pub fn getCollapsed(self: *const AbstractRange) bool {
self._start_offset == self._end_offset; self._start_offset == self._end_offset;
} }
pub fn getCommonAncestorContainer(self: *const AbstractRange) *Node {
// Let container be start container
var container = self._start_container;
// While container is not an inclusive ancestor of end container
while (!isInclusiveAncestorOf(container, self._end_container)) {
// Let container be container's parent
container = container.parentNode() orelse break;
}
return container;
}
pub fn isStartAfterEnd(self: *const AbstractRange) bool { pub fn isStartAfterEnd(self: *const AbstractRange) bool {
return compareBoundaryPoints( return compareBoundaryPoints(
self._start_container, self._start_container,
@@ -84,7 +97,7 @@ const BoundaryComparison = enum {
after, after,
}; };
fn compareBoundaryPoints( pub fn compareBoundaryPoints(
node_a: *Node, node_a: *Node,
offset_a: u32, offset_a: u32,
node_b: *Node, node_b: *Node,
@@ -195,6 +208,13 @@ fn isAncestorOf(potential_ancestor: *Node, node: *Node) bool {
return false; return false;
} }
fn isInclusiveAncestorOf(potential_ancestor: *Node, node: *Node) bool {
if (potential_ancestor == node) {
return true;
}
return isAncestorOf(potential_ancestor, node);
}
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(AbstractRange); pub const bridge = js.Bridge(AbstractRange);
@@ -209,4 +229,5 @@ pub const JsApi = struct {
pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{}); pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{});
pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{}); pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{});
pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{}); pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{});
pub const commonAncestorContainer = bridge.accessor(AbstractRange.getCommonAncestorContainer, null, .{});
}; };

View File

@@ -37,22 +37,35 @@ pub fn init(page: *Page) !*Range {
} }
pub fn setStart(self: *Range, node: *Node, offset: u32) !void { pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
if (offset > node.getLength()) {
return error.IndexSizeError;
}
self._proto._start_container = node; self._proto._start_container = node;
self._proto._start_offset = offset; self._proto._start_offset = offset;
// If start is now after end, collapse to start // If start is now after end, or nodes are in different trees, collapse to start
if (self._proto.isStartAfterEnd()) { const end_root = self._proto._end_container.getRootNode(null);
const start_root = node.getRootNode(null);
if (end_root != start_root or self._proto.isStartAfterEnd()) {
self._proto._end_container = self._proto._start_container; self._proto._end_container = self._proto._start_container;
self._proto._end_offset = self._proto._start_offset; self._proto._end_offset = self._proto._start_offset;
} }
} }
pub fn setEnd(self: *Range, node: *Node, offset: u32) !void { pub fn setEnd(self: *Range, node: *Node, offset: u32) !void {
// Validate offset
if (offset > node.getLength()) {
return error.IndexSizeError;
}
self._proto._end_container = node; self._proto._end_container = node;
self._proto._end_offset = offset; self._proto._end_offset = offset;
// If end is now before start, collapse to end // If end is now before start, or nodes are in different trees, collapse to end
if (self._proto.isStartAfterEnd()) { const start_root = self._proto._start_container.getRootNode(null);
const end_root = node.getRootNode(null);
if (start_root != end_root or self._proto.isStartAfterEnd()) {
self._proto._start_container = self._proto._end_container; self._proto._start_container = self._proto._end_container;
self._proto._start_offset = self._proto._end_offset; self._proto._start_offset = self._proto._end_offset;
} }
@@ -105,6 +118,181 @@ pub fn collapse(self: *Range, to_start: ?bool) void {
} }
} }
pub fn detach(_: *Range) void {
// Legacy no-op method kept for backwards compatibility
// Modern spec: "The detach() method must do nothing."
}
pub fn compareBoundaryPoints(self: *const Range, how_raw: i32, source_range: *const Range) !i16 {
// Convert how parameter per WebIDL unsigned short conversion
// This handles negative numbers and out-of-range values
const how_mod = @mod(how_raw, 65536);
const how: u16 = if (how_mod < 0) @intCast(@as(i32, how_mod) + 65536) else @intCast(how_mod);
// If how is not one of 0, 1, 2, or 3, throw NotSupportedError
if (how > 3) {
return error.NotSupported;
}
// If the two ranges' root is different, throw WrongDocumentError
const this_root = self._proto._start_container.getRootNode(null);
const source_root = source_range._proto._start_container.getRootNode(null);
if (this_root != source_root) {
return error.WrongDocument;
}
// Determine which boundary points to compare based on how parameter
const result = switch (how) {
0 => AbstractRange.compareBoundaryPoints( // START_TO_START
self._proto._start_container,
self._proto._start_offset,
source_range._proto._start_container,
source_range._proto._start_offset,
),
1 => AbstractRange.compareBoundaryPoints( // START_TO_END
self._proto._start_container,
self._proto._start_offset,
source_range._proto._end_container,
source_range._proto._end_offset,
),
2 => AbstractRange.compareBoundaryPoints( // END_TO_END
self._proto._end_container,
self._proto._end_offset,
source_range._proto._end_container,
source_range._proto._end_offset,
),
3 => AbstractRange.compareBoundaryPoints( // END_TO_START
self._proto._end_container,
self._proto._end_offset,
source_range._proto._start_container,
source_range._proto._start_offset,
),
else => unreachable,
};
return switch (result) {
.before => -1,
.equal => 0,
.after => 1,
};
}
pub fn comparePoint(self: *const Range, node: *Node, offset: u32) !i16 {
if (offset > node.getLength()) {
return error.IndexSizeError;
}
// Check if node is in a different tree than the range
const node_root = node.getRootNode(null);
const start_root = self._proto._start_container.getRootNode(null);
if (node_root != start_root) {
return error.WrongDocument;
}
// Compare point with start boundary
const cmp_start = AbstractRange.compareBoundaryPoints(
node,
offset,
self._proto._start_container,
self._proto._start_offset,
);
if (cmp_start == .before) {
return -1;
}
const cmp_end = AbstractRange.compareBoundaryPoints(
node,
offset,
self._proto._end_container,
self._proto._end_offset,
);
return if (cmp_end == .after) 1 else 0;
}
pub fn isPointInRange(self: *const Range, node: *Node, offset: u32) !bool {
// If node's root is different from the context object's root, return false
const node_root = node.getRootNode(null);
const start_root = self._proto._start_container.getRootNode(null);
if (node_root != start_root) {
return false;
}
if (node._type == .document_type) {
return error.InvalidNodeType;
}
// If offset is greater than node's length, throw IndexSizeError
if (offset > node.getLength()) {
return error.IndexSizeError;
}
// If (node, offset) is before start or after end, return false
const cmp_start = AbstractRange.compareBoundaryPoints(
node,
offset,
self._proto._start_container,
self._proto._start_offset,
);
if (cmp_start == .before) {
return false;
}
const cmp_end = AbstractRange.compareBoundaryPoints(
node,
offset,
self._proto._end_container,
self._proto._end_offset,
);
return cmp_end != .after;
}
pub fn intersectsNode(self: *const Range, node: *Node) bool {
// If node's root is different from the context object's root, return false
const node_root = node.getRootNode(null);
const start_root = self._proto._start_container.getRootNode(null);
if (node_root != start_root) {
return false;
}
// Let parent be node's parent
const parent = node.parentNode() orelse {
// If parent is null, return true
return true;
};
// Let offset be node's index
const offset = parent.getChildIndex(node) orelse {
// Should not happen if node has a parent
return false;
};
// If (parent, offset) is before end and (parent, offset + 1) is after start, return true
const before_end = AbstractRange.compareBoundaryPoints(
parent,
offset,
self._proto._end_container,
self._proto._end_offset,
);
const after_start = AbstractRange.compareBoundaryPoints(
parent,
offset + 1,
self._proto._start_container,
self._proto._start_offset,
);
if (before_end == .before and after_start == .after) {
return true;
}
// Return false
return false;
}
pub fn cloneRange(self: *const Range, page: *Page) !*Range { pub fn cloneRange(self: *const Range, page: *Page) !*Range {
const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page); const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page);
clone._proto._end_offset = self._proto._end_offset; clone._proto._end_offset = self._proto._end_offset;
@@ -308,24 +496,35 @@ pub const JsApi = struct {
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
}; };
// Constants for compareBoundaryPoints
pub const START_TO_START = bridge.property(0);
pub const START_TO_END = bridge.property(1);
pub const END_TO_END = bridge.property(2);
pub const END_TO_START = bridge.property(3);
pub const constructor = bridge.constructor(Range.init, .{}); pub const constructor = bridge.constructor(Range.init, .{});
pub const setStart = bridge.function(Range.setStart, .{}); pub const setStart = bridge.function(Range.setStart, .{ .dom_exception = true });
pub const setEnd = bridge.function(Range.setEnd, .{}); pub const setEnd = bridge.function(Range.setEnd, .{ .dom_exception = true });
pub const setStartBefore = bridge.function(Range.setStartBefore, .{}); pub const setStartBefore = bridge.function(Range.setStartBefore, .{ .dom_exception = true });
pub const setStartAfter = bridge.function(Range.setStartAfter, .{}); pub const setStartAfter = bridge.function(Range.setStartAfter, .{ .dom_exception = true });
pub const setEndBefore = bridge.function(Range.setEndBefore, .{}); pub const setEndBefore = bridge.function(Range.setEndBefore, .{ .dom_exception = true });
pub const setEndAfter = bridge.function(Range.setEndAfter, .{}); pub const setEndAfter = bridge.function(Range.setEndAfter, .{ .dom_exception = true });
pub const selectNode = bridge.function(Range.selectNode, .{}); pub const selectNode = bridge.function(Range.selectNode, .{ .dom_exception = true });
pub const selectNodeContents = bridge.function(Range.selectNodeContents, .{}); pub const selectNodeContents = bridge.function(Range.selectNodeContents, .{});
pub const collapse = bridge.function(Range.collapse, .{}); pub const collapse = bridge.function(Range.collapse, .{ .dom_exception = true });
pub const cloneRange = bridge.function(Range.cloneRange, .{}); pub const detach = bridge.function(Range.detach, .{});
pub const insertNode = bridge.function(Range.insertNode, .{}); pub const compareBoundaryPoints = bridge.function(Range.compareBoundaryPoints, .{ .dom_exception = true });
pub const deleteContents = bridge.function(Range.deleteContents, .{}); pub const comparePoint = bridge.function(Range.comparePoint, .{ .dom_exception = true });
pub const cloneContents = bridge.function(Range.cloneContents, .{}); pub const isPointInRange = bridge.function(Range.isPointInRange, .{ .dom_exception = true });
pub const extractContents = bridge.function(Range.extractContents, .{}); pub const intersectsNode = bridge.function(Range.intersectsNode, .{});
pub const surroundContents = bridge.function(Range.surroundContents, .{}); pub const cloneRange = bridge.function(Range.cloneRange, .{ .dom_exception = true });
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{}); pub const insertNode = bridge.function(Range.insertNode, .{ .dom_exception = true });
pub const toString = bridge.function(Range.toString, .{}); pub const deleteContents = bridge.function(Range.deleteContents, .{ .dom_exception = true });
pub const cloneContents = bridge.function(Range.cloneContents, .{ .dom_exception = true });
pub const extractContents = bridge.function(Range.extractContents, .{ .dom_exception = true });
pub const surroundContents = bridge.function(Range.surroundContents, .{ .dom_exception = true });
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{ .dom_exception = true });
pub const toString = bridge.function(Range.toString, .{ .dom_exception = true });
}; };
const testing = @import("../../testing.zig"); const testing = @import("../../testing.zig");