From ee034943b68115180583da23e39f3079b07d014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 12 Mar 2026 16:27:25 +0900 Subject: [PATCH 01/35] feat(css): implement stylesheet rule management Adds a CSS rule parser and implements `insertRule`, `deleteRule`, and `replaceSync` in `CSSStyleSheet`. Also updates `CSSRuleList` to use dynamic storage and populates sheets from ` + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 53a0b07a..33e29952 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -586,7 +586,7 @@ 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 (element.checkVisibility(page)) { + if (element.checkVisibilityCached(null, page)) { const rect = element.getBoundingClientRectForVisible(page); if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) { topmost = element; diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index d2d61451..58cc168c 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -24,6 +24,7 @@ const String = @import("../../string.zig").String; const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); +const StyleManager = @import("../StyleManager.zig"); const reflect = @import("../reflect.zig"); const Node = @import("Node.zig"); @@ -1041,135 +1042,32 @@ pub fn parentElement(self: *Element) ?*Element { return self._proto.parentElement(); } -const CSSStyleRule = @import("css/CSSStyleRule.zig"); -const StyleSheetList = @import("css/StyleSheetList.zig"); +/// Cache for visibility checks - re-exported from StyleManager for convenience. +pub const VisibilityCache = StyleManager.VisibilityCache; -pub const CssProperties = struct { - display_none: bool = false, - visibility_hidden: bool = false, - opacity_zero: bool = false, - pointer_events_none: bool = false, +/// Cache for pointer-events checks - re-exported from StyleManager for convenience. +pub const PointerEventsCache = StyleManager.PointerEventsCache; + +pub fn hasPointerEventsNone(self: *Element, cache: ?*PointerEventsCache, page: *Page) bool { + return page._style_manager.hasPointerEventsNone(self, cache); +} + +pub fn checkVisibilityCached(self: *Element, cache: ?*VisibilityCache, page: *Page) bool { + return !page._style_manager.isHidden(self, cache, .{}); +} + +const CheckVisibilityOpts = struct { + checkOpacity: bool = false, + opacityProperty: bool = false, + checkVisibilityCSS: bool = false, + visibilityProperty: bool = false, }; - -pub const CssCache = std.AutoHashMapUnmanaged(*Element, CssProperties); - -fn getCssProperties(el: *Element, doc_sheets: ?*StyleSheetList, cache: ?*CssCache, page: *Page) CssProperties { - if (cache) |c| { - if (c.get(el)) |props| return props; - } - - var props = CssProperties{}; - - // Check stylesheets first - if (doc_sheets) |sheets| { - for (0..sheets.length()) |i| { - const sheet = sheets.item(i) orelse continue; - const rules = sheet.getCssRules(page) catch continue; - for (0..rules.length()) |j| { - const rule = rules.item(j) orelse continue; - if (rule.is(CSSStyleRule)) |style_rule| { - const selector = style_rule.getSelectorText(); - if (el.matches(selector, page) catch false) { - const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration(); - - const display = style.getPropertyValue("display", page); - if (std.mem.eql(u8, display, "none")) { - props.display_none = true; - } else if (display.len > 0) { - props.display_none = false; - } - - const visibility = style.getPropertyValue("visibility", page); - if (std.mem.eql(u8, visibility, "hidden") or std.mem.eql(u8, visibility, "collapse")) { - props.visibility_hidden = true; - } else if (visibility.len > 0) { - props.visibility_hidden = false; - } - - const opacity = style.getPropertyValue("opacity", page); - if (std.mem.eql(u8, opacity, "0")) { - props.opacity_zero = true; - } else if (opacity.len > 0) { - props.opacity_zero = false; - } - - const pointer_events = style.getPropertyValue("pointer-events", page); - if (std.mem.eql(u8, pointer_events, "none")) { - props.pointer_events_none = true; - } else if (pointer_events.len > 0) { - props.pointer_events_none = false; - } - } - } - } - } - } - - // Check inline styles overrides - if (el.getOrCreateStyle(page) catch null) |style| { - const decl = style.asCSSStyleDeclaration(); - const display = decl.getPropertyValue("display", page); - if (std.mem.eql(u8, display, "none")) { - props.display_none = true; - } else if (display.len > 0) { - props.display_none = false; - } - - const visibility = decl.getPropertyValue("visibility", page); - if (std.mem.eql(u8, visibility, "hidden") or std.mem.eql(u8, visibility, "collapse")) { - props.visibility_hidden = true; - } else if (visibility.len > 0) { - props.visibility_hidden = false; - } - - const opacity = decl.getPropertyValue("opacity", page); - if (std.mem.eql(u8, opacity, "0")) { - props.opacity_zero = true; - } else if (opacity.len > 0) { - props.opacity_zero = false; - } - - const pointer_events = decl.getPropertyValue("pointer-events", page); - if (std.mem.eql(u8, pointer_events, "none")) { - props.pointer_events_none = true; - } else if (pointer_events.len > 0) { - props.pointer_events_none = false; - } - } - - if (cache) |c| { - c.put(page.call_arena, el, props) catch {}; - } - - return props; -} - -pub fn hasPointerEventsNone(self: *Element, cache: ?*CssCache, page: *Page) bool { - const doc_sheets = page.document.getStyleSheets(page) catch null; - var current: ?*Element = self; - while (current) |el| { - const props = el.getCssProperties(doc_sheets, cache, page); - if (props.pointer_events_none) return true; - current = el.parentElement(); - } - return false; -} - -pub fn checkVisibilityCached(self: *Element, cache: ?*CssCache, page: *Page) bool { - const doc_sheets = page.document.getStyleSheets(page) catch null; - var current: ?*Element = self; - - while (current) |el| { - const props = getCssProperties(el, doc_sheets, cache, page); - if (props.display_none or props.visibility_hidden or props.opacity_zero) return false; - current = el.parentElement(); - } - - return true; -} - -pub fn checkVisibility(self: *Element, page: *Page) bool { - return self.checkVisibilityCached(null, page); +pub fn checkVisibility(self: *Element, opts_: ?CheckVisibilityOpts, page: *Page) bool { + const opts = opts_ orelse CheckVisibilityOpts{}; + return !page._style_manager.isHidden(self, null, .{ + .check_opacity = opts.checkOpacity or opts.opacityProperty, + .check_visibility = opts.visibilityProperty or opts.checkVisibilityCSS, + }); } fn getElementDimensions(self: *Element, page: *Page) struct { width: f64, height: f64 } { @@ -1206,7 +1104,7 @@ fn getElementDimensions(self: *Element, page: *Page) struct { width: f64, height } pub fn getClientWidth(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } const dims = self.getElementDimensions(page); @@ -1214,7 +1112,7 @@ pub fn getClientWidth(self: *Element, page: *Page) f64 { } pub fn getClientHeight(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } const dims = self.getElementDimensions(page); @@ -1222,7 +1120,7 @@ pub fn getClientHeight(self: *Element, page: *Page) f64 { } pub fn getBoundingClientRect(self: *Element, page: *Page) DOMRect { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return .{ ._x = 0.0, ._y = 0.0, @@ -1252,7 +1150,7 @@ pub fn getBoundingClientRectForVisible(self: *Element, page: *Page) DOMRect { } pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return &.{}; } const rects = try page.call_arena.alloc(DOMRect, 1); @@ -1297,7 +1195,7 @@ pub fn getScrollWidth(self: *Element, page: *Page) f64 { } pub fn getOffsetHeight(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } const dims = self.getElementDimensions(page); @@ -1305,7 +1203,7 @@ pub fn getOffsetHeight(self: *Element, page: *Page) f64 { } pub fn getOffsetWidth(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } const dims = self.getElementDimensions(page); @@ -1313,14 +1211,14 @@ pub fn getOffsetWidth(self: *Element, page: *Page) f64 { } pub fn getOffsetTop(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } return calculateDocumentPosition(self.asNode()); } pub fn getOffsetLeft(self: *Element, page: *Page) f64 { - if (!self.checkVisibility(page)) { + if (!self.checkVisibilityCached(null, page)) { return 0.0; } return calculateSiblingPosition(self.asNode()); diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 13499033..d207c11a 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -77,10 +77,11 @@ pub fn item(self: *const CSSStyleDeclaration, index: u32) []const u8 { pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) []const u8 { const normalized = normalizePropertyName(property_name, &page.buf); - const prop = self.findProperty(normalized) orelse { + const wrapped = String.wrap(normalized); + const prop = self.findProperty(wrapped) orelse { // Only return default values for computed styles if (self._is_computed) { - return getDefaultPropertyValue(self, normalized); + return getDefaultPropertyValue(self, wrapped); } return ""; }; @@ -89,7 +90,7 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []const u8, page: *Page) []const u8 { const normalized = normalizePropertyName(property_name, &page.buf); - const prop = self.findProperty(normalized) orelse return ""; + const prop = self.findProperty(.wrap(normalized)) orelse return ""; return if (prop._important) "important" else ""; } @@ -120,7 +121,7 @@ fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value: const normalized_value = try normalizePropertyValue(page.call_arena, normalized, value); // Find existing property - if (self.findProperty(normalized)) |existing| { + if (self.findProperty(.wrap(normalized))) |existing| { existing._value = try String.init(page.arena, normalized_value, .{}); existing._important = important; return; @@ -144,7 +145,7 @@ pub fn removeProperty(self: *CSSStyleDeclaration, property_name: []const u8, pag fn removePropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, page: *Page) ![]const u8 { const normalized = normalizePropertyName(property_name, &page.buf); - const prop = self.findProperty(normalized) orelse return ""; + const prop = self.findProperty(.wrap(normalized)) orelse return ""; // the value might not be on the heap (it could be inlined in the small string // optimization), so we need to dupe it. @@ -208,11 +209,11 @@ pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void { } } -fn findProperty(self: *const CSSStyleDeclaration, name: []const u8) ?*Property { +pub fn findProperty(self: *const CSSStyleDeclaration, name: String) ?*Property { var node = self._properties.first; while (node) |n| { const prop = Property.fromNodeLink(n); - if (prop._name.eqlSlice(name)) { + if (prop._name.eql(name)) { return prop; } node = n.next; @@ -617,26 +618,36 @@ fn isLengthProperty(name: []const u8) bool { return length_properties.has(name); } -fn getDefaultPropertyValue(self: *const CSSStyleDeclaration, normalized_name: []const u8) []const u8 { - if (std.mem.eql(u8, normalized_name, "visibility")) { - return "visible"; +fn getDefaultPropertyValue(self: *const CSSStyleDeclaration, name: String) []const u8 { + switch (name.len) { + 5 => { + if (name.eql(comptime .wrap("color"))) { + const element = self._element orelse return ""; + return getDefaultColor(element); + } + }, + 7 => { + if (name.eql(comptime .wrap("opacity"))) { + return "1"; + } + if (name.eql(comptime .wrap("display"))) { + const element = self._element orelse return ""; + return getDefaultDisplay(element); + } + }, + 10 => { + if (name.eql(comptime .wrap("visibility"))) { + return "visible"; + } + }, + 16 => { + if (name.eqlSlice("background-color")) { + // transparent + return "rgba(0, 0, 0, 0)"; + } + }, + else => {}, } - if (std.mem.eql(u8, normalized_name, "opacity")) { - return "1"; - } - if (std.mem.eql(u8, normalized_name, "display")) { - const element = self._element orelse return ""; - return getDefaultDisplay(element); - } - if (std.mem.eql(u8, normalized_name, "color")) { - const element = self._element orelse return ""; - return getDefaultColor(element); - } - if (std.mem.eql(u8, normalized_name, "background-color")) { - // transparent - return "rgba(0, 0, 0, 0)"; - } - return ""; } diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index aa8655f1..2b41fffa 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -69,12 +69,19 @@ pub fn insertRule(self: *CSSStyleSheet, rule: []const u8, maybe_index: ?u32, pag const rules = try self.getCssRules(page); try rules.insert(index, style_rule._proto, page); + + // Notify StyleManager that rules have changed + page._style_manager.sheetModified(); + return index; } pub fn deleteRule(self: *CSSStyleSheet, index: u32, page: *Page) !void { const rules = try self.getCssRules(page); try rules.remove(index); + + // Notify StyleManager that rules have changed + page._style_manager.sheetModified(); } pub fn replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise { @@ -99,6 +106,9 @@ pub fn replaceSync(self: *CSSStyleSheet, text: []const u8, page: *Page) !void { try rules.insert(index, style_rule._proto, page); index += 1; } + + // Notify StyleManager that rules have changed + page._style_manager.sheetModified(); } pub const JsApi = struct { diff --git a/src/browser/webapi/css/StyleSheetList.zig b/src/browser/webapi/css/StyleSheetList.zig index 11efb45c..c0732c73 100644 --- a/src/browser/webapi/css/StyleSheetList.zig +++ b/src/browser/webapi/css/StyleSheetList.zig @@ -24,6 +24,15 @@ pub fn add(self: *StyleSheetList, sheet: *CSSStyleSheet, page: *Page) !void { try self._sheets.append(page.arena, sheet); } +pub fn remove(self: *StyleSheetList, sheet: *CSSStyleSheet) void { + for (self._sheets.items, 0..) |s, i| { + if (s == sheet) { + _ = self._sheets.orderedRemove(i); + return; + } + } +} + pub const JsApi = struct { pub const bridge = js.Bridge(StyleSheetList); diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index b6389d61..fc52cf38 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -106,7 +106,10 @@ pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { pub fn styleAddedCallback(self: *Style, page: *Page) !void { // Force stylesheet initialization so rules are parsed immediately - _ = self.getSheet(page) catch null; + if (self.getSheet(page) catch null) |sheet| { + // Notify StyleManager about the new stylesheet + page._style_manager.sheetAdded(sheet) catch {}; + } // if we're planning on navigating to another page, don't trigger load event. if (page.isGoingAway()) { From dda5e2c5427d73a0099d64e39646643f0dcca6d2 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 19 Mar 2026 06:47:40 +0800 Subject: [PATCH 22/35] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrià Arrufat <1671644+arrufat@users.noreply.github.com> --- src/browser/StyleManager.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index d968c38a..000bd89d 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -357,7 +357,7 @@ pub fn computeSpecificity(selector: Selector.Selector) u32 { // Pack into single u32: (ids << 20) | (classes << 10) | elements // This gives us 10 bits each, supporting up to 1023 of each type - return (ids << 20) | (classes << 10) | elements; + return (@min(ids, 1023) << 20) | (@min(classes, 1023) << 10) | @min(elements, 1023); } fn countCompoundSpecificity(compound: Selector.Compound, ids: *u32, classes: *u32, elements: *u32) void { @@ -379,6 +379,7 @@ fn countCompoundSpecificity(compound: Selector.Compound, ids: *u32, classes: *u3 const spec = computeSpecificity(nested_sel); if (spec > max_nested) max_nested = spec; } + max_nested = @min(max_nested, 1023); // Unpack and add to our counts ids.* += (max_nested >> 20) & 0x3FF; classes.* += (max_nested >> 10) & 0x3FF; From c1bb27c45046ea242892af16b5655110537f6540 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 19 Mar 2026 06:52:41 +0800 Subject: [PATCH 23/35] better encapsulate arena reset --- src/ArenaPool.zig | 5 +++++ src/browser/StyleManager.zig | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ArenaPool.zig b/src/ArenaPool.zig index 2f8de17f..608445f6 100644 --- a/src/ArenaPool.zig +++ b/src/ArenaPool.zig @@ -100,6 +100,11 @@ pub fn reset(_: *const ArenaPool, allocator: Allocator, retain: usize) void { _ = arena.reset(.{ .retain_with_limit = retain }); } +pub fn resetRetain(_: *const ArenaPool, allocator: Allocator) void { + const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr)); + _ = arena.reset(.retain_capacity); +} + const testing = std.testing; test "arena pool - basic acquire and use" { diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 000bd89d..a7765814 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -90,10 +90,7 @@ fn rebuildIfDirty(self: *StyleManager) !void { self.dirty = false; const item_count = self.rules.items.len; - - const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(self.arena.ptr)); - _ = arena.reset(.retain_capacity); - + self.page._session.arena_pool.resetRetain(self.arena); self.rules = try .initCapacity(self.arena, item_count); const sheets = self.page.document._style_sheets orelse return; for (sheets._sheets.items) |sheet| { From 10e379e4fb55fc484e5d1635426d814ae9f549f0 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 19 Mar 2026 07:00:26 +0800 Subject: [PATCH 24/35] fix clamping --- src/browser/StyleManager.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index a7765814..4163b657 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -354,7 +354,7 @@ pub fn computeSpecificity(selector: Selector.Selector) u32 { // Pack into single u32: (ids << 20) | (classes << 10) | elements // This gives us 10 bits each, supporting up to 1023 of each type - return (@min(ids, 1023) << 20) | (@min(classes, 1023) << 10) | @min(elements, 1023); + return (@as(u32, @min(ids, 1023)) << 20) | (@as(u32, @min(classes, 1023)) << 10) | @min(elements, 1023); } fn countCompoundSpecificity(compound: Selector.Compound, ids: *u32, classes: *u32, elements: *u32) void { @@ -376,7 +376,6 @@ fn countCompoundSpecificity(compound: Selector.Compound, ids: *u32, classes: *u3 const spec = computeSpecificity(nested_sel); if (spec > max_nested) max_nested = spec; } - max_nested = @min(max_nested, 1023); // Unpack and add to our counts ids.* += (max_nested >> 20) & 0x3FF; classes.* += (max_nested >> 10) & 0x3FF; From b2a996e5c704f3c15e6c78c12d595adc8553c7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 19 Mar 2026 11:13:04 +0900 Subject: [PATCH 25/35] StyleManager: restore dirty state on rebuild allocation failure --- src/browser/StyleManager.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 4163b657..211e4fed 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -89,6 +89,8 @@ fn rebuildIfDirty(self: *StyleManager) !void { } self.dirty = false; + errdefer self.dirty = true; + const item_count = self.rules.items.len; self.page._session.arena_pool.resetRetain(self.arena); self.rules = try .initCapacity(self.arena, item_count); From 94d8f90a96748afc0ce964bbed5ff6ce5a49f71d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 19 Mar 2026 08:44:21 +0800 Subject: [PATCH 26/35] Bucket stylesheet rules In the first iteration of this, we kept an ArrayList of all rules with visibility properties. Why bother evaluating if a rule's selector matches an element if that rule doesn't have any meanignful (i.e. visibility) properties? This commit enhances that approach by bucketing the rules. Given the following selectors: .hidden {....} .footer > .small {...} We can store the rules based on their right-most selector. So, given an element we can do: if (getId(el)) |id| { const rules = id_lookup.get(id) orelse continue; // check rules } if (getClasses(el)) |classes| { for (classes) |c| { const rules = class_lookup(c) orelse continue; // chck rules } } ... On an amazon product page, the total list of visibility-related rules was ~230. Now, scanning 230 rules for a match isn't _aweful_, but remember that this has to be done up the ancestor tree AND, for Amazon, this is called over 20K times. This change requires that the StyleManager becomes more matching/parsing-aware but a typical visibility check on that same Amazon product page only has to check 2 rules (down from 230) and often has to check 0 rules. Also, we now filter out a few more interactive-related pseudo-elements, e.g. :hover. These aren't supported by the browser as a whole (i.e. they can _never_ match), so they can be filtered out early, when building the rules lookup. --- src/browser/StyleManager.zig | 299 ++++++++++++++++++++++++++++++----- 1 file changed, 263 insertions(+), 36 deletions(-) diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 211e4fed..57fea65d 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -41,14 +41,24 @@ pub const VisibilityCache = std.AutoHashMapUnmanaged(*Element, bool); pub const PointerEventsCache = std.AutoHashMapUnmanaged(*Element, bool); // Tracks visibility-relevant CSS rules from