tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a7f9c002e364a683d5b273aa86e8455ff725acde
parent 9e2bd6b599eab198b8d575033aca4b4843eb50b6
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date:   Fri, 24 Oct 2025 08:20:34 +0000

Bug 1996018 - Improve representation of position-area, and move fallbacks to StyleAdjuster. r=layout-anchor-positioning-reviewers,firefox-style-system-reviewers,layout-reviewers,dshin

Differential Revision: https://phabricator.services.mozilla.com/D269760

Diffstat:
Mlayout/base/AnchorPositioningUtils.cpp | 296++-----------------------------------------------------------------------------
Mlayout/base/AnchorPositioningUtils.h | 3+--
Mlayout/generic/AbsoluteContainingBlock.cpp | 14++++----------
Mlayout/style/nsStyleStruct.cpp | 2--
Mservo/components/style/style_adjuster.rs | 13+++++++++++++
Mservo/components/style/values/computed/position.rs | 68++++++++++++++------------------------------------------------------
Mservo/components/style/values/specified/position.rs | 639+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mservo/ports/geckolib/cbindgen.toml | 6++++++
Mservo/ports/geckolib/glue.rs | 13+++++++++++--
9 files changed, 464 insertions(+), 590 deletions(-)

diff --git a/layout/base/AnchorPositioningUtils.cpp b/layout/base/AnchorPositioningUtils.cpp @@ -487,186 +487,6 @@ Maybe<AnchorPosInfo> AnchorPositioningUtils::GetAnchorPosRect( } /** - * Strips the Span and SelfWM flags from a position-area keyword value. - */ -static inline StylePositionAreaKeyword StripSpanAndSelfWMFlags( - StylePositionAreaKeyword aValue) { - return StylePositionAreaKeyword(uint8_t(aValue) & - ~(uint8_t(StylePositionAreaKeyword::Span) | - uint8_t(StylePositionAreaKeyword::SelfWM))); -} - -static inline uint8_t SpanAndSelfWM(StylePositionAreaKeyword aValue) { - return uint8_t(aValue) & (uint8_t(StylePositionAreaKeyword::Span) | - uint8_t(StylePositionAreaKeyword::SelfWM)); -} - -/** - * Returns the given PositionArea with the second keyword converted to the - * implied keyword if it was not specified (its value is `None`). - */ -static inline StylePositionArea MakeMissingSecondExplicit( - StylePositionArea aPositionArea) { - auto first = aPositionArea.first; - if (aPositionArea.second == StylePositionAreaKeyword::None) { - switch (StripSpanAndSelfWMFlags(first)) { - // Per spec, if the single specified keyword is ambiguous about its axis - // then it is repeated. - case StylePositionAreaKeyword::Center: - case StylePositionAreaKeyword::SpanAll: - case StylePositionAreaKeyword::Start: - case StylePositionAreaKeyword::End: - return {first, first}; - - // Otherwise, the other keyword is `span-all`. The "first" keyword may - // actually belong canonically in the second position, depending which - // axis it refers to, but that will be resolved later. - default: - return {first, StylePositionAreaKeyword::SpanAll}; - } - } - return aPositionArea; -} - -static StylePositionAreaKeyword FlipInAxis(StylePositionAreaKeyword aKw, - PhysicalAxis aAxis) { - auto bits = SpanAndSelfWM(aKw); - auto stripped = StripSpanAndSelfWMFlags(aKw); - switch (stripped) { - case StylePositionAreaKeyword::Top: - case StylePositionAreaKeyword::Bottom: - if (aAxis != PhysicalAxis::Vertical) { - break; - } - return StylePositionAreaKeyword( - uint8_t(stripped == StylePositionAreaKeyword::Top - ? StylePositionAreaKeyword::Bottom - : StylePositionAreaKeyword::Top) | - bits); - case StylePositionAreaKeyword::Left: - case StylePositionAreaKeyword::Right: - if (aAxis != PhysicalAxis::Horizontal) { - break; - } - return StylePositionAreaKeyword( - uint8_t(stripped == StylePositionAreaKeyword::Left - ? StylePositionAreaKeyword::Right - : StylePositionAreaKeyword::Left) | - bits); - case StylePositionAreaKeyword::Center: - case StylePositionAreaKeyword::SpanAll: - break; - default: - MOZ_ASSERT_UNREACHABLE("Expected a physical position area"); - break; - } - return aKw; -} - -static void FlipInAxis(StylePositionArea& aArea, PhysicalAxis aAxis) { - aArea.first = FlipInAxis(aArea.first, aAxis); - aArea.second = FlipInAxis(aArea.second, aAxis); -} - -static void FlipStartsAndEnds(StylePositionArea& aArea, WritingMode aWM) { - auto flipAxes = [](StylePositionAreaKeyword aKw, - WritingMode aWM) -> StylePositionAreaKeyword { - auto bits = SpanAndSelfWM(aKw); - auto stripped = StripSpanAndSelfWMFlags(aKw); - // If stripped value is a physical side, convert it to a logical side. - Maybe<LogicalSide> logicalSide; - switch (stripped) { - case StylePositionAreaKeyword::Top: - logicalSide = Some(aWM.LogicalSideForPhysicalSide(Side::eSideTop)); - break; - case StylePositionAreaKeyword::Bottom: - logicalSide = Some(aWM.LogicalSideForPhysicalSide(Side::eSideBottom)); - break; - case StylePositionAreaKeyword::Left: - logicalSide = Some(aWM.LogicalSideForPhysicalSide(Side::eSideLeft)); - break; - case StylePositionAreaKeyword::Right: - logicalSide = Some(aWM.LogicalSideForPhysicalSide(Side::eSideRight)); - break; - case StylePositionAreaKeyword::Center: - case StylePositionAreaKeyword::SpanAll: - break; - default: - MOZ_ASSERT_UNREACHABLE("expected a physical positon-area"); - break; - } - if (logicalSide) { - // Swap inline/block axes and convert back to physical side. - mozilla::Side side; - switch (*logicalSide) { - case LogicalSide::IStart: - side = aWM.PhysicalSide(LogicalSide::BStart); - break; - case LogicalSide::IEnd: - side = aWM.PhysicalSide(LogicalSide::BEnd); - break; - case LogicalSide::BStart: - side = aWM.PhysicalSide(LogicalSide::IStart); - break; - case LogicalSide::BEnd: - side = aWM.PhysicalSide(LogicalSide::IEnd); - break; - } - switch (side) { - case eSideTop: - stripped = StylePositionAreaKeyword::Top; - break; - case eSideBottom: - stripped = StylePositionAreaKeyword::Bottom; - break; - case eSideLeft: - stripped = StylePositionAreaKeyword::Left; - break; - case eSideRight: - stripped = StylePositionAreaKeyword::Right; - break; - } - } - return StylePositionAreaKeyword(uint8_t(stripped) | bits); - }; - - aArea.first = flipAxes(aArea.first, aWM); - aArea.second = flipAxes(aArea.second, aWM); - - std::swap(aArea.first, aArea.second); -} - -static void ApplyFallbackTactic( - StylePositionArea& aPhysicalArea, - StylePositionTryFallbacksTryTacticKeyword aTactic, WritingMode aWM) { - switch (aTactic) { - case StylePositionTryFallbacksTryTacticKeyword::FlipBlock: - FlipInAxis(aPhysicalArea, aWM.PhysicalAxis(LogicalAxis::Block)); - return; - case StylePositionTryFallbacksTryTacticKeyword::FlipInline: - FlipInAxis(aPhysicalArea, aWM.PhysicalAxis(LogicalAxis::Inline)); - return; - case StylePositionTryFallbacksTryTacticKeyword::FlipStart: - FlipStartsAndEnds(aPhysicalArea, aWM); - return; - case StylePositionTryFallbacksTryTacticKeyword::FlipX: - FlipInAxis(aPhysicalArea, PhysicalAxis::Horizontal); - return; - case StylePositionTryFallbacksTryTacticKeyword::FlipY: - FlipInAxis(aPhysicalArea, PhysicalAxis::Vertical); - return; - } -} - -static void ApplyFallbackTactic( - StylePositionArea& aArea, const StylePositionTryFallbacksTryTactic& aTactic, - WritingMode aWM) { - for (auto t : aTactic) { - ApplyFallbackTactic(aArea, t, aWM); - } -} - -/** * Returns an equivalent StylePositionArea that contains: * [ * [ left | center | right | span-left | span-right | span-all] @@ -676,118 +496,15 @@ static void ApplyFallbackTactic( static StylePositionArea ToPhysicalPositionArea(StylePositionArea aPosArea, WritingMode aCbWM, WritingMode aPosWM) { - aPosArea = MakeMissingSecondExplicit(aPosArea); - - auto toPhysical = [=](StylePositionAreaKeyword aValue, - bool aImplicitIsBlock) -> StylePositionAreaKeyword { - if (aValue < StylePositionAreaKeyword::Left) { - return aValue; - } - - // Extract the `span` and `selfWM` bits and mask them out of aValue. - uint8_t span = uint8_t(aValue) & uint8_t(StylePositionAreaKeyword::Span); - uint8_t selfWM = - uint8_t(aValue) & uint8_t(StylePositionAreaKeyword::SelfWM); - aValue = StripSpanAndSelfWMFlags(aValue); - - // Determine which logical side, if any, is used. - Maybe<LogicalSide> ls; - WritingMode wm = selfWM ? aPosWM : aCbWM; - switch (aValue) { - case StylePositionAreaKeyword::Start: - ls = Some(aImplicitIsBlock ? LogicalSide::BStart : LogicalSide::IStart); - break; - case StylePositionAreaKeyword::End: - ls = Some(aImplicitIsBlock ? LogicalSide::BEnd : LogicalSide::IEnd); - break; - - case StylePositionAreaKeyword::BlockStart: - ls = Some(LogicalSide::BStart); - break; - case StylePositionAreaKeyword::BlockEnd: - ls = Some(LogicalSide::BEnd); - break; - case StylePositionAreaKeyword::InlineStart: - ls = Some(LogicalSide::IStart); - break; - case StylePositionAreaKeyword::InlineEnd: - ls = Some(LogicalSide::IEnd); - break; - - case StylePositionAreaKeyword::XStart: - ls = Some(wm.IsVertical() ? LogicalSide::BStart : LogicalSide::IStart); - break; - case StylePositionAreaKeyword::XEnd: - ls = Some(wm.IsVertical() ? LogicalSide::BEnd : LogicalSide::IEnd); - break; - case StylePositionAreaKeyword::YStart: - ls = Some(wm.IsVertical() ? LogicalSide::IStart : LogicalSide::BStart); - break; - case StylePositionAreaKeyword::YEnd: - ls = Some(wm.IsVertical() ? LogicalSide::IEnd : LogicalSide::BEnd); - break; - - default: - break; - } - - // If a logical side was used, resolve it to physical using the appropriate - // writing-mode. - if (ls.isSome()) { - switch (wm.PhysicalSide(ls.ref())) { - case Side::eSideLeft: - aValue = StylePositionAreaKeyword::Left; - break; - case Side::eSideRight: - aValue = StylePositionAreaKeyword::Right; - break; - case Side::eSideTop: - aValue = StylePositionAreaKeyword::Top; - break; - case Side::eSideBottom: - aValue = StylePositionAreaKeyword::Bottom; - break; - } - } - - // Restore the `span` component of the value, if present originally. - return StylePositionAreaKeyword(uint8_t(aValue) | span); - }; - - aPosArea.first = toPhysical(aPosArea.first, /* aImplicitIsBlock = */ true); - aPosArea.second = toPhysical(aPosArea.second, /* aImplicitIsBlock = */ false); - - // Ensure the physical values are in the expected order, with Left or Right - // in the first position, Top or Bottom in second. (Center and SpanAll may - // occur in either slot.) - switch (StripSpanAndSelfWMFlags(aPosArea.first)) { - case StylePositionAreaKeyword::Top: - case StylePositionAreaKeyword::Bottom: - std::swap(aPosArea.first, aPosArea.second); - break; - - case StylePositionAreaKeyword::Center: - case StylePositionAreaKeyword::SpanAll: - switch (StripSpanAndSelfWMFlags(aPosArea.second)) { - case StylePositionAreaKeyword::Left: - case StylePositionAreaKeyword::Right: - std::swap(aPosArea.first, aPosArea.second); - break; - default: - break; - } - break; - - default: - break; - } + StyleWritingMode cbwm{aCbWM.GetBits()}; + StyleWritingMode wm{aPosWM.GetBits()}; + Servo_PhysicalizePositionArea(&aPosArea, &cbwm, &wm); return aPosArea; } nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea( const nsRect& aAnchorRect, const nsRect& aCBRect, WritingMode aPositionedWM, - WritingMode aCBWM, const StylePositionArea& aPosArea, - const StylePositionTryFallbacksTryTactic* aFallbackTactic) { + WritingMode aCBWM, const StylePositionArea& aPosArea) { // Get the boundaries of 3x3 grid in CB's frame space. The edges of the // default anchor box are clamped to the bounds of the CB, even if that // results in zero width/height cells. @@ -819,11 +536,6 @@ nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea( // PositionArea, resolved to only contain Left/Right/Top/Bottom values. StylePositionArea posArea = ToPhysicalPositionArea(aPosArea, aCBWM, aPositionedWM); - if (aFallbackTactic) { - // See https://github.com/w3c/csswg-drafts/issues/12869 for which WM to use - // here. - ApplyFallbackTactic(posArea, *aFallbackTactic, aPositionedWM); - } nscoord right = ltrEdges[3]; if (posArea.first == StylePositionAreaKeyword::Left) { diff --git a/layout/base/AnchorPositioningUtils.h b/layout/base/AnchorPositioningUtils.h @@ -114,8 +114,7 @@ struct AnchorPositioningUtils { static nsRect AdjustAbsoluteContainingBlockRectForPositionArea( const nsRect& aAnchorRect, const nsRect& aCBRect, WritingMode aPositionedWM, WritingMode aCBWM, - const StylePositionArea& aPosArea, - const StylePositionTryFallbacksTryTactic* aFallbackTactic); + const StylePositionArea& aPosArea); /** * Gets the used anchor name for an anchor positioned frame. diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp @@ -962,15 +962,9 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( } auto positionArea = aKidFrame->StylePosition()->mPositionArea; - const StylePositionTryFallbacksTryTactic* tactic = nullptr; - if (currentFallback) { - if (currentFallback->IsIdentAndOrTactic()) { - const auto& item = currentFallback->AsIdentAndOrTactic(); - tactic = &item.try_tactic; - } else { - MOZ_ASSERT(currentFallback->IsPositionArea()); - positionArea = currentFallback->AsPositionArea(); - } + if (currentFallback && currentFallback->IsPositionArea()) { + MOZ_ASSERT(currentFallback->IsPositionArea()); + positionArea = currentFallback->AsPositionArea(); } if (!positionArea.IsNone()) { @@ -981,7 +975,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame( AdjustAbsoluteContainingBlockRectForPositionArea( *defaultAnchorInfo.mRect, aOriginalContainingBlockRect, aKidFrame->GetWritingMode(), - aDelegatingFrame->GetWritingMode(), positionArea, tactic); + aDelegatingFrame->GetWritingMode(), positionArea); } } diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp @@ -1048,8 +1048,6 @@ nsStylePosition::nsStylePosition() mMinHeight(StyleSize::Auto()), mMaxHeight(StyleMaxSize::None()), mPositionAnchor(StylePositionAnchor::Auto()), - mPositionArea(StylePositionArea{StylePositionAreaKeyword::None, - StylePositionAreaKeyword::None}), mPositionVisibility(StylePositionVisibility::ALWAYS), mPositionTryFallbacks(StylePositionTryFallbacks()), mPositionTryOrder(StylePositionTryOrder::Normal), diff --git a/servo/components/style/style_adjuster.rs b/servo/components/style/style_adjuster.rs @@ -961,9 +961,22 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { self.flip_start(); }, } + self.apply_position_area_tactic(*tactic); } } + fn apply_position_area_tactic(&mut self, tactic: PositionTryFallbacksTryTacticKeyword) { + let pos = self.style.get_position(); + let old = pos.clone_position_area(); + let wm = self.style.writing_mode; + let new = old.with_tactic(wm, tactic); + if new == old { + return; + } + let pos = self.style.mutate_position(); + pos.set_position_area(new); + } + fn swap_insets(&mut self, a_side: PhysicalSide, b_side: PhysicalSide) { debug_assert_ne!(a_side, b_side); let pos = self.style.get_position(); diff --git a/servo/components/style/values/computed/position.rs b/servo/components/style/values/computed/position.rs @@ -20,8 +20,8 @@ use crate::values::generics::position::{ use crate::values::generics::position::{AspectRatio as GenericAspectRatio, GenericInset}; pub use crate::values::specified::position::{ AnchorName, AnchorScope, DashedIdentAndOrTryTactic, PositionAnchor, PositionArea, - PositionAreaKeyword, PositionAreaType, PositionTryFallbacks, PositionTryOrder, - PositionVisibility, + PositionAreaAxis, PositionAreaKeyword, PositionAreaType, PositionTryFallbacks, + PositionTryOrder, PositionVisibility, }; pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow}; use crate::Zero; @@ -143,61 +143,24 @@ impl GenericPositionComponent for LengthPercentage { #[inline] fn block_or_inline_to_inferred(keyword: PositionAreaKeyword) -> PositionAreaKeyword { - match keyword { - PositionAreaKeyword::BlockStart | PositionAreaKeyword::InlineStart => { - PositionAreaKeyword::Start - }, - PositionAreaKeyword::BlockEnd | PositionAreaKeyword::InlineEnd => PositionAreaKeyword::End, - PositionAreaKeyword::SpanBlockStart | PositionAreaKeyword::SpanInlineStart => { - PositionAreaKeyword::SpanStart - }, - PositionAreaKeyword::SpanBlockEnd | PositionAreaKeyword::SpanInlineEnd => { - PositionAreaKeyword::SpanEnd - }, - PositionAreaKeyword::SelfBlockStart | PositionAreaKeyword::SelfInlineStart => { - PositionAreaKeyword::SelfStart - }, - PositionAreaKeyword::SelfBlockEnd | PositionAreaKeyword::SelfInlineEnd => { - PositionAreaKeyword::SelfEnd - }, - PositionAreaKeyword::SpanSelfBlockStart | PositionAreaKeyword::SpanSelfInlineStart => { - PositionAreaKeyword::SpanSelfStart - }, - PositionAreaKeyword::SpanSelfBlockEnd | PositionAreaKeyword::SpanSelfInlineEnd => { - PositionAreaKeyword::SpanSelfEnd - }, - other => other, + if matches!( + keyword.axis(), + PositionAreaAxis::Block | PositionAreaAxis::Inline + ) { + keyword.with_axis(PositionAreaAxis::Inferred) + } else { + keyword } } #[inline] fn inferred_to_block(keyword: PositionAreaKeyword) -> PositionAreaKeyword { - match keyword { - PositionAreaKeyword::Start => PositionAreaKeyword::BlockStart, - PositionAreaKeyword::End => PositionAreaKeyword::BlockEnd, - PositionAreaKeyword::SpanStart => PositionAreaKeyword::SpanBlockStart, - PositionAreaKeyword::SpanEnd => PositionAreaKeyword::SpanBlockEnd, - PositionAreaKeyword::SelfStart => PositionAreaKeyword::SelfBlockStart, - PositionAreaKeyword::SelfEnd => PositionAreaKeyword::SelfBlockEnd, - PositionAreaKeyword::SpanSelfStart => PositionAreaKeyword::SpanSelfBlockStart, - PositionAreaKeyword::SpanSelfEnd => PositionAreaKeyword::SpanSelfBlockEnd, - other => other, - } + keyword.with_inferred_axis(PositionAreaAxis::Block) } #[inline] fn inferred_to_inline(keyword: PositionAreaKeyword) -> PositionAreaKeyword { - match keyword { - PositionAreaKeyword::Start => PositionAreaKeyword::InlineStart, - PositionAreaKeyword::End => PositionAreaKeyword::InlineEnd, - PositionAreaKeyword::SpanStart => PositionAreaKeyword::SpanInlineStart, - PositionAreaKeyword::SpanEnd => PositionAreaKeyword::SpanInlineEnd, - PositionAreaKeyword::SelfStart => PositionAreaKeyword::SelfInlineStart, - PositionAreaKeyword::SelfEnd => PositionAreaKeyword::SelfInlineEnd, - PositionAreaKeyword::SpanSelfStart => PositionAreaKeyword::SpanSelfInlineStart, - PositionAreaKeyword::SpanSelfEnd => PositionAreaKeyword::SpanSelfInlineEnd, - other => other, - } + keyword.with_inferred_axis(PositionAreaAxis::Inline) } // This exists because the spec currently says that further simplifications @@ -207,13 +170,11 @@ fn inferred_to_inline(keyword: PositionAreaKeyword) -> PositionAreaKeyword { // PositionArea::parse_internal(). // See also https://github.com/w3c/csswg-drafts/issues/12759 impl ToComputedValue for PositionArea { - type ComputedValue = PositionArea; + type ComputedValue = Self; - fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { + fn to_computed_value(&self, _context: &Context) -> Self { let mut computed = self.clone(); - let pair_type = self.get_type(); - if pair_type == PositionAreaType::Logical || pair_type == PositionAreaType::SelfLogical { if computed.second != PositionAreaKeyword::None { computed.first = block_or_inline_to_inferred(computed.first); @@ -237,11 +198,10 @@ impl ToComputedValue for PositionArea { if computed.first == computed.second { computed.second = PositionAreaKeyword::None; } - computed } - fn from_computed_value(computed: &Self::ComputedValue) -> Self { + fn from_computed_value(computed: &Self) -> Self { computed.clone() } } diff --git a/servo/components/style/values/specified/position.rs b/servo/components/style/values/specified/position.rs @@ -7,6 +7,7 @@ //! //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position +use crate::logical_geometry::{LogicalAxis, LogicalSide, PhysicalSide, WritingMode}; use crate::parser::{Parse, ParserContext}; use crate::selector_map::PrecomputedHashMap; use crate::str::HTML_SPACE_CHARACTERS; @@ -24,6 +25,7 @@ use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegati use crate::values::DashedIdent; use crate::{Atom, Zero}; use cssparser::Parser; +use num_traits::FromPrimitive; use selectors::parser::SelectorParseErrorKind; use servo_arc::Arc; use smallvec::{smallvec, SmallVec}; @@ -811,9 +813,10 @@ impl PositionVisibility { } } -#[derive(PartialEq)] /// A value indicating which high level group in the formal grammar a /// PositionAreaKeyword or PositionArea belongs to. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PositionAreaType { /// X || Y Physical, @@ -831,6 +834,121 @@ pub enum PositionAreaType { None, } +/// A three-bit value that represents the axis in which position-area operates on. +/// Represented as 4 bits: axis type (physical or logical), direction type (physical or logical), +/// axis value. +/// +/// There are two special values on top (Inferred and None) that represent ambiguous or axis-less +/// keywords, respectively. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)] +#[allow(missing_docs)] +pub enum PositionAreaAxis { + Horizontal = 0b000, + Vertical = 0b001, + + X = 0b010, + Y = 0b011, + + Block = 0b110, + Inline = 0b111, + + Inferred = 0b100, + None = 0b101, +} + +impl PositionAreaAxis { + /// Whether this axis is physical or not. + pub fn is_physical(self) -> bool { + (self as u8 & 0b100) == 0 + } + + /// Whether the direction is logical or not. + fn is_flow_relative_direction(self) -> bool { + self == Self::Inferred || (self as u8 & 0b10) != 0 + } + + /// Whether this axis goes first in the canonical syntax. + fn is_canonically_first(self) -> bool { + self != Self::Inferred && (self as u8) & 1 == 0 + } + + #[allow(unused)] + fn flip(self) -> Self { + if matches!(self, Self::Inferred | Self::None) { + return self; + } + Self::from_u8(self as u8 ^ 1u8).unwrap() + } + + fn to_logical(self, wm: WritingMode, inferred: LogicalAxis) -> Option<LogicalAxis> { + Some(match self { + PositionAreaAxis::Horizontal | PositionAreaAxis::X => { + if wm.is_vertical() { + LogicalAxis::Block + } else { + LogicalAxis::Inline + } + }, + PositionAreaAxis::Vertical | PositionAreaAxis::Y => { + if wm.is_vertical() { + LogicalAxis::Inline + } else { + LogicalAxis::Block + } + }, + PositionAreaAxis::Block => LogicalAxis::Block, + PositionAreaAxis::Inline => LogicalAxis::Inline, + PositionAreaAxis::Inferred => inferred, + PositionAreaAxis::None => return None, + }) + } +} + +/// Specifies which tracks(s) on the axis that the position-area span occupies. +/// Represented as 3 bits: start, center, end track. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, FromPrimitive)] +pub enum PositionAreaTrack { + /// First track + Start = 0b001, + /// First and center. + SpanStart = 0b011, + /// Last track. + End = 0b100, + /// Last and center. + SpanEnd = 0b110, + /// Center track. + Center = 0b010, + /// All tracks + SpanAll = 0b111, +} + +impl PositionAreaTrack { + fn flip(self) -> Self { + match self { + Self::Start => Self::End, + Self::SpanStart => Self::SpanEnd, + Self::End => Self::Start, + Self::SpanEnd => Self::SpanStart, + Self::Center | Self::SpanAll => self, + } + } + + fn start(self) -> bool { + self as u8 & 1 != 0 + } +} + +/// The shift to the left needed to set the axis. +pub const AXIS_SHIFT: usize = 3; +/// The mask used to extract the axis. +pub const AXIS_MASK: u8 = 0b111u8 << AXIS_SHIFT; +/// The mask used to extract the track. +pub const TRACK_MASK: u8 = 0b111u8; +/// The self-wm bit. +pub const SELF_WM: u8 = 1u8 << 6; + #[derive( Clone, Copy, @@ -845,232 +963,226 @@ pub enum PositionAreaType { ToCss, ToResolvedValue, ToShmem, + FromPrimitive, )] #[allow(missing_docs)] #[repr(u8)] /// Possible values for the `position-area` property's keywords. +/// Represented by [0z xxx yyy], where z means "self wm resolution", xxxx is the axis (as in +/// PositionAreaAxis) and yyy is the PositionAreaTrack /// https://drafts.csswg.org/css-anchor-position-1/#propdef-position-area pub enum PositionAreaKeyword { #[default] - None = 0, + None = (PositionAreaAxis::None as u8) << AXIS_SHIFT, // Common (shared) keywords: - Center = 1, - SpanAll = 2, + Center = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::Center as u8, + SpanAll = ((PositionAreaAxis::None as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanAll as u8, + + // Inferred-axis edges: + Start = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + End = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, + SpanStart = + ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanEnd = ((PositionAreaAxis::Inferred as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, // Purely physical edges: - Left = 3, - Right = 4, - Top = 5, - Bottom = 6, + Left = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + Right = ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, + Top = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + Bottom = ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, // Flow-relative physical-axis edges: - XStart = 7, - XEnd = 8, - YStart = 9, - YEnd = 10, + XStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + XEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, + YStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + YEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, // Logical edges: - BlockStart = 11, - BlockEnd = 12, - InlineStart = 13, - InlineEnd = 14, - - // Inferred-axis edges: - Start = 15, - End = 16, - - // Flags that modify the above edge values. We require these to be separate - // bits in the underlying u8 value, so that they can be individually tested - // and masked independently of the rest of the value. - // These are not exposed to CSS, they only function as part of the composite - // values defined below. - #[css(skip)] - Span = 1u8 << 5, - #[css(skip)] - SelfWM = 1u8 << 6, // use target's writing-mode to resolve logical edges + BlockStart = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + BlockEnd = ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, + InlineStart = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::Start as u8, + InlineEnd = ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::End as u8, // Composite values with Span: - SpanLeft = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::Left as u8, - SpanRight = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::Right as u8, - SpanTop = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::Top as u8, - SpanBottom = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::Bottom as u8, - - SpanXStart = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::XStart as u8, - SpanXEnd = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::XEnd as u8, - SpanYStart = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::YStart as u8, - SpanYEnd = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::YEnd as u8, + SpanLeft = + ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanRight = + ((PositionAreaAxis::Horizontal as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, + SpanTop = + ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanBottom = + ((PositionAreaAxis::Vertical as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, - SpanBlockStart = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::BlockStart as u8, - SpanBlockEnd = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::BlockEnd as u8, - SpanInlineStart = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::InlineStart as u8, - SpanInlineEnd = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::InlineEnd as u8, + // Flow-relative physical-axis edges: + SpanXStart = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanXEnd = ((PositionAreaAxis::X as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, + SpanYStart = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanYEnd = ((PositionAreaAxis::Y as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, - SpanStart = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::Start as u8, - SpanEnd = PositionAreaKeyword::Span as u8 | PositionAreaKeyword::End as u8, + // Logical edges: + SpanBlockStart = + ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanBlockEnd = + ((PositionAreaAxis::Block as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, + SpanInlineStart = + ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanStart as u8, + SpanInlineEnd = + ((PositionAreaAxis::Inline as u8) << AXIS_SHIFT) | PositionAreaTrack::SpanEnd as u8, // Values using the Self element's writing-mode: - SelfXStart = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::XStart as u8, - SelfXEnd = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::XEnd as u8, - SelfYStart = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::YStart as u8, - SelfYEnd = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::YEnd as u8, - - SelfBlockStart = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::BlockStart as u8, - SelfBlockEnd = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::BlockEnd as u8, - SelfInlineStart = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::InlineStart as u8, - SelfInlineEnd = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::InlineEnd as u8, - - SelfStart = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::Start as u8, - SelfEnd = PositionAreaKeyword::SelfWM as u8 | PositionAreaKeyword::End as u8, - - // Values with Span and SelfWM: - SpanSelfXStart = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::XStart as u8, - SpanSelfXEnd = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::XEnd as u8, - SpanSelfYStart = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::YStart as u8, - SpanSelfYEnd = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::YEnd as u8, - - SpanSelfBlockStart = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::BlockStart as u8, - SpanSelfBlockEnd = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::BlockEnd as u8, - SpanSelfInlineStart = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::InlineStart as u8, - SpanSelfInlineEnd = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::InlineEnd as u8, - - SpanSelfStart = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::Start as u8, - SpanSelfEnd = PositionAreaKeyword::Span as u8 - | PositionAreaKeyword::SelfWM as u8 - | PositionAreaKeyword::End as u8, + SelfStart = SELF_WM | (Self::Start as u8), + SelfEnd = SELF_WM | (Self::End as u8), + SpanSelfStart = SELF_WM | (Self::SpanStart as u8), + SpanSelfEnd = SELF_WM | (Self::SpanEnd as u8), + + SelfXStart = SELF_WM | (Self::XStart as u8), + SelfXEnd = SELF_WM | (Self::XEnd as u8), + SelfYStart = SELF_WM | (Self::YStart as u8), + SelfYEnd = SELF_WM | (Self::YEnd as u8), + SelfBlockStart = SELF_WM | (Self::BlockStart as u8), + SelfBlockEnd = SELF_WM | (Self::BlockEnd as u8), + SelfInlineStart = SELF_WM | (Self::InlineStart as u8), + SelfInlineEnd = SELF_WM | (Self::InlineEnd as u8), + + SpanSelfXStart = SELF_WM | (Self::SpanXStart as u8), + SpanSelfXEnd = SELF_WM | (Self::SpanXEnd as u8), + SpanSelfYStart = SELF_WM | (Self::SpanYStart as u8), + SpanSelfYEnd = SELF_WM | (Self::SpanYEnd as u8), + SpanSelfBlockStart = SELF_WM | (Self::SpanBlockStart as u8), + SpanSelfBlockEnd = SELF_WM | (Self::SpanBlockEnd as u8), + SpanSelfInlineStart = SELF_WM | (Self::SpanInlineStart as u8), + SpanSelfInlineEnd = SELF_WM | (Self::SpanInlineEnd as u8), } -#[allow(missing_docs)] impl PositionAreaKeyword { + /// Returns the 'none' value. #[inline] pub fn none() -> Self { Self::None } + /// Returns true if this is the none keyword. pub fn is_none(&self) -> bool { *self == Self::None } - // TODO: Investigate better perf: https://bugzilla.mozilla.org/show_bug.cgi?id=1987803 - fn get_type(&self) -> PositionAreaType { - use PositionAreaKeyword::*; - match self { - // X-axis - Left | Right | SpanLeft | SpanRight | XStart | XEnd | SpanXStart | SpanXEnd - | SelfXStart | SelfXEnd | SpanSelfXStart | SpanSelfXEnd => PositionAreaType::Physical, - - // Y-axis - Top | Bottom | SpanTop | SpanBottom | YStart | YEnd | SpanYStart | SpanYEnd - | SelfYStart | SelfYEnd | SpanSelfYStart | SpanSelfYEnd => PositionAreaType::Physical, - - // Block - BlockStart | BlockEnd | SpanBlockStart | SpanBlockEnd => PositionAreaType::Logical, - - // Inline - InlineStart | InlineEnd | SpanInlineStart | SpanInlineEnd => PositionAreaType::Logical, - - // Self block - SelfBlockStart | SelfBlockEnd | SpanSelfBlockStart | SpanSelfBlockEnd => { - PositionAreaType::SelfLogical - }, - - // Self inline - SelfInlineStart | SelfInlineEnd | SpanSelfInlineStart | SpanSelfInlineEnd => { - PositionAreaType::SelfLogical - }, - - // Inferred - Start | End | SpanStart | SpanEnd => PositionAreaType::Inferred, - - // Self inferred - SelfStart | SelfEnd | SpanSelfStart | SpanSelfEnd => PositionAreaType::SelfInferred, + /// Whether we're one of the self-wm keywords. + pub fn self_wm(self) -> bool { + (self as u8 & SELF_WM) != 0 + } - // Common - Center | SpanAll => PositionAreaType::Common, + /// Get this keyword's axis. + pub fn axis(self) -> PositionAreaAxis { + PositionAreaAxis::from_u8((self as u8 >> AXIS_SHIFT) & 0b111).unwrap() + } - None => PositionAreaType::None, + /// Returns this keyword but with the axis swapped by the argument. + pub fn with_axis(self, axis: PositionAreaAxis) -> Self { + Self::from_u8(((self as u8) & !AXIS_MASK) | ((axis as u8) << AXIS_SHIFT)).unwrap() + } - // Flag bits that cannot occur by themselves - SelfWM | Span => panic!("invalid PositionAreaKeyword value"), + /// If this keyword uses an inferred axis, replaces it. + pub fn with_inferred_axis(self, axis: PositionAreaAxis) -> Self { + if self.axis() == PositionAreaAxis::Inferred { + self.with_axis(axis) + } else { + self } } - #[inline] - pub fn canonical_order_is_first(&self) -> bool { - use PositionAreaKeyword::*; - matches!( - self, - Left | Right - | SpanLeft - | SpanRight - | XStart - | XEnd - | SpanXStart - | SpanXEnd - | SelfXStart - | SelfXEnd - | SpanSelfXStart - | SpanSelfXEnd - | BlockStart - | BlockEnd - | SpanBlockStart - | SpanBlockEnd - | SelfBlockStart - | SelfBlockEnd - | SpanSelfBlockStart - | SpanSelfBlockEnd - ) + /// Get this keyword's track, or None if we're the `None` keyword. + pub fn track(self) -> Option<PositionAreaTrack> { + let result = PositionAreaTrack::from_u8(self as u8 & TRACK_MASK); + debug_assert_eq!( + result.is_none(), + self.is_none(), + "Only the none keyword has no track" + ); + result } - #[inline] - pub fn canonical_order_is_second(&self) -> bool { - use PositionAreaKeyword::*; - matches!( - self, - Top | Bottom - | SpanTop - | SpanBottom - | YStart - | YEnd - | SpanYStart - | SpanYEnd - | SelfYStart - | SelfYEnd - | SpanSelfYStart - | SpanSelfYEnd - | InlineStart - | InlineEnd - | SpanInlineStart - | SpanInlineEnd - | SelfInlineStart - | SelfInlineEnd - | SpanSelfInlineStart - | SpanSelfInlineEnd - ) + fn group_type(self) -> PositionAreaType { + let axis = self.axis(); + if axis == PositionAreaAxis::None { + if self.is_none() { + return PositionAreaType::None; + } + return PositionAreaType::Common; + } + if axis == PositionAreaAxis::Inferred { + return if self.self_wm() { + PositionAreaType::SelfInferred + } else { + PositionAreaType::Inferred + }; + } + if axis.is_physical() { + return PositionAreaType::Physical; + } + if self.self_wm() { + PositionAreaType::SelfLogical + } else { + PositionAreaType::Logical + } } - #[inline] - pub fn has_same_canonical_order(&self, other: PositionAreaKeyword) -> bool { - self.canonical_order_is_first() == other.canonical_order_is_first() - || self.canonical_order_is_second() == other.canonical_order_is_second() + fn to_physical( + self, + cb_wm: WritingMode, + self_wm: WritingMode, + inferred_axis: LogicalAxis, + ) -> Self { + let wm = if self.self_wm() { self_wm } else { cb_wm }; + let axis = self.axis(); + if !axis.is_flow_relative_direction() { + return self; + } + let Some(logical_axis) = axis.to_logical(wm, inferred_axis) else { + return self; + }; + let Some(track) = self.track() else { + debug_assert!(false, "How did we end up with no track here? {self:?}"); + return self; + }; + let start = track.start(); + let logical_side = match logical_axis { + LogicalAxis::Block => { + if start { + LogicalSide::BlockStart + } else { + LogicalSide::BlockEnd + } + }, + LogicalAxis::Inline => { + if start { + LogicalSide::InlineStart + } else { + LogicalSide::InlineEnd + } + }, + }; + let physical_side = logical_side.to_physical(wm); + let physical_start = matches!(physical_side, PhysicalSide::Top | PhysicalSide::Left); + let new_track = if physical_start != start { + track.flip() + } else { + track + }; + let new_axis = if matches!(physical_side, PhysicalSide::Top | PhysicalSide::Bottom) { + PositionAreaAxis::Vertical + } else { + PositionAreaAxis::Horizontal + }; + Self::from_u8(new_track as u8 | ((new_axis as u8) << AXIS_SHIFT)).unwrap() + } + + fn flip_track(self) -> Self { + let Some(old_track) = self.track() else { + return self; + }; + let new_track = old_track.flip(); + Self::from_u8((self as u8 & !TRACK_MASK) | new_track as u8).unwrap() } } @@ -1097,8 +1209,8 @@ pub struct PositionArea { pub second: PositionAreaKeyword, } -#[allow(missing_docs)] impl PositionArea { + /// Returns the none value. #[inline] pub fn none() -> Self { Self { @@ -1107,11 +1219,13 @@ impl PositionArea { } } + /// Returns whether we're the none value. #[inline] pub fn is_none(&self) -> bool { self.first.is_none() } + /// Parses a <position-area> without allowing `none`. pub fn parse_except_none<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -1119,43 +1233,30 @@ impl PositionArea { Self::parse_internal(context, input, /*allow_none*/ false) } - #[inline] + /// Get the high-level grammar group of this. pub fn get_type(&self) -> PositionAreaType { - match (self.first.get_type(), self.second.get_type()) { - (PositionAreaType::Physical, PositionAreaType::Physical) - if !self.first.has_same_canonical_order(self.second) => - { - PositionAreaType::Physical - }, - (PositionAreaType::Logical, PositionAreaType::Logical) - if !self.first.has_same_canonical_order(self.second) => - { - PositionAreaType::Logical - }, - (PositionAreaType::SelfLogical, PositionAreaType::SelfLogical) - if !self.first.has_same_canonical_order(self.second) => - { - PositionAreaType::SelfLogical - }, - (PositionAreaType::Inferred, PositionAreaType::Inferred) => PositionAreaType::Inferred, - (PositionAreaType::SelfInferred, PositionAreaType::SelfInferred) => { - PositionAreaType::SelfInferred - }, - (PositionAreaType::Common, PositionAreaType::Common) => PositionAreaType::Common, - - // Allow mixing Common with any other types except `none` - (PositionAreaType::Common, other) | (other, PositionAreaType::Common) - if other != PositionAreaType::None => - { - other - }, - - _ => PositionAreaType::None, + let first = self.first.group_type(); + let second = self.second.group_type(); + if matches!(second, PositionAreaType::None | PositionAreaType::Common) { + return first; + } + if first == PositionAreaType::Common { + return second; + } + if first != second { + return PositionAreaType::None; } + let first_axis = self.first.axis(); + if first_axis != PositionAreaAxis::Inferred + && first_axis.is_canonically_first() == self.second.axis().is_canonically_first() + { + return PositionAreaType::None; + } + first } fn parse_internal<'i, 't>( - _context: &ParserContext, + _: &ParserContext, input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<Self, ParseError<'i>> { @@ -1185,16 +1286,12 @@ impl PositionArea { } let pair_type = Self { first, second }.get_type(); - if pair_type == PositionAreaType::None { - // `none` is only allowed as a standalone value, and we've handled - // that already. + // Mismatched types or what not. return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - - // For types that have a canonical order, put them in order and remove - // 'span-all' (the default behavior; unnecessary for keyword pairs with - // a known order). + // For types that have a canonical order, remove 'span-all' (the default behavior; + // unnecessary for keyword pairs with a known order). if matches!( pair_type, PositionAreaType::Physical | PositionAreaType::Logical | PositionAreaType::SelfLogical @@ -1206,15 +1303,101 @@ impl PositionArea { } else if first == PositionAreaKeyword::SpanAll { first = second; second = PositionAreaKeyword::None; - } else if first.canonical_order_is_second() || second.canonical_order_is_first() { - std::mem::swap(&mut first, &mut second); } } if first == second { second = PositionAreaKeyword::None; } + let mut result = Self { first, second }; + result.canonicalize_order(); + Ok(result) + } + + fn canonicalize_order(&mut self) { + let first_axis = self.first.axis(); + if first_axis.is_canonically_first() || self.second.is_none() { + return; + } + let second_axis = self.second.axis(); + if first_axis == second_axis { + // Inferred or axis-less keywords. + return; + } + if second_axis.is_canonically_first() + || (second_axis == PositionAreaAxis::None && first_axis != PositionAreaAxis::Inferred) + { + std::mem::swap(&mut self.first, &mut self.second); + } + } + + fn make_missing_second_explicit(&mut self) { + if !self.second.is_none() { + return; + } + let axis = self.first.axis(); + if matches!(axis, PositionAreaAxis::Inferred | PositionAreaAxis::None) { + self.second = self.first; + return; + } + self.second = PositionAreaKeyword::SpanAll; + if !axis.is_canonically_first() { + std::mem::swap(&mut self.first, &mut self.second); + } + } + + /// Turns this <position-area> value into a physical <position-area>. + pub fn to_physical(mut self, cb_wm: WritingMode, self_wm: WritingMode) -> Self { + self.make_missing_second_explicit(); + self.first = self.first.to_physical(cb_wm, self_wm, LogicalAxis::Block); + self.second = self.second.to_physical(cb_wm, self_wm, LogicalAxis::Inline); + self.canonicalize_order(); + self + } + + fn flip_logical_axis(&mut self, wm: WritingMode, axis: LogicalAxis) { + if self.first.axis().to_logical(wm, LogicalAxis::Block) == Some(axis) { + self.first = self.first.flip_track(); + } else { + self.second = self.second.flip_track(); + } + } + + fn flip_start(&mut self) { + self.first = self.first.with_axis(self.first.axis().flip()); + self.second = self.second.with_axis(self.second.axis().flip()); + } - Ok(Self { first, second }) + /// Applies a try tactic to this `<position-area>` value. + pub fn with_tactic( + mut self, + wm: WritingMode, + tactic: PositionTryFallbacksTryTacticKeyword, + ) -> Self { + self.make_missing_second_explicit(); + let axis_to_flip = match tactic { + PositionTryFallbacksTryTacticKeyword::FlipStart => { + self.flip_start(); + return self; + }, + PositionTryFallbacksTryTacticKeyword::FlipBlock => LogicalAxis::Block, + PositionTryFallbacksTryTacticKeyword::FlipInline => LogicalAxis::Inline, + PositionTryFallbacksTryTacticKeyword::FlipX => { + if wm.is_horizontal() { + LogicalAxis::Inline + } else { + LogicalAxis::Block + } + }, + PositionTryFallbacksTryTacticKeyword::FlipY => { + if wm.is_vertical() { + LogicalAxis::Inline + } else { + LogicalAxis::Block + } + }, + }; + self.flip_logical_axis(wm, axis_to_flip); + self } } @@ -1223,7 +1406,7 @@ impl Parse for PositionArea { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, /*allow_none*/ true) + Self::parse_internal(context, input, /* allow_none = */ true) } } diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml @@ -126,6 +126,8 @@ include = [ "Platform", "PositionAnchor", "PositionArea", + "PositionAreaAxis", + "PositionAreaTrack", "PositionAreaKeyword", "PositionTryFallbacks", "PositionTryOrder", @@ -1212,4 +1214,8 @@ renaming_overrides_prefixing = true "PositionArea" = """ inline bool IsNone() const; + constexpr StylePositionArea() + : first(StylePositionAreaKeyword::None), + second(StylePositionAreaKeyword::None) + {} """ diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs @@ -106,7 +106,7 @@ use style::invalidation::element::relative_selector::{ }; use style::invalidation::element::restyle_hints::RestyleHint; use style::invalidation::stylesheets::RuleChangeKind; -use style::logical_geometry::{PhysicalAxis, PhysicalSide}; +use style::logical_geometry::{PhysicalAxis, PhysicalSide, WritingMode}; use style::media_queries::MediaList; use style::parser::{Parse, ParserContext}; use style::properties::declaration_block::PropertyTypedValue; @@ -162,7 +162,7 @@ use style::values::computed::font::{ use style::values::computed::length_percentage::{ AllowAnchorPosResolutionInCalcPercentage, Unpacked, }; -use style::values::computed::position::AnchorFunction; +use style::values::computed::position::{AnchorFunction, PositionArea}; use style::values::computed::{self, ContentVisibility, Context, ToComputedValue}; use style::values::distance::{ComputeSquaredDistance, SquaredDistance}; use style::values::generics::color::ColorMixFlags; @@ -10749,3 +10749,12 @@ pub extern "C" fn Servo_ResolveAnchorSizeFunctionForMaxSize( AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(prop_axis), ); } + +#[no_mangle] +pub extern "C" fn Servo_PhysicalizePositionArea( + area: &mut PositionArea, + cb_wm: &WritingMode, + self_wm: &WritingMode, +) { + *area = area.to_physical(*cb_wm, *self_wm); +}