mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-14 23:38:57 +00:00
Element.checkVisibility and Element.checkVisibility
This commit is contained in:
@@ -488,6 +488,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/DOMImplementation.zig"),
|
||||
@import("../webapi/DOMTreeWalker.zig"),
|
||||
@import("../webapi/DOMNodeIterator.zig"),
|
||||
@import("../webapi/DOMRect.zig"),
|
||||
@import("../webapi/NodeFilter.zig"),
|
||||
@import("../webapi/Element.zig"),
|
||||
@import("../webapi/element/DOMStringMap.zig"),
|
||||
|
||||
64
src/browser/webapi/DOMRect.zig
Normal file
64
src/browser/webapi/DOMRect.zig
Normal file
@@ -0,0 +1,64 @@
|
||||
const DOMRect = @This();
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
_x: f64,
|
||||
_y: f64,
|
||||
_width: f64,
|
||||
_height: f64,
|
||||
_top: f64,
|
||||
_right: f64,
|
||||
_bottom: f64,
|
||||
_left: f64,
|
||||
|
||||
pub fn getX(self: *DOMRect) f64 {
|
||||
return self._x;
|
||||
}
|
||||
|
||||
pub fn getY(self: *DOMRect) f64 {
|
||||
return self._y;
|
||||
}
|
||||
|
||||
pub fn getWidth(self: *DOMRect) f64 {
|
||||
return self._width;
|
||||
}
|
||||
|
||||
pub fn getHeight(self: *DOMRect) f64 {
|
||||
return self._height;
|
||||
}
|
||||
|
||||
pub fn getTop(self: *DOMRect) f64 {
|
||||
return self._top;
|
||||
}
|
||||
|
||||
pub fn getRight(self: *DOMRect) f64 {
|
||||
return self._right;
|
||||
}
|
||||
|
||||
pub fn getBottom(self: *DOMRect) f64 {
|
||||
return self._bottom;
|
||||
}
|
||||
|
||||
pub fn getLeft(self: *DOMRect) f64 {
|
||||
return self._left;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(DOMRect);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "DOMRect";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const x = bridge.accessor(DOMRect.getX, null, .{});
|
||||
pub const y = bridge.accessor(DOMRect.getY, null, .{});
|
||||
pub const width = bridge.accessor(DOMRect.getWidth, null, .{});
|
||||
pub const height = bridge.accessor(DOMRect.getHeight, null, .{});
|
||||
pub const top = bridge.accessor(DOMRect.getTop, null, .{});
|
||||
pub const right = bridge.accessor(DOMRect.getRight, null, .{});
|
||||
pub const bottom = bridge.accessor(DOMRect.getBottom, null, .{});
|
||||
pub const left = bridge.accessor(DOMRect.getLeft, null, .{});
|
||||
};
|
||||
@@ -13,6 +13,8 @@ const Selector = @import("selector/Selector.zig");
|
||||
pub const Attribute = @import("element/Attribute.zig");
|
||||
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
||||
pub const DOMStringMap = @import("element/DOMStringMap.zig");
|
||||
const DOMRect = @import("DOMRect.zig");
|
||||
const css = @import("css.zig");
|
||||
|
||||
pub const Svg = @import("element/Svg.zig");
|
||||
pub const Html = @import("element/Html.zig");
|
||||
@@ -467,6 +469,126 @@ pub fn querySelectorAll(self: *Element, input: []const u8, page: *Page) !*Select
|
||||
return Selector.querySelectorAll(self.asNode(), input, page);
|
||||
}
|
||||
|
||||
pub fn parentElement(self: *Element) ?*Element {
|
||||
return self._proto.parentElement();
|
||||
}
|
||||
|
||||
pub fn checkVisibility(self: *Element, page: *Page) !bool {
|
||||
var current: ?*Element = self;
|
||||
|
||||
while (current) |el| {
|
||||
const style = try el.getStyle(page);
|
||||
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
||||
if (std.mem.eql(u8, display, "none")) {
|
||||
return false;
|
||||
}
|
||||
current = el.parentElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect {
|
||||
const is_visible = try self.checkVisibility(page);
|
||||
if (!is_visible) {
|
||||
return page._factory.create(DOMRect{
|
||||
._x = 0.0,
|
||||
._y = 0.0,
|
||||
._width = 0.0,
|
||||
._height = 0.0,
|
||||
._top = 0.0,
|
||||
._right = 0.0,
|
||||
._bottom = 0.0,
|
||||
._left = 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
const y = calculateDocumentPosition(self.asNode());
|
||||
|
||||
var width: f64 = 1.0;
|
||||
var height: f64 = 1.0;
|
||||
|
||||
const style = try self.getStyle(page);
|
||||
const decl = style.asCSSStyleDeclaration();
|
||||
width = css.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0;
|
||||
height = css.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0;
|
||||
|
||||
if (width == 1.0 or height == 1.0) {
|
||||
const tag = self.getTag();
|
||||
if (tag == .img or tag == .iframe) {
|
||||
if (self.getAttributeSafe("width")) |w| {
|
||||
width = std.fmt.parseFloat(f64, w) catch width;
|
||||
}
|
||||
if (self.getAttributeSafe("height")) |h| {
|
||||
height = std.fmt.parseFloat(f64, h) catch height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const x: f64 = 0.0;
|
||||
const top = y;
|
||||
const left = x;
|
||||
const right = x + width;
|
||||
const bottom = y + height;
|
||||
|
||||
return page._factory.create(DOMRect{
|
||||
._x = x,
|
||||
._y = y,
|
||||
._width = width,
|
||||
._height = height,
|
||||
._top = top,
|
||||
._right = right,
|
||||
._bottom = bottom,
|
||||
._left = left,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculates a pseudo-position in the document using an efficient heuristic.
|
||||
//
|
||||
// Instead of walking the entire DOM tree (which would be O(total_nodes)), this
|
||||
// function walks UP the tree counting previous siblings at each level. Each level
|
||||
// uses exponential weighting (1000x per depth level) to preserve document order.
|
||||
//
|
||||
// This gives O(depth * avg_siblings) complexity while maintaining relative positioning
|
||||
// that's useful for scraping and understanding element flow in the document.
|
||||
//
|
||||
// Example:
|
||||
// <body> → position 0
|
||||
// <div> → position 0 (0 siblings at level 1)
|
||||
// <span></span> → position 0 (0 siblings at level 2)
|
||||
// <span></span> → position 1 (1 sibling at level 2)
|
||||
// </div>
|
||||
// <div> → position 1000 (1 sibling at level 1, weighted by 1000)
|
||||
// <p></p> → position 1000 (0 siblings at level 2, parent has 1000)
|
||||
// </div>
|
||||
// </body>
|
||||
//
|
||||
// Trade-offs:
|
||||
// - Much faster than full tree-walking for deep/large DOMs
|
||||
// - Positions reflect document order and parent-child relationships
|
||||
// - Not pixel-accurate, but sufficient for 1x1 layout heuristics
|
||||
fn calculateDocumentPosition(node: *Node) f64 {
|
||||
var position: f64 = 0.0;
|
||||
var multiplier: f64 = 1.0;
|
||||
var current = node;
|
||||
|
||||
while (current.parentNode()) |parent| {
|
||||
var count: f64 = 0.0;
|
||||
var sibling = parent.firstChild();
|
||||
while (sibling) |s| {
|
||||
if (s == current) break;
|
||||
count += 1.0;
|
||||
sibling = s.nextSibling();
|
||||
}
|
||||
|
||||
position += count * multiplier;
|
||||
multiplier *= 1000.0;
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
const GetElementsByTagNameResult = union(enum) {
|
||||
tag: collections.NodeLive(.tag),
|
||||
tag_name: collections.NodeLive(.tag_name),
|
||||
@@ -702,6 +824,8 @@ pub const JsApi = struct {
|
||||
pub const matches = bridge.function(Element.matches, .{ .dom_exception = true });
|
||||
pub const querySelector = bridge.function(Element.querySelector, .{ .dom_exception = true });
|
||||
pub const querySelectorAll = bridge.function(Element.querySelectorAll, .{ .dom_exception = true });
|
||||
pub const checkVisibility = bridge.function(Element.checkVisibility, .{});
|
||||
pub const getBoundingClientRect = bridge.function(Element.getBoundingClientRect, .{});
|
||||
pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{});
|
||||
pub const getElementsByClassName = bridge.function(Element.getElementsByClassName, .{});
|
||||
pub const children = bridge.accessor(Element.getChildren, null, .{});
|
||||
|
||||
14
src/browser/webapi/css.zig
Normal file
14
src/browser/webapi/css.zig
Normal file
@@ -0,0 +1,14 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn parseDimension(value: []const u8) ?f64 {
|
||||
if (value.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var num_str = value;
|
||||
if (std.mem.endsWith(u8, value, "px")) {
|
||||
num_str = value[0 .. value.len - 2];
|
||||
}
|
||||
|
||||
return std.fmt.parseFloat(f64, num_str) catch null;
|
||||
}
|
||||
@@ -57,13 +57,13 @@ pub fn item(self: *const CSSStyleDeclaration, index: u32) []const u8 {
|
||||
return "";
|
||||
}
|
||||
|
||||
pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) ![]const u8 {
|
||||
pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) []const u8 {
|
||||
const normalized = normalizePropertyName(property_name, &page.buf);
|
||||
const prop = self.findProperty(normalized) orelse return "";
|
||||
return prop._value.str();
|
||||
}
|
||||
|
||||
pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) ![]const u8 {
|
||||
pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) []const u8 {
|
||||
const normalized = normalizePropertyName(property_name, &page.buf);
|
||||
const prop = self.findProperty(normalized) orelse return "";
|
||||
return if (prop._important) "important" else "";
|
||||
|
||||
@@ -154,7 +154,7 @@ pub const JsApi = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const value = try self._proto.getPropertyValue(dash_case, page);
|
||||
const value = self._proto.getPropertyValue(dash_case, page);
|
||||
|
||||
// Property accessors have special handling for empty values:
|
||||
// - Known CSS properties return '' when not set
|
||||
|
||||
Reference in New Issue
Block a user