From 745b6de9a7b8024084dc4412e0b7104fbfa9760c Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Thu, 30 May 2024 19:39:07 +0000 Subject: [PATCH] Bug 1898887. Implement parsing of CSS 'inset-area' property. r=emilio,firefox-style-system-reviewers Differential Revision: https://phabricator.services.mozilla.com/D211955 --- .../server/actors/animation-type-longhand.js | 1 + dom/base/use_counter_metrics.yaml | 45 ++- layout/style/ServoBindings.toml | 1 + layout/style/nsStyleStruct.cpp | 6 +- layout/style/nsStyleStruct.h | 1 + layout/style/test/property_database.js | 79 ++++ servo/components/style/properties/data.py | 2 + .../properties/longhands/position.mako.rs | 12 + servo/components/style/values/computed/mod.rs | 1 + .../style/values/computed/position.rs | 2 +- .../components/style/values/specified/mod.rs | 1 + .../style/values/specified/position.rs | 339 ++++++++++++++++++ servo/ports/geckolib/cbindgen.toml | 2 + 13 files changed, 484 insertions(+), 8 deletions(-) diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 8366131edde5..11fbfb3c11c6 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -98,6 +98,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "ime-mode", "-moz-inert", "initial-letter", + "inset-area", "isolation", "justify-content", "justify-items", diff --git a/dom/base/use_counter_metrics.yaml b/dom/base/use_counter_metrics.yaml index dabf291be240..038ce07bac3f 100644 --- a/dom/base/use_counter_metrics.yaml +++ b/dom/base/use_counter_metrics.yaml @@ -107,7 +107,7 @@ use.counter: send_in_pings: - use-counters -# Total of 2329 use counter metrics (excludes denominators). +# Total of 2343 use counter metrics (excludes denominators). # Total of 364 'page' use counters. use.counter.page: svgsvgelement_getelementbyid: @@ -14177,7 +14177,7 @@ use.counter.worker.service: send_in_pings: - use-counters -# Total of 50 'deprecated operations (page)' use counters. +# Total of 56 'deprecated operations (page)' use counters. use.counter.deprecated_ops.page: domsubtree_modified: type: counter @@ -15131,7 +15131,7 @@ use.counter.deprecated_ops.page: send_in_pings: - use-counters -# Total of 50 'deprecated operations (document)' use counters. +# Total of 56 'deprecated operations (document)' use counters. use.counter.deprecated_ops.doc: domsubtree_modified: type: counter @@ -16085,7 +16085,7 @@ use.counter.deprecated_ops.doc: send_in_pings: - use-counters -# Total of 701 'CSS (page)' use counters. +# Total of 702 'CSS (page)' use counters. use.counter.css.page: css_align_content: type: counter @@ -16903,6 +16903,23 @@ use.counter.css.page: send_in_pings: - use-counters + css_inset_area: + type: counter + description: > + Whether a page used the CSS property inset-area. + Compare against `use.counter.top_level_content_documents_destroyed` + to calculate the rate. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + notification_emails: + - dom-core@mozilla.com + - emilio@mozilla.com + expires: never + send_in_pings: + - use-counters + css_isolation: type: counter description: > @@ -28004,7 +28021,7 @@ use.counter.css.page: send_in_pings: - use-counters -# Total of 701 'CSS (document)' use counters. +# Total of 702 'CSS (document)' use counters. use.counter.css.doc: css_align_content: type: counter @@ -28822,6 +28839,23 @@ use.counter.css.doc: send_in_pings: - use-counters + css_inset_area: + type: counter + description: > + Whether a document used the CSS property inset-area. + Compare against `use.counter.content_documents_destroyed` + to calculate the rate. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 + notification_emails: + - dom-core@mozilla.com + - emilio@mozilla.com + expires: never + send_in_pings: + - use-counters + css_isolation: type: counter description: > @@ -39922,4 +39956,3 @@ use.counter.css.doc: expires: never send_in_pings: - use-counters - diff --git a/layout/style/ServoBindings.toml b/layout/style/ServoBindings.toml index e6f5b102509b..7210267636d0 100644 --- a/layout/style/ServoBindings.toml +++ b/layout/style/ServoBindings.toml @@ -627,6 +627,7 @@ cbindgen-types = [ { gecko = "StyleAnchorScope", servo = "crate::values::computed::position::AnchorScope" }, { gecko = "StylePositionAnchor", servo = "crate::values::computed::position::PositionAnchor" }, { gecko = "StylePositionVisibility", servo = "crate::values::computed::position::PositionVisibility" }, + { gecko = "StyleInsetArea", servo = "crate::values::computed::position::InsetArea" }, { gecko = "StyleFontVariantEastAsian", servo = "crate::values::computed::font::FontVariantEastAsian" }, { gecko = "StyleFontVariantLigatures", servo = "crate::values::computed::font::FontVariantLigatures" }, { gecko = "StyleFontVariantNumeric", servo = "crate::values::computed::font::FontVariantNumeric" }, diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 09788cb7f500..a70ec14d7eac 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1051,6 +1051,8 @@ nsStylePosition::nsStylePosition() mMaxHeight(StyleMaxSize::None()), mPositionAnchor(StylePositionAnchor::Auto()), mPositionVisibility(StylePositionVisibility::ALWAYS), + mInsetArea(StyleInsetArea{StyleInsetAreaKeyword::None, + StyleInsetAreaKeyword::None}), mFlexBasis(StyleFlexBasis::Size(StyleSize::Auto())), mAspectRatio(StyleAspectRatio::Auto()), mGridAutoFlow(StyleGridAutoFlow::ROW), @@ -1101,6 +1103,7 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) mMaxHeight(aSource.mMaxHeight), mPositionAnchor(aSource.mPositionAnchor), mPositionVisibility(aSource.mPositionVisibility), + mInsetArea(aSource.mInsetArea), mFlexBasis(aSource.mFlexBasis), mGridAutoColumns(aSource.mGridAutoColumns), mGridAutoRows(aSource.mGridAutoRows), @@ -1285,7 +1288,8 @@ nsChangeHint nsStylePosition::CalcDifference( hint |= nsChangeHint_NeutralChange; } - if (mPositionVisibility != aNewData.mPositionVisibility) { + if (mPositionVisibility != aNewData.mPositionVisibility || + mInsetArea != aNewData.mInsetArea) { hint |= nsChangeHint_NeutralChange; } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index a65b1708203f..2a627fa17bcb 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -747,6 +747,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition { // element. mozilla::StylePositionAnchor mPositionAnchor; mozilla::StylePositionVisibility mPositionVisibility; + mozilla::StyleInsetArea mInsetArea; mozilla::StyleFlexBasis mFlexBasis; StyleImplicitGridTracks mGridAutoColumns; diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 2252d4ec01a3..bce9663f1339 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -13338,6 +13338,85 @@ if (IsCSSPropertyPrefEnabled("layout.css.anchor-positioning.enabled")) { ], }; + gCSSProperties["inset-area"] = { + domProp: "insetArea", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "center", + "span-all", + "left", + "right", + "span-left", + "span-right", + "x-start", + "x-end", + "span-x-start", + "span-x-end", + "x-self-start", + "x-self-end", + "span-x-self-start", + "span-x-self-end", + "top", + "bottom", + "span-top", + "span-bottom", + "y-start", + "y-end", + "span-y-start", + "span-y-end", + "y-self-start", + "y-self-end", + "span-y-self-start", + "span-y-self-end", + "block-start", + "block-end", + "span-block-start", + "span-block-end", + "inline-start", + "inline-end", + "span-inline-start", + "span-inline-end", + "self-block-start", + "self-block-end", + "span-self-block-start", + "span-self-block-end", + "self-inline-start", + "self-inline-end", + "span-self-inline-start", + "span-self-inline-end", + "start", + "end", + "span-start", + "span-end", + "self-start", + "self-end", + "span-self-start", + "span-self-end", + "center span-all", + "left center", + "span-left bottom", + "span-block-end inline-start", + "span-inline-start block-end", + "self-block-end span-self-inline-start", + "start center", + "span-start span-end", + "self-end span-self-start", + ], + invalid_values: [ + "auto", + "none left", + "left self-top", + "right block-end", + "top self-end", + "y-self-end x-end", + "inline-start self-block-end", + "span-self-inline-start start", + "end span-self-start", + ], + }; + gCSSProperties["position-anchor"] = { domProp: "positionAnchor", inherited: false, diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py index bc3b0733f109..11f44cf631ac 100644 --- a/servo/components/style/properties/data.py +++ b/servo/components/style/properties/data.py @@ -566,6 +566,8 @@ class Longhand(Property): "ImageRendering", "InitialLetter", "Integer", + "InsetArea", + "InsetAreaKeyword", "JustifyContent", "JustifyItems", "JustifySelf", diff --git a/servo/components/style/properties/longhands/position.mako.rs b/servo/components/style/properties/longhands/position.mako.rs index 80edb8033124..f84f9429c8ae 100644 --- a/servo/components/style/properties/longhands/position.mako.rs +++ b/servo/components/style/properties/longhands/position.mako.rs @@ -354,6 +354,18 @@ ${helpers.predefined_type( affects="layout", )} +${helpers.predefined_type( + "inset-area", + "InsetArea", + "computed::InsetArea::none()", + engines="gecko", + initial_specified_value="specified::InsetArea::none()", + animation_value_type="discrete", + gecko_pref="layout.css.anchor-positioning.enabled", + spec="https://drafts.csswg.org/css-anchor-position-1/#typedef-inset-area", + affects="layout", +)} + ${helpers.single_keyword( "box-sizing", "content-box border-box", diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index df85dd4e30db..c16b99f39bd8 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -92,6 +92,7 @@ pub use self::position::AnchorName; pub use self::position::AnchorScope; pub use self::position::PositionAnchor; pub use self::position::PositionVisibility; +pub use self::position::{InsetArea, InsetAreaKeyword}; pub use self::position::AspectRatio; pub use self::position::{ GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, ZIndex, diff --git a/servo/components/style/values/computed/position.rs b/servo/components/style/values/computed/position.rs index f36d20aa3b68..4fb92d2a8a0a 100644 --- a/servo/components/style/values/computed/position.rs +++ b/servo/components/style/values/computed/position.rs @@ -14,7 +14,7 @@ use crate::values::generics::position::PositionComponent as GenericPositionCompo use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; use crate::values::generics::position::ZIndex as GenericZIndex; pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow}; -pub use crate::values::specified::position::{AnchorName, AnchorScope, PositionAnchor, PositionVisibility}; +pub use crate::values::specified::position::{AnchorName, AnchorScope, InsetArea, InsetAreaKeyword, PositionAnchor, PositionVisibility}; use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs index 978aff93c33d..a0d777a50d16 100644 --- a/servo/components/style/values/specified/mod.rs +++ b/servo/components/style/values/specified/mod.rs @@ -83,6 +83,7 @@ pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::AnchorName; pub use self::position::AnchorScope; +pub use self::position::{InsetArea, InsetAreaKeyword}; pub use self::position::PositionAnchor; pub use self::position::PositionVisibility; pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; diff --git a/servo/components/style/values/specified/position.rs b/servo/components/style/values/specified/position.rs index 6148b021a80e..9f5b92315bb0 100644 --- a/servo/components/style/values/specified/position.rs +++ b/servo/components/style/values/specified/position.rs @@ -535,6 +535,345 @@ impl PositionVisibility { } } +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +#[repr(u8)] +/// Possible values for the `inset-area` preperty's keywords. +/// https://drafts.csswg.org/css-anchor-position-1/#propdef-inset-area +pub enum InsetAreaKeyword { + None, + + // Common (shared) keywords: + Center, + SpanAll, + + // Horizontal keywords: + Left, + Right, + SpanLeft, + SpanRight, + XStart, + XEnd, + SpanXStart, + SpanXEnd, + XSelfStart, + XSelfEnd, + SpanXSelfStart, + SpanXSelfEnd, + // Vertical keywords: + Top, + Bottom, + SpanTop, + SpanBottom, + YStart, + YEnd, + SpanYStart, + SpanYEnd, + YSelfStart, + YSelfEnd, + SpanYSelfStart, + SpanYSelfEnd, + + // Block keywords: + BlockStart, + BlockEnd, + SpanBlockStart, + SpanBlockEnd, + // Inline keywords: + InlineStart, + InlineEnd, + SpanInlineStart, + SpanInlineEnd, + + // "Self" block keywords: + SelfBlockStart, + SelfBlockEnd, + SpanSelfBlockStart, + SpanSelfBlockEnd, + // "Self" inline keywords: + SelfInlineStart, + SelfInlineEnd, + SpanSelfInlineStart, + SpanSelfInlineEnd, + + // Inferred axis keywords: + Start, + End, + SpanStart, + SpanEnd, + + // "Self" inferred axis keywords: + SelfStart, + SelfEnd, + SpanSelfStart, + SpanSelfEnd, +} + +impl Default for InsetAreaKeyword { + fn default() -> Self { + Self::None + } +} + +#[allow(missing_docs)] +impl InsetAreaKeyword { + #[inline] + pub fn none() -> Self { + Self::None + } + + pub fn is_none(&self) -> bool { + *self == Self::None + } + + /// Is a value that's common to all compatible keyword groupings. + pub fn is_common(&self) -> bool { + *self == Self::Center || *self == Self::SpanAll + } + + pub fn is_horizontal(&self) -> bool { + matches!( + self, + Self::Left | + Self::Right | + Self::SpanLeft | + Self::SpanRight | + Self::XStart | + Self::XEnd | + Self::SpanXStart | + Self::SpanXEnd | + Self::XSelfStart | + Self::XSelfEnd | + Self::SpanXSelfStart | + Self::SpanXSelfEnd + ) + } + pub fn is_vertical(&self) -> bool { + matches!( + self, + Self::Top | + Self::Bottom | + Self::SpanTop | + Self::SpanBottom | + Self::YStart | + Self::YEnd | + Self::SpanYStart | + Self::SpanYEnd | + Self::YSelfStart | + Self::YSelfEnd | + Self::SpanYSelfStart | + Self::SpanYSelfEnd + ) + } + + pub fn is_block(&self) -> bool { + matches!( + self, + Self::BlockStart | Self::BlockEnd | Self::SpanBlockStart | Self::SpanBlockEnd + ) + } + pub fn is_inline(&self) -> bool { + matches!( + self, + Self::InlineStart | Self::InlineEnd | Self::SpanInlineStart | Self::SpanInlineEnd + ) + } + + pub fn is_self_block(&self) -> bool { + matches!( + self, + Self::SelfBlockStart | + Self::SelfBlockEnd | + Self::SpanSelfBlockStart | + Self::SpanSelfBlockEnd + ) + } + pub fn is_self_inline(&self) -> bool { + matches!( + self, + Self::SelfInlineStart | + Self::SelfInlineEnd | + Self::SpanSelfInlineStart | + Self::SpanSelfInlineEnd + ) + } + + pub fn is_inferred_logical(&self) -> bool { + matches!( + self, + Self::Start | Self::End | Self::SpanStart | Self::SpanEnd + ) + } + + pub fn is_self_inferred_logical(&self) -> bool { + matches!( + self, + Self::SelfStart | Self::SelfEnd | Self::SpanSelfStart | Self::SpanSelfEnd + ) + } +} + +#[inline] +fn is_compatible_pairing(first: InsetAreaKeyword, second: InsetAreaKeyword) -> bool { + if first.is_none() || second.is_none() { + // `none` is not allowed as one of the keywords when two keywords are + // provided. + return false; + } + if first.is_common() || second.is_common() { + return true; + } + if first.is_horizontal() { + return second.is_vertical(); + } + if first.is_vertical() { + return second.is_horizontal(); + } + if first.is_block() { + return second.is_inline(); + } + if first.is_inline() { + return second.is_block(); + } + if first.is_self_block() { + return second.is_self_inline(); + } + if first.is_self_inline() { + return second.is_self_block(); + } + if first.is_inferred_logical() { + return second.is_inferred_logical(); + } + if first.is_self_inferred_logical() { + return second.is_self_inferred_logical(); + } + + debug_assert!(false, "Not reached"); + + // Return false to increase the chances of this being reported to us if we + // ever were to get here. + false +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +/// https://drafts.csswg.org/css-anchor-position-1/#propdef-inset-area +pub struct InsetArea { + /// First keyword, if any. + pub first: InsetAreaKeyword, + /// Second keyword, if any. + #[css(skip_if = "InsetAreaKeyword::is_none")] + pub second: InsetAreaKeyword, +} + +#[allow(missing_docs)] +impl InsetArea { + #[inline] + pub fn none() -> Self { + Self { + first: InsetAreaKeyword::None, + second: InsetAreaKeyword::None, + } + } + + #[inline] + pub fn is_none(&self) -> bool { + self.first.is_none() + } +} + +impl Parse for InsetArea { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut first = InsetAreaKeyword::parse(input)?; + if first.is_none() { + return Ok(Self::none()); + } + + let location = input.current_source_location(); + let second = input.try_parse(InsetAreaKeyword::parse); + if let Ok(InsetAreaKeyword::None) = second { + // `none` is only allowed as a single value + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + let mut second = second.unwrap_or(InsetAreaKeyword::None); + if second.is_none() { + // Either there was no second keyword and try_parse returned a + // BasicParseErrorKind::EndOfInput, or else the second "keyword" + // was invalid. We assume the former case here, and if it's the + // latter case then our caller detects the error (try_parse will, + // have rewound, leaving an unparsed token). + return Ok(Self { first, second }); + } + + if !is_compatible_pairing(first, second) { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + // Normalize by applying the shortest serialization principle: + // https://drafts.csswg.org/cssom/#serializing-css-values + if first.is_inferred_logical() || + second.is_inferred_logical() || + first.is_self_inferred_logical() || + second.is_self_inferred_logical() || + (first.is_common() && second.is_common()) + { + // In these cases we must not change the order of the keywords + // since their meaning is inferred from their order. However, if + // both keywords are the same, only one should be set. + if first == second { + second = InsetAreaKeyword::None; + } + } else if second == InsetAreaKeyword::SpanAll { + // Span-all is the default behavior, so specifying `span-all` is + // superfluous. + second = InsetAreaKeyword::None; + } else if first == InsetAreaKeyword::SpanAll { + // Same here, but the non-superfluous keyword must come first. + first = second; + second = InsetAreaKeyword::None; + } else if first.is_vertical() || + second.is_horizontal() || + first.is_inline() || + second.is_block() || + first.is_self_inline() || + second.is_self_block() + { + // Canonical order is horizontal before vertical, block before inline. + std::mem::swap(&mut first, &mut second); + } + + Ok(Self { first, second }) + } +} + /// Represents a side, either horizontal or vertical, of a CSS position. pub trait Side { /// Returns the start side. diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml index 105f9831ea59..6412c61017ab 100644 --- a/servo/ports/geckolib/cbindgen.toml +++ b/servo/ports/geckolib/cbindgen.toml @@ -105,6 +105,8 @@ include = [ "FontVariantEastAsian", "FontVariantLigatures", "FontVariantNumeric", + "InsetArea", + "InsetAreaKeyword", "ComputedFontStretchRange", "ComputedFontStyleDescriptor", "ComputedFontWeightRange",