commit fb40638bf7baee173d13c948ed397bf66c0d6215
parent bea800de3bb2e1a29db79dfc16ee6adfc99c4990
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Mon, 20 Oct 2025 20:24:05 +0000
Bug 1995121 - Move property flips to style adjuster. r=dshin,firefox-style-system-reviewers,layout-anchor-positioning-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D269138
Diffstat:
4 files changed, 169 insertions(+), 83 deletions(-)
diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs
@@ -383,8 +383,11 @@ where
context.builder.clear_modified_reset();
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
- StyleAdjuster::new(&mut context.builder)
- .adjust(layout_parent_style.unwrap_or(inherited_style), element, try_tactic);
+ StyleAdjuster::new(&mut context.builder).adjust(
+ layout_parent_style.unwrap_or(inherited_style),
+ element,
+ try_tactic,
+ );
}
if context.builder.modified_reset() || using_cached_reset_properties {
@@ -642,7 +645,11 @@ struct Cascade<'b> {
}
impl<'b> Cascade<'b> {
- fn new(first_line_reparenting: FirstLineReparenting<'b>, try_tactic: PositionTryFallbacksTryTactic, ignore_colors: bool) -> Self {
+ fn new(
+ first_line_reparenting: FirstLineReparenting<'b>,
+ try_tactic: PositionTryFallbacksTryTactic,
+ ignore_colors: bool,
+ ) -> Self {
Self {
first_line_reparenting,
try_tactic,
@@ -869,9 +876,6 @@ impl<'b> Cascade<'b> {
.set_writing_mode_dependency(wm);
longhand_id = longhand_id.to_physical(wm);
}
- if !self.try_tactic.is_empty() {
- longhand_id = self.try_tactic.apply_to_property(longhand_id, context.builder.writing_mode);
- }
self.apply_one_longhand(
context,
longhand_id,
diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs
@@ -11,6 +11,7 @@
<%namespace name="helpers" file="/helpers.mako.rs" />
use crate::Atom;
+use crate::logical_geometry::PhysicalSide;
use app_units::Au;
use crate::computed_value_flags::*;
use crate::custom_properties::ComputedCustomProperties;
@@ -273,6 +274,25 @@ impl ComputedValuesInner {
}
</%def>
+<%def name="impl_physical_sides(ident, props)">
+ pub fn get_${ident}(&self, s: PhysicalSide) -> &longhands::${data.longhands_by_name[props[0]].ident}::computed_value::T {
+ match s {
+ PhysicalSide::Top => &self.${data.longhands_by_name[props[0]].gecko_ffi_name},
+ PhysicalSide::Right => &self.${data.longhands_by_name[props[1]].gecko_ffi_name},
+ PhysicalSide::Bottom => &self.${data.longhands_by_name[props[2]].gecko_ffi_name},
+ PhysicalSide::Left => &self.${data.longhands_by_name[props[3]].gecko_ffi_name},
+ }
+ }
+ pub fn set_${ident}(&mut self, s: PhysicalSide, v: longhands::${data.longhands_by_name[props[0]].ident}::computed_value::T) {
+ match s {
+ PhysicalSide::Top => self.set_${data.longhands_by_name[props[0]].ident}(v),
+ PhysicalSide::Right => self.set_${data.longhands_by_name[props[1]].ident}(v),
+ PhysicalSide::Bottom => self.set_${data.longhands_by_name[props[2]].ident}(v),
+ PhysicalSide::Left => self.set_${data.longhands_by_name[props[3]].ident}(v),
+ }
+ }
+</%def>
+
<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)">
#[allow(non_snake_case)]
pub fn copy_${ident}_from(&mut self, other: &Self) {
@@ -611,11 +631,14 @@ fn static_assert() {
% endfor
</%self:impl_trait>
-<%self:impl_trait style_struct_name="Margin"></%self:impl_trait>
+<%self:impl_trait style_struct_name="Margin">
+ ${impl_physical_sides("margin", ["margin-top", "margin-right", "margin-bottom", "margin-left"])}
+</%self:impl_trait>
<%self:impl_trait style_struct_name="Padding"></%self:impl_trait>
<%self:impl_trait style_struct_name="Page"></%self:impl_trait>
<%self:impl_trait style_struct_name="Position">
+ ${impl_physical_sides("inset", ["top", "right", "bottom", "left"])}
pub fn set_computed_justify_items(&mut self, v: values::specified::JustifyItems) {
debug_assert_ne!(v, values::specified::JustifyItems::legacy());
self.mJustifyItems.computed = v;
diff --git a/servo/components/style/style_adjuster.rs b/servo/components/style/style_adjuster.rs
@@ -7,6 +7,7 @@
use crate::computed_value_flags::ComputedValueFlags;
use crate::dom::TElement;
+use crate::logical_geometry::PhysicalSide;
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::longhands::float::computed_value::T as Float;
use crate::properties::longhands::position::computed_value::T as Position;
@@ -25,6 +26,23 @@ use crate::values::specified::position::{
#[cfg(feature = "gecko")]
use selectors::parser::PseudoElement;
+macro_rules! flip_property {
+ ($adjuster:ident, $struct_getter:ident, $struct_setter:ident, $a_getter:ident, $a_setter:ident, $b_getter:ident, $b_setter:ident) => {{
+ loop {
+ let s = $adjuster.style.$struct_getter();
+ let a = s.$a_getter();
+ let b = s.$b_getter();
+ if a == b {
+ break;
+ }
+ let s = $adjuster.style.$struct_setter();
+ s.$b_setter(a);
+ s.$a_setter(b);
+ break;
+ }
+ }};
+}
+
/// A struct that implements all the adjustment methods.
///
/// NOTE(emilio): If new adjustments are introduced that depend on reset
@@ -917,23 +935,139 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
/// overkill for now.
fn adjust_for_try_tactic(&mut self, tactic: PositionTryFallbacksTryTactic) {
debug_assert!(!tactic.is_empty());
+ let horizontal = self.style.writing_mode.is_horizontal();
// TODO: Flip inset / margin / sizes percentages and anchor lookup sides as necessary.
for tactic in tactic.into_iter() {
match tactic {
PositionTryFallbacksTryTacticKeyword::None => break,
PositionTryFallbacksTryTacticKeyword::FlipBlock => {
self.flip_self_alignment(/* block = */ true);
+ self.flip_insets_and_margins(!horizontal);
},
PositionTryFallbacksTryTacticKeyword::FlipInline => {
self.flip_self_alignment(/* block = */ false);
+ self.flip_insets_and_margins(horizontal);
},
PositionTryFallbacksTryTacticKeyword::FlipStart => {
- self.flip_alignment_start();
+ self.flip_start();
},
}
}
}
+ fn swap_insets(&mut self, a_side: PhysicalSide, b_side: PhysicalSide) {
+ debug_assert_ne!(a_side, b_side);
+ let pos = self.style.get_position();
+ let a = pos.get_inset(a_side);
+ let b = pos.get_inset(b_side);
+ if a == b {
+ return;
+ }
+ let a = a.clone();
+ let b = b.clone();
+ let pos = self.style.mutate_position();
+ pos.set_inset(a_side, b);
+ pos.set_inset(b_side, a);
+ }
+
+ fn swap_margins(&mut self, a_side: PhysicalSide, b_side: PhysicalSide) {
+ debug_assert_ne!(a_side, b_side);
+ let margin = self.style.get_margin();
+ let a = margin.get_margin(a_side);
+ let b = margin.get_margin(b_side);
+ if a == b {
+ return;
+ }
+ let a = a.clone();
+ let b = b.clone();
+ let margin = self.style.mutate_margin();
+ margin.set_margin(a_side, b);
+ margin.set_margin(b_side, a);
+ }
+
+ fn flip_start(&mut self) {
+ flip_property!(
+ self,
+ get_position,
+ mutate_position,
+ clone_width,
+ set_width,
+ clone_height,
+ set_height
+ );
+ flip_property!(
+ self,
+ get_position,
+ mutate_position,
+ clone_min_width,
+ set_min_width,
+ clone_min_height,
+ set_min_height
+ );
+ flip_property!(
+ self,
+ get_position,
+ mutate_position,
+ clone_max_width,
+ set_max_width,
+ clone_max_height,
+ set_max_height
+ );
+ let wm = self.style.writing_mode;
+ let bs = wm.block_start_physical_side();
+ let is = wm.inline_start_physical_side();
+ let be = wm.block_end_physical_side();
+ let ie = wm.inline_end_physical_side();
+ self.swap_insets(bs, is);
+ self.swap_insets(ie, be);
+ self.swap_margins(bs, is);
+ self.swap_margins(ie, be);
+ self.flip_alignment_start();
+ }
+
+ fn flip_insets_and_margins(&mut self, horizontal: bool) {
+ if horizontal {
+ // TODO: Avoid the clone here?
+ flip_property!(
+ self,
+ get_position,
+ mutate_position,
+ clone_left,
+ set_left,
+ clone_right,
+ set_right
+ );
+ flip_property!(
+ self,
+ get_margin,
+ mutate_margin,
+ clone_margin_left,
+ set_margin_left,
+ clone_margin_right,
+ set_margin_right
+ );
+ } else {
+ flip_property!(
+ self,
+ get_position,
+ mutate_position,
+ clone_top,
+ set_top,
+ clone_bottom,
+ set_bottom
+ );
+ flip_property!(
+ self,
+ get_margin,
+ mutate_margin,
+ clone_margin_top,
+ set_margin_top,
+ clone_margin_bottom,
+ set_margin_bottom
+ );
+ }
+ }
+
fn flip_alignment_start(&mut self) {
let pos = self.style.get_position();
let align = pos.clone_align_self();
diff --git a/servo/components/style/values/specified/position.rs b/servo/components/style/values/specified/position.rs
@@ -7,9 +7,7 @@
//!
//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
-use crate::logical_geometry::{PhysicalSide, WritingMode};
use crate::parser::{Parse, ParserContext};
-use crate::properties::LonghandId;
use crate::selector_map::PrecomputedHashMap;
use crate::str::HTML_SPACE_CHARACTERS;
use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
@@ -586,84 +584,11 @@ impl PositionTryFallbacksTryTactic {
self.0.is_none()
}
- fn flip_vertical(id: LonghandId) -> LonghandId {
- match id {
- LonghandId::Top => LonghandId::Bottom,
- LonghandId::Bottom => LonghandId::Top,
- LonghandId::MarginTop => LonghandId::MarginBottom,
- LonghandId::MarginBottom => LonghandId::MarginTop,
- _ => id,
- }
- }
-
- fn flip_horizontal(id: LonghandId) -> LonghandId {
- match id {
- LonghandId::Left => LonghandId::Right,
- LonghandId::Right => LonghandId::Left,
- LonghandId::MarginLeft => LonghandId::MarginRight,
- LonghandId::MarginRight => LonghandId::MarginLeft,
- _ => id,
- }
- }
-
- fn flip_start(id: LonghandId, wm: WritingMode) -> LonghandId {
- use LonghandId::*;
- let (physical_side, is_margin) = match id {
- Width => return Height,
- Height => return Width,
- MinWidth => return MinHeight,
- MinHeight => return MinWidth,
- MaxWidth => return MaxHeight,
- MaxHeight => return MaxWidth,
-
- Top => (PhysicalSide::Top, false),
- Right => (PhysicalSide::Right, false),
- Bottom => (PhysicalSide::Bottom, false),
- Left => (PhysicalSide::Left, false),
-
- MarginTop => (PhysicalSide::Top, true),
- MarginRight => (PhysicalSide::Right, true),
- MarginBottom => (PhysicalSide::Bottom, true),
- MarginLeft => (PhysicalSide::Left, true),
- _ => return id,
- };
-
- match wm.flipped_start_side(physical_side) {
- PhysicalSide::Top => if is_margin { MarginTop } else { Top },
- PhysicalSide::Right => if is_margin { MarginRight } else { Right },
- PhysicalSide::Bottom => if is_margin { MarginBottom } else { Bottom },
- PhysicalSide::Left => if is_margin { MarginLeft } else { Left },
- }
- }
-
/// Iterates over the fallbacks in order.
#[inline]
pub fn into_iter(&self) -> impl IntoIterator<Item = PositionTryFallbacksTryTacticKeyword> {
[self.0, self.1, self.2]
}
-
- /// Applies a try tactic to a given property.
- pub fn apply_to_property(&self, mut id: LonghandId, wm: WritingMode) -> LonghandId {
- debug_assert!(!id.is_logical(), "Logical props should've been replaced already");
- debug_assert!(!self.is_empty(), "Should have something to do");
- // TODO(emilio): Consider building a LonghandIdSet to check for unaffected properties, and
- // bailing out earlier?
- for tactic in self.into_iter() {
- id = match tactic {
- PositionTryFallbacksTryTacticKeyword::None => break,
- PositionTryFallbacksTryTacticKeyword::FlipInline |
- PositionTryFallbacksTryTacticKeyword::FlipBlock => {
- if wm.is_horizontal() == (tactic == PositionTryFallbacksTryTacticKeyword::FlipInline) {
- Self::flip_horizontal(id)
- } else {
- Self::flip_vertical(id)
- }
- },
- PositionTryFallbacksTryTacticKeyword::FlipStart => Self::flip_start(id, wm),
- }
- }
- id
- }
}
#[derive(