mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-16 16:01:30 -04:00
Materialize inline styles where layout reads them
Visibility probes no longer decide whether an element's style attribute gets parsed into a CSSStyleProperties object. The three layout readers (getElementAxis, positionStyle, horizontalPosition) create it on demand through Element.inlineStyle, and StyleManager only ever folds the attribute text. That removes the scan/materialize mode threaded through every probe, and a JS layout read now only materializes the elements whose inline style it actually reads.
This commit is contained in:
1 parent
1583a4b45f
commit
c856f56289
13 files changed
+104
-127
No files matched your search
@@ -130,9 +130,9 @@ fn walk(
|
||||
// Hidden subtrees are never entered, so below the root only the
|
||||
// element's own display matters.
|
||||
const hidden = if (current_depth == 0)
|
||||
!el.isVisible(self.frame, .scan)
|
||||
!el.isVisible(self.frame)
|
||||
else
|
||||
self.frame._style_manager.hasDisplayNone(el, .scan);
|
||||
self.frame._style_manager.hasDisplayNone(el);
|
||||
if (hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3121,7 +3121,7 @@ pub fn attributeRemove(self: *Frame, element: *Element, name: String, old_value:
|
||||
}
|
||||
|
||||
fn styleAttributeChanged(self: *Frame, element: *Element, value: ?[]const u8) void {
|
||||
const style = element.getStyle(self) orelse return;
|
||||
const style = element.existingStyle(self) orelse return;
|
||||
style.asCSSStyleDeclaration().styleAttributeChanged(value, self) catch |err| {
|
||||
log.err(.frame, "style attribute reparse", .{ .err = err, .type = self._type, .url = self.url });
|
||||
};
|
||||
|
||||
@@ -152,7 +152,7 @@ fn visibleDisplay(el: *Element, frame: *Frame) ?StyleManager.Display {
|
||||
if (tag.isMetadata() or tag == .svg) {
|
||||
return null;
|
||||
}
|
||||
const d = frame._style_manager.display(el, .scan);
|
||||
const d = frame._style_manager.display(el);
|
||||
if (d == .none) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -587,30 +587,30 @@ const Probe = enum { hidden, visibility, pointer_events };
|
||||
|
||||
const Memo = std.AutoHashMapUnmanaged(*Element, Props);
|
||||
|
||||
pub fn isHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOptions, comptime access: InlineAccess) bool {
|
||||
pub fn isHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOptions) bool {
|
||||
self.rebuildIfDirty() catch return false;
|
||||
return self.anyInChain(el, access, .hidden, options);
|
||||
return self.anyInChain(el, .hidden, options);
|
||||
}
|
||||
|
||||
/// 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".
|
||||
pub fn hasDisplayNone(self: *StyleManager, el: *Element, comptime access: InlineAccess) bool {
|
||||
return self.display(el, access) == .none;
|
||||
pub fn hasDisplayNone(self: *StyleManager, el: *Element) bool {
|
||||
return self.display(el) == .none;
|
||||
}
|
||||
|
||||
/// Own property, no ancestor walk; honors the UA hidden-element rules.
|
||||
pub fn display(self: *StyleManager, el: *Element, comptime access: InlineAccess) Display {
|
||||
pub fn display(self: *StyleManager, el: *Element) Display {
|
||||
self.rebuildIfDirty() catch return .other;
|
||||
return self.ownProps(el, access).display;
|
||||
return self.ownProps(el).display;
|
||||
}
|
||||
|
||||
/// Computed display:none coming only from inline style or an author stylesheet
|
||||
/// rule — the UA stylesheet's hidden elements (<head>, <script>, [hidden], …)
|
||||
/// are NOT counted, so document scaffolding is preserved. Used by the HTML
|
||||
/// dump's "invisible" strip mode.
|
||||
pub fn hasAuthorDisplayNone(self: *StyleManager, el: *Element, comptime access: InlineAccess) bool {
|
||||
pub fn hasAuthorDisplayNone(self: *StyleManager, el: *Element) bool {
|
||||
self.rebuildIfDirty() catch return false;
|
||||
const p = self.ownProps(el, access);
|
||||
const p = self.ownProps(el);
|
||||
return p.author_display and p.display == .none;
|
||||
}
|
||||
|
||||
@@ -620,18 +620,18 @@ pub fn hasAuthorDisplayNone(self: *StyleManager, el: *Element, comptime access:
|
||||
/// rendered, but its computed `visibility` still reflects inherited visibility.
|
||||
pub fn hasVisibilityHiddenInherited(self: *StyleManager, el: *Element) bool {
|
||||
self.rebuildIfDirty() catch return false;
|
||||
return self.anyInChain(el, .materialize, .visibility, .{});
|
||||
return self.anyInChain(el, .visibility, .{});
|
||||
}
|
||||
|
||||
pub fn hasPointerEventsNone(self: *StyleManager, el: *Element, comptime access: InlineAccess) bool {
|
||||
pub fn hasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
||||
self.rebuildIfDirty() catch return false;
|
||||
return self.anyInChain(el, access, .pointer_events, .{});
|
||||
return self.anyInChain(el, .pointer_events, .{});
|
||||
}
|
||||
|
||||
fn anyInChain(self: *StyleManager, el: *Element, comptime access: InlineAccess, comptime what: Probe, options: CheckVisibilityOptions) bool {
|
||||
fn anyInChain(self: *StyleManager, el: *Element, comptime what: Probe, options: CheckVisibilityOptions) bool {
|
||||
var current: ?*Element = el;
|
||||
while (current) |elem| : (current = elem.parentElement()) {
|
||||
if (self.ownProps(elem, access).probe(what, options)) {
|
||||
if (self.ownProps(elem).probe(what, options)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -640,7 +640,7 @@ fn anyInChain(self: *StyleManager, el: *Element, comptime access: InlineAccess,
|
||||
|
||||
/// The memoized own-element result. Callers must have run rebuildIfDirty,
|
||||
/// which resets the memo.
|
||||
fn ownProps(self: *StyleManager, el: *Element, comptime access: InlineAccess) Props {
|
||||
fn ownProps(self: *StyleManager, el: *Element) Props {
|
||||
const version = self.frame._page.style_version;
|
||||
if (self.memo_version != version) {
|
||||
self.memo.clearRetainingCapacity();
|
||||
@@ -649,17 +649,12 @@ fn ownProps(self: *StyleManager, el: *Element, comptime access: InlineAccess) Pr
|
||||
|
||||
const gop = self.memo.getOrPut(self.arena.allocator(), el) catch |err| {
|
||||
log.warn(.browser, "StyleManager memo", .{ .err = err });
|
||||
return self.compute(el, access);
|
||||
return self.compute(el);
|
||||
};
|
||||
if (gop.found_existing) {
|
||||
// Layout reads the inline style object directly, so a hit must still
|
||||
// create what a scan-mode miss left unparsed.
|
||||
if (access == .materialize and el._flags.has_inline_style) {
|
||||
_ = inlineStyle(el, .materialize, self.frame);
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
gop.value_ptr.* = self.compute(el, access);
|
||||
gop.value_ptr.* = self.compute(el);
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
@@ -682,12 +677,12 @@ const Priorities = struct {
|
||||
}
|
||||
};
|
||||
|
||||
fn compute(self: *StyleManager, el: *Element, comptime access: InlineAccess) Props {
|
||||
fn compute(self: *StyleManager, el: *Element) Props {
|
||||
const frame = self.frame;
|
||||
var p: Props = .{};
|
||||
var priorities: Priorities = .{};
|
||||
|
||||
const inline_props = inlineProps(el, access, frame);
|
||||
const inline_props = inlineProps(el, frame);
|
||||
inline for (property_fields) |field| {
|
||||
if (@field(inline_props, field)) |value| {
|
||||
@field(p, field) = value;
|
||||
@@ -1072,52 +1067,19 @@ const CheckVisibilityOptions = struct {
|
||||
// its field max, so a real rule can never pack to all-ones.
|
||||
const INLINE_PRIORITY: u64 = std.math.maxInt(u64);
|
||||
|
||||
/// How a probe reads an element's inline `style=` attribute.
|
||||
pub const InlineAccess = enum {
|
||||
/// Parse the attribute into the element's CSSStyleProperties (the object
|
||||
/// `el.style` hands out) and keep it, so repeat probes on the same element
|
||||
/// cost a list lookup. Layout reads that object directly (getElementAxis,
|
||||
/// horizontalPosition), so every JS-reachable path must materialize.
|
||||
materialize,
|
||||
/// Fold the attribute text on each call; allocates nothing. For
|
||||
/// Zig-initiated tooling that walks the whole document once per turn
|
||||
/// (markdown, dump, tree), where pinning a parsed declaration list in the
|
||||
/// page arena for every inline-styled element outlives its use. An
|
||||
/// object JS already created is still read.
|
||||
scan,
|
||||
};
|
||||
|
||||
fn inlineProps(el: *Element, comptime access: InlineAccess, frame: *Frame) VisibilityProperties {
|
||||
// `frame` must be el's owner frame (el.ownerFrame): that is the map where a
|
||||
// parsed inline style lives. Without one the attribute text is folded in
|
||||
// place; layout materializes the object itself when it needs it.
|
||||
fn inlineProps(el: *Element, frame: *Frame) VisibilityProperties {
|
||||
if (!el._flags.has_inline_style) {
|
||||
// Neither a style object nor a style attribute; skip both lookups.
|
||||
return .{};
|
||||
}
|
||||
if (inlineStyle(el, access, frame)) |style| {
|
||||
if (el.existingStyle(frame)) |style| {
|
||||
return extractVisibilityProperties(style);
|
||||
}
|
||||
// A JS-created object is read by both modes; `scan` folds the attribute
|
||||
// text instead of parsing it into the page arena.
|
||||
if (access == .scan) {
|
||||
if (el.getAttributeInterned("style")) |attr| {
|
||||
return scanInlineProps(attr);
|
||||
}
|
||||
}
|
||||
return .{};
|
||||
}
|
||||
|
||||
/// `frame` must be el's owner frame (el.ownerFrame): that is the map where
|
||||
/// the materialized style lives.
|
||||
fn inlineStyle(el: *Element, comptime access: InlineAccess, frame: *Frame) ?*CSSStyleProperties {
|
||||
if (el.getStyle(frame)) |style| {
|
||||
return style;
|
||||
}
|
||||
if (access == .scan or el.getAttributeInterned("style") == null) {
|
||||
return null;
|
||||
}
|
||||
return el.getOrCreateStyle(frame) catch |err| {
|
||||
log.err(.browser, "StyleManager getOrCreateStyle", .{ .err = err });
|
||||
return null;
|
||||
};
|
||||
const attr = el.getAttributeInterned("style") orelse return .{};
|
||||
return scanInlineProps(attr);
|
||||
}
|
||||
|
||||
fn styleValue(style: *CSSStyleProperties, property_name: String) ?[]const u8 {
|
||||
@@ -1171,7 +1133,7 @@ fn scanInlineProps(attr: []const u8) VisibilityProperties {
|
||||
/// inline style (the same source `el.style` exposes), so `getComputedStyle` and
|
||||
/// `el.style` agree on inline values instead of resolving them independently.
|
||||
pub fn inlineStyleValue(self: *StyleManager, el: *Element, property_name: String) ?[]const u8 {
|
||||
const style = inlineStyle(el, .materialize, self.frame) orelse return null;
|
||||
const style = el.inlineStyle(self.frame) orelse return null;
|
||||
return styleValue(style, property_name);
|
||||
}
|
||||
|
||||
@@ -1395,7 +1357,7 @@ test "StyleManager: packed priority bounds" {
|
||||
try testing.expect(MAX_LAYERS < UNLAYERED_RANK);
|
||||
}
|
||||
|
||||
test "StyleManager: inlineProps: scan matches materialize" {
|
||||
test "StyleManager: inlineProps: scan matches the parsed style object" {
|
||||
const frame = try testing.createFrame();
|
||||
defer testing.test_session.closeAllPages();
|
||||
|
||||
@@ -1429,10 +1391,10 @@ test "StyleManager: inlineProps: scan matches materialize" {
|
||||
var child = div.asNode().firstChild();
|
||||
while (child) |node| : (child = node.nextSibling()) {
|
||||
const el = node.is(Element) orelse continue;
|
||||
const scanned = inlineProps(el, .scan, frame);
|
||||
const scanned = inlineProps(el, frame);
|
||||
// scanning never creates the style object
|
||||
try testing.expectEqual(null, el.getStyle(frame));
|
||||
const materialized = inlineProps(el, .materialize, frame);
|
||||
try testing.expectEqual(null, el.existingStyle(frame));
|
||||
const materialized = extractVisibilityProperties(try el.getOrCreateStyle(frame));
|
||||
inline for (property_fields) |field| {
|
||||
try testing.expectEqual(@field(expected[i], field), @field(scanned, field));
|
||||
try testing.expectEqual(@field(expected[i], field), @field(materialized, field));
|
||||
@@ -1455,41 +1417,38 @@ test "StyleManager: memo: reuse and invalidation" {
|
||||
const b = p.asNode().firstChild().?.as(Element);
|
||||
|
||||
// The walk memoizes the element and every ancestor
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}, .scan));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}));
|
||||
try testing.expectEqual(3, sm.memo.count());
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}, .scan));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}));
|
||||
try testing.expectEqual(3, sm.memo.count());
|
||||
|
||||
// A scan never creates the style object; a materialize hit does
|
||||
try testing.expectEqual(null, b.getStyle(frame));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}, .materialize));
|
||||
try testing.expect(b.getStyle(frame) != null);
|
||||
try testing.expectEqual(3, sm.memo.count());
|
||||
// Probes never create the style object
|
||||
try testing.expectEqual(null, b.existingStyle(frame));
|
||||
|
||||
// An attribute change anywhere invalidates the memo
|
||||
try p.setAttributeSafe(comptime .wrap("hidden"), .wrap(""), frame);
|
||||
try testing.expectEqual(true, sm.isHidden(b, .{}, .scan));
|
||||
try testing.expectEqual(false, sm.hasDisplayNone(b, .scan));
|
||||
try testing.expectEqual(true, sm.hasDisplayNone(p, .scan));
|
||||
try testing.expectEqual(false, sm.hasAuthorDisplayNone(p, .scan));
|
||||
try testing.expectEqual(true, sm.isHidden(b, .{}));
|
||||
try testing.expectEqual(false, sm.hasDisplayNone(b));
|
||||
try testing.expectEqual(true, sm.hasDisplayNone(p));
|
||||
try testing.expectEqual(false, sm.hasAuthorDisplayNone(p));
|
||||
|
||||
p.removeAttributeSafe(comptime .wrap("hidden"), frame);
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}, .scan));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}));
|
||||
|
||||
try b.setStyle("display: none; pointer-events: none", frame);
|
||||
try testing.expectEqual(true, sm.hasAuthorDisplayNone(b, .scan));
|
||||
try testing.expectEqual(true, sm.hasPointerEventsNone(b, .scan));
|
||||
try testing.expectEqual(false, sm.hasPointerEventsNone(p, .scan));
|
||||
try testing.expectEqual(true, sm.hasAuthorDisplayNone(b));
|
||||
try testing.expectEqual(true, sm.hasPointerEventsNone(b));
|
||||
try testing.expectEqual(false, sm.hasPointerEventsNone(p));
|
||||
|
||||
try b.setStyle("display: flex; visibility: hidden", frame);
|
||||
try testing.expectEqual(.flex, sm.display(b, .scan));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}, .scan));
|
||||
try testing.expectEqual(true, sm.isHidden(b, .{ .check_visibility = true }, .scan));
|
||||
try testing.expectEqual(.flex, sm.display(b));
|
||||
try testing.expectEqual(false, sm.isHidden(b, .{}));
|
||||
try testing.expectEqual(true, sm.isHidden(b, .{ .check_visibility = true }));
|
||||
try testing.expectEqual(true, sm.hasVisibilityHiddenInherited(b));
|
||||
try testing.expectEqual(false, sm.hasPointerEventsNone(b, .scan));
|
||||
try testing.expectEqual(false, sm.hasPointerEventsNone(b));
|
||||
|
||||
// A stylesheet change resets the memo
|
||||
sm.sheetModified();
|
||||
try testing.expectEqual(false, sm.isHidden(p, .{}, .scan));
|
||||
try testing.expectEqual(false, sm.isHidden(p, .{}));
|
||||
try testing.expectEqual(2, sm.memo.count());
|
||||
}
|
||||
@@ -408,7 +408,7 @@ pub fn shouldStripElement(el: *Node.Element, strip: Opts.Strip, frame: *Frame) b
|
||||
if (std.mem.eql(u8, tag_name, "iframe")) return true;
|
||||
}
|
||||
|
||||
if (strip.invisible and frame._style_manager.hasAuthorDisplayNone(el, .scan)) {
|
||||
if (strip.invisible and frame._style_manager.hasAuthorDisplayNone(el)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ fn walkInteractive(
|
||||
|
||||
if (root.is(Element)) |root_el| {
|
||||
// root is outside of the tree walk, so check its visibility upfront.
|
||||
if (!root_el.isVisible(frame, .scan)) {
|
||||
if (!root_el.isVisible(frame)) {
|
||||
return &.{};
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ fn walkInteractive(
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (frame._style_manager.hasDisplayNone(el, .scan)) {
|
||||
if (frame._style_manager.hasDisplayNone(el)) {
|
||||
tw.skipChildren();
|
||||
continue;
|
||||
}
|
||||
@@ -298,7 +298,7 @@ pub fn classifyInteractivity(
|
||||
html_el: *Element.Html,
|
||||
listener_targets: ListenerTargetMap,
|
||||
) ?InteractivityType {
|
||||
if (el.hasPointerEventsNone(frame, .scan)) return null;
|
||||
if (el.hasPointerEventsNone(frame)) return null;
|
||||
|
||||
// 1. Native interactive by tag
|
||||
switch (el.getTag()) {
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn collectLinks(arena: Allocator, root: *Node, frame: *Frame) ![]Link {
|
||||
for (list._nodes) |node| {
|
||||
const anchor = node.is(Element.Html.Anchor) orelse continue;
|
||||
const el = anchor.asElement();
|
||||
if (!el.isVisible(frame, .scan)) continue;
|
||||
if (!el.isVisible(frame)) continue;
|
||||
|
||||
const href = anchor.getHref(frame) catch |err| {
|
||||
log.err(.app, "resolve href failed", .{ .err = err });
|
||||
|
||||
@@ -936,7 +936,7 @@ fn elementFromPointImpl(self: *Document, x: f64, y: f64, ignore_x: bool, frame:
|
||||
|
||||
preorder_index += 1;
|
||||
if (node.is(Element)) |element| {
|
||||
if (element.isVisible(frame, .materialize)) {
|
||||
if (element.isVisible(frame)) {
|
||||
if (y >= pos and y <= pos + element.boxAxis(frame, .height)) {
|
||||
if (ignore_x) {
|
||||
topmost = element;
|
||||
|
||||
@@ -1012,13 +1012,31 @@ pub fn getOrCreateStyle(self: *Element, frame: *Frame) !*CSSStyleProperties {
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn getStyle(self: *Element, frame: *Frame) ?*CSSStyleProperties {
|
||||
pub fn existingStyle(self: *Element, frame: *Frame) ?*CSSStyleProperties {
|
||||
if (!self._flags.has_inline_style) {
|
||||
return null;
|
||||
}
|
||||
return self.ownerFrame(frame)._element_styles.get(self);
|
||||
}
|
||||
|
||||
/// The inline style object, parsed from the style attribute on first use;
|
||||
/// null when the element has neither.
|
||||
pub fn inlineStyle(self: *Element, frame: *Frame) ?*CSSStyleProperties {
|
||||
if (!self._flags.has_inline_style) {
|
||||
return null;
|
||||
}
|
||||
if (self.existingStyle(frame)) |style| {
|
||||
return style;
|
||||
}
|
||||
if (self.getAttributeInterned("style") == null) {
|
||||
return null;
|
||||
}
|
||||
return self.getOrCreateStyle(frame) catch |err| {
|
||||
log.err(.browser, "inline style parse", .{ .err = err });
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -1162,7 +1180,7 @@ pub fn focus(self: *Element, frame: *Frame) !void {
|
||||
|
||||
// Per HTML spec §6.4.4, an element must be "being rendered" (not
|
||||
// display:none on self or any ancestor) to be focusable.
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1353,12 +1371,12 @@ pub fn parentElement(self: *Element) ?*Element {
|
||||
// Style checks go through the StyleManager of the element's own frame, not
|
||||
// the caller's: its stylesheets and materialized inline styles are per-frame,
|
||||
// and a same-origin script can reach an element in another frame.
|
||||
pub fn hasPointerEventsNone(self: *Element, frame: *Frame, comptime access: StyleManager.InlineAccess) bool {
|
||||
return self.ownerFrame(frame)._style_manager.hasPointerEventsNone(self, access);
|
||||
pub fn hasPointerEventsNone(self: *Element, frame: *Frame) bool {
|
||||
return self.ownerFrame(frame)._style_manager.hasPointerEventsNone(self);
|
||||
}
|
||||
|
||||
pub fn isVisible(self: *Element, frame: *Frame, comptime access: StyleManager.InlineAccess) bool {
|
||||
return !self.ownerFrame(frame)._style_manager.isHidden(self, .{}, access);
|
||||
pub fn isVisible(self: *Element, frame: *Frame) bool {
|
||||
return !self.ownerFrame(frame)._style_manager.isHidden(self, .{});
|
||||
}
|
||||
|
||||
const CheckVisibilityOpts = struct {
|
||||
@@ -1372,7 +1390,7 @@ pub fn checkVisibility(self: *Element, opts_: ?CheckVisibilityOpts, frame: *Fram
|
||||
return !self.ownerFrame(frame)._style_manager.isHidden(self, .{
|
||||
.check_opacity = opts.checkOpacity or opts.opacityProperty,
|
||||
.check_visibility = opts.visibilityProperty or opts.checkVisibilityCSS,
|
||||
}, .materialize);
|
||||
});
|
||||
}
|
||||
|
||||
pub const Axis = enum {
|
||||
@@ -1387,7 +1405,7 @@ pub const Axis = enum {
|
||||
};
|
||||
|
||||
pub fn getElementAxis(self: *Element, frame: *Frame, comptime axis: Axis) Axis.State {
|
||||
if (self.getStyle(frame)) |style| {
|
||||
if (self.inlineStyle(frame)) |style| {
|
||||
const decl = style.asCSSStyleDeclaration();
|
||||
if (CSS.parseDimensionViewport(decl.getPropertyValue(@tagName(axis), frame), frame)) |v| {
|
||||
return .{ .value = v, .explicit = true };
|
||||
@@ -1426,7 +1444,7 @@ pub fn getClientHeight(self: *Element, frame: *Frame) f64 {
|
||||
}
|
||||
|
||||
fn clientAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
return self.viewportAxis(frame, axis) orelse self.boxAxis(frame, axis);
|
||||
@@ -1474,7 +1492,7 @@ pub fn getBoundingClientRect(self: *Element, frame: *Frame) !*DOMRect {
|
||||
// getBoundingClientRect, getClientRects, and IntersectionObserver. A DOMRect is
|
||||
// only materialized at the JS boundary.
|
||||
pub fn boundingClientRectValues(self: *Element, frame: *Frame) DOMRect.Data {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return .{};
|
||||
}
|
||||
return self.boundingClientRectValuesForVisible(frame);
|
||||
@@ -1491,7 +1509,7 @@ pub fn boundingClientRectValuesForVisible(self: *Element, frame: *Frame) DOMRect
|
||||
}
|
||||
|
||||
pub fn getClientRects(self: *Element, frame: *Frame) ![]*DOMRect {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return &.{};
|
||||
}
|
||||
const rects = try frame.local_arena.alloc(*DOMRect, 1);
|
||||
@@ -1543,7 +1561,7 @@ pub fn setScrollLeft(self: *Element, value: i32, frame: *Frame) !void {
|
||||
}
|
||||
|
||||
pub fn getScrollHeight(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -1560,7 +1578,7 @@ pub fn getScrollHeight(self: *Element, frame: *Frame) f64 {
|
||||
}
|
||||
|
||||
pub fn getScrollWidth(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -1583,7 +1601,7 @@ pub fn getScrollWidth(self: *Element, frame: *Frame) f64 {
|
||||
// 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
|
||||
// inline-block strip), so the single-row assumption covers the case that
|
||||
// matters. We can't detect the layout mode to do better: getStyle() sees only
|
||||
// matters. We can't detect the layout mode to do better: existingStyle() sees only
|
||||
// the inline `style=` attribute, and the computed cascade resolves stylesheet
|
||||
// rules for `display:none` and `visibility` alone.
|
||||
//
|
||||
@@ -1610,7 +1628,7 @@ fn contentAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
|
||||
var child = self.asNode().firstChild();
|
||||
while (child) |node| : (child = node.nextSibling()) {
|
||||
if (node.is(Element)) |el| {
|
||||
if (!style_manager.hasDisplayNone(el, .materialize)) {
|
||||
if (!style_manager.hasDisplayNone(el)) {
|
||||
total += el.getElementAxis(frame, axis).value;
|
||||
}
|
||||
}
|
||||
@@ -1622,35 +1640,35 @@ fn contentAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
|
||||
// Unlike clientHeight, the root's offsetHeight is its box (the document
|
||||
// extent), so it stays on the synthetic root default.
|
||||
pub fn getOffsetHeight(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
return self.boxAxis(frame, .height);
|
||||
}
|
||||
|
||||
pub fn getOffsetWidth(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
return self.boxAxis(frame, .width);
|
||||
}
|
||||
|
||||
pub fn getOffsetTop(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
return calculateDocumentPosition(self.asNode());
|
||||
}
|
||||
|
||||
pub fn getOffsetLeft(self: *Element, frame: *Frame) f64 {
|
||||
if (!self.isVisible(frame, .materialize)) {
|
||||
if (!self.isVisible(frame)) {
|
||||
return 0.0;
|
||||
}
|
||||
return self.horizontalPosition(frame);
|
||||
}
|
||||
|
||||
pub fn getOffsetParent(self: *Element, frame: *Frame) ?*Element {
|
||||
if (!self.asNode().isConnected() or !self.isVisible(frame, .materialize)) {
|
||||
if (!self.asNode().isConnected() or !self.isVisible(frame)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1691,7 +1709,7 @@ pub fn getOffsetParent(self: *Element, frame: *Frame) ?*Element {
|
||||
}
|
||||
|
||||
fn positionStyle(self: *Element, frame: *Frame) []const u8 {
|
||||
const style = self.getStyle(frame) orelse return "";
|
||||
const style = self.inlineStyle(frame) orelse return "";
|
||||
return style.asCSSStyleDeclaration().getPropertyValue("position", frame);
|
||||
}
|
||||
|
||||
@@ -1775,13 +1793,13 @@ pub fn horizontalPosition(self: *Element, frame: *Frame) f64 {
|
||||
var current = self.asNode();
|
||||
const style_manager = &self.ownerFrame(frame)._style_manager;
|
||||
|
||||
if (self.getStyle(frame)) |style| {
|
||||
if (self.inlineStyle(frame)) |style| {
|
||||
x += CSS.parseTranslateX(style.asCSSStyleDeclaration().getPropertyValue("transform", frame));
|
||||
}
|
||||
|
||||
while (current.parentNode()) |parent| {
|
||||
if (parent.is(Element)) |el| {
|
||||
if (el.getStyle(frame)) |style| {
|
||||
if (el.inlineStyle(frame)) |style| {
|
||||
x += CSS.parseTranslateX(style.asCSSStyleDeclaration().getPropertyValue("transform", frame));
|
||||
}
|
||||
}
|
||||
@@ -1789,7 +1807,7 @@ pub fn horizontalPosition(self: *Element, frame: *Frame) f64 {
|
||||
while (sibling) |s| : (sibling = s.nextSibling()) {
|
||||
if (s == current) break;
|
||||
if (s.is(Element)) |el| {
|
||||
if (!style_manager.hasDisplayNone(el, .materialize)) {
|
||||
if (!style_manager.hasDisplayNone(el)) {
|
||||
x += el.getElementAxis(frame, .width).value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ pub fn deliverEntries(self: *ResizeObserver, frame: *Frame) !void {
|
||||
obs.connected = connected;
|
||||
|
||||
const width, const height = blk: {
|
||||
if (!connected or !target.isVisible(frame, .materialize)) {
|
||||
if (!connected or !target.isVisible(frame)) {
|
||||
break :blk .{ 0, 0 };
|
||||
}
|
||||
break :blk .{
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const
|
||||
if (self._element) |element| {
|
||||
const style_manager = &element.ownerFrame(frame)._style_manager;
|
||||
if (wrapped.eql(comptime .wrap("display"))) {
|
||||
if (style_manager.hasDisplayNone(element, .materialize)) return "none";
|
||||
if (style_manager.hasDisplayNone(element)) return "none";
|
||||
} else if (wrapped.eql(comptime .wrap("visibility"))) {
|
||||
if (style_manager.hasVisibilityHiddenInherited(element)) return "hidden";
|
||||
}
|
||||
@@ -122,7 +122,7 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const
|
||||
}
|
||||
|
||||
fn resolvedDimension(element: *Element, dimension: enum { width, height }, frame: *Frame) []const u8 {
|
||||
if (!element.isVisible(frame, .materialize)) {
|
||||
if (!element.isVisible(frame)) {
|
||||
return "auto";
|
||||
}
|
||||
const value = switch (dimension) {
|
||||
|
||||
@@ -1638,7 +1638,7 @@ fn handleChildElement(
|
||||
// is hidden through its parent. If you can el.innerText on an element, the
|
||||
// visibility of el.parent doesn't matter. So we only care about visibility
|
||||
// on the element itself and then on each child. This is much simpler too.
|
||||
if (state.frame._style_manager.hasDisplayNone(he.asElement(), .materialize)) {
|
||||
if (state.frame._style_manager.hasDisplayNone(he.asElement())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1295,7 +1295,7 @@ fn isHidden(elt: *DOMNode.Element, frame: *Frame) bool {
|
||||
|
||||
// CSS display:none and visibility:hidden (both inherited from ancestors via
|
||||
// style computation). Matches Chromium's AX tree which prunes both.
|
||||
if (frame._style_manager.isHidden(elt, .{ .check_visibility = true }, .scan)) {
|
||||
if (frame._style_manager.isHidden(elt, .{ .check_visibility = true })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user