@@ -21,8 +21,9 @@ const std = @import("std");
2121const parser = @import ("../netsurf.zig" );
2222const Page = @import ("../page.zig" ).Page ;
2323
24- const NodeUnion = @import ("node.zig" ).Union ;
2524const Node = @import ("node.zig" ).Node ;
25+ const NodeUnion = @import ("node.zig" ).Union ;
26+ const DOMException = @import ("exceptions.zig" ).DOMException ;
2627
2728pub const Interfaces = .{
2829 AbstractRange ,
@@ -32,9 +33,9 @@ pub const Interfaces = .{
3233pub const AbstractRange = struct {
3334 collapsed : bool ,
3435 end_container : * parser.Node ,
35- end_offset : i32 ,
36+ end_offset : u32 ,
3637 start_container : * parser.Node ,
37- start_offset : i32 ,
38+ start_offset : u32 ,
3839
3940 pub fn updateCollapsed (self : * AbstractRange ) void {
4041 // TODO: Eventually, compare properly.
@@ -49,20 +50,21 @@ pub const AbstractRange = struct {
4950 return Node .toInterface (self .end_container );
5051 }
5152
52- pub fn get_endOffset (self : * const AbstractRange ) i32 {
53+ pub fn get_endOffset (self : * const AbstractRange ) u32 {
5354 return self .end_offset ;
5455 }
5556
5657 pub fn get_startContainer (self : * const AbstractRange ) ! NodeUnion {
5758 return Node .toInterface (self .start_container );
5859 }
5960
60- pub fn get_startOffset (self : * const AbstractRange ) i32 {
61+ pub fn get_startOffset (self : * const AbstractRange ) u32 {
6162 return self .start_offset ;
6263 }
6364};
6465
6566pub const Range = struct {
67+ pub const Exception = DOMException ;
6668 pub const prototype = * AbstractRange ;
6769
6870 proto : AbstractRange ,
@@ -82,18 +84,83 @@ pub const Range = struct {
8284 return .{ .proto = proto };
8385 }
8486
85- pub fn _setStart (self : * Range , node : * parser.Node , offset : i32 ) void {
87+ pub fn _setStart (self : * Range , node : * parser.Node , offset_ : i32 ) ! void {
88+ const relative = self ._comparePoint (node , offset_ ) catch | err | switch (err ) {
89+ error .WrongDocument = > blk : {
90+ // comparePoint doesn't check this on WrongDocument.
91+ try ensureValidOffset (node , offset_ );
92+
93+ // allow a node with a different root than the current, or
94+ // a disconnected one. Treat it as if it's "after", so that
95+ // we also update the end_offset and end_container.
96+ break :blk 1 ;
97+ },
98+ else = > return err ,
99+ };
100+
101+ const offset : u32 = @intCast (offset_ );
102+ if (relative == 1 ) {
103+ // if we're setting the node after the current start, the end must
104+ // be set too.
105+ self .proto .end_offset = offset ;
106+ self .proto .end_container = node ;
107+ }
86108 self .proto .start_container = node ;
87109 self .proto .start_offset = offset ;
88110 self .proto .updateCollapsed ();
89111 }
90112
91- pub fn _setEnd (self : * Range , node : * parser.Node , offset : i32 ) void {
113+ pub fn _setStartBefore (self : * Range , node : * parser.Node ) ! void {
114+ const parent , const index = try getParentAndIndex (node );
115+ self .proto .start_container = parent ;
116+ self .proto .start_offset = index ;
117+ }
118+
119+ pub fn _setStartAfter (self : * Range , node : * parser.Node ) ! void {
120+ const parent , const index = try getParentAndIndex (node );
121+ self .proto .start_container = parent ;
122+ self .proto .start_offset = index + 1 ;
123+ }
124+
125+ pub fn _setEnd (self : * Range , node : * parser.Node , offset_ : i32 ) ! void {
126+ const relative = self ._comparePoint (node , offset_ ) catch | err | switch (err ) {
127+ error .WrongDocument = > blk : {
128+ // comparePoint doesn't check this on WrongDocument.
129+ try ensureValidOffset (node , offset_ );
130+
131+ // allow a node with a different root than the current, or
132+ // a disconnected one. Treat it as if it's "before", so that
133+ // we also update the end_offset and end_container.
134+ break :blk -1 ;
135+ },
136+ else = > return err ,
137+ };
138+
139+ const offset : u32 = @intCast (offset_ );
140+ if (relative == -1 ) {
141+ // if we're setting the node before the current start, the start
142+ // must be
143+ self .proto .start_offset = offset ;
144+ self .proto .start_container = node ;
145+ }
146+
92147 self .proto .end_container = node ;
93148 self .proto .end_offset = offset ;
94149 self .proto .updateCollapsed ();
95150 }
96151
152+ pub fn _setEndBefore (self : * Range , node : * parser.Node ) ! void {
153+ const parent , const index = try getParentAndIndex (node );
154+ self .proto .end_container = parent ;
155+ self .proto .end_offset = index ;
156+ }
157+
158+ pub fn _setEndAfter (self : * Range , node : * parser.Node ) ! void {
159+ const parent , const index = try getParentAndIndex (node );
160+ self .proto .end_container = parent ;
161+ self .proto .end_offset = index + 1 ;
162+ }
163+
97164 pub fn _createContextualFragment (_ : * Range , fragment : []const u8 , page : * Page ) ! * parser.DocumentFragment {
98165 const document_html = page .window .document ;
99166 const document = parser .documentHTMLToDocument (document_html );
@@ -127,13 +194,159 @@ pub const Range = struct {
127194 self .proto .updateCollapsed ();
128195 }
129196
197+ // creates a copy
198+ pub fn _cloneRange (self : * const Range ) Range {
199+ return .{
200+ .proto = .{
201+ .collapsed = self .proto .collapsed ,
202+ .end_container = self .proto .end_container ,
203+ .end_offset = self .proto .end_offset ,
204+ .start_container = self .proto .start_container ,
205+ .start_offset = self .proto .start_offset ,
206+ },
207+ };
208+ }
209+
210+ pub fn _comparePoint (self : * const Range , ref_node : * parser.Node , offset_ : i32 ) ! i32 {
211+ const start = self .proto .start_container ;
212+ if (try parser .nodeGetRootNode (start ) != try parser .nodeGetRootNode (ref_node )) {
213+ // WPT really wants this error to be first. Later, when we check
214+ // if the relative position is 'disconnected', it'll also catch this
215+ // case, but WPT will complain because it sometimes also sends
216+ // invalid offsets, and it wants WrongDocument to be raised.
217+ return error .WrongDocument ;
218+ }
219+
220+ if (try parser .nodeType (ref_node ) == .document_type ) {
221+ return error .InvalidNodeType ;
222+ }
223+
224+ try ensureValidOffset (ref_node , offset_ );
225+
226+ const offset : u32 = @intCast (offset_ );
227+ if (ref_node == start ) {
228+ // This is a simple and common case, where the reference node and
229+ // our start node are the same, so we just have to compare the offsets
230+ const start_offset = self .proto .start_offset ;
231+ if (offset == start_offset ) {
232+ return 0 ;
233+ }
234+ return if (offset < start_offset ) -1 else 1 ;
235+ }
236+
237+ // We're probably comparing two different nodes. "Probably", because the
238+ // above case on considered the offset if the two nodes were the same
239+ // as-is. They could still be the same here, if we first consider the
240+ // offset.
241+ // Furthermore, as far as I can tell, if either or both nodes are textual,
242+ // then we're doing a node comparison of their parents. This kind of
243+ // makes sense, one/two text nodes which aren't the same, can only
244+ // be positionally compared in relation to it/their parents.
245+
246+ const adjusted_start = try getNodeForCompare (start , self .proto .start_offset );
247+ const adjusted_ref_node = try getNodeForCompare (ref_node , offset );
248+
249+ const relative = try Node ._compareDocumentPosition (adjusted_start , adjusted_ref_node );
250+
251+ if (relative & @intFromEnum (parser .DocumentPosition .disconnected ) == @intFromEnum (parser .DocumentPosition .disconnected )) {
252+ return error .WrongDocument ;
253+ }
254+
255+ if (relative & @intFromEnum (parser .DocumentPosition .preceding ) == @intFromEnum (parser .DocumentPosition .preceding )) {
256+ return -1 ;
257+ }
258+
259+ if (relative & @intFromEnum (parser .DocumentPosition .following ) == @intFromEnum (parser .DocumentPosition .following )) {
260+ return 1 ;
261+ }
262+
263+ // DUNNO
264+ // unreachable??
265+ return 0 ;
266+ }
267+
268+ pub fn _isPointInRange (self : * const Range , ref_node : * parser.Node , offset_ : i32 ) ! bool {
269+ return self ._comparePoint (ref_node , offset_ ) catch | err | switch (err ) {
270+ error .WrongDocument = > return false ,
271+ else = > return err ,
272+ } == 0 ;
273+ }
274+
130275 // The Range.detach() method does nothing. It used to disable the Range
131276 // object and enable the browser to release associated resources. The
132277 // method has been kept for compatibility.
133278 // https://developer.mozilla.org/en-US/docs/Web/API/Range/detach
134279 pub fn _detach (_ : * Range ) void {}
135280};
136281
282+ fn getNodeForCompare (node : * parser.Node , offset : u32 ) ! * parser.Node {
283+ if (try isTextual (node )) {
284+ // when we're comparing a text node to another node which is not the same
285+ // then we're really compare the position of the parent. It doesn't
286+ // matter if the other node is a text node itself or not, all that matters
287+ // is we're sure it isn't the same text node (because if they are the
288+ // same text node, then we're comparing the offset (character position)
289+ // of the text node)
290+
291+ // not sure this is the correct error
292+ return (try parser .nodeParentNode (node )) orelse return error .WrongDocument ;
293+ }
294+ if (offset == 0 ) {
295+ return node ;
296+ }
297+
298+ const children = try parser .nodeGetChildNodes (node );
299+
300+ // not sure about this error
301+ // - 1 because, while the offset is 0 based, 0 seems to represent the parent
302+ return (try parser .nodeListItem (children , offset - 1 )) orelse error .IndexSize ;
303+ }
304+
305+ fn ensureValidOffset (node : * parser.Node , offset : i32 ) ! void {
306+ if (offset < 0 ) {
307+ return error .IndexSize ;
308+ }
309+
310+ // not >= because 0 seems to represent the node itself.
311+ if (offset > try nodeLength (node )) {
312+ return error .IndexSize ;
313+ }
314+ }
315+
316+ fn nodeLength (node : * parser.Node ) ! usize {
317+ switch (try isTextual (node )) {
318+ true = > return ((try parser .nodeTextContent (node )) orelse "" ).len ,
319+ false = > {
320+ const children = try parser .nodeGetChildNodes (node );
321+ return @intCast (try parser .nodeListLength (children ));
322+ },
323+ }
324+ }
325+
326+ fn isTextual (node : * parser.Node ) ! bool {
327+ return switch (try parser .nodeType (node )) {
328+ .text , .comment , .cdata_section = > true ,
329+ else = > false ,
330+ };
331+ }
332+
333+ fn getParentAndIndex (child : * parser.Node ) ! struct { * parser .Node , u32 } {
334+ const parent = (try parser .nodeParentNode (child )) orelse return error .InvalidNodeType ;
335+ const children = try parser .nodeGetChildNodes (parent );
336+ const ln = try parser .nodeListLength (children );
337+ var i : u32 = 0 ;
338+ while (i < ln ) {
339+ defer i += 1 ;
340+ const c = try parser .nodeListItem (children , i ) orelse continue ;
341+ if (c == child ) {
342+ return .{ parent , i };
343+ }
344+ }
345+
346+ // should not be possible to reach this point
347+ return error .InvalidNodeType ;
348+ }
349+
137350const testing = @import ("../../testing.zig" );
138351test "Browser.Range" {
139352 var runner = try testing .jsRunner (testing .tracking_allocator , .{});
0 commit comments