mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-09 04:42:30 -04:00
css: share the axis shorthand table with the CSSOM
The cascade expanded overscroll-behavior into longhands but CSSStyleDeclaration didn't, so setting the shorthand left overscrollBehaviorX reading empty and a style= block round-tripped through the object lost it. CssParser.axis_shorthands is now the one list, with axisShorthand and axisLonghand as the lookups both sides use: the CSSOM's overflow-only special cases (set, apply, remove, priority, serialize) became that lookup, and OverflowPair became AxisPair. Verified against Chrome 153: `overscroll-behavior: contain auto` reads back per axis, collapses to `contain` when both match, and serializes as one declaration.
This commit is contained in:
1 parent
fdee7d558c
commit
93c551bed6
4 files changed
+107
-56
No files matched your search
@@ -1303,12 +1303,6 @@ fn foldDeclarations(block: []const u8, customs: ?*CustomSink) !TrackedProperties
|
||||
return slots.props();
|
||||
}
|
||||
|
||||
// The `<x> [<y>]` shorthands the cascade expands into the tracked longhands.
|
||||
const axis_shorthands = [_]struct { name: []const u8, x: []const u8, y: []const u8 }{
|
||||
.{ .name = "overflow", .x = "overflow-x", .y = "overflow-y" },
|
||||
.{ .name = "overscroll-behavior", .x = "overscroll-behavior-x", .y = "overscroll-behavior-y" },
|
||||
};
|
||||
|
||||
/// One block's winning value per tracked property, folded in declaration
|
||||
/// order.
|
||||
const Slots = struct {
|
||||
@@ -1332,13 +1326,11 @@ const Slots = struct {
|
||||
slots: [property_names.len]Slot = @splat(.{}),
|
||||
|
||||
fn apply(self: *Slots, name: []const u8, value: []const u8, important: bool) void {
|
||||
for (axis_shorthands) |shorthand| {
|
||||
if (std.ascii.eqlIgnoreCase(name, shorthand.name)) {
|
||||
const values = CssParser.splitAxisPair(value) orelse return;
|
||||
self.apply(shorthand.x, values.x, important);
|
||||
self.apply(shorthand.y, values.y, important);
|
||||
return;
|
||||
}
|
||||
if (CssParser.axisShorthand(name)) |shorthand| {
|
||||
const values = CssParser.splitAxisPair(value) orelse return;
|
||||
self.apply(shorthand.x, values.x, important);
|
||||
self.apply(shorthand.y, values.y, important);
|
||||
return;
|
||||
}
|
||||
for (property_names, &self.slots) |tracked, *slot| {
|
||||
if (std.ascii.eqlIgnoreCase(name, tracked)) {
|
||||
|
||||
@@ -27,6 +27,53 @@ pub const Declaration = struct {
|
||||
|
||||
pub const AxisPair = struct { x: []const u8, y: []const u8 };
|
||||
|
||||
pub const AxisShorthand = struct {
|
||||
name: []const u8,
|
||||
x: []const u8,
|
||||
y: []const u8,
|
||||
};
|
||||
|
||||
// The `<x> [<y>]` shorthands whose longhands the style cascade tracks. Both the
|
||||
// CSSOM object and the cascade store these expanded: setting one sets both
|
||||
// longhands, reading or serializing it recombines them.
|
||||
pub const axis_shorthands = [_]AxisShorthand{
|
||||
.{ .name = "overflow", .x = "overflow-x", .y = "overflow-y" },
|
||||
.{ .name = "overscroll-behavior", .x = "overscroll-behavior-x", .y = "overscroll-behavior-y" },
|
||||
};
|
||||
|
||||
/// The axis shorthand `name` names, if it names one.
|
||||
pub fn axisShorthand(name: []const u8) ?AxisShorthand {
|
||||
for (axis_shorthands) |shorthand| {
|
||||
if (std.ascii.eqlIgnoreCase(name, shorthand.name)) {
|
||||
return shorthand;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const AxisLonghand = struct {
|
||||
shorthand: AxisShorthand,
|
||||
is_x: bool,
|
||||
|
||||
/// The longhand on the other axis.
|
||||
pub fn partner(self: AxisLonghand) []const u8 {
|
||||
return if (self.is_x) self.shorthand.y else self.shorthand.x;
|
||||
}
|
||||
};
|
||||
|
||||
/// The axis shorthand `name` is a longhand of, if it is one.
|
||||
pub fn axisLonghand(name: []const u8) ?AxisLonghand {
|
||||
for (axis_shorthands) |shorthand| {
|
||||
if (std.ascii.eqlIgnoreCase(name, shorthand.x)) {
|
||||
return .{ .shorthand = shorthand, .is_x = true };
|
||||
}
|
||||
if (std.ascii.eqlIgnoreCase(name, shorthand.y)) {
|
||||
return .{ .shorthand = shorthand, .is_x = false };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// An `<x> [<y>]` axis shorthand such as `overflow` or `overscroll-behavior`;
|
||||
/// a single value applies to both axes. More than two values is invalid and
|
||||
/// null, as is an empty declaration.
|
||||
|
||||
@@ -113,6 +113,25 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="overscrollBehaviorShorthand">
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
div.style.cssText = 'overscroll-behavior: contain auto';
|
||||
testing.expectEqual('contain', div.style.overscrollBehaviorX);
|
||||
testing.expectEqual('auto', div.style.overscrollBehaviorY);
|
||||
testing.expectEqual('contain auto', div.style.overscrollBehavior);
|
||||
testing.expectEqual(2, div.style.length);
|
||||
|
||||
div.style.overscrollBehaviorY = 'contain';
|
||||
testing.expectEqual('contain', div.style.overscrollBehavior);
|
||||
testing.expectEqual('overscroll-behavior: contain;', div.style.cssText);
|
||||
|
||||
div.style.setProperty('overscroll-behavior', 'none');
|
||||
testing.expectEqual('none', div.style.overscrollBehaviorX);
|
||||
testing.expectEqual('none', div.style.overscrollBehaviorY);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="overflowShorthand">
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
|
||||
@@ -136,13 +136,13 @@ pub fn getPropertyValue(self: *const CSSStyleDeclaration, property_name: []const
|
||||
return getDefaultPropertyValue(self, wrapped);
|
||||
}
|
||||
|
||||
/// The value of a declared property, or null when it isn't declared. The
|
||||
/// `overflow` shorthand reads as its longhands when both are present with
|
||||
/// the same priority, the way the CSSOM serializes a shorthand.
|
||||
/// The value of a declared property, or null when it isn't declared. An axis
|
||||
/// shorthand reads as its longhands when both are present with the same
|
||||
/// priority, the way the CSSOM serializes a shorthand.
|
||||
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 pair = self.overflowPair(x) orelse return null;
|
||||
if (CssParser.axisShorthand(name.str())) |shorthand| {
|
||||
const x = self.findProperty(.wrap(shorthand.x)) orelse return null;
|
||||
const pair = self.axisPair(x) orelse return null;
|
||||
var buf = std.Io.Writer.Allocating.init(frame.local_arena);
|
||||
pair.formatValue(&buf.writer) catch return null;
|
||||
return buf.written();
|
||||
@@ -151,19 +151,14 @@ pub fn declaredValue(self: *const CSSStyleDeclaration, name: String, frame: *Fra
|
||||
return prop._value.str();
|
||||
}
|
||||
|
||||
// `overflow` is the one shorthand whose longhands the style cascade folds
|
||||
// (StyleManager tracks overflow-x and overflow-y), so it is the one this
|
||||
// object stores expanded, as the CSSOM does for every shorthand: setting it
|
||||
// sets both longhands, reading or serializing it recombines them.
|
||||
const overflow_shorthand: String = .wrap("overflow");
|
||||
|
||||
/// Both overflow longhands, declared with the same priority: the pair reads
|
||||
/// and serializes as the shorthand.
|
||||
const OverflowPair = struct {
|
||||
/// Both longhands of an axis shorthand, declared with the same priority: the
|
||||
/// pair reads and serializes as the shorthand.
|
||||
const AxisPair = struct {
|
||||
name: []const u8,
|
||||
x: *const Property,
|
||||
y: *const Property,
|
||||
|
||||
fn formatValue(self: OverflowPair, writer: *std.Io.Writer) !void {
|
||||
fn formatValue(self: AxisPair, writer: *std.Io.Writer) !void {
|
||||
try self.x._value.format(writer);
|
||||
if (!self.x._value.eql(self.y._value)) {
|
||||
try writer.writeByte(' ');
|
||||
@@ -171,26 +166,24 @@ const OverflowPair = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn format(self: OverflowPair, writer: *std.Io.Writer) !void {
|
||||
try writer.writeAll("overflow: ");
|
||||
fn format(self: AxisPair, writer: *std.Io.Writer) !void {
|
||||
try writer.writeAll(self.name);
|
||||
try writer.writeAll(": ");
|
||||
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;
|
||||
}
|
||||
const other: String = if (is_x) comptime .wrap("overflow-y") else comptime .wrap("overflow-x");
|
||||
const partner = self.findProperty(other) orelse return null;
|
||||
/// The pair `prop` belongs to, when it is an axis longhand and the other is
|
||||
/// declared with the same priority.
|
||||
fn axisPair(self: *const CSSStyleDeclaration, prop: *const Property) ?AxisPair {
|
||||
const longhand = CssParser.axisLonghand(prop._name.str()) orelse return null;
|
||||
const partner = self.findProperty(.wrap(longhand.partner())) orelse return null;
|
||||
if (partner._important != prop._important) {
|
||||
return null;
|
||||
}
|
||||
return if (is_x) .{ .x = prop, .y = partner } else .{ .x = partner, .y = prop };
|
||||
const name = longhand.shorthand.name;
|
||||
return if (longhand.is_x) .{ .name = name, .x = prop, .y = partner } else .{ .name = name, .x = partner, .y = prop };
|
||||
}
|
||||
|
||||
fn resolvedDimension(element: *Element, dimension: enum { width, height }, frame: *Frame) []const u8 {
|
||||
@@ -207,9 +200,9 @@ fn resolvedDimension(element: *Element, dimension: enum { width, height }, frame
|
||||
pub fn getPropertyPriority(self: *const CSSStyleDeclaration, property_name: []const u8, frame: *Frame) []const u8 {
|
||||
const normalized = normalizePropertyName(property_name, &frame.buf);
|
||||
const wrapped = String.wrap(normalized);
|
||||
if (wrapped.eql(overflow_shorthand)) {
|
||||
const x = self.findProperty(comptime .wrap("overflow-x")) orelse return "";
|
||||
const pair = self.overflowPair(x) orelse return "";
|
||||
if (CssParser.axisShorthand(normalized)) |shorthand| {
|
||||
const x = self.findProperty(.wrap(shorthand.x)) orelse return "";
|
||||
const pair = self.axisPair(x) orelse return "";
|
||||
return if (pair.x._important) "important" else "";
|
||||
}
|
||||
const prop = self.findProperty(wrapped) orelse return "";
|
||||
@@ -240,10 +233,10 @@ 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 (overflow_shorthand.eqlSlice(normalized)) {
|
||||
if (CssParser.axisShorthand(normalized)) |shorthand| {
|
||||
const values = CssParser.splitAxisPair(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);
|
||||
try self.applyParsedDeclaration(.{ .name = shorthand.x, .value = values.x, .important = declaration.important }, frame);
|
||||
try self.applyParsedDeclaration(.{ .name = shorthand.y, .value = values.y, .important = declaration.important }, frame);
|
||||
return;
|
||||
}
|
||||
if (!declaration.important) {
|
||||
@@ -266,10 +259,10 @@ fn setPropertyImpl(self: *CSSStyleDeclaration, property_name: []const u8, value:
|
||||
}
|
||||
|
||||
const normalized = normalizePropertyName(property_name, &frame.buf);
|
||||
if (overflow_shorthand.eqlSlice(normalized)) {
|
||||
if (CssParser.axisShorthand(normalized)) |shorthand| {
|
||||
const values = CssParser.splitAxisPair(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);
|
||||
const x = try self.setPropertyImpl(shorthand.x, values.x, important, frame);
|
||||
const y = try self.setPropertyImpl(shorthand.y, values.y, important, frame);
|
||||
return x or y;
|
||||
}
|
||||
|
||||
@@ -309,10 +302,10 @@ 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 (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 (CssParser.axisShorthand(normalized)) |shorthand| {
|
||||
const old_value = self.declaredValue(.wrap(shorthand.name), frame) orelse "";
|
||||
const x = try self.removePropertyImpl(shorthand.x, frame);
|
||||
const y = try self.removePropertyImpl(shorthand.y, frame);
|
||||
if (x == null and y == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -402,7 +395,7 @@ pub fn replaceCssText(self: *CSSStyleDeclaration, text: []const u8, frame: *Fram
|
||||
|
||||
pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void {
|
||||
var first = true;
|
||||
// An overflow pair serializes once, where its first longhand sits.
|
||||
// An axis pair serializes once, where its first longhand sits.
|
||||
var skip: ?*const Property = null;
|
||||
var it = self.iterator();
|
||||
while (it.next()) |prop| {
|
||||
@@ -413,7 +406,7 @@ pub fn format(self: *const CSSStyleDeclaration, writer: *std.Io.Writer) !void {
|
||||
try writer.writeByte(' ');
|
||||
}
|
||||
first = false;
|
||||
if (self.overflowPair(prop)) |pair| {
|
||||
if (self.axisPair(prop)) |pair| {
|
||||
try pair.format(writer);
|
||||
skip = if (pair.x == prop) pair.y else pair.x;
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user