@@ -13,6 +13,8 @@ const Selector = @import("selector/Selector.zig");
1313pub const Attribute = @import ("element/Attribute.zig" );
1414const CSSStyleProperties = @import ("css/CSSStyleProperties.zig" );
1515pub const DOMStringMap = @import ("element/DOMStringMap.zig" );
16+ const DOMRect = @import ("DOMRect.zig" );
17+ const css = @import ("css.zig" );
1618
1719pub const Svg = @import ("element/Svg.zig" );
1820pub const Html = @import ("element/Html.zig" );
@@ -467,6 +469,126 @@ pub fn querySelectorAll(self: *Element, input: []const u8, page: *Page) !*Select
467469 return Selector .querySelectorAll (self .asNode (), input , page );
468470}
469471
472+ pub fn parentElement (self : * Element ) ? * Element {
473+ return self ._proto .parentElement ();
474+ }
475+
476+ pub fn checkVisibility (self : * Element , page : * Page ) ! bool {
477+ var current : ? * Element = self ;
478+
479+ while (current ) | el | {
480+ const style = try el .getStyle (page );
481+ const display = style .asCSSStyleDeclaration ().getPropertyValue ("display" , page );
482+ if (std .mem .eql (u8 , display , "none" )) {
483+ return false ;
484+ }
485+ current = el .parentElement ();
486+ }
487+
488+ return true ;
489+ }
490+
491+ pub fn getBoundingClientRect (self : * Element , page : * Page ) ! * DOMRect {
492+ const is_visible = try self .checkVisibility (page );
493+ if (! is_visible ) {
494+ return page ._factory .create (DOMRect {
495+ ._x = 0.0 ,
496+ ._y = 0.0 ,
497+ ._width = 0.0 ,
498+ ._height = 0.0 ,
499+ ._top = 0.0 ,
500+ ._right = 0.0 ,
501+ ._bottom = 0.0 ,
502+ ._left = 0.0 ,
503+ });
504+ }
505+
506+ const y = calculateDocumentPosition (self .asNode ());
507+
508+ var width : f64 = 1.0 ;
509+ var height : f64 = 1.0 ;
510+
511+ const style = try self .getStyle (page );
512+ const decl = style .asCSSStyleDeclaration ();
513+ width = css .parseDimension (decl .getPropertyValue ("width" , page )) orelse 1.0 ;
514+ height = css .parseDimension (decl .getPropertyValue ("height" , page )) orelse 1.0 ;
515+
516+ if (width == 1.0 or height == 1.0 ) {
517+ const tag = self .getTag ();
518+ if (tag == .img or tag == .iframe ) {
519+ if (self .getAttributeSafe ("width" )) | w | {
520+ width = std .fmt .parseFloat (f64 , w ) catch width ;
521+ }
522+ if (self .getAttributeSafe ("height" )) | h | {
523+ height = std .fmt .parseFloat (f64 , h ) catch height ;
524+ }
525+ }
526+ }
527+
528+ const x : f64 = 0.0 ;
529+ const top = y ;
530+ const left = x ;
531+ const right = x + width ;
532+ const bottom = y + height ;
533+
534+ return page ._factory .create (DOMRect {
535+ ._x = x ,
536+ ._y = y ,
537+ ._width = width ,
538+ ._height = height ,
539+ ._top = top ,
540+ ._right = right ,
541+ ._bottom = bottom ,
542+ ._left = left ,
543+ });
544+ }
545+
546+ // Calculates a pseudo-position in the document using an efficient heuristic.
547+ //
548+ // Instead of walking the entire DOM tree (which would be O(total_nodes)), this
549+ // function walks UP the tree counting previous siblings at each level. Each level
550+ // uses exponential weighting (1000x per depth level) to preserve document order.
551+ //
552+ // This gives O(depth * avg_siblings) complexity while maintaining relative positioning
553+ // that's useful for scraping and understanding element flow in the document.
554+ //
555+ // Example:
556+ // <body> → position 0
557+ // <div> → position 0 (0 siblings at level 1)
558+ // <span></span> → position 0 (0 siblings at level 2)
559+ // <span></span> → position 1 (1 sibling at level 2)
560+ // </div>
561+ // <div> → position 1000 (1 sibling at level 1, weighted by 1000)
562+ // <p></p> → position 1000 (0 siblings at level 2, parent has 1000)
563+ // </div>
564+ // </body>
565+ //
566+ // Trade-offs:
567+ // - Much faster than full tree-walking for deep/large DOMs
568+ // - Positions reflect document order and parent-child relationships
569+ // - Not pixel-accurate, but sufficient for 1x1 layout heuristics
570+ fn calculateDocumentPosition (node : * Node ) f64 {
571+ var position : f64 = 0.0 ;
572+ var multiplier : f64 = 1.0 ;
573+ var current = node ;
574+
575+ while (current .parentNode ()) | parent | {
576+ var count : f64 = 0.0 ;
577+ var sibling = parent .firstChild ();
578+ while (sibling ) | s | {
579+ if (s == current ) break ;
580+ count += 1.0 ;
581+ sibling = s .nextSibling ();
582+ }
583+
584+ position += count * multiplier ;
585+ multiplier *= 1000.0 ;
586+ current = parent ;
587+ }
588+
589+ return position ;
590+ }
591+
470592const GetElementsByTagNameResult = union (enum ) {
471593 tag : collections .NodeLive (.tag ),
472594 tag_name : collections .NodeLive (.tag_name ),
@@ -702,6 +824,8 @@ pub const JsApi = struct {
702824 pub const matches = bridge .function (Element .matches , .{ .dom_exception = true });
703825 pub const querySelector = bridge .function (Element .querySelector , .{ .dom_exception = true });
704826 pub const querySelectorAll = bridge .function (Element .querySelectorAll , .{ .dom_exception = true });
827+ pub const checkVisibility = bridge .function (Element .checkVisibility , .{});
828+ pub const getBoundingClientRect = bridge .function (Element .getBoundingClientRect , .{});
705829 pub const getElementsByTagName = bridge .function (Element .getElementsByTagName , .{});
706830 pub const getElementsByClassName = bridge .function (Element .getElementsByClassName , .{});
707831 pub const children = bridge .accessor (Element .getChildren , null , .{});
0 commit comments