mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Improve Range, adding missing functions and more validation
This commit is contained in:
@@ -376,3 +376,447 @@
|
||||
testing.expectEqual('Bold', fragment.childNodes[0].textContent);
|
||||
}
|
||||
</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>
|
||||
|
||||
@@ -69,6 +69,19 @@ pub fn getCollapsed(self: *const AbstractRange) bool {
|
||||
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 {
|
||||
return compareBoundaryPoints(
|
||||
self._start_container,
|
||||
@@ -84,7 +97,7 @@ const BoundaryComparison = enum {
|
||||
after,
|
||||
};
|
||||
|
||||
fn compareBoundaryPoints(
|
||||
pub fn compareBoundaryPoints(
|
||||
node_a: *Node,
|
||||
offset_a: u32,
|
||||
node_b: *Node,
|
||||
@@ -195,6 +208,13 @@ fn isAncestorOf(potential_ancestor: *Node, node: *Node) bool {
|
||||
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 bridge = js.Bridge(AbstractRange);
|
||||
|
||||
@@ -209,4 +229,5 @@ pub const JsApi = struct {
|
||||
pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{});
|
||||
pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{});
|
||||
pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{});
|
||||
pub const commonAncestorContainer = bridge.accessor(AbstractRange.getCommonAncestorContainer, null, .{});
|
||||
};
|
||||
|
||||
@@ -37,22 +37,35 @@ pub fn init(page: *Page) !*Range {
|
||||
}
|
||||
|
||||
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_offset = offset;
|
||||
|
||||
// If start is now after end, collapse to start
|
||||
if (self._proto.isStartAfterEnd()) {
|
||||
// If start is now after end, or nodes are in different trees, collapse to start
|
||||
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_offset = self._proto._start_offset;
|
||||
}
|
||||
}
|
||||
|
||||
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_offset = offset;
|
||||
|
||||
// If end is now before start, collapse to end
|
||||
if (self._proto.isStartAfterEnd()) {
|
||||
// If end is now before start, or nodes are in different trees, collapse to end
|
||||
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_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 {
|
||||
const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page);
|
||||
clone._proto._end_offset = self._proto._end_offset;
|
||||
@@ -308,24 +496,35 @@ pub const JsApi = struct {
|
||||
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 setStart = bridge.function(Range.setStart, .{});
|
||||
pub const setEnd = bridge.function(Range.setEnd, .{});
|
||||
pub const setStartBefore = bridge.function(Range.setStartBefore, .{});
|
||||
pub const setStartAfter = bridge.function(Range.setStartAfter, .{});
|
||||
pub const setEndBefore = bridge.function(Range.setEndBefore, .{});
|
||||
pub const setEndAfter = bridge.function(Range.setEndAfter, .{});
|
||||
pub const selectNode = bridge.function(Range.selectNode, .{});
|
||||
pub const setStart = bridge.function(Range.setStart, .{ .dom_exception = true });
|
||||
pub const setEnd = bridge.function(Range.setEnd, .{ .dom_exception = true });
|
||||
pub const setStartBefore = bridge.function(Range.setStartBefore, .{ .dom_exception = true });
|
||||
pub const setStartAfter = bridge.function(Range.setStartAfter, .{ .dom_exception = true });
|
||||
pub const setEndBefore = bridge.function(Range.setEndBefore, .{ .dom_exception = true });
|
||||
pub const setEndAfter = bridge.function(Range.setEndAfter, .{ .dom_exception = true });
|
||||
pub const selectNode = bridge.function(Range.selectNode, .{ .dom_exception = true });
|
||||
pub const selectNodeContents = bridge.function(Range.selectNodeContents, .{});
|
||||
pub const collapse = bridge.function(Range.collapse, .{});
|
||||
pub const cloneRange = bridge.function(Range.cloneRange, .{});
|
||||
pub const insertNode = bridge.function(Range.insertNode, .{});
|
||||
pub const deleteContents = bridge.function(Range.deleteContents, .{});
|
||||
pub const cloneContents = bridge.function(Range.cloneContents, .{});
|
||||
pub const extractContents = bridge.function(Range.extractContents, .{});
|
||||
pub const surroundContents = bridge.function(Range.surroundContents, .{});
|
||||
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{});
|
||||
pub const toString = bridge.function(Range.toString, .{});
|
||||
pub const collapse = bridge.function(Range.collapse, .{ .dom_exception = true });
|
||||
pub const detach = bridge.function(Range.detach, .{});
|
||||
pub const compareBoundaryPoints = bridge.function(Range.compareBoundaryPoints, .{ .dom_exception = true });
|
||||
pub const comparePoint = bridge.function(Range.comparePoint, .{ .dom_exception = true });
|
||||
pub const isPointInRange = bridge.function(Range.isPointInRange, .{ .dom_exception = true });
|
||||
pub const intersectsNode = bridge.function(Range.intersectsNode, .{});
|
||||
pub const cloneRange = bridge.function(Range.cloneRange, .{ .dom_exception = true });
|
||||
pub const insertNode = bridge.function(Range.insertNode, .{ .dom_exception = true });
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user