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:
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);
+}