mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Reduce cost of various Element render-related properties.
Added a get-only `getStyle` which doesn't lazily create a new style if none exists. This can be used in the (frequently used) `checkVisibility` to avoid an allocation. Added a specialized getBoundingClientRectForVisible which skips the checkVisibility check, since a few callers have already done their own visibility check. DOMRect is now off the heap. This avoids _a lot_ of allocation when a DOMRect is only needed for internal calculation, e.g. in Document.elementFromPoint.
This commit is contained in:
@@ -36,35 +36,35 @@ pub fn init(x: f64, y: f64, width: f64, height: f64, page: *Page) !*DOMRect {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getX(self: *DOMRect) f64 {
|
||||
pub fn getX(self: *const DOMRect) f64 {
|
||||
return self._x;
|
||||
}
|
||||
|
||||
pub fn getY(self: *DOMRect) f64 {
|
||||
pub fn getY(self: *const DOMRect) f64 {
|
||||
return self._y;
|
||||
}
|
||||
|
||||
pub fn getWidth(self: *DOMRect) f64 {
|
||||
pub fn getWidth(self: *const DOMRect) f64 {
|
||||
return self._width;
|
||||
}
|
||||
|
||||
pub fn getHeight(self: *DOMRect) f64 {
|
||||
pub fn getHeight(self: *const DOMRect) f64 {
|
||||
return self._height;
|
||||
}
|
||||
|
||||
pub fn getTop(self: *DOMRect) f64 {
|
||||
pub fn getTop(self: *const DOMRect) f64 {
|
||||
return @min(self._y, self._y + self._height);
|
||||
}
|
||||
|
||||
pub fn getRight(self: *DOMRect) f64 {
|
||||
pub fn getRight(self: *const DOMRect) f64 {
|
||||
return @max(self._x, self._x + self._width);
|
||||
}
|
||||
|
||||
pub fn getBottom(self: *DOMRect) f64 {
|
||||
pub fn getBottom(self: *const DOMRect) f64 {
|
||||
return @max(self._y, self._y + self._height);
|
||||
}
|
||||
|
||||
pub fn getLeft(self: *DOMRect) f64 {
|
||||
pub fn getLeft(self: *const DOMRect) f64 {
|
||||
return @min(self._x, self._x + self._width);
|
||||
}
|
||||
|
||||
|
||||
@@ -555,8 +555,8 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, page: *Page) !?*Element
|
||||
while (stack.items.len > 0) {
|
||||
const node = stack.pop() orelse break;
|
||||
if (node.is(Element)) |element| {
|
||||
if (try element.checkVisibility(page)) {
|
||||
const rect = try element.getBoundingClientRect(page);
|
||||
if (element.checkVisibility(page)) {
|
||||
const rect = element.getBoundingClientRectForVisible(page);
|
||||
if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) {
|
||||
topmost = element;
|
||||
}
|
||||
|
||||
@@ -663,7 +663,7 @@ pub fn getAttributeNamedNodeMap(self: *Element, page: *Page) !*Attribute.NamedNo
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn getStyle(self: *Element, page: *Page) !*CSSStyleProperties {
|
||||
pub fn getOrCreateStyle(self: *Element, page: *Page) !*CSSStyleProperties {
|
||||
const gop = try page._element_styles.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = try CSSStyleProperties.init(self, false, page);
|
||||
@@ -671,6 +671,10 @@ pub fn getStyle(self: *Element, page: *Page) !*CSSStyleProperties {
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
fn getStyle(self: *Element, page: *Page) ?*CSSStyleProperties {
|
||||
return page._element_styles.get(self);
|
||||
}
|
||||
|
||||
pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||
const gop = try page._element_class_lists.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
@@ -943,26 +947,31 @@ pub fn parentElement(self: *Element) ?*Element {
|
||||
return self._proto.parentElement();
|
||||
}
|
||||
|
||||
pub fn checkVisibility(self: *Element, page: *Page) !bool {
|
||||
pub fn checkVisibility(self: *Element, page: *Page) bool {
|
||||
var current: ?*Element = self;
|
||||
|
||||
while (current) |el| {
|
||||
const style = try el.getStyle(page);
|
||||
if (el.getStyle(page)) |style| {
|
||||
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
||||
if (std.mem.eql(u8, display, "none")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
current = el.parentElement();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, height: f64 } {
|
||||
const style = try self.getStyle(page);
|
||||
fn getElementDimensions(self: *Element, page: *Page) struct { width: f64, height: f64 } {
|
||||
var width: f64 = 5.0;
|
||||
var height: f64 = 5.0;
|
||||
|
||||
if (self.getStyle(page)) |style| {
|
||||
const decl = style.asCSSStyleDeclaration();
|
||||
var width = CSS.parseDimension(decl.getPropertyValue("width", page)) orelse 5.0;
|
||||
var height = CSS.parseDimension(decl.getPropertyValue("height", page)) orelse 5.0;
|
||||
width = CSS.parseDimension(decl.getPropertyValue("width", page)) orelse 5.0;
|
||||
height = CSS.parseDimension(decl.getPropertyValue("height", page)) orelse 5.0;
|
||||
}
|
||||
|
||||
if (width == 5.0 or height == 5.0) {
|
||||
const tag = self.getTag();
|
||||
@@ -987,52 +996,59 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh
|
||||
return .{ .width = width, .height = height };
|
||||
}
|
||||
|
||||
pub fn getClientWidth(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getClientWidth(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
const dims = self.getElementDimensions(page);
|
||||
return dims.width;
|
||||
}
|
||||
|
||||
pub fn getClientHeight(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getClientHeight(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
const dims = self.getElementDimensions(page);
|
||||
return dims.height;
|
||||
}
|
||||
|
||||
pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
return page._factory.create(DOMRect{
|
||||
pub fn getBoundingClientRect(self: *Element, page: *Page) DOMRect {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return .{
|
||||
._x = 0.0,
|
||||
._y = 0.0,
|
||||
._width = 0.0,
|
||||
._height = 0.0,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return self.getBoundingClientRectForVisible(page);
|
||||
}
|
||||
|
||||
// Some cases need a the BoundingClientRect but have already done the
|
||||
// visibility check.
|
||||
pub fn getBoundingClientRectForVisible(self: *Element, page: *Page) DOMRect {
|
||||
const y = calculateDocumentPosition(self.asNode());
|
||||
const dims = try self.getElementDimensions(page);
|
||||
const dims = self.getElementDimensions(page);
|
||||
|
||||
// Use sibling position for x coordinate to ensure siblings have different x values
|
||||
const x = calculateSiblingPosition(self.asNode());
|
||||
|
||||
return page._factory.create(DOMRect{
|
||||
return .{
|
||||
._x = x,
|
||||
._y = y,
|
||||
._width = dims.width,
|
||||
._height = dims.height,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return &.{};
|
||||
}
|
||||
const ptr = try self.getBoundingClientRect(page);
|
||||
return ptr[0..1];
|
||||
const rects = try page.call_arena.alloc(DOMRect, 1);
|
||||
rects[0] = self.getBoundingClientRectForVisible(page);
|
||||
return rects;
|
||||
}
|
||||
|
||||
pub fn getScrollTop(self: *Element, page: *Page) u32 {
|
||||
@@ -1061,41 +1077,41 @@ pub fn setScrollLeft(self: *Element, value: i32, page: *Page) !void {
|
||||
gop.value_ptr.x = @intCast(@max(0, value));
|
||||
}
|
||||
|
||||
pub fn getScrollHeight(self: *Element, page: *Page) !f64 {
|
||||
pub fn getScrollHeight(self: *Element, page: *Page) f64 {
|
||||
// In our dummy layout engine, content doesn't overflow
|
||||
return self.getClientHeight(page);
|
||||
}
|
||||
|
||||
pub fn getScrollWidth(self: *Element, page: *Page) !f64 {
|
||||
pub fn getScrollWidth(self: *Element, page: *Page) f64 {
|
||||
// In our dummy layout engine, content doesn't overflow
|
||||
return self.getClientWidth(page);
|
||||
}
|
||||
|
||||
pub fn getOffsetHeight(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getOffsetHeight(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
const dims = self.getElementDimensions(page);
|
||||
return dims.height;
|
||||
}
|
||||
|
||||
pub fn getOffsetWidth(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getOffsetWidth(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
const dims = self.getElementDimensions(page);
|
||||
return dims.width;
|
||||
}
|
||||
|
||||
pub fn getOffsetTop(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getOffsetTop(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
return calculateDocumentPosition(self.asNode());
|
||||
}
|
||||
|
||||
pub fn getOffsetLeft(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
pub fn getOffsetLeft(self: *Element, page: *Page) f64 {
|
||||
if (!self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
return calculateSiblingPosition(self.asNode());
|
||||
@@ -1541,7 +1557,7 @@ pub const JsApi = struct {
|
||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
||||
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{});
|
||||
pub const dataset = bridge.accessor(Element.getDataset, null, .{});
|
||||
pub const style = bridge.accessor(Element.getStyle, null, .{});
|
||||
pub const style = bridge.accessor(Element.getOrCreateStyle, null, .{});
|
||||
pub const attributes = bridge.accessor(Element.getAttributeNamedNodeMap, null, .{});
|
||||
pub const hasAttribute = bridge.function(Element.hasAttribute, .{});
|
||||
pub const hasAttributes = bridge.function(Element.hasAttributes, .{});
|
||||
|
||||
@@ -177,19 +177,19 @@ fn calculateIntersection(
|
||||
target: *Element,
|
||||
page: *Page,
|
||||
) !IntersectionData {
|
||||
const target_rect = try target.getBoundingClientRect(page);
|
||||
const target_rect = target.getBoundingClientRect(page);
|
||||
|
||||
// Use root element's rect or viewport (simplified: assume 1920x1080)
|
||||
const root_rect = if (self._root) |root|
|
||||
try root.getBoundingClientRect(page)
|
||||
root.getBoundingClientRect(page)
|
||||
else
|
||||
// Simplified viewport - assume 1920x1080 for now
|
||||
try page._factory.create(DOMRect{
|
||||
DOMRect{
|
||||
._x = 0.0,
|
||||
._y = 0.0,
|
||||
._width = 1920.0,
|
||||
._height = 1080.0,
|
||||
});
|
||||
};
|
||||
|
||||
// For a headless browser without real layout, we treat all elements as fully visible.
|
||||
// This avoids fingerprinting issues (massive viewports) and matches the behavior
|
||||
@@ -200,7 +200,7 @@ fn calculateIntersection(
|
||||
const intersection_ratio: f64 = if (has_parent) 1.0 else 0.0;
|
||||
|
||||
// Intersection rect is the same as the target rect if visible, otherwise zero rect
|
||||
const intersection_rect = if (has_parent) target_rect else &zero_rect;
|
||||
const intersection_rect = if (has_parent) target_rect else zero_rect;
|
||||
|
||||
return .{
|
||||
.is_intersecting = is_intersecting,
|
||||
@@ -214,9 +214,9 @@ fn calculateIntersection(
|
||||
const IntersectionData = struct {
|
||||
is_intersecting: bool,
|
||||
intersection_ratio: f64,
|
||||
intersection_rect: *DOMRect,
|
||||
bounding_client_rect: *DOMRect,
|
||||
root_bounds: *DOMRect,
|
||||
intersection_rect: DOMRect,
|
||||
bounding_client_rect: DOMRect,
|
||||
root_bounds: DOMRect,
|
||||
};
|
||||
|
||||
fn meetsThreshold(self: *IntersectionObserver, ratio: f64) bool {
|
||||
@@ -241,17 +241,19 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
||||
|
||||
if (should_report) {
|
||||
const arena = try page.getArena(.{ .debug = "IntersectionObserverEntry" });
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const entry = try arena.create(IntersectionObserverEntry);
|
||||
entry.* = .{
|
||||
._page = page,
|
||||
._arena = arena,
|
||||
._target = target,
|
||||
._time = page.window._performance.now(),
|
||||
._bounding_client_rect = data.bounding_client_rect,
|
||||
._intersection_rect = data.intersection_rect,
|
||||
._root_bounds = data.root_bounds,
|
||||
._intersection_ratio = data.intersection_ratio,
|
||||
._is_intersecting = is_now_intersecting,
|
||||
._root_bounds = try page._factory.create(data.root_bounds),
|
||||
._intersection_rect = try page._factory.create(data.intersection_rect),
|
||||
._bounding_client_rect = try page._factory.create(data.bounding_client_rect),
|
||||
._intersection_ratio = data.intersection_ratio,
|
||||
};
|
||||
|
||||
try self._pending_entries.append(self._arena, entry);
|
||||
|
||||
@@ -356,7 +356,7 @@ const BoxModel = struct {
|
||||
// shapeOutside: ?ShapeOutsideInfo,
|
||||
};
|
||||
|
||||
fn rectToQuad(rect: *const DOMNode.Element.DOMRect) Quad {
|
||||
fn rectToQuad(rect: DOMNode.Element.DOMRect) Quad {
|
||||
return Quad{
|
||||
rect._x,
|
||||
rect._y,
|
||||
@@ -434,9 +434,7 @@ fn getContentQuads(cmd: anytype) !void {
|
||||
// Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
|
||||
// Elements like SVGElement may have multiple quads.
|
||||
|
||||
const rect = try element.getBoundingClientRect(page);
|
||||
const quad = rectToQuad(rect);
|
||||
|
||||
const quad = rectToQuad(element.getBoundingClientRect(page));
|
||||
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
|
||||
}
|
||||
|
||||
@@ -455,7 +453,7 @@ fn getBoxModel(cmd: anytype) !void {
|
||||
// TODO implement for document or text
|
||||
const element = node.dom.is(DOMNode.Element) orelse return error.NodeIsNotAnElement;
|
||||
|
||||
const rect = try element.getBoundingClientRect(page);
|
||||
const rect = element.getBoundingClientRect(page);
|
||||
const quad = rectToQuad(rect);
|
||||
const zero = [_]f64{0.0} ** 8;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user