mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
StyleManager: optimize rule evaluation using SoA and early rejection
This commit is contained in:
@@ -45,7 +45,7 @@ pub const PointerEventsCache = std.AutoHashMapUnmanaged(*Element, bool);
|
|||||||
const StyleManager = @This();
|
const StyleManager = @This();
|
||||||
|
|
||||||
const Tag = Element.Tag;
|
const Tag = Element.Tag;
|
||||||
const RuleList = std.ArrayList(VisibilityRule);
|
const RuleList = std.MultiArrayList(VisibilityRule);
|
||||||
|
|
||||||
page: *Page,
|
page: *Page,
|
||||||
|
|
||||||
@@ -103,22 +103,23 @@ fn rebuildIfDirty(self: *StyleManager) !void {
|
|||||||
const id_rules_count = self.id_rules.count();
|
const id_rules_count = self.id_rules.count();
|
||||||
const class_rules_count = self.class_rules.count();
|
const class_rules_count = self.class_rules.count();
|
||||||
const tag_rules_count = self.tag_rules.count();
|
const tag_rules_count = self.tag_rules.count();
|
||||||
const other_rules_count = self.other_rules.items.len;
|
const other_rules_count = self.other_rules.len;
|
||||||
|
|
||||||
self.page._session.arena_pool.resetRetain(self.arena);
|
self.page._session.arena_pool.resetRetain(self.arena);
|
||||||
|
|
||||||
self.next_doc_order = 0;
|
self.next_doc_order = 0;
|
||||||
|
|
||||||
self.id_rules = .empty;
|
self.id_rules = .empty;
|
||||||
try self.id_rules.ensureUnusedCapacity(self.arena, id_rules_count);
|
try self.id_rules.ensureTotalCapacity(self.arena, id_rules_count);
|
||||||
|
|
||||||
self.class_rules = .empty;
|
self.class_rules = .empty;
|
||||||
try self.class_rules.ensureUnusedCapacity(self.arena, class_rules_count);
|
try self.class_rules.ensureTotalCapacity(self.arena, class_rules_count);
|
||||||
|
|
||||||
self.tag_rules = .empty;
|
self.tag_rules = .empty;
|
||||||
try self.tag_rules.ensureUnusedCapacity(self.arena, tag_rules_count);
|
try self.tag_rules.ensureTotalCapacity(self.arena, tag_rules_count);
|
||||||
|
|
||||||
self.other_rules = try .initCapacity(self.arena, other_rules_count);
|
self.other_rules = .{};
|
||||||
|
try self.other_rules.ensureTotalCapacity(self.arena, other_rules_count);
|
||||||
|
|
||||||
const sheets = self.page.document._style_sheets orelse return;
|
const sheets = self.page.document._style_sheets orelse return;
|
||||||
for (sheets._sheets.items) |sheet| {
|
for (sheets._sheets.items) |sheet| {
|
||||||
@@ -243,28 +244,52 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
|||||||
return spec > best_spec or (spec == best_spec and doc_order > best_doc_order);
|
return spec > best_spec or (spec == best_spec and doc_order > best_doc_order);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checkRule(ctx: @This(), rule: VisibilityRule) void {
|
fn checkRules(ctx: @This(), rules: *const RuleList) void {
|
||||||
// Skip rules that can't possibly beat current best for any property
|
if (ctx.display_spec.* == INLINE_SPECIFICITY and
|
||||||
const dominated = (rule.props.display_none == null or !beats(rule.specificity, rule.doc_order, ctx.display_spec.*, ctx.display_doc_order.*)) and
|
ctx.visibility_spec.* == INLINE_SPECIFICITY and
|
||||||
(rule.props.visibility_hidden == null or !beats(rule.specificity, rule.doc_order, ctx.visibility_spec.*, ctx.visibility_doc_order.*)) and
|
ctx.opacity_spec.* == INLINE_SPECIFICITY)
|
||||||
(rule.props.opacity_zero == null or !beats(rule.specificity, rule.doc_order, ctx.opacity_spec.*, ctx.opacity_doc_order.*));
|
{
|
||||||
if (dominated) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (matchesSelector(ctx.el, rule.selector, ctx.page)) {
|
const len = rules.len;
|
||||||
if (rule.props.display_none != null and beats(rule.specificity, rule.doc_order, ctx.display_spec.*, ctx.display_doc_order.*)) {
|
const specificities = rules.items(.specificity);
|
||||||
ctx.display_none.* = rule.props.display_none;
|
const doc_orders = rules.items(.doc_order);
|
||||||
ctx.display_spec.* = rule.specificity;
|
|
||||||
ctx.display_doc_order.* = rule.doc_order;
|
for (0..len) |i| {
|
||||||
|
const spec = specificities[i];
|
||||||
|
const doc_order = doc_orders[i];
|
||||||
|
|
||||||
|
if (!beats(spec, doc_order, ctx.display_spec.*, ctx.display_doc_order.*) and
|
||||||
|
!beats(spec, doc_order, ctx.visibility_spec.*, ctx.visibility_doc_order.*) and
|
||||||
|
!beats(spec, doc_order, ctx.opacity_spec.*, ctx.opacity_doc_order.*))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (rule.props.visibility_hidden != null and beats(rule.specificity, rule.doc_order, ctx.visibility_spec.*, ctx.visibility_doc_order.*)) {
|
|
||||||
ctx.visibility_hidden.* = rule.props.visibility_hidden;
|
const props = rules.items(.props)[i];
|
||||||
ctx.visibility_spec.* = rule.specificity;
|
const dominated = (props.display_none == null or !beats(spec, doc_order, ctx.display_spec.*, ctx.display_doc_order.*)) and
|
||||||
ctx.visibility_doc_order.* = rule.doc_order;
|
(props.visibility_hidden == null or !beats(spec, doc_order, ctx.visibility_spec.*, ctx.visibility_doc_order.*)) and
|
||||||
|
(props.opacity_zero == null or !beats(spec, doc_order, ctx.opacity_spec.*, ctx.opacity_doc_order.*));
|
||||||
|
if (dominated) continue;
|
||||||
|
|
||||||
|
const selector = rules.items(.selector)[i];
|
||||||
|
if (matchesSelector(ctx.el, selector, ctx.page)) {
|
||||||
|
if (props.display_none != null and beats(spec, doc_order, ctx.display_spec.*, ctx.display_doc_order.*)) {
|
||||||
|
ctx.display_none.* = props.display_none;
|
||||||
|
ctx.display_spec.* = spec;
|
||||||
|
ctx.display_doc_order.* = doc_order;
|
||||||
|
}
|
||||||
|
if (props.visibility_hidden != null and beats(spec, doc_order, ctx.visibility_spec.*, ctx.visibility_doc_order.*)) {
|
||||||
|
ctx.visibility_hidden.* = props.visibility_hidden;
|
||||||
|
ctx.visibility_spec.* = spec;
|
||||||
|
ctx.visibility_doc_order.* = doc_order;
|
||||||
|
}
|
||||||
|
if (props.opacity_zero != null and beats(spec, doc_order, ctx.opacity_spec.*, ctx.opacity_doc_order.*)) {
|
||||||
|
ctx.opacity_zero.* = props.opacity_zero;
|
||||||
|
ctx.opacity_spec.* = spec;
|
||||||
|
ctx.opacity_doc_order.* = doc_order;
|
||||||
}
|
}
|
||||||
if (rule.props.opacity_zero != null and beats(rule.specificity, rule.doc_order, ctx.opacity_spec.*, ctx.opacity_doc_order.*)) {
|
|
||||||
ctx.opacity_zero.* = rule.props.opacity_zero;
|
|
||||||
ctx.opacity_spec.* = rule.specificity;
|
|
||||||
ctx.opacity_doc_order.* = rule.doc_order;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,9 +310,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
|||||||
|
|
||||||
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||||
if (self.id_rules.get(id)) |rules| {
|
if (self.id_rules.get(id)) |rules| {
|
||||||
for (rules.items) |rule| {
|
ctx.checkRules(&rules);
|
||||||
ctx.checkRule(rule);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,22 +318,16 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
|||||||
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
|
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
|
||||||
while (it.next()) |class| {
|
while (it.next()) |class| {
|
||||||
if (self.class_rules.get(class)) |rules| {
|
if (self.class_rules.get(class)) |rules| {
|
||||||
for (rules.items) |rule| {
|
ctx.checkRules(&rules);
|
||||||
ctx.checkRule(rule);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.tag_rules.get(el.getTag())) |rules| {
|
if (self.tag_rules.get(el.getTag())) |rules| {
|
||||||
for (rules.items) |rule| {
|
ctx.checkRules(&rules);
|
||||||
ctx.checkRule(rule);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.other_rules.items) |rule| {
|
ctx.checkRules(&self.other_rules);
|
||||||
ctx.checkRule(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (display_none orelse false) or (visibility_hidden orelse false) or (opacity_zero orelse false);
|
return (display_none orelse false) or (visibility_hidden orelse false) or (opacity_zero orelse false);
|
||||||
}
|
}
|
||||||
@@ -366,28 +383,44 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
|||||||
var best_doc_order: u32 = 0;
|
var best_doc_order: u32 = 0;
|
||||||
|
|
||||||
// Helper to check a single rule
|
// Helper to check a single rule
|
||||||
const checkRule = struct {
|
const checkRules = struct {
|
||||||
fn beats(spec: u32, doc_order: u32, b_spec: u32, b_doc_order: u32) bool {
|
fn beats(spec: u32, doc_order: u32, b_spec: u32, b_doc_order: u32) bool {
|
||||||
return spec > b_spec or (spec == b_spec and doc_order > b_doc_order);
|
return spec > b_spec or (spec == b_spec and doc_order > b_doc_order);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(rule: VisibilityRule, res: *?bool, spec: *u32, doc_order: *u32, elem: *Element, p: *Page) void {
|
fn check(rules: *const RuleList, res: *?bool, spec: *u32, doc_order: *u32, elem: *Element, p: *Page) void {
|
||||||
if (rule.props.pointer_events_none == null or !beats(rule.specificity, rule.doc_order, spec.*, doc_order.*)) {
|
if (spec.* == INLINE_SPECIFICITY) return;
|
||||||
return;
|
|
||||||
|
const len = rules.len;
|
||||||
|
const specificities = rules.items(.specificity);
|
||||||
|
const doc_orders = rules.items(.doc_order);
|
||||||
|
|
||||||
|
for (0..len) |i| {
|
||||||
|
const rule_spec = specificities[i];
|
||||||
|
const rule_doc_order = doc_orders[i];
|
||||||
|
|
||||||
|
if (!beats(rule_spec, rule_doc_order, spec.*, doc_order.*)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = rules.items(.props)[i];
|
||||||
|
if (props.pointer_events_none == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selector = rules.items(.selector)[i];
|
||||||
|
if (matchesSelector(elem, selector, p)) {
|
||||||
|
res.* = props.pointer_events_none;
|
||||||
|
spec.* = rule_spec;
|
||||||
|
doc_order.* = rule_doc_order;
|
||||||
}
|
}
|
||||||
if (matchesSelector(elem, rule.selector, p)) {
|
|
||||||
res.* = rule.props.pointer_events_none;
|
|
||||||
spec.* = rule.specificity;
|
|
||||||
doc_order.* = rule.doc_order;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.check;
|
}.check;
|
||||||
|
|
||||||
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||||
if (self.id_rules.get(id)) |rules| {
|
if (self.id_rules.get(id)) |rules| {
|
||||||
for (rules.items) |rule| {
|
checkRules(&rules, &result, &best_spec, &best_doc_order, el, page);
|
||||||
checkRule(rule, &result, &best_spec, &best_doc_order, el, page);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,22 +428,16 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
|||||||
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
|
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
|
||||||
while (it.next()) |class| {
|
while (it.next()) |class| {
|
||||||
if (self.class_rules.get(class)) |rules| {
|
if (self.class_rules.get(class)) |rules| {
|
||||||
for (rules.items) |rule| {
|
checkRules(&rules, &result, &best_spec, &best_doc_order, el, page);
|
||||||
checkRule(rule, &result, &best_spec, &best_doc_order, el, page);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.tag_rules.get(el.getTag())) |rules| {
|
if (self.tag_rules.get(el.getTag())) |rules| {
|
||||||
for (rules.items) |rule| {
|
checkRules(&rules, &result, &best_spec, &best_doc_order, el, page);
|
||||||
checkRule(rule, &result, &best_spec, &best_doc_order, el, page);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.other_rules.items) |rule| {
|
checkRules(&self.other_rules, &result, &best_spec, &best_doc_order, el, page);
|
||||||
checkRule(rule, &result, &best_spec, &best_doc_order, el, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result orelse false;
|
return result orelse false;
|
||||||
}
|
}
|
||||||
@@ -461,17 +488,17 @@ fn addRule(self: *StyleManager, style_rule: *CSSStyleRule) !void {
|
|||||||
switch (bucket_key) {
|
switch (bucket_key) {
|
||||||
.id => |id| {
|
.id => |id| {
|
||||||
const gop = try self.id_rules.getOrPut(self.arena, id);
|
const gop = try self.id_rules.getOrPut(self.arena, id);
|
||||||
if (!gop.found_existing) gop.value_ptr.* = .empty;
|
if (!gop.found_existing) gop.value_ptr.* = .{};
|
||||||
try gop.value_ptr.append(self.arena, rule);
|
try gop.value_ptr.append(self.arena, rule);
|
||||||
},
|
},
|
||||||
.class => |class| {
|
.class => |class| {
|
||||||
const gop = try self.class_rules.getOrPut(self.arena, class);
|
const gop = try self.class_rules.getOrPut(self.arena, class);
|
||||||
if (!gop.found_existing) gop.value_ptr.* = .empty;
|
if (!gop.found_existing) gop.value_ptr.* = .{};
|
||||||
try gop.value_ptr.append(self.arena, rule);
|
try gop.value_ptr.append(self.arena, rule);
|
||||||
},
|
},
|
||||||
.tag => |tag| {
|
.tag => |tag| {
|
||||||
const gop = try self.tag_rules.getOrPut(self.arena, tag);
|
const gop = try self.tag_rules.getOrPut(self.arena, tag);
|
||||||
if (!gop.found_existing) gop.value_ptr.* = .empty;
|
if (!gop.found_existing) gop.value_ptr.* = .{};
|
||||||
try gop.value_ptr.append(self.arena, rule);
|
try gop.value_ptr.append(self.arena, rule);
|
||||||
},
|
},
|
||||||
.other => {
|
.other => {
|
||||||
|
|||||||
Reference in New Issue
Block a user