mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
add AbstractRange
This commit is contained in:
@@ -36,6 +36,7 @@ const Document = @import("webapi/Document.zig");
|
|||||||
const EventTarget = @import("webapi/EventTarget.zig");
|
const EventTarget = @import("webapi/EventTarget.zig");
|
||||||
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
|
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
|
||||||
const Blob = @import("webapi/Blob.zig");
|
const Blob = @import("webapi/Blob.zig");
|
||||||
|
const AbstractRange = @import("webapi/AbstractRange.zig");
|
||||||
|
|
||||||
const Factory = @This();
|
const Factory = @This();
|
||||||
_page: *Page,
|
_page: *Page,
|
||||||
@@ -224,6 +225,22 @@ pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
|
|||||||
return chain.get(1);
|
return chain.get(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn abstractRange(self: *Factory, child: anytype, page: *Page) !*@TypeOf(child) {
|
||||||
|
const allocator = self._slab.allocator();
|
||||||
|
const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(allocator);
|
||||||
|
|
||||||
|
const doc = page.document.asNode();
|
||||||
|
chain.set(0, AbstractRange{
|
||||||
|
._type = unionInit(AbstractRange.Type, chain.get(1)),
|
||||||
|
._end_offset = 0,
|
||||||
|
._start_offset = 0,
|
||||||
|
._end_container = doc,
|
||||||
|
._start_container = doc,
|
||||||
|
});
|
||||||
|
chain.setLeaf(1, child);
|
||||||
|
return chain.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
|
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||||
const allocator = self._slab.allocator();
|
const allocator = self._slab.allocator();
|
||||||
return try AutoPrototypeChain(
|
return try AutoPrototypeChain(
|
||||||
|
|||||||
@@ -520,6 +520,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/DOMRect.zig"),
|
@import("../webapi/DOMRect.zig"),
|
||||||
@import("../webapi/DOMParser.zig"),
|
@import("../webapi/DOMParser.zig"),
|
||||||
@import("../webapi/XMLSerializer.zig"),
|
@import("../webapi/XMLSerializer.zig"),
|
||||||
|
@import("../webapi/AbstractRange.zig"),
|
||||||
@import("../webapi/Range.zig"),
|
@import("../webapi/Range.zig"),
|
||||||
@import("../webapi/NodeFilter.zig"),
|
@import("../webapi/NodeFilter.zig"),
|
||||||
@import("../webapi/Element.zig"),
|
@import("../webapi/Element.zig"),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
testing.expectEqual('object', typeof range);
|
testing.expectEqual('object', typeof range);
|
||||||
testing.expectEqual(true, range instanceof Range);
|
testing.expectEqual(true, range instanceof Range);
|
||||||
|
testing.expectEqual(true, range instanceof AbstractRange);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
212
src/browser/webapi/AbstractRange.zig
Normal file
212
src/browser/webapi/AbstractRange.zig
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const js = @import("../js/js.zig");
|
||||||
|
|
||||||
|
const Node = @import("Node.zig");
|
||||||
|
const Range = @import("Range.zig");
|
||||||
|
|
||||||
|
const AbstractRange = @This();
|
||||||
|
|
||||||
|
const _prototype_root = true;
|
||||||
|
|
||||||
|
_type: Type,
|
||||||
|
|
||||||
|
_end_offset: u32,
|
||||||
|
_start_offset: u32,
|
||||||
|
_end_container: *Node,
|
||||||
|
_start_container: *Node,
|
||||||
|
|
||||||
|
pub const Type = union(enum) {
|
||||||
|
range: *Range,
|
||||||
|
// TODO: static_range: *StaticRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn as(self: *AbstractRange, comptime T: type) *T {
|
||||||
|
return self.is(T).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is(self: *AbstractRange, comptime T: type) ?*T {
|
||||||
|
switch (self._type) {
|
||||||
|
.range => |r| return if (T == Range) r else null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getStartContainer(self: *const AbstractRange) *Node {
|
||||||
|
return self._start_container;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getStartOffset(self: *const AbstractRange) u32 {
|
||||||
|
return self._start_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getEndContainer(self: *const AbstractRange) *Node {
|
||||||
|
return self._end_container;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getEndOffset(self: *const AbstractRange) u32 {
|
||||||
|
return self._end_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCollapsed(self: *const AbstractRange) bool {
|
||||||
|
return self._start_container == self._end_container and
|
||||||
|
self._start_offset == self._end_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isStartAfterEnd(self: *const AbstractRange) bool {
|
||||||
|
return compareBoundaryPoints(
|
||||||
|
self._start_container,
|
||||||
|
self._start_offset,
|
||||||
|
self._end_container,
|
||||||
|
self._end_offset,
|
||||||
|
) == .after;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoundaryComparison = enum {
|
||||||
|
before,
|
||||||
|
equal,
|
||||||
|
after,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn compareBoundaryPoints(
|
||||||
|
node_a: *Node,
|
||||||
|
offset_a: u32,
|
||||||
|
node_b: *Node,
|
||||||
|
offset_b: u32,
|
||||||
|
) BoundaryComparison {
|
||||||
|
// If same container, just compare offsets
|
||||||
|
if (node_a == node_b) {
|
||||||
|
if (offset_a < offset_b) return .before;
|
||||||
|
if (offset_a > offset_b) return .after;
|
||||||
|
return .equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if one contains the other
|
||||||
|
if (isAncestorOf(node_a, node_b)) {
|
||||||
|
// A contains B, so A's position comes before B
|
||||||
|
// But we need to check if the offset in A comes after B
|
||||||
|
var child = node_b;
|
||||||
|
var parent = child.parentNode();
|
||||||
|
while (parent) |p| {
|
||||||
|
if (p == node_a) {
|
||||||
|
const child_index = p.getChildIndex(child) orelse unreachable;
|
||||||
|
if (offset_a <= child_index) {
|
||||||
|
return .before;
|
||||||
|
}
|
||||||
|
return .after;
|
||||||
|
}
|
||||||
|
child = p;
|
||||||
|
parent = p.parentNode();
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAncestorOf(node_b, node_a)) {
|
||||||
|
// B contains A, so B's position comes before A
|
||||||
|
var child = node_a;
|
||||||
|
var parent = child.parentNode();
|
||||||
|
while (parent) |p| {
|
||||||
|
if (p == node_b) {
|
||||||
|
const child_index = p.getChildIndex(child) orelse unreachable;
|
||||||
|
if (child_index < offset_b) {
|
||||||
|
return .before;
|
||||||
|
}
|
||||||
|
return .after;
|
||||||
|
}
|
||||||
|
child = p;
|
||||||
|
parent = p.parentNode();
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither contains the other, find their relative position in tree order
|
||||||
|
// Walk up from A to find all ancestors
|
||||||
|
var current = node_a;
|
||||||
|
var a_count: usize = 0;
|
||||||
|
var a_ancestors: [64]*Node = undefined;
|
||||||
|
while (a_count < 64) {
|
||||||
|
a_ancestors[a_count] = current;
|
||||||
|
a_count += 1;
|
||||||
|
current = current.parentNode() orelse break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk up from B and find first common ancestor
|
||||||
|
current = node_b;
|
||||||
|
while (current.parentNode()) |parent| {
|
||||||
|
for (a_ancestors[0..a_count]) |ancestor| {
|
||||||
|
if (ancestor != parent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found common ancestor
|
||||||
|
// Now compare positions of the children in this ancestor
|
||||||
|
const a_child = blk: {
|
||||||
|
var node = node_a;
|
||||||
|
while (node.parentNode()) |p| {
|
||||||
|
if (p == parent) break :blk node;
|
||||||
|
node = p;
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
};
|
||||||
|
const b_child = current;
|
||||||
|
|
||||||
|
const a_index = parent.getChildIndex(a_child) orelse unreachable;
|
||||||
|
const b_index = parent.getChildIndex(b_child) orelse unreachable;
|
||||||
|
|
||||||
|
if (a_index < b_index) {
|
||||||
|
return .before;
|
||||||
|
}
|
||||||
|
if (a_index > b_index) {
|
||||||
|
return .after;
|
||||||
|
}
|
||||||
|
return .equal;
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should not reach here if nodes are in the same tree
|
||||||
|
return .before;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isAncestorOf(potential_ancestor: *Node, node: *Node) bool {
|
||||||
|
var current = node.parentNode();
|
||||||
|
while (current) |parent| {
|
||||||
|
if (parent == potential_ancestor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current = parent.parentNode();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(AbstractRange);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "AbstractRange";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{});
|
||||||
|
pub const startOffset = bridge.accessor(AbstractRange.getStartOffset, null, .{});
|
||||||
|
pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{});
|
||||||
|
pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{});
|
||||||
|
pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{});
|
||||||
|
};
|
||||||
@@ -22,65 +22,39 @@ const js = @import("../js/js.zig");
|
|||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
const DocumentFragment = @import("DocumentFragment.zig");
|
const DocumentFragment = @import("DocumentFragment.zig");
|
||||||
|
const AbstractRange = @import("AbstractRange.zig");
|
||||||
|
|
||||||
const Range = @This();
|
const Range = @This();
|
||||||
|
|
||||||
_end_offset: u32,
|
_proto: *AbstractRange,
|
||||||
_start_offset: u32,
|
|
||||||
_end_container: *Node,
|
pub fn asAbstractRange(self: *Range) *AbstractRange {
|
||||||
_start_container: *Node,
|
return self._proto;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(page: *Page) !*Range {
|
pub fn init(page: *Page) !*Range {
|
||||||
// Per spec, a new range starts collapsed at the document's first position
|
return page._factory.abstractRange(Range{._proto = undefined}, page);
|
||||||
const doc = page.document.asNode();
|
|
||||||
return page._factory.create(Range{
|
|
||||||
._end_offset = 0,
|
|
||||||
._start_offset = 0,
|
|
||||||
._end_container = doc,
|
|
||||||
._start_container = doc,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getStartContainer(self: *const Range) *Node {
|
|
||||||
return self._start_container;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getStartOffset(self: *const Range) u32 {
|
|
||||||
return self._start_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getEndContainer(self: *const Range) *Node {
|
|
||||||
return self._end_container;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getEndOffset(self: *const Range) u32 {
|
|
||||||
return self._end_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getCollapsed(self: *const Range) bool {
|
|
||||||
return self._start_container == self._end_container and
|
|
||||||
self._start_offset == self._end_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
||||||
self._start_container = node;
|
self._proto._start_container = node;
|
||||||
self._start_offset = offset;
|
self._proto._start_offset = offset;
|
||||||
|
|
||||||
// If start is now after end, collapse to start
|
// If start is now after end, collapse to start
|
||||||
if (self.isStartAfterEnd()) {
|
if (self._proto.isStartAfterEnd()) {
|
||||||
self._end_container = self._start_container;
|
self._proto._end_container = self._proto._start_container;
|
||||||
self._end_offset = self._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 {
|
||||||
self._end_container = node;
|
self._proto._end_container = node;
|
||||||
self._end_offset = offset;
|
self._proto._end_offset = offset;
|
||||||
|
|
||||||
// If end is now before start, collapse to end
|
// If end is now before start, collapse to end
|
||||||
if (self.isStartAfterEnd()) {
|
if (self._proto.isStartAfterEnd()) {
|
||||||
self._start_container = self._end_container;
|
self._proto._start_container = self._proto._end_container;
|
||||||
self._start_offset = self._end_offset;
|
self._proto._start_offset = self._proto._end_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,27 +97,27 @@ pub fn selectNodeContents(self: *Range, node: *Node) !void {
|
|||||||
|
|
||||||
pub fn collapse(self: *Range, to_start: ?bool) void {
|
pub fn collapse(self: *Range, to_start: ?bool) void {
|
||||||
if (to_start orelse true) {
|
if (to_start orelse true) {
|
||||||
self._end_container = self._start_container;
|
self._proto._end_container = self._proto._start_container;
|
||||||
self._end_offset = self._start_offset;
|
self._proto._end_offset = self._proto._start_offset;
|
||||||
} else {
|
} else {
|
||||||
self._start_container = self._end_container;
|
self._proto._start_container = self._proto._end_container;
|
||||||
self._start_offset = self._end_offset;
|
self._proto._start_offset = self._proto._end_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
||||||
return page._factory.create(Range{
|
const clone = try page._factory.abstractRange(Range{._proto = undefined}, page);
|
||||||
._end_offset = self._end_offset,
|
clone._proto._end_offset = self._proto._end_offset;
|
||||||
._start_offset = self._start_offset,
|
clone._proto._start_offset = self._proto._start_offset;
|
||||||
._end_container = self._end_container,
|
clone._proto._end_container = self._proto._end_container;
|
||||||
._start_container = self._start_container,
|
clone._proto._start_container = self._proto._start_container;
|
||||||
});
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
||||||
// Insert node at the start of the range
|
// Insert node at the start of the range
|
||||||
const container = self._start_container;
|
const container = self._proto._start_container;
|
||||||
const offset = self._start_offset;
|
const offset = self._proto._start_offset;
|
||||||
|
|
||||||
if (container.is(Node.CData)) |_| {
|
if (container.is(Node.CData)) |_| {
|
||||||
// If container is a text node, we need to split it
|
// If container is a text node, we need to split it
|
||||||
@@ -175,33 +149,33 @@ pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update range to be after the inserted node
|
// Update range to be after the inserted node
|
||||||
if (self._start_container == self._end_container) {
|
if (self._proto._start_container == self._proto._end_container) {
|
||||||
self._end_offset += 1;
|
self._proto._end_offset += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deleteContents(self: *Range, page: *Page) !void {
|
pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||||
if (self.getCollapsed()) {
|
if (self._proto.getCollapsed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple case: same container
|
// Simple case: same container
|
||||||
if (self._start_container == self._end_container) {
|
if (self._proto._start_container == self._proto._end_container) {
|
||||||
if (self._start_container.is(Node.CData)) |_| {
|
if (self._proto._start_container.is(Node.CData)) |_| {
|
||||||
// Delete part of text node
|
// Delete part of text node
|
||||||
const text_data = self._start_container.getData();
|
const text_data = self._proto._start_container.getData();
|
||||||
const new_text = try std.mem.concat(
|
const new_text = try std.mem.concat(
|
||||||
page.arena,
|
page.arena,
|
||||||
u8,
|
u8,
|
||||||
&.{ text_data[0..self._start_offset], text_data[self._end_offset..] },
|
&.{ text_data[0..self._proto._start_offset], text_data[self._proto._end_offset..] },
|
||||||
);
|
);
|
||||||
self._start_container.setData(new_text);
|
self._proto._start_container.setData(new_text);
|
||||||
} else {
|
} else {
|
||||||
// Delete child nodes in range
|
// Delete child nodes in range
|
||||||
var offset = self._start_offset;
|
var offset = self._proto._start_offset;
|
||||||
while (offset < self._end_offset) : (offset += 1) {
|
while (offset < self._proto._end_offset) : (offset += 1) {
|
||||||
if (self._start_container.getChildAt(self._start_offset)) |child| {
|
if (self._proto._start_container.getChildAt(self._proto._start_offset)) |child| {
|
||||||
_ = try self._start_container.removeChild(child, page);
|
_ = try self._proto._start_container.removeChild(child, page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,23 +191,23 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
|||||||
pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||||
const fragment = try DocumentFragment.init(page);
|
const fragment = try DocumentFragment.init(page);
|
||||||
|
|
||||||
if (self.getCollapsed()) return fragment;
|
if (self._proto.getCollapsed()) return fragment;
|
||||||
|
|
||||||
// Simple case: same container
|
// Simple case: same container
|
||||||
if (self._start_container == self._end_container) {
|
if (self._proto._start_container == self._proto._end_container) {
|
||||||
if (self._start_container.is(Node.CData)) |_| {
|
if (self._proto._start_container.is(Node.CData)) |_| {
|
||||||
// Clone part of text node
|
// Clone part of text node
|
||||||
const text_data = self._start_container.getData();
|
const text_data = self._proto._start_container.getData();
|
||||||
if (self._start_offset < text_data.len and self._end_offset <= text_data.len) {
|
if (self._proto._start_offset < text_data.len and self._proto._end_offset <= text_data.len) {
|
||||||
const cloned_text = text_data[self._start_offset..self._end_offset];
|
const cloned_text = text_data[self._proto._start_offset..self._proto._end_offset];
|
||||||
const text_node = try page.createTextNode(cloned_text);
|
const text_node = try page.createTextNode(cloned_text);
|
||||||
_ = try fragment.asNode().appendChild(text_node, page);
|
_ = try fragment.asNode().appendChild(text_node, page);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Clone child nodes in range
|
// Clone child nodes in range
|
||||||
var offset = self._start_offset;
|
var offset = self._proto._start_offset;
|
||||||
while (offset < self._end_offset) : (offset += 1) {
|
while (offset < self._proto._end_offset) : (offset += 1) {
|
||||||
if (self._start_container.getChildAt(offset)) |child| {
|
if (self._proto._start_container.getChildAt(offset)) |child| {
|
||||||
const cloned = try child.cloneNode(true, page);
|
const cloned = try child.cloneNode(true, page);
|
||||||
_ = try fragment.asNode().appendChild(cloned, page);
|
_ = try fragment.asNode().appendChild(cloned, page);
|
||||||
}
|
}
|
||||||
@@ -265,7 +239,7 @@ pub fn surroundContents(self: *Range, new_parent: *Node, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Page) !*DocumentFragment {
|
pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Page) !*DocumentFragment {
|
||||||
var context_node = self._start_container;
|
var context_node = self._proto._start_container;
|
||||||
|
|
||||||
// If start container is a text node, use its parent as context
|
// If start container is a text node, use its parent as context
|
||||||
if (context_node.is(Node.CData)) |_| {
|
if (context_node.is(Node.CData)) |_| {
|
||||||
@@ -306,15 +280,15 @@ pub fn toString(self: *const Range, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn writeTextContent(self: *const Range, writer: *std.Io.Writer) !void {
|
fn writeTextContent(self: *const Range, writer: *std.Io.Writer) !void {
|
||||||
if (self.getCollapsed()) {
|
if (self._proto.getCollapsed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self._start_container == self._end_container) {
|
if (self._proto._start_container == self._proto._end_container) {
|
||||||
if (self._start_container.is(Node.CData)) |cdata| {
|
if (self._proto._start_container.is(Node.CData)) |cdata| {
|
||||||
const data = cdata.getData();
|
const data = cdata.getData();
|
||||||
if (self._start_offset < data.len and self._end_offset <= data.len) {
|
if (self._proto._start_offset < data.len and self._proto._end_offset <= data.len) {
|
||||||
try writer.writeAll(data[self._start_offset..self._end_offset]);
|
try writer.writeAll(data[self._proto._start_offset..self._proto._end_offset]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For elements, would need to iterate children
|
// For elements, would need to iterate children
|
||||||
@@ -325,134 +299,6 @@ fn writeTextContent(self: *const Range, writer: *std.Io.Writer) !void {
|
|||||||
// For now, just return empty
|
// For now, just return empty
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isStartAfterEnd(self: *const Range) bool {
|
|
||||||
return compareBoundaryPoints(
|
|
||||||
self._start_container,
|
|
||||||
self._start_offset,
|
|
||||||
self._end_container,
|
|
||||||
self._end_offset,
|
|
||||||
) == .after;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BoundaryComparison = enum {
|
|
||||||
before,
|
|
||||||
equal,
|
|
||||||
after,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Compare two boundary points in tree order
|
|
||||||
/// Returns whether (nodeA, offsetA) is before/equal/after (nodeB, offsetB)
|
|
||||||
fn compareBoundaryPoints(
|
|
||||||
node_a: *Node,
|
|
||||||
offset_a: u32,
|
|
||||||
node_b: *Node,
|
|
||||||
offset_b: u32,
|
|
||||||
) BoundaryComparison {
|
|
||||||
// If same container, just compare offsets
|
|
||||||
if (node_a == node_b) {
|
|
||||||
if (offset_a < offset_b) return .before;
|
|
||||||
if (offset_a > offset_b) return .after;
|
|
||||||
return .equal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if one contains the other
|
|
||||||
if (isAncestorOf(node_a, node_b)) {
|
|
||||||
// A contains B, so A's position comes before B
|
|
||||||
// But we need to check if the offset in A comes after B
|
|
||||||
var child = node_b;
|
|
||||||
var parent = child.parentNode();
|
|
||||||
while (parent) |p| {
|
|
||||||
if (p == node_a) {
|
|
||||||
const child_index = p.getChildIndex(child) orelse unreachable;
|
|
||||||
if (offset_a <= child_index) {
|
|
||||||
return .before;
|
|
||||||
}
|
|
||||||
return .after;
|
|
||||||
}
|
|
||||||
child = p;
|
|
||||||
parent = p.parentNode();
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAncestorOf(node_b, node_a)) {
|
|
||||||
// B contains A, so B's position comes before A
|
|
||||||
var child = node_a;
|
|
||||||
var parent = child.parentNode();
|
|
||||||
while (parent) |p| {
|
|
||||||
if (p == node_b) {
|
|
||||||
const child_index = p.getChildIndex(child) orelse unreachable;
|
|
||||||
if (child_index < offset_b) {
|
|
||||||
return .before;
|
|
||||||
}
|
|
||||||
return .after;
|
|
||||||
}
|
|
||||||
child = p;
|
|
||||||
parent = p.parentNode();
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Neither contains the other, find their relative position in tree order
|
|
||||||
// Walk up from A to find all ancestors
|
|
||||||
var current = node_a;
|
|
||||||
var a_count: usize = 0;
|
|
||||||
var a_ancestors: [64]*Node = undefined;
|
|
||||||
while (a_count < 64) {
|
|
||||||
a_ancestors[a_count] = current;
|
|
||||||
a_count += 1;
|
|
||||||
current = current.parentNode() orelse break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk up from B and find first common ancestor
|
|
||||||
current = node_b;
|
|
||||||
while (current.parentNode()) |parent| {
|
|
||||||
for (a_ancestors[0..a_count]) |ancestor| {
|
|
||||||
if (ancestor != parent) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found common ancestor
|
|
||||||
// Now compare positions of the children in this ancestor
|
|
||||||
const a_child = blk: {
|
|
||||||
var node = node_a;
|
|
||||||
while (node.parentNode()) |p| {
|
|
||||||
if (p == parent) break :blk node;
|
|
||||||
node = p;
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
};
|
|
||||||
const b_child = current;
|
|
||||||
|
|
||||||
const a_index = parent.getChildIndex(a_child) orelse unreachable;
|
|
||||||
const b_index = parent.getChildIndex(b_child) orelse unreachable;
|
|
||||||
|
|
||||||
if (a_index < b_index) {
|
|
||||||
return .before;
|
|
||||||
}
|
|
||||||
if (a_index > b_index) {
|
|
||||||
return .after;
|
|
||||||
}
|
|
||||||
return .equal;
|
|
||||||
}
|
|
||||||
current = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should not reach here if nodes are in the same tree
|
|
||||||
return .before;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isAncestorOf(potential_ancestor: *Node, node: *Node) bool {
|
|
||||||
var current = node.parentNode();
|
|
||||||
while (current) |parent| {
|
|
||||||
if (parent == potential_ancestor) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
current = parent.parentNode();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(Range);
|
pub const bridge = js.Bridge(Range);
|
||||||
|
|
||||||
@@ -463,11 +309,6 @@ pub const JsApi = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const constructor = bridge.constructor(Range.init, .{});
|
pub const constructor = bridge.constructor(Range.init, .{});
|
||||||
pub const startContainer = bridge.accessor(Range.getStartContainer, null, .{});
|
|
||||||
pub const startOffset = bridge.accessor(Range.getStartOffset, null, .{});
|
|
||||||
pub const endContainer = bridge.accessor(Range.getEndContainer, null, .{});
|
|
||||||
pub const endOffset = bridge.accessor(Range.getEndOffset, null, .{});
|
|
||||||
pub const collapsed = bridge.accessor(Range.getCollapsed, null, .{});
|
|
||||||
pub const setStart = bridge.function(Range.setStart, .{});
|
pub const setStart = bridge.function(Range.setStart, .{});
|
||||||
pub const setEnd = bridge.function(Range.setEnd, .{});
|
pub const setEnd = bridge.function(Range.setEnd, .{});
|
||||||
pub const setStartBefore = bridge.function(Range.setStartBefore, .{});
|
pub const setStartBefore = bridge.function(Range.setStartBefore, .{});
|
||||||
|
|||||||
Reference in New Issue
Block a user