From e4a36552b8a71f1cfbe511f4cc7fc19d2000065e Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 26 Aug 2026 12:50:53 +0800 Subject: [PATCH] mem: Optional CSSStyleDeclaration materialization StyleManager ultimately ends up calling el.getOrCreateStyle() which either returns the element's CSSStyleProperties OR (creates it AND stores it in the Frame._element_styles for future lookups). The goal behind this caching is twofold: 1 - Performance of not having to reparse the "style" attribute 2 - Identity: two calls from JS to get the properties should return the same value (2) is non-negotiable, so the 'getOrCreate' _has_ to exist for JS-facing APIs. But (1) is CPU vs memory optimization that we've decided should always favor the CPU. But, in any case where we dump an entire tree, that memory cost can be significant (# of elements with a style attribute) and the CPU gains are questionable (it isn't like a JS loop re-checking an element's properties, it's a one-time dump). So, the StyleManager now takes a comptime `InlineAccess` which is either `.scan` or `.materialize`. When it's `.materialize` it behaves as before. When it's `.scan` is will use an existing `_element_styles` if available else it will re-parse but not store the value. --- src/SemanticTree.zig | 2 +- src/browser/StyleManager.zig | 137 ++++++++++++++---- src/browser/dump.zig | 2 +- src/browser/markdown.zig | 2 +- src/browser/webapi/Document.zig | 2 +- src/browser/webapi/Element.zig | 28 ++-- src/browser/webapi/ResizeObserver.zig | 2 +- .../webapi/css/CSSStyleDeclaration.zig | 4 +- src/browser/webapi/element/Html.zig | 2 +- src/cdp/AXNode.zig | 2 +- 10 files changed, 131 insertions(+), 52 deletions(-) diff --git a/src/SemanticTree.zig b/src/SemanticTree.zig index ad224bab9..20f6010ab 100644 --- a/src/SemanticTree.zig +++ b/src/SemanticTree.zig @@ -131,7 +131,7 @@ fn walk( if (tag == .datalist or tag == .option or tag == .optgroup) return; // Check visibility using the engine's checkVisibility which handles CSS display: none - if (!el.checkVisibilityCached(ctx.visibility_cache, self.frame)) { + if (!el.checkVisibilityCached(ctx.visibility_cache, self.frame, .scan)) { return; } diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index b633d2204..6f377a5ac 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -33,7 +33,6 @@ const SelectorList = @import("webapi/selector/List.zig"); const CSSStyleRule = @import("webapi/css/CSSStyleRule.zig"); const CSSStyleSheet = @import("webapi/css/CSSStyleSheet.zig"); const CSSStyleProperties = @import("webapi/css/CSSStyleProperties.zig"); -const CSSStyleProperty = @import("webapi/css/CSSStyleDeclaration.zig").Property; const log = lp.log; const String = lp.String; @@ -573,7 +572,7 @@ fn rebuildIfDirty(self: *StyleManager) !void { // Check if an element is hidden based on options. // By default only checks display:none. // Walks up the tree to check ancestors. -pub fn isHidden(self: *StyleManager, el: *Element, cache: ?*VisibilityCache, options: CheckVisibilityOptions) bool { +pub fn isHidden(self: *StyleManager, el: *Element, cache: ?*VisibilityCache, options: CheckVisibilityOptions, comptime access: InlineAccess) bool { self.rebuildIfDirty() catch return false; var current: ?*Element = el; @@ -590,7 +589,7 @@ pub fn isHidden(self: *StyleManager, el: *Element, cache: ?*VisibilityCache, opt } } - const hidden = self.isElementHidden(elem, options); + const hidden = self.isElementHidden(elem, options, access); // Store in cache if (cache) |c| { @@ -611,18 +610,18 @@ pub fn isHidden(self: *StyleManager, el: *Element, cache: ?*VisibilityCache, opt /// Computed display:none for a single element (own property, no ancestor walk). /// Honors the UA stylesheet rules per HTML Rendering §15.3.1 "Hidden elements" /// via `isElementHidden`. -pub fn hasDisplayNone(self: *StyleManager, el: *Element) bool { +pub fn hasDisplayNone(self: *StyleManager, el: *Element, comptime access: InlineAccess) bool { self.rebuildIfDirty() catch return false; - return self.isElementHidden(el, .{}); + return self.isElementHidden(el, .{}, access); } /// Computed display:none coming only from inline style or an author stylesheet /// rule — the UA stylesheet's hidden elements (,