mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-08 20:32:00 -04:00
Simplify the overflow and wheel changes
The overflow shorthand split lives once, in the CSS parser, for both the style declaration and the StyleManager attribute fold; the two copies disagreed on three-value input. The declaration states the longhand-pair rule once and serializes the pair where its first longhand sits without a second scan. The two listener scans merge into one with a filter. The per-axis scroll walk goes: the cascade memoizes each element, so calling the single-axis walk twice costs a few lookups.
This commit is contained in:
1 parent
b0f6066be7
commit
e7d151d982
8 files changed
+91
-129
No files matched your search
@@ -431,7 +431,7 @@ fn anyNonPassive(self: *EventManager, path: []const *EventTarget, event: *Event)
|
||||
return true;
|
||||
}
|
||||
if (self.listenersFor(target, event)) |listeners| {
|
||||
if (EventManagerBase.hasNonPassiveListener(listeners.list)) {
|
||||
if (EventManagerBase.hasListener(listeners.list, .non_passive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -449,7 +449,7 @@ const TargetListeners = struct {
|
||||
/// `mousewheel` listener fires only where no `wheel` listener is registered.
|
||||
fn listenersFor(self: *EventManager, target: *EventTarget, event: *const Event) ?TargetListeners {
|
||||
if (self.base.getListeners(target, event._type_string)) |list| {
|
||||
if (EventManagerBase.hasLiveListener(list)) {
|
||||
if (EventManagerBase.hasListener(list, .any)) {
|
||||
return .{ .list = list, .typ = event._type_string };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,27 +205,18 @@ pub fn getListeners(self: *EventManagerBase, target: *EventTarget, event_type: S
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether the list still holds a listener. One removed during a dispatch
|
||||
/// stays linked until the dispatch unwinds, but isn't "in" the list anymore,
|
||||
/// same as findListener.
|
||||
pub fn hasLiveListener(list: *const std.DoublyLinkedList) bool {
|
||||
/// Whether the list still holds a listener, or one that can call
|
||||
/// preventDefault. A listener removed during a dispatch stays linked until
|
||||
/// the dispatch unwinds, but isn't "in" the list anymore, same as
|
||||
/// findListener.
|
||||
pub fn hasListener(list: *const std.DoublyLinkedList, comptime which: enum { any, non_passive }) bool {
|
||||
var node = list.first;
|
||||
while (node) |n| : (node = n.next) {
|
||||
const listener: *align(8) Listener = @fieldParentPtr("node", n);
|
||||
if (!listener.removed) {
|
||||
return true;
|
||||
if (listener.removed) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Whether a listener in the list can call preventDefault. A removed
|
||||
/// listener isn't "in" the list anymore, same as findListener.
|
||||
pub fn hasNonPassiveListener(list: *const std.DoublyLinkedList) bool {
|
||||
var node = list.first;
|
||||
while (node) |n| : (node = n.next) {
|
||||
const listener: *align(8) Listener = @fieldParentPtr("node", n);
|
||||
if (!listener.removed and !listener.passive) {
|
||||
if (which == .any or !listener.passive) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1286,14 +1286,12 @@ const Slots = struct {
|
||||
|
||||
fn apply(self: *Slots, name: []const u8, value: []const u8, important: bool) void {
|
||||
if (std.ascii.eqlIgnoreCase(name, "overflow")) {
|
||||
// `overflow: <x> [<y>]`; a single value applies to both axes.
|
||||
var it = std.mem.tokenizeAny(u8, value, &std.ascii.whitespace);
|
||||
const x = it.next() orelse "";
|
||||
self.apply("overflow-x", x, important);
|
||||
self.apply("overflow-y", it.next() orelse x, important);
|
||||
const values = CssParser.splitOverflow(value) orelse return;
|
||||
self.apply("overflow-x", values.x, important);
|
||||
self.apply("overflow-y", values.y, important);
|
||||
return;
|
||||
}
|
||||
inline for (property_names, &self.slots) |tracked, *slot| {
|
||||
for (property_names, &self.slots) |tracked, *slot| {
|
||||
if (std.ascii.eqlIgnoreCase(name, tracked)) {
|
||||
slot.apply(value, important);
|
||||
return;
|
||||
@@ -1303,7 +1301,7 @@ const Slots = struct {
|
||||
|
||||
fn props(self: Slots) TrackedProperties {
|
||||
var p: TrackedProperties = .{};
|
||||
inline for (property_names, self.slots) |name, s| {
|
||||
for (property_names, self.slots) |name, s| {
|
||||
if (s.value) |value| {
|
||||
p.apply(name, value);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,20 @@ pub const Declaration = struct {
|
||||
important: bool,
|
||||
};
|
||||
|
||||
pub const OverflowValues = struct { x: []const u8, y: []const u8 };
|
||||
|
||||
/// `overflow: <x> [<y>]`; a single value applies to both axes. More than two
|
||||
/// values is invalid and null, as is an empty declaration.
|
||||
pub fn splitOverflow(value: []const u8) ?OverflowValues {
|
||||
var it = std.mem.tokenizeAny(u8, value, &std.ascii.whitespace);
|
||||
const x = it.next() orelse return null;
|
||||
const y = it.next() orelse x;
|
||||
if (it.next() != null) {
|
||||
return null;
|
||||
}
|
||||
return .{ .x = x, .y = y };
|
||||
}
|
||||
|
||||
const TokenSpan = struct {
|
||||
token: Tokenizer.Token,
|
||||
start: usize,
|
||||
|
||||
@@ -320,10 +320,9 @@ pub fn wheel(frame: *Frame, target: *Element, x: f64, y: f64, delta_x: f64, delt
|
||||
/// else the viewport. Relative deltas may land on different scrollers per
|
||||
/// axis, unlike an absolute position.
|
||||
fn wheelScroll(target: *Element, delta_x: i32, delta_y: i32, frame: *Frame) !void {
|
||||
const targets = target.scrollContainers(.{ .x = delta_x != 0, .y = delta_y != 0 }, frame);
|
||||
// A zero delta resolves to .viewport and scrolls it by nothing.
|
||||
try targets.x.scrollBy(delta_x, 0, frame);
|
||||
try targets.y.scrollBy(0, delta_y, frame);
|
||||
try target.scrollContainer(.{ .x = delta_x != 0 }, frame).scrollBy(delta_x, 0, frame);
|
||||
try target.scrollContainer(.{ .y = delta_y != 0 }, frame).scrollBy(0, delta_y, frame);
|
||||
}
|
||||
|
||||
fn deltaToScroll(d: f64) i32 {
|
||||
|
||||
@@ -22,7 +22,6 @@ const lp = @import("lightpanda");
|
||||
const js = @import("../js/js.zig");
|
||||
const dump = @import("../dump.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const StyleManager = @import("../StyleManager.zig");
|
||||
const Factory = @import("../Factory.zig");
|
||||
|
||||
const CSS = @import("CSS.zig");
|
||||
@@ -1624,15 +1623,12 @@ const ScrollTarget = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
const ScrollTargets = struct {
|
||||
x: ScrollTarget = .viewport,
|
||||
y: ScrollTarget = .viewport,
|
||||
};
|
||||
|
||||
/// Nearest ancestor-or-self scroll container along any of `axes`, for callers
|
||||
/// positioning one scroller with an absolute offset.
|
||||
/// Nearest ancestor-or-self scroll container along any of `axes`. The walk
|
||||
/// is cheap to repeat per axis: the cascade memoizes each element's props.
|
||||
pub fn scrollContainer(self: *Element, axes: ScrollAxes, frame: *Frame) ScrollTarget {
|
||||
const style_manager = self.scrollStyleManager(axes, frame) orelse return .viewport;
|
||||
if (!axes.x and !axes.y) return .viewport;
|
||||
const owner = self.ownerFrame(frame) orelse return .viewport;
|
||||
const style_manager = &owner._style_manager;
|
||||
var current: ?*Element = self;
|
||||
while (current) |el| : (current = el.parentElement()) {
|
||||
if (el.scrollsViewport()) break;
|
||||
@@ -1644,37 +1640,6 @@ pub fn scrollContainer(self: *Element, axes: ScrollAxes, frame: *Frame) ScrollTa
|
||||
return .viewport;
|
||||
}
|
||||
|
||||
/// Nearest ancestor-or-self scroll container per requested axis, in one walk.
|
||||
/// Relative deltas may land on a different scroller per axis. An axis not in
|
||||
/// `axes` stays `.viewport`.
|
||||
pub fn scrollContainers(self: *Element, axes: ScrollAxes, frame: *Frame) ScrollTargets {
|
||||
var targets: ScrollTargets = .{};
|
||||
var pending = axes;
|
||||
const style_manager = self.scrollStyleManager(axes, frame) orelse return targets;
|
||||
var current: ?*Element = self;
|
||||
while (current) |el| : (current = el.parentElement()) {
|
||||
if (el.scrollsViewport()) break;
|
||||
const scrolls = style_manager.overflowAxes(el);
|
||||
if (pending.x and scrolls.x) {
|
||||
targets.x = .{ .container = el };
|
||||
pending.x = false;
|
||||
}
|
||||
if (pending.y and scrolls.y) {
|
||||
targets.y = .{ .container = el };
|
||||
pending.y = false;
|
||||
}
|
||||
if (!pending.x and !pending.y) break;
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
/// null when the walk cannot find anything: no axis asked for, or detached.
|
||||
fn scrollStyleManager(self: *Element, axes: ScrollAxes, frame: *Frame) ?*StyleManager {
|
||||
if (!axes.x and !axes.y) return null;
|
||||
const owner = self.ownerFrame(frame) orelse return null;
|
||||
return &owner._style_manager;
|
||||
}
|
||||
|
||||
fn scrollsViewport(self: *const Element) bool {
|
||||
return switch (self.getTag()) {
|
||||
.html, .body => true,
|
||||
|
||||
@@ -603,8 +603,6 @@ fn dispatch(target: *EventTarget, event: *Event, frame: *Frame, typ: []const u8)
|
||||
}
|
||||
|
||||
fn dispatchTouch(el: *Element, comptime typ: []const u8, frame: *Frame) void {
|
||||
// Listeners live in the event manager of the element's own frame, which
|
||||
// is not the caller's when the element belongs to an iframe's document.
|
||||
const owner = el.ownerFrame(frame) orelse return;
|
||||
const event = TouchEvent.initTrusted(typ, .{
|
||||
.bubbles = true,
|
||||
|
||||
@@ -142,11 +142,10 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const
|
||||
pub fn declaredValue(self: *const CSSStyleDeclaration, name: String, frame: *Frame) ?[]const u8 {
|
||||
if (name.eql(overflow_shorthand)) {
|
||||
const x = self.findProperty(comptime .wrap("overflow-x")) orelse return null;
|
||||
const y = self.overflowPartner(x) orelse return null;
|
||||
if (x._value.eql(y._value)) {
|
||||
return x._value.str();
|
||||
}
|
||||
return std.fmt.allocPrint(frame.local_arena, "{f} {f}", .{ x._value, y._value }) catch return null;
|
||||
const pair = self.overflowPair(x) orelse return null;
|
||||
var buf = std.Io.Writer.Allocating.init(frame.local_arena);
|
||||
pair.formatValue(&buf.writer) catch return null;
|
||||
return buf.written();
|
||||
}
|
||||
const prop = self.findProperty(name) orelse return null;
|
||||
return prop._value.str();
|
||||
@@ -158,34 +157,40 @@ pub fn declaredValue(self: *const CSSStyleDeclaration, name: String, frame: *Fra
|
||||
// sets both longhands, reading or serializing it recombines them.
|
||||
const overflow_shorthand: String = .wrap("overflow");
|
||||
|
||||
const OverflowValues = struct { x: []const u8, y: []const u8 };
|
||||
/// Both overflow longhands, declared with the same priority: the pair reads
|
||||
/// and serializes as the shorthand.
|
||||
const OverflowPair = struct {
|
||||
x: *const Property,
|
||||
y: *const Property,
|
||||
|
||||
// `overflow: <x> [<y>]`; a single value applies to both axes. More than two
|
||||
// values is invalid and ignored.
|
||||
fn splitOverflow(value: []const u8) ?OverflowValues {
|
||||
var it = std.mem.tokenizeAny(u8, value, &std.ascii.whitespace);
|
||||
const x = it.next() orelse return null;
|
||||
const y = it.next() orelse x;
|
||||
if (it.next() != null) {
|
||||
fn formatValue(self: OverflowPair, writer: *std.Io.Writer) !void {
|
||||
try self.x._value.format(writer);
|
||||
if (!self.x._value.eql(self.y._value)) {
|
||||
try writer.writeByte(' ');
|
||||
try self.y._value.format(writer);
|
||||
}
|
||||
}
|
||||
|
||||
fn format(self: OverflowPair, writer: *std.Io.Writer) !void {
|
||||
try writer.writeAll("overflow: ");
|
||||
try self.formatValue(writer);
|
||||
try formatDeclarationEnd(self.x._important, writer);
|
||||
}
|
||||
};
|
||||
|
||||
/// The pair `prop` belongs to, when it is an overflow longhand and the other
|
||||
/// is declared with the same priority.
|
||||
fn overflowPair(self: *const CSSStyleDeclaration, prop: *const Property) ?OverflowPair {
|
||||
const is_x = prop._name.eql(comptime .wrap("overflow-x"));
|
||||
if (!is_x and !prop._name.eql(comptime .wrap("overflow-y"))) {
|
||||
return null;
|
||||
}
|
||||
return .{ .x = x, .y = y };
|
||||
}
|
||||
|
||||
/// The other overflow longhand when `prop` is one and both are declared with
|
||||
/// the same priority: the pair reads and serializes as the shorthand.
|
||||
fn overflowPartner(self: *const CSSStyleDeclaration, prop: *const Property) ?*Property {
|
||||
const other: String = if (prop._name.eql(comptime .wrap("overflow-x")))
|
||||
comptime .wrap("overflow-y")
|
||||
else if (prop._name.eql(comptime .wrap("overflow-y")))
|
||||
comptime .wrap("overflow-x")
|
||||
else
|
||||
return null;
|
||||
const other: String = if (is_x) comptime .wrap("overflow-y") else comptime .wrap("overflow-x");
|
||||
const partner = self.findProperty(other) orelse return null;
|
||||
if (partner._important != prop._important) {
|
||||
return null;
|
||||
}
|
||||
return partner;
|
||||
return if (is_x) .{ .x = prop, .y = partner } else .{ .x = partner, .y = prop };
|
||||
}
|
||||
|
||||
fn resolvedDimension(element: *Element, dimension: enum { width, height }, frame: *Frame) []const u8 {
|
||||
@@ -203,10 +208,9 @@ pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []co
|
||||
const normalized = normalizePropertyName(property_name, &frame.buf);
|
||||
const wrapped = String.wrap(normalized);
|
||||
if (wrapped.eql(overflow_shorthand)) {
|
||||
// A shorthand is important when all its longhands are.
|
||||
const x = self.findProperty(comptime .wrap("overflow-x")) orelse return "";
|
||||
const y = self.findProperty(comptime .wrap("overflow-y")) orelse return "";
|
||||
return if (x._important and y._important) "important" else "";
|
||||
const pair = self.overflowPair(x) orelse return "";
|
||||
return if (pair.x._important) "important" else "";
|
||||
}
|
||||
const prop = self.findProperty(wrapped) orelse return "";
|
||||
return if (prop._important) "important" else "";
|
||||
@@ -236,8 +240,8 @@ pub fn setProperty(self: *CSSStyleDeclaration, property_name: []const u8, value:
|
||||
/// not override an earlier !important one (CSS cascade precedence).
|
||||
fn applyParsedDeclaration(self: *CSSStyleDeclaration, declaration: CssParser.Declaration, frame: *Frame) !void {
|
||||
const normalized = normalizePropertyName(declaration.name, &frame.buf);
|
||||
if (std.mem.eql(u8, normalized, "overflow")) {
|
||||
const values = splitOverflow(declaration.value) orelse return;
|
||||
if (overflow_shorthand.eqlSlice(normalized)) {
|
||||
const values = CssParser.splitOverflow(declaration.value) orelse return;
|
||||
try self.applyParsedDeclaration(.{ .name = "overflow-x", .value = values.x, .important = declaration.important }, frame);
|
||||
try self.applyParsedDeclaration(.{ .name = "overflow-y", .value = values.y, .important = declaration.important }, frame);
|
||||
return;
|
||||
@@ -262,8 +266,8 @@ fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value:
|
||||
}
|
||||
|
||||
const normalized = normalizePropertyName(property_name, &frame.buf);
|
||||
if (std.mem.eql(u8, normalized, "overflow")) {
|
||||
const values = splitOverflow(value) orelse return false;
|
||||
if (overflow_shorthand.eqlSlice(normalized)) {
|
||||
const values = CssParser.splitOverflow(value) orelse return false;
|
||||
const x = try self.setPropertyImpl("overflow-x", values.x, important, frame);
|
||||
const y = try self.setPropertyImpl("overflow-y", values.y, important, frame);
|
||||
return x or y;
|
||||
@@ -305,8 +309,8 @@ pub fn removeProperty(self: *CSSStyleDeclaration, property_name: []const u8, fra
|
||||
|
||||
fn removePropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, frame: *Frame) !?[]const u8 {
|
||||
const normalized = normalizePropertyName(property_name, &frame.buf);
|
||||
if (std.mem.eql(u8, normalized, "overflow")) {
|
||||
const old_value = try frame.call_arena.dupe(u8, self.declaredValue(overflow_shorthand, frame) orelse "");
|
||||
if (overflow_shorthand.eqlSlice(normalized)) {
|
||||
const old_value = self.declaredValue(overflow_shorthand, frame) orelse "";
|
||||
const x = try self.removePropertyImpl("overflow-x", frame);
|
||||
const y = try self.removePropertyImpl("overflow-y", frame);
|
||||
if (x == null and y == null) {
|
||||
@@ -398,30 +402,20 @@ pub fn replaceCssText(self: *CSSStyleDeclaration, text: []const u8, frame: *Fram
|
||||
|
||||
pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void {
|
||||
var first = true;
|
||||
var node = self._properties.first;
|
||||
while (node) |n| : (node = n.next) {
|
||||
const prop = Property.fromNodeLink(n);
|
||||
// A pair of overflow longhands serializes once, as the shorthand,
|
||||
// where overflow-x sits.
|
||||
const partner = self.overflowPartner(prop);
|
||||
if (partner != null and prop._name.eql(comptime .wrap("overflow-y"))) {
|
||||
// An overflow pair serializes once, where its first longhand sits.
|
||||
var skip: ?*const Property = null;
|
||||
var it = self.iterator();
|
||||
while (it.next()) |prop| {
|
||||
if (prop == skip) {
|
||||
continue;
|
||||
}
|
||||
if (!first) {
|
||||
try writer.writeByte(' ');
|
||||
}
|
||||
first = false;
|
||||
if (partner) |y| {
|
||||
try writer.writeAll("overflow: ");
|
||||
try prop._value.format(writer);
|
||||
if (!prop._value.eql(y._value)) {
|
||||
try writer.writeByte(' ');
|
||||
try y._value.format(writer);
|
||||
}
|
||||
if (prop._important) {
|
||||
try writer.writeAll(" !important");
|
||||
}
|
||||
try writer.writeByte(';');
|
||||
if (self.overflowPair(prop)) |pair| {
|
||||
try pair.format(writer);
|
||||
skip = if (pair.x == prop) pair.y else pair.x;
|
||||
} else {
|
||||
try prop.format(writer);
|
||||
}
|
||||
@@ -1049,14 +1043,17 @@ pub const Property = struct {
|
||||
try self._name.format(writer);
|
||||
try writer.writeAll(": ");
|
||||
try self._value.format(writer);
|
||||
|
||||
if (self._important) {
|
||||
try writer.writeAll(" !important");
|
||||
}
|
||||
try writer.writeByte(';');
|
||||
try formatDeclarationEnd(self._important, writer);
|
||||
}
|
||||
};
|
||||
|
||||
fn formatDeclarationEnd(important: bool, writer: *std.Io.Writer) !void {
|
||||
if (important) {
|
||||
try writer.writeAll(" !important");
|
||||
}
|
||||
try writer.writeByte(';');
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(CSSStyleDeclaration);
|
||||
|
||||
|
||||
Reference in new issue
Block a user