Merge pull request #3229 from lightpanda-io/element-left-position

webapi: improve "rendering" of an element's left position
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-08-21 07:31:11 +08:00
commit 4a001f932c
12 files changed
+277 -171

No files matched your search

+2 -1
View File
@@ -2965,6 +2965,7 @@ pub fn attributeChange(self: *Frame, element: *Element, name: String, value: Str
const old = if (old_value) |o| o.str() else null;
popover.attributeChanged(element, old, value.str(), self);
} else if (name.eql(comptime .wrap("style"))) {
element._flags.has_inline_style = true;
self.styleAttributeChanged(element, value.str());
}
}
@@ -2993,7 +2994,7 @@ pub fn attributeRemove(self: *Frame, element: *Element, name: String, old_value:
}
fn styleAttributeChanged(self: *Frame, element: *Element, value: ?[]const u8) void {
const style = self._element_styles.get(element) orelse return;
const style = element.getStyle(self) orelse return;
style.asCSSStyleDeclaration().styleAttributeChanged(value, self) catch |err| {
log.err(.frame, "style attribute reparse", .{ .err = err, .type = self._type, .url = self.url });
};
+5 -1
View File
@@ -1194,7 +1194,11 @@ const INLINE_PRIORITY: u64 = std.math.maxInt(u64);
// `frame` is the StyleManager's frame, which callers guarantee is el's owner
// frame (el.ownerFrame) — the map where the materialized style lives.
fn getInlineStyleProperty(el: *Element, property_name: String, frame: *Frame) ?*CSSStyleProperty {
const style = frame._element_styles.get(el) orelse blk: {
if (!el._flags.has_inline_style) {
// Neither a style object nor a style attribute; skip both lookups.
return null;
}
const style = el.getStyle(frame) orelse blk: {
// No JS-set style object and no style attribute -> nothing inline to read.
if (el.getAttributeSafe(comptime .wrap("style")) == null) return null;
break :blk el.getOrCreateStyle(frame) catch |err| {
+4 -1
View File
@@ -1073,7 +1073,9 @@ fn initSvgElement(frame: *Frame, element: *Element, attribute_iterator: anytype)
fn populateElementAttributes(frame: *Frame, element: *Element, list: anytype) !void {
if (@TypeOf(list) == *Element.Attribute.List or @TypeOf(list) == *const Element.Attribute.List) {
// from cloneNode
return element._attributes.cloneFrom(list, frame);
try element._attributes.cloneFrom(list, frame);
element.noteStyleAttribute();
return;
}
// from the parser
@@ -1090,6 +1092,7 @@ fn populateElementAttributes(frame: *Frame, element: *Element, list: anytype) !v
const name = try parserAttributeName(frame, attr.name);
try attributes.putNew(name, attr.value.slice(), frame);
}
element.noteStyleAttribute();
}
// Attributes are keyed by qualified name (no namespace model), so a prefixed
+1 -1
View File
@@ -212,7 +212,7 @@ pub fn performScheduledResizeChecks(frame: *Frame) void {
}
// Only these attributes can change an element's size or visibility in our
// styling model (StyleManager.isHidden + Element.getElementDimensions), and
// styling model (StyleManager.isHidden + Element.getElementAxis), and
// only for the element itself and its descendants — so a delivery is only
// scheduled when an observed element is in the changed element's subtree.
fn resizeAttributeChanged(frame: *Frame, element: *Element, name: String) void {
+1
View File
@@ -617,6 +617,7 @@ fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.Attrib
// putNew only adds if the attribute doesn't already exist
try attr_list.putNew(name, value, frame);
}
element.noteStyleAttribute();
}
fn getTemplateContentsCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque {
+77
View File
@@ -168,6 +168,83 @@
}
</script>
<script id="recycleTickerLoop">
{
// The recycle variant of the marquee idiom (lseo.com): slide the track with
// translateX and, whenever the last card has left the shell on one side,
// move it to the other end of the track and offset the translation by its
// width so the remaining cards stay put. The loop is bounded only because
// the translation is honored: ignore it and each rotation pushes the cards
// further out of the shell.
const shell = document.createElement('div');
const track = document.createElement('div');
for (let i = 0; i < 4; i++) {
track.appendChild(document.createElement('div'));
}
shell.appendChild(track);
document.body.appendChild(shell);
const sRect = shell.getBoundingClientRect();
const recycle = (x, reverse) => {
let guard = 0;
while (guard < 50) {
const el = reverse ? track.lastElementChild : track.firstElementChild;
const r = el.getBoundingClientRect();
if (reverse ? r.left < sRect.right : r.right > sRect.left) break;
const w = el.offsetWidth;
if (reverse) {
track.insertBefore(el, track.firstElementChild);
x -= w;
} else {
track.appendChild(el);
x += w;
}
track.style.transform = `translateX(${x}px)`;
guard++;
}
return guard;
};
// Right-to-left: the track has slid 60px; every card is out on the right.
track.style.transform = 'translateX(60px)';
testing.expectTrue(recycle(60, true) < 50);
testing.expectTrue(track.firstElementChild.getBoundingClientRect().left < sRect.right);
// Left-to-right, from the same shifted state.
track.style.transform = 'translateX(-60px)';
testing.expectTrue(recycle(-60, false) < 50);
testing.expectTrue(track.lastElementChild.getBoundingClientRect().right > sRect.left);
}
</script>
<script id="translateX">
{
const shell = document.createElement('div');
const track = document.createElement('div');
const card = document.createElement('div');
track.appendChild(card);
shell.appendChild(track);
document.body.appendChild(shell);
// A row starts at its parent's left edge.
const base = shell.getBoundingClientRect().x;
testing.expectEqual(base, card.getBoundingClientRect().x);
testing.expectEqual(base, card.offsetLeft);
// The X translation of the element and of its ancestors moves it.
track.style.transform = 'translateX(-12.5px)';
testing.expectEqual(base - 12.5, card.getBoundingClientRect().x);
testing.expectEqual(base - 12.5, card.offsetLeft);
card.style.transform = 'translate3d(2px, 100px, 0) rotate(45deg) matrix(1, 0, 0, 1, 3, 9)';
testing.expectEqual(base - 7.5, card.getBoundingClientRect().x);
testing.expectEqual(base - 7.5, card.offsetLeft);
// Percentages and other units are not resolved.
track.style.transform = 'translateX(-100%)';
testing.expectEqual(base + 5, card.getBoundingClientRect().x);
}
</script>
<script id="computedWidthMarqueeLoop">
{
// The getComputedStyle variant of the marquee idiom (inchurch.com.br's
+41
View File
@@ -20,6 +20,7 @@ const std = @import("std");
const js = @import("../js/js.zig");
const Frame = @import("../Frame.zig");
const units = @import("../css/units.zig");
const Tokenizer = @import("../css/Tokenizer.zig");
const CSS = @This();
_pad: bool = false,
@@ -46,6 +47,46 @@ pub fn parseDimensionViewport(value: []const u8, frame: *Frame) ?f64 {
};
}
// Extract the X value from a transfrom. This could come from a translate,
// translatex, translate3d or matrix function.
pub fn parseTranslateX(value: []const u8) f64 {
var total: f64 = 0.0;
var tokenizer: Tokenizer = .{ .input = value };
while (tokenizer.next()) |token| {
const name = switch (token) {
.function => |name| name,
else => continue,
};
const x_arg: usize = blk: {
if (std.ascii.eqlIgnoreCase(name, "translatex") or std.ascii.eqlIgnoreCase(name, "translate") or std.ascii.eqlIgnoreCase(name, "translate3d")) {
// x is the first arg in all these cases
break :blk 0;
}
if (std.ascii.eqlIgnoreCase(name, "matrix")) {
// x is the 4th arg in the matrix
break :blk 4;
}
continue;
};
var arg: usize = 0;
while (tokenizer.next()) |t| {
switch (t) {
.close_parenthesis => break,
.comma => arg += 1,
.number => |n| if (arg == x_arg) {
total += n.value;
},
.dimension => |d| if (arg == x_arg and std.ascii.eqlIgnoreCase(d.unit, "px")) {
total += d.value;
},
else => {},
}
}
}
return total;
}
/// Escapes a CSS identifier string
/// https://drafts.csswg.org/cssom/#the-css.escape()-method
pub fn escape(value: []const u8, frame: *Frame) ![]const u8 {
+11 -9
View File
@@ -917,15 +917,17 @@ fn elementFromPointImpl(self: *Document, x: f64, y: f64, ignore_x: bool, frame:
preorder_index += 1;
if (node.is(Element)) |element| {
if (element.checkVisibilityCached(&visibility_cache, frame)) {
const dims = element.getElementDimensions(frame);
// x and y both come from preorder position in our faux layout.
const left = pos;
const top = pos;
const right = pos + dims.width;
const bottom = pos + dims.height;
const x_contained = ignore_x or (x >= left and x <= right);
if (x_contained and y >= top and y <= bottom) {
topmost = element;
if (y >= pos and y <= pos + element.boxAxis(frame, .height)) {
if (ignore_x) {
topmost = element;
} else {
// only bother calculating the horiziontal position for
// elements contained by the vertical band
const left = element.horizontalPosition(frame);
if (x >= left and x <= left + element.boxAxis(frame, .width)) {
topmost = element;
}
}
}
}
}
+127 -151
View File
@@ -146,7 +146,13 @@ pub const Flags = packed struct(u8) {
// approach
click_in_progress: bool = false,
_unused: u5 = 0,
// The element may have inline style: a materialized entry in the frame's
// _element_styles, or a `style` attribute not yet materialized. Set once,
// never cleared. A clear bit lets the faux layout skip both the map probe
// and the attribute scan for the many elements that have neither.
has_inline_style: bool = false,
_unused: u4 = 0,
};
_type: Type,
@@ -979,13 +985,26 @@ pub fn getOrCreateStyle(self: *Element, frame: *Frame) !*CSSStyleProperties {
if (!gop.found_existing) {
gop.value_ptr.* = try CSSStyleProperties.init(self, false, owner);
}
self._flags.has_inline_style = true;
return gop.value_ptr.*;
}
fn getStyle(self: *Element, frame: *Frame) ?*CSSStyleProperties {
pub fn getStyle(self: *Element, frame: *Frame) ?*CSSStyleProperties {
if (!self._flags.has_inline_style) {
return null;
}
return self.ownerFrame(frame)._element_styles.get(self);
}
// Marks the element as possibly having inline style once a `style` attribute
// lands on it. Attribute population paths that bypass attributeChange (the
// parser, cloneNode) call this after filling the list.
pub fn noteStyleAttribute(self: *Element) void {
if (self._attributes.hasSafe(comptime .wrap("style"))) {
self._flags.has_inline_style = true;
}
}
pub fn setStyle(self: *Element, value: []const u8, frame: *Frame) !void {
const style = try self.getOrCreateStyle(frame);
try style.asCSSStyleDeclaration().setCssText(value, frame);
@@ -1314,6 +1333,14 @@ pub fn checkVisibilityCached(self: *Element, cache: ?*VisibilityCache, frame: *F
return !self.ownerFrame(frame)._style_manager.isHidden(self, cache, .{});
}
// The element's own display:none only, no ancestor walk. For a child or
// sibling of an element already known to be visible, that is the whole
// answer: they share the visible ancestor chain — and the owner frame, which
// the caller resolves once rather than per element.
fn isVisibleSelf(self: *Element, style_manager: *StyleManager) bool {
return !style_manager.hasDisplayNone(self);
}
const CheckVisibilityOpts = struct {
checkOpacity: bool = false,
opacityProperty: bool = false,
@@ -1328,100 +1355,77 @@ pub fn checkVisibility(self: *Element, opts_: ?CheckVisibilityOpts, frame: *Fram
});
}
pub const Dimensions = struct {
width: f64,
height: f64,
// if the value is explicit (e.g. inline style, width attribute, ...) or defaulted
explicit_width: bool = false,
explicit_height: bool = false,
pub const Axis = enum {
width,
height,
// The axis' value, and whether it's explicit or the default
pub const State = struct {
value: f64,
explicit: bool = false,
};
};
pub fn getElementDimensions(self: *Element, frame: *Frame) Dimensions {
var dims: Dimensions = .{ .width = 5.0, .height = 5.0 };
pub fn getElementAxis(self: *Element, frame: *Frame, comptime axis: Axis) Axis.State {
if (self.getStyle(frame)) |style| {
const decl = style.asCSSStyleDeclaration();
if (CSS.parseDimensionViewport(decl.getPropertyValue("width", frame), frame)) |w| {
dims.width = w;
dims.explicit_width = true;
}
if (CSS.parseDimensionViewport(decl.getPropertyValue("height", frame), frame)) |h| {
dims.height = h;
dims.explicit_height = true;
if (CSS.parseDimensionViewport(decl.getPropertyValue(@tagName(axis), frame), frame)) |v| {
return .{ .value = v, .explicit = true };
}
}
if (dims.width == 5.0 or dims.height == 5.0) {
const tag = self.getTag();
switch (self.getTag()) {
// Root containers get large default size to contain descendant positions.
// With calculateDocumentPosition using linear depth scaling (100px per level),
// even very deep trees (100 levels) stay within 10,000px.
// 100M pixels is plausible for very long documents.
if (tag == .html or tag == .body) {
if (dims.width == 5.0) dims.width = 1920.0;
if (dims.height == 5.0) dims.height = 100_000_000.0;
} else if (tag == .img or tag == .iframe) {
if (self.getAttributeSafe(comptime .wrap("width"))) |w| {
if (std.fmt.parseFloat(f64, w)) |parsed| {
dims.width = parsed;
dims.explicit_width = true;
.html, .body => return .{ .value = if (axis == .width) 1920.0 else 100_000_000.0 },
.img, .iframe => {
if (self.getAttributeSafe(comptime .wrap(@tagName(axis)))) |attr| {
if (std.fmt.parseFloat(f64, attr)) |parsed| {
return .{ .value = parsed, .explicit = true };
} else |_| {}
}
if (self.getAttributeSafe(comptime .wrap("height"))) |h| {
if (std.fmt.parseFloat(f64, h)) |parsed| {
dims.height = parsed;
dims.explicit_height = true;
} else |_| {}
}
}
},
else => {},
}
return dims;
return .{ .value = 5.0 };
}
// We can't do this correctly without full styles and more rendering. We also
// can't just ignore the children since some sites append nodes until a certain
// width / height treshold is reached. If the size isn't explicit, we fallback
// to contentWidth/contentHeight
// to the content size.
pub fn getClientWidth(self: *Element, frame: *Frame) f64 {
var visibility_cache: VisibilityCache = .{};
return self.getClientWidthWithCache(frame, &visibility_cache);
}
pub fn getClientWidthWithCache(self: *Element, frame: *Frame, visibility_cache: *VisibilityCache) f64 {
if (!self.checkVisibilityCached(visibility_cache, frame)) {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
const dims = self.getElementDimensions(frame);
const tag = self.getTag();
if (tag == .html or tag == .body or dims.explicit_width) {
return dims.width;
}
return @max(dims.width, self.contentWidth(frame, visibility_cache));
return self.boxAxis(frame, .width);
}
pub fn getClientHeight(self: *Element, frame: *Frame) f64 {
var visibility_cache: VisibilityCache = .{};
return self.getClientHeightWithCache(frame, &visibility_cache);
}
pub fn getClientHeightWithCache(self: *Element, frame: *Frame, visibility_cache: *VisibilityCache) f64 {
if (!self.checkVisibilityCached(visibility_cache, frame)) {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
return self.boxAxis(frame, .height);
}
const dims = self.getElementDimensions(frame);
const tag = self.getTag();
if (tag == .html or tag == .body or dims.explicit_height) {
return dims.height;
// Caller must have made sure self is visible.
pub fn boxAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
const own = self.getElementAxis(frame, axis);
if (own.explicit) {
// an explicitly set value always wins
return own.value;
}
return @max(dims.height, self.contentHeight(frame, visibility_cache));
const tag = self.getTag();
if (tag == .html or tag == .body) {
// html/body return their set value regardless of children.
return own.value;
}
return @max(own.value, self.contentAxis(frame, axis));
}
pub fn getBoundingClientRect(self: *Element, frame: *Frame) !*DOMRect {
@@ -1440,17 +1444,11 @@ pub fn boundingClientRectValues(self: *Element, frame: *Frame) DOMRect.Data {
// Some cases need the bounding rect but have already done the visibility check.
pub fn boundingClientRectValuesForVisible(self: *Element, frame: *Frame) DOMRect.Data {
const y = calculateDocumentPosition(self.asNode());
const dims = self.getElementDimensions(frame);
// Use sibling position for x coordinate to ensure siblings have different x values
const x = calculateSiblingPosition(self.asNode());
return .{
.x = x,
.y = y,
.width = dims.width,
.height = dims.height,
.x = self.horizontalPosition(frame),
.y = calculateDocumentPosition(self.asNode()),
.width = self.boxAxis(frame, .width),
.height = self.boxAxis(frame, .height),
};
}
@@ -1507,12 +1505,11 @@ pub fn setScrollLeft(self: *Element, value: i32, frame: *Frame) !void {
}
pub fn getScrollHeight(self: *Element, frame: *Frame) f64 {
var visibility_cache: VisibilityCache = .{};
if (!self.checkVisibilityCached(&visibility_cache, frame)) {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
const height = self.getElementDimensions(frame).height;
const height = self.getElementAxis(frame, .height).value;
const tag = self.getTag();
// As in getScrollWidth: the root containers carry artificial giant
@@ -1521,54 +1518,29 @@ pub fn getScrollHeight(self: *Element, frame: *Frame) f64 {
return height;
}
return @max(height, self.contentHeight(frame, &visibility_cache));
}
// The height of the direct child elements stacked vertically, the counterpart
// of contentWidth.
//
// Note that the two assume contradictory arrangements — contentWidth lays the
// children out in a row, this stacks them. That is deliberate. We can't detect
// the real layout mode (see contentWidth), so each axis independently assumes
// the arrangement that produces overflow. Together they bound the content
// extent per axis rather than describing one coherent layout: reporting no
// overflow when there is some is what wedges measure-then-mutate loops,
// while the reverse merely over-reports.
fn contentHeight(self: *Element, frame: *Frame, visibility_cache: *VisibilityCache) f64 {
var total: f64 = 0;
var child = self.asNode().firstChild();
while (child) |node| : (child = node.nextSibling()) {
if (node.is(Element)) |el| {
if (el.checkVisibilityCached(visibility_cache, frame)) {
total += el.getElementDimensions(frame).height;
}
}
}
return total;
return @max(height, self.contentAxis(frame, .height));
}
pub fn getScrollWidth(self: *Element, frame: *Frame) f64 {
var visibility_cache: VisibilityCache = .{};
if (!self.checkVisibilityCached(&visibility_cache, frame)) {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
const width = self.getElementDimensions(frame).width;
const width = self.getElementAxis(frame, .width).value;
const tag = self.getTag();
// The root containers carry artificial giant defaults (1920 and
// 100_000_000, see getElementDimensions). Stacking their children on
// 100_000_000, see getElementAxis). Stacking their children on
// top would inflate a value sites read to detect page overflow.
if (tag == .html or tag == .body) {
return width;
}
return @max(width, self.contentWidth(frame, &visibility_cache));
return @max(width, self.contentAxis(frame, .width));
}
// The width of the direct child elements laid end to end on a single row.
// One axis of the direct child elements' size: laid end to end on a single
// row for the width, stacked for the height.
//
// The dummy layout engine has no line-breaking, and an element only overflows
// horizontally when its children don't wrap (white-space:nowrap, a flex row, an
@@ -1578,30 +1550,30 @@ pub fn getScrollWidth(self: *Element, frame: *Frame) f64 {
// rules for `display:none` and `visibility` alone.
//
// Only direct children are measured, never the whole subtree. This runs on
// every scrollWidth read, and recursing would make an element's cost O(subtree)
// rather than O(fan-out).
// every size read, and recursing would make an element's cost O(subtree)
// rather than O(fan-out). It also keeps an ancestor from growing in lockstep
// with its descendants, so "append until the track outgrows its shell" still
// crosses the threshold.
//
// Growing with the child count is the point: JS that appends content until
// `scrollWidth` passes a threshold (the infinite-marquee idiom) never
// terminates when the metric ignores what it just inserted.
//
// Text children are not measured, matching contentHeight. Estimating a text run
// from its length would need a per-character advance, which in turn has to track
// font-size or "shrink the font until it fits" loops stop converging — and it
// would report overflow for practically every element containing text, since a
// few words already exceed the default box. Element children are what content
// grown by script actually consists of.
fn contentWidth(self: *Element, frame: *Frame, visibility_cache: *VisibilityCache) f64 {
// Text children are not measured. Estimating a text run from its length would
// need a per-character advance, which in turn has to track font-size or
// "shrink the font until it fits" loops stop converging — and it would report
// overflow for practically every element containing text, since a few words
// already exceed the default box. Element children are what content grown by
// script actually consists of.
fn contentAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
var total: f64 = 0;
const style_manager = &self.ownerFrame(frame)._style_manager;
// The cache arrives seeded by the caller's own visibility walk, and
// siblings share that ancestor chain, so the loop costs one own-element
// check per child rather than N ancestor walks.
var child = self.asNode().firstChild();
while (child) |node| : (child = node.nextSibling()) {
if (node.is(Element)) |el| {
if (el.checkVisibilityCached(visibility_cache, frame)) {
total += el.getElementDimensions(frame).width;
if (el.isVisibleSelf(style_manager)) {
total += el.getElementAxis(frame, axis).value;
}
}
}
@@ -1610,19 +1582,11 @@ fn contentWidth(self: *Element, frame: *Frame, visibility_cache: *VisibilityCach
}
pub fn getOffsetHeight(self: *Element, frame: *Frame) f64 {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
const dims = self.getElementDimensions(frame);
return dims.height;
return self.getClientHeight(frame);
}
pub fn getOffsetWidth(self: *Element, frame: *Frame) f64 {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
const dims = self.getElementDimensions(frame);
return dims.width;
return self.getClientWidth(frame);
}
pub fn getOffsetTop(self: *Element, frame: *Frame) f64 {
@@ -1636,7 +1600,7 @@ pub fn getOffsetLeft(self: *Element, frame: *Frame) f64 {
if (!self.checkVisibilityCached(null, frame)) {
return 0.0;
}
return calculateSiblingPosition(self.asNode());
return self.horizontalPosition(frame);
}
pub fn getOffsetParent(self: *Element, frame: *Frame) ?*Element {
@@ -1754,28 +1718,40 @@ fn countSubtreeNodes(node: *Node) f64 {
return count;
}
// Calculates horizontal position using the same approach as y,
// just scaled differently for visual distinction
fn calculateSiblingPosition(node: *Node) f64 {
var position: f64 = 0.0;
var current = node;
// The horizontal position follows the same single-row assumption as
// contentAxis: an element sits to the right of the visible element
// siblings before it.
// translateX is commonly used to shift elements around, e.g. in a carousel to
// shift things around. So we honor any transform: translateX inline styles.
pub fn horizontalPosition(self: *Element, frame: *Frame) f64 {
var x: f64 = 0.0;
var current = self.asNode();
const style_manager = &self.ownerFrame(frame)._style_manager;
if (self.getStyle(frame)) |style| {
x += CSS.parseTranslateX(style.asCSSStyleDeclaration().getPropertyValue("transform", frame));
}
// Walk up to root, counting preceding nodes (same as y)
while (current.parentNode()) |parent| {
// Count all previous siblings and their descendants
var sibling = parent.firstChild();
while (sibling) |s| {
if (s == current) break;
position += countSubtreeNodes(s);
sibling = s.nextSibling();
if (parent.is(Element)) |el| {
if (el.getStyle(frame)) |style| {
x += CSS.parseTranslateX(style.asCSSStyleDeclaration().getPropertyValue("transform", frame));
}
}
var sibling = parent.firstChild();
while (sibling) |s| : (sibling = s.nextSibling()) {
if (s == current) break;
if (s.is(Element)) |el| {
if (el.isVisibleSelf(style_manager)) {
x += el.getElementAxis(frame, .width).value;
}
}
}
// Count the parent itself
position += 1.0;
current = parent;
}
return position * 5.0; // 5px per node
return x;
}
pub fn getElementsByTagName(self: *Element, tag_name: []const u8, frame: *Frame) !Node.GetElementsByTagNameResult {
+4 -2
View File
@@ -165,8 +165,10 @@ pub fn deliverEntries(self: *ResizeObserver, frame: *Frame) !void {
if (!connected or !target.checkVisibilityCached(&visibility_cache, frame)) {
break :blk .{ 0, 0 };
}
const dims = target.getElementDimensions(frame);
break :blk .{ dims.width, dims.height };
break :blk .{
target.getElementAxis(frame, .width).value,
target.getElementAxis(frame, .height).value,
};
};
if (width == obs.last_width and height == obs.last_height) {
@@ -122,13 +122,12 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const
}
fn resolvedDimension(element: *Element, dimension: enum { width, height }, frame: *Frame) []const u8 {
var visibility_cache: Element.VisibilityCache = .{};
if (!element.checkVisibilityCached(&visibility_cache, frame)) {
if (!element.checkVisibilityCached(null, frame)) {
return "auto";
}
const value = switch (dimension) {
.width => element.getClientWidthWithCache(frame, &visibility_cache),
.height => element.getClientHeightWithCache(frame, &visibility_cache),
.width => element.boxAxis(frame, .width),
.height => element.boxAxis(frame, .height),
};
return std.fmt.allocPrint(frame.local_arena, "{d}px", .{value}) catch "auto";
}
+1 -1
View File
@@ -1156,7 +1156,7 @@ test "cdp.dom: getBoxModel" {
.params = .{ .nodeId = 3 },
});
try ctx.expectSentResult(.{ .model = BoxModel{
.content = Quad{ 25.0, 25.0, 30.0, 25.0, 30.0, 30.0, 25.0, 30.0 },
.content = Quad{ 0.0, 25.0, 5.0, 25.0, 5.0, 30.0, 0.0, 30.0 },
.padding = Quad{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
.border = Quad{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
.margin = Quad{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },