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;
|
return self._x;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getY(self: *DOMRect) f64 {
|
pub fn getY(self: *const DOMRect) f64 {
|
||||||
return self._y;
|
return self._y;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getWidth(self: *DOMRect) f64 {
|
pub fn getWidth(self: *const DOMRect) f64 {
|
||||||
return self._width;
|
return self._width;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getHeight(self: *DOMRect) f64 {
|
pub fn getHeight(self: *const DOMRect) f64 {
|
||||||
return self._height;
|
return self._height;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTop(self: *DOMRect) f64 {
|
pub fn getTop(self: *const DOMRect) f64 {
|
||||||
return @min(self._y, self._y + self._height);
|
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);
|
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);
|
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);
|
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) {
|
while (stack.items.len > 0) {
|
||||||
const node = stack.pop() orelse break;
|
const node = stack.pop() orelse break;
|
||||||
if (node.is(Element)) |element| {
|
if (node.is(Element)) |element| {
|
||||||
if (try element.checkVisibility(page)) {
|
if (element.checkVisibility(page)) {
|
||||||
const rect = try element.getBoundingClientRect(page);
|
const rect = element.getBoundingClientRectForVisible(page);
|
||||||
if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) {
|
if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) {
|
||||||
topmost = element;
|
topmost = element;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ pub fn getAttributeNamedNodeMap(self: *Element, page: *Page) !*Attribute.NamedNo
|
|||||||
return gop.value_ptr.*;
|
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);
|
const gop = try page._element_styles.getOrPut(page.arena, self);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
gop.value_ptr.* = try CSSStyleProperties.init(self, false, page);
|
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.*;
|
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 {
|
pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||||
const gop = try page._element_class_lists.getOrPut(page.arena, self);
|
const gop = try page._element_class_lists.getOrPut(page.arena, self);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
@@ -943,14 +947,15 @@ pub fn parentElement(self: *Element) ?*Element {
|
|||||||
return self._proto.parentElement();
|
return self._proto.parentElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkVisibility(self: *Element, page: *Page) !bool {
|
pub fn checkVisibility(self: *Element, page: *Page) bool {
|
||||||
var current: ?*Element = self;
|
var current: ?*Element = self;
|
||||||
|
|
||||||
while (current) |el| {
|
while (current) |el| {
|
||||||
const style = try el.getStyle(page);
|
if (el.getStyle(page)) |style| {
|
||||||
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
||||||
if (std.mem.eql(u8, display, "none")) {
|
if (std.mem.eql(u8, display, "none")) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
current = el.parentElement();
|
current = el.parentElement();
|
||||||
}
|
}
|
||||||
@@ -958,11 +963,15 @@ pub fn checkVisibility(self: *Element, page: *Page) !bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, height: f64 } {
|
fn getElementDimensions(self: *Element, page: *Page) struct { width: f64, height: f64 } {
|
||||||
const style = try self.getStyle(page);
|
var width: f64 = 5.0;
|
||||||
const decl = style.asCSSStyleDeclaration();
|
var height: f64 = 5.0;
|
||||||
var width = CSS.parseDimension(decl.getPropertyValue("width", page)) orelse 5.0;
|
|
||||||
var height = CSS.parseDimension(decl.getPropertyValue("height", page)) orelse 5.0;
|
if (self.getStyle(page)) |style| {
|
||||||
|
const decl = style.asCSSStyleDeclaration();
|
||||||
|
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) {
|
if (width == 5.0 or height == 5.0) {
|
||||||
const tag = self.getTag();
|
const tag = self.getTag();
|
||||||
@@ -987,52 +996,59 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh
|
|||||||
return .{ .width = width, .height = height };
|
return .{ .width = width, .height = height };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClientWidth(self: *Element, page: *Page) !f64 {
|
pub fn getClientWidth(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
const dims = try self.getElementDimensions(page);
|
const dims = self.getElementDimensions(page);
|
||||||
return dims.width;
|
return dims.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClientHeight(self: *Element, page: *Page) !f64 {
|
pub fn getClientHeight(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
const dims = try self.getElementDimensions(page);
|
const dims = self.getElementDimensions(page);
|
||||||
return dims.height;
|
return dims.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect {
|
pub fn getBoundingClientRect(self: *Element, page: *Page) DOMRect {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return page._factory.create(DOMRect{
|
return .{
|
||||||
._x = 0.0,
|
._x = 0.0,
|
||||||
._y = 0.0,
|
._y = 0.0,
|
||||||
._width = 0.0,
|
._width = 0.0,
|
||||||
._height = 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 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
|
// Use sibling position for x coordinate to ensure siblings have different x values
|
||||||
const x = calculateSiblingPosition(self.asNode());
|
const x = calculateSiblingPosition(self.asNode());
|
||||||
|
|
||||||
return page._factory.create(DOMRect{
|
return .{
|
||||||
._x = x,
|
._x = x,
|
||||||
._y = y,
|
._y = y,
|
||||||
._width = dims.width,
|
._width = dims.width,
|
||||||
._height = dims.height,
|
._height = dims.height,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect {
|
pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return &.{};
|
return &.{};
|
||||||
}
|
}
|
||||||
const ptr = try self.getBoundingClientRect(page);
|
const rects = try page.call_arena.alloc(DOMRect, 1);
|
||||||
return ptr[0..1];
|
rects[0] = self.getBoundingClientRectForVisible(page);
|
||||||
|
return rects;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getScrollTop(self: *Element, page: *Page) u32 {
|
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));
|
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
|
// In our dummy layout engine, content doesn't overflow
|
||||||
return self.getClientHeight(page);
|
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
|
// In our dummy layout engine, content doesn't overflow
|
||||||
return self.getClientWidth(page);
|
return self.getClientWidth(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOffsetHeight(self: *Element, page: *Page) !f64 {
|
pub fn getOffsetHeight(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
const dims = try self.getElementDimensions(page);
|
const dims = self.getElementDimensions(page);
|
||||||
return dims.height;
|
return dims.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOffsetWidth(self: *Element, page: *Page) !f64 {
|
pub fn getOffsetWidth(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
const dims = try self.getElementDimensions(page);
|
const dims = self.getElementDimensions(page);
|
||||||
return dims.width;
|
return dims.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOffsetTop(self: *Element, page: *Page) !f64 {
|
pub fn getOffsetTop(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
return calculateDocumentPosition(self.asNode());
|
return calculateDocumentPosition(self.asNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOffsetLeft(self: *Element, page: *Page) !f64 {
|
pub fn getOffsetLeft(self: *Element, page: *Page) f64 {
|
||||||
if (!try self.checkVisibility(page)) {
|
if (!self.checkVisibility(page)) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
return calculateSiblingPosition(self.asNode());
|
return calculateSiblingPosition(self.asNode());
|
||||||
@@ -1541,7 +1557,7 @@ pub const JsApi = struct {
|
|||||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
||||||
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{});
|
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{});
|
||||||
pub const dataset = bridge.accessor(Element.getDataset, null, .{});
|
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 attributes = bridge.accessor(Element.getAttributeNamedNodeMap, null, .{});
|
||||||
pub const hasAttribute = bridge.function(Element.hasAttribute, .{});
|
pub const hasAttribute = bridge.function(Element.hasAttribute, .{});
|
||||||
pub const hasAttributes = bridge.function(Element.hasAttributes, .{});
|
pub const hasAttributes = bridge.function(Element.hasAttributes, .{});
|
||||||
|
|||||||
@@ -177,19 +177,19 @@ fn calculateIntersection(
|
|||||||
target: *Element,
|
target: *Element,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !IntersectionData {
|
) !IntersectionData {
|
||||||
const target_rect = try target.getBoundingClientRect(page);
|
const target_rect = target.getBoundingClientRect(page);
|
||||||
|
|
||||||
// Use root element's rect or viewport (simplified: assume 1920x1080)
|
// Use root element's rect or viewport (simplified: assume 1920x1080)
|
||||||
const root_rect = if (self._root) |root|
|
const root_rect = if (self._root) |root|
|
||||||
try root.getBoundingClientRect(page)
|
root.getBoundingClientRect(page)
|
||||||
else
|
else
|
||||||
// Simplified viewport - assume 1920x1080 for now
|
// Simplified viewport - assume 1920x1080 for now
|
||||||
try page._factory.create(DOMRect{
|
DOMRect{
|
||||||
._x = 0.0,
|
._x = 0.0,
|
||||||
._y = 0.0,
|
._y = 0.0,
|
||||||
._width = 1920.0,
|
._width = 1920.0,
|
||||||
._height = 1080.0,
|
._height = 1080.0,
|
||||||
});
|
};
|
||||||
|
|
||||||
// For a headless browser without real layout, we treat all elements as fully visible.
|
// For a headless browser without real layout, we treat all elements as fully visible.
|
||||||
// This avoids fingerprinting issues (massive viewports) and matches the behavior
|
// 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;
|
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
|
// 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 .{
|
return .{
|
||||||
.is_intersecting = is_intersecting,
|
.is_intersecting = is_intersecting,
|
||||||
@@ -214,9 +214,9 @@ fn calculateIntersection(
|
|||||||
const IntersectionData = struct {
|
const IntersectionData = struct {
|
||||||
is_intersecting: bool,
|
is_intersecting: bool,
|
||||||
intersection_ratio: f64,
|
intersection_ratio: f64,
|
||||||
intersection_rect: *DOMRect,
|
intersection_rect: DOMRect,
|
||||||
bounding_client_rect: *DOMRect,
|
bounding_client_rect: DOMRect,
|
||||||
root_bounds: *DOMRect,
|
root_bounds: DOMRect,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn meetsThreshold(self: *IntersectionObserver, ratio: f64) bool {
|
fn meetsThreshold(self: *IntersectionObserver, ratio: f64) bool {
|
||||||
@@ -241,17 +241,19 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
|||||||
|
|
||||||
if (should_report) {
|
if (should_report) {
|
||||||
const arena = try page.getArena(.{ .debug = "IntersectionObserverEntry" });
|
const arena = try page.getArena(.{ .debug = "IntersectionObserverEntry" });
|
||||||
|
errdefer page.releaseArena(arena);
|
||||||
|
|
||||||
const entry = try arena.create(IntersectionObserverEntry);
|
const entry = try arena.create(IntersectionObserverEntry);
|
||||||
entry.* = .{
|
entry.* = .{
|
||||||
._page = page,
|
._page = page,
|
||||||
._arena = arena,
|
._arena = arena,
|
||||||
._target = target,
|
._target = target,
|
||||||
._time = page.window._performance.now(),
|
._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,
|
._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);
|
try self._pending_entries.append(self._arena, entry);
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ const BoxModel = struct {
|
|||||||
// shapeOutside: ?ShapeOutsideInfo,
|
// shapeOutside: ?ShapeOutsideInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn rectToQuad(rect: *const DOMNode.Element.DOMRect) Quad {
|
fn rectToQuad(rect: DOMNode.Element.DOMRect) Quad {
|
||||||
return Quad{
|
return Quad{
|
||||||
rect._x,
|
rect._x,
|
||||||
rect._y,
|
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 = ""?
|
// Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
|
||||||
// Elements like SVGElement may have multiple quads.
|
// Elements like SVGElement may have multiple quads.
|
||||||
|
|
||||||
const rect = try element.getBoundingClientRect(page);
|
const quad = rectToQuad(element.getBoundingClientRect(page));
|
||||||
const quad = rectToQuad(rect);
|
|
||||||
|
|
||||||
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
|
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +453,7 @@ fn getBoxModel(cmd: anytype) !void {
|
|||||||
// TODO implement for document or text
|
// TODO implement for document or text
|
||||||
const element = node.dom.is(DOMNode.Element) orelse return error.NodeIsNotAnElement;
|
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 quad = rectToQuad(rect);
|
||||||
const zero = [_]f64{0.0} ** 8;
|
const zero = [_]f64{0.0} ** 8;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user