From d40f68ba5685df1a08b0f52480164d3febfd9da4 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 19 Aug 2026 18:28:28 +0800 Subject: [PATCH] webapi: improve "rendering" of an element's left position 1. The horizontal position of an element is now calculated based on the horizontal position of it's siblings (up the tree). Rather than merely the count * default width. 2. translateX is parsed on inline styles and applies to the horizontal position 1 & 2 fix the https://lseo.com/ issue, but they add overhead to APIs that can be used a lot. So: 3. Most element dimensions api relied on getElementDimensions which calculated both the height and width dimensions, even though they only needed once (e.g. getClientHeight only needs the height). These methods now only get the axis that they care about 4. Because we do more style sheet lookups, and thus more lookups in Frame._element_styles, Element.flags now has an has_inline_style which helps avoid the lookup. --- src/browser/Frame.zig | 3 +- src/browser/StyleManager.zig | 6 +- src/browser/frame/node_factory.zig | 5 +- src/browser/frame/observers.zig | 2 +- src/browser/parser/Parser.zig | 1 + src/browser/tests/element/position.html | 77 +++++ src/browser/webapi/CSS.zig | 41 +++ src/browser/webapi/Document.zig | 20 +- src/browser/webapi/Element.zig | 278 ++++++++---------- src/browser/webapi/ResizeObserver.zig | 6 +- .../webapi/css/CSSStyleDeclaration.zig | 7 +- src/cdp/domains/dom.zig | 2 +- 12 files changed, 277 insertions(+), 171 deletions(-) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 1ff57df9d..a4cdb1845 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -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 }); }; diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 6dc83ead9..8f2791751 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -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| { diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index 6de08a90d..042d05ea3 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -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 diff --git a/src/browser/frame/observers.zig b/src/browser/frame/observers.zig index 60ada05ef..34126fb76 100644 --- a/src/browser/frame/observers.zig +++ b/src/browser/frame/observers.zig @@ -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 { diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index f735916ad..95d403f96 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -590,6 +590,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 { diff --git a/src/browser/tests/element/position.html b/src/browser/tests/element/position.html index 2d653d46c..6147cb0b4 100644 --- a/src/browser/tests/element/position.html +++ b/src/browser/tests/element/position.html @@ -168,6 +168,83 @@ } + + + +