Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 18 additions & 28 deletions src/css/properties/margin_padding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,42 +676,32 @@ impl<S: SizeHandlerSpec> SizeHandler<S> {
);
} else if tag == S::SHORTHAND {
let val = S::extract_shorthand(property);
self.flush_helper_physical(
PhysicalSlot::Top,
S::shorthand_top(val),
S::SHORTHAND_CATEGORY,
dest,
context,
);
self.flush_helper_physical(
PhysicalSlot::Right,
S::shorthand_right(val),
S::SHORTHAND_CATEGORY,
dest,
context,
);
self.flush_helper_physical(
PhysicalSlot::Bottom,
S::shorthand_bottom(val),
S::SHORTHAND_CATEGORY,
dest,
context,
);
self.flush_helper_physical(
PhysicalSlot::Left,
S::shorthand_left(val),
S::SHORTHAND_CATEGORY,
dest,
context,
);

// The buffered values are only live for a target that rejects the whole declaration.
if let Some(browsers) = context.targets.browsers {
let rejected_by_a_target = [
S::shorthand_top(val),
S::shorthand_right(val),
S::shorthand_bottom(val),
S::shorthand_left(val),
]
.into_iter()
.any(|v| !v.is_compatible(&browsers));
if rejected_by_a_target {
self.flush(dest, context);
}
}

self.top = Some(S::shorthand_top(val).clone());
self.right = Some(S::shorthand_right(val).clone());
self.bottom = Some(S::shorthand_bottom(val).clone());
self.left = Some(S::shorthand_left(val).clone());
// Dead in every writing mode; flushing would put compiled inline ones after this rule.
self.block_start = None;
self.block_end = None;
self.inline_start = None;
self.inline_end = None;
self.category = S::SHORTHAND_CATEGORY;
self.has_any = true;
} else {
return false;
Expand Down
133 changes: 133 additions & 0 deletions test/js/bun/css/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2436,6 +2436,139 @@ describe("css tests", () => {
);
});

// A 4-side shorthand sets all four physical sides, so logical longhands declared before it in the
// same rule are dead in every writing mode. They must not survive into the output: when logical
// properties are compiled for old targets, inline longhands become separate :lang() rules emitted
// after the rule, which would win over the shorthand. The one reason to keep anything declared
// before the shorthand is a target that rejects the shorthand's value.
describe("logical longhands overridden by a 4-side shorthand", () => {
for (const [shorthand, inlineStart, inlineEnd, inline, blockStart, top] of [
["margin", "margin-inline-start", "margin-inline-end", "margin-inline", "margin-block-start", "margin-top"],
["padding", "padding-inline-start", "padding-inline-end", "padding-inline", "padding-block-start", "padding-top"],
]) {
// Safari 8 compiles logical properties away (the :lang() cases above use it too); Safari 15
// supports them.
for (const [label, targets] of [
["logical properties compiled", { safari: 8 << 16 }],
["logical properties supported", { safari: 15 << 16 }],
] as const) {
describe(`${shorthand}, ${label}`, () => {
for (const decls of [
`${inlineStart}: 2px`,
`${inlineEnd}: 2px`,
`${inline}: 2px 4px`,
`${blockStart}: 2px`,
`${inlineStart}: var(--x)`,
`${inlineStart}: 2px; ${blockStart}: 3px`,
]) {
prefix_test(
`
.foo {
${decls};
${shorthand}: 0;
}
`,
indoc`
.foo {
${shorthand}: 0;
}
`,
targets,
);
}
});
}

// Safari 14 does not support container query units, so it rejects the whole shorthand and the
// earlier declaration still applies there: it has to stay as a fallback, whether it is logical
// or a physical side whose own new value (5px) would have been fine. Safari 16 supports them,
// so the earlier declaration is dead.
for (const earlier of [`${inlineStart}: 2px`, `${top}: 10px`]) {
prefix_test(
`
.foo {
${earlier};
${shorthand}: 5px max(2cqw, 22px);
}
`,
indoc`
.foo {
${earlier};
${shorthand}: 5px max(2cqw, 22px);
}
`,
{
safari: 14 << 16,
},
);

prefix_test(
`
.foo {
${earlier};
${shorthand}: 5px max(2cqw, 22px);
}
`,
indoc`
.foo {
${shorthand}: 5px max(2cqw, 22px);
}
`,
{
safari: 16 << 16,
},
);
}

// A logical longhand after the shorthand is live and stays.
prefix_test(
`
.foo {
${shorthand}: 0;
${inlineStart}: 2px;
}
`,
indoc`
.foo {
${shorthand}: 0;
${inlineStart}: 2px;
}
`,
{
safari: 15 << 16,
},
);
}

for (const [shorthand, inlineStart, inline, top, left] of [
["margin", "margin-inline-start", "margin-inline", "margin-top", "margin-left"],
["padding", "padding-inline-start", "padding-inline", "padding-top", "padding-left"],
["inset", "inset-inline-start", "inset-inline", "top", "left"],
[
"scroll-margin",
"scroll-margin-inline-start",
"scroll-margin-inline",
"scroll-margin-top",
"scroll-margin-left",
],
]) {
minify_test(`.a{${inlineStart}:2px;${shorthand}:0}`, `.a{${shorthand}:0}`);
minify_test(`.a{${inline}:2px 4px;${shorthand}:0}`, `.a{${shorthand}:0}`);
minify_test(`.a{${inlineStart}:2px!important;${shorthand}:0!important}`, `.a{${shorthand}:0!important}`);
// Longhands after the shorthand still fold into it.
minify_test(`.a{${inlineStart}:2px;${shorthand}:0;${top}:5px}`, `.a{${shorthand}:5px 0 0}`);

// Only the 4-side shorthand overrides the other category: a 2-side logical shorthand leaves
// the physical sides alone (they map to different sides depending on the writing mode), and a
// logical longhand after the shorthand is live.
minify_test(`.a{${left}:2px;${inline}:0}`, `.a{${left}:2px;${inline}:0}`);
minify_test(`.a{${shorthand}:0;${inlineStart}:2px}`, `.a{${shorthand}:0;${inlineStart}:2px}`);
// An unparsed shorthand may hold a value some browser rejects, so the earlier declaration is
// kept as a fallback.
minify_test(`.a{${inlineStart}:2px;${shorthand}:var(--v)}`, `.a{${inlineStart}:2px;${shorthand}:var(--v)}`);
}
});

describe("scroll-paddding", () => {
prefix_test(
`
Expand Down
Loading