@@ -215,6 +215,9 @@ pub const HTMLDocument = struct {
215215 return "" ;
216216 }
217217
218+ // Returns the topmost Element at the specified coordinates (relative to the viewport).
219+ // Since LightPanda requires the client to know what they are clicking on we do not return the underlying element at this moment
220+ // This can currenty only happen if the first pixel is click without having rendered any element. This will change when css properties are supported.
218221 // This returns an ElementUnion instead of a *Parser.Element in case the element somehow hasn't passed through the js runtime yet.
219222 pub fn _elementFromPoint (_ : * parser.DocumentHTML , x : f32 , y : f32 , state : * SessionState ) ! ? ElementUnion {
220223 const ix : i32 = @intFromFloat (@floor (x ));
@@ -224,18 +227,31 @@ pub const HTMLDocument = struct {
224227 return try Element .toInterface (element );
225228 }
226229
230+ // Returns an array of all elements at the specified coordinates (relative to the viewport). The elements are ordered from the topmost to the bottommost box of the viewport.
227231 pub fn _elementsFromPoint (_ : * parser.DocumentHTML , x : f32 , y : f32 , state : * SessionState ) ! []ElementUnion {
228232 const ix : i32 = @intFromFloat (@floor (x ));
229233 const iy : i32 = @intFromFloat (@floor (y ));
230234 const element = state .renderer .getElementAtPosition (ix , iy ) orelse return &.{};
231235 // TODO if pointer-events set to none the underlying element should be returned (parser.documentGetDocumentElement(self.document);?)
232236
233- // We need to return either 0 or 1 item, so we cannot fix the size to [1]*parser.Element
234- // Converting the pointer to a slice []parser.Element is not supported by our framework.
235- // So instead we just need to allocate the pointer to create a slice of 1.
236- const heap_ptr = try state .call_arena .create (ElementUnion );
237- heap_ptr .* = try Element .toInterface (element );
238- return heap_ptr [0.. 1];
237+ var list = try std .ArrayList (ElementUnion ).initCapacity (state .call_arena , 3 );
238+ try list .append (try Element .toInterface (element ));
239+
240+ // Since we are using a flat renderer there is no hierarchy of elements. What we do know is that the element is part of the main document.
241+ // Thus we can add the HtmlHtmlElement and it's child HTMLBodyElement to the returned list.
242+ // TBD Should we instead return every parent that is an element? Note that a child does not physically need to be overlapping the parent.
243+ // Should we do a render pass on demand?
244+ const doc_elem = try parser .documentGetDocumentElement (parser .documentHTMLToDocument (state .document .? )) orelse {
245+ return list .items ;
246+ };
247+ const body = try parser .documentHTMLBody (state .document .? ) orelse {
248+ try list .append (try Element .toInterface (doc_elem ));
249+ return list .items ;
250+ };
251+
252+ try list .append (try Element .toInterface (parser .bodyToElement (body )));
253+ try list .append (try Element .toInterface (doc_elem ));
254+ return list .items ;
239255 }
240256};
241257
@@ -292,7 +308,7 @@ test "Browser.HTML.Document" {
292308 }, .{});
293309
294310 try runner .testCases (&.{
295- .{ "document.elementFromPoint(0.5, 0.5)" , "null" }, // Should these be document?
311+ .{ "document.elementFromPoint(0.5, 0.5)" , "null" }, // Return null since we only return element s when they have previously been localized
296312 .{ "document.elementsFromPoint(0.5, 0.5)" , "" },
297313 .{
298314 \\ let div1 = document.createElement('div');
@@ -303,8 +319,10 @@ test "Browser.HTML.Document" {
303319 },
304320 .{ "document.elementFromPoint(0.5, 0.5)" , "[object HTMLDivElement]" },
305321 .{ "let elems = document.elementsFromPoint(0.5, 0.5)" , null },
306- .{ "elems.length" , "1 " },
322+ .{ "elems.length" , "3 " },
307323 .{ "elems[0]" , "[object HTMLDivElement]" },
324+ .{ "elems[1]" , "[object HTMLBodyElement]" },
325+ .{ "elems[2]" , "[object HTMLHtmlElement]" },
308326 }, .{});
309327
310328 try runner .testCases (&.{
0 commit comments