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);
|
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>
|
||||||
|
|||||||
@@ -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, .{});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user