commit 50e9eff04372c3cb6504b9e876bed2721fdeed0e
parent 59187fbc73b5de73905cf534cc3cfb0e9ecbc1d0
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Thu, 2 Oct 2025 08:34:18 +0000
Bug 1991989 - Implement @position-try style resolution. r=firefox-style-system-reviewers,layout-anchor-positioning-reviewers,layout-reviewers,dshin
Unfortunately, only a couple tests start passing, because I'm only
applying the fallback style to position-area so far, and most tests use
width/height plus the inset properties.
However, the styles are correct, and those should be fixable in
follow-ups. This seems worth landing on its own.
Differential Revision: https://phabricator.services.mozilla.com/D267062
Diffstat:
11 files changed, 156 insertions(+), 11 deletions(-)
diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp
@@ -26,6 +26,7 @@
#include "nsIFrameInlines.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
#ifdef DEBUG
# include "nsBlockFrame.h"
@@ -894,6 +895,7 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
}
do {
const StylePositionTryFallbacksItem* currentFallback = nullptr;
+ RefPtr<ComputedStyle> currentFallbackStyle;
if (currentFallbackIndex) {
currentFallback = &fallbacks[*currentFallbackIndex];
}
@@ -907,11 +909,21 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
auto positionArea = stylePos->mPositionArea;
const StylePositionTryFallbacksTryTactic* tactic = nullptr;
if (currentFallback) {
- if (currentFallback->IsPositionArea()) {
- positionArea = currentFallback->AsPositionArea();
- } else if (currentFallback->IsIdentAndOrTactic()) {
+ if (currentFallback->IsIdentAndOrTactic()) {
const auto& item = currentFallback->AsIdentAndOrTactic();
+ if (!item.ident.AsAtom()->IsEmpty()) {
+ currentFallbackStyle = aPresContext->StyleSet()->ResolvePositionTry(
+ *aKidFrame->GetContent()->AsElement(), *aKidFrame->Style(),
+ item.ident.AsAtom());
+ if (currentFallbackStyle) {
+ positionArea =
+ currentFallbackStyle->StylePosition()->mPositionArea;
+ }
+ }
tactic = &item.try_tactic;
+ } else {
+ MOZ_ASSERT(currentFallback->IsPositionArea());
+ positionArea = currentFallback->AsPositionArea();
}
}
diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp
@@ -608,6 +608,14 @@ already_AddRefed<ComputedStyle> ServoStyleSet::ResolveStartingStyle(
.Consume();
}
+already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePositionTry(
+ dom::Element& aElement, ComputedStyle& aStyle, nsAtom* aName) {
+ MOZ_ASSERT(aName);
+ return Servo_ComputedValues_GetForPositionTry(mRawData.get(), &aStyle,
+ &aElement, aName)
+ .Consume();
+}
+
// manage the set of style sheets in the style set
void ServoStyleSet::AppendStyleSheet(StyleSheet& aSheet) {
MOZ_ASSERT(aSheet.IsApplicable());
diff --git a/layout/style/ServoStyleSet.h b/layout/style/ServoStyleSet.h
@@ -262,6 +262,10 @@ class ServoStyleSet {
// function after checking if it may have rules inside @starting-style.
already_AddRefed<ComputedStyle> ResolveStartingStyle(dom::Element& aElement);
+ already_AddRefed<ComputedStyle> ResolvePositionTry(dom::Element& aElement,
+ ComputedStyle& aStyle,
+ nsAtom* aName);
+
size_t SheetCount(Origin) const;
StyleSheet* SheetAt(Origin, size_t aIndex) const;
diff --git a/servo/components/servo_arc/lib.rs b/servo/components/servo_arc/lib.rs
@@ -1064,7 +1064,7 @@ impl<A, B> ArcUnion<A, B> {
/// Returns an enum representing a borrow of either A or B.
#[inline]
- pub fn borrow(&self) -> ArcUnionBorrow<A, B> {
+ pub fn borrow(&self) -> ArcUnionBorrow<'_, A, B> {
if self.is_first() {
let ptr = self.p.as_ptr() as *const ArcInner<A>;
let borrow = unsafe { ArcBorrow::from_ref(&(*ptr).data) };
diff --git a/servo/components/style/gecko_bindings/sugar/ownership.rs b/servo/components/style/gecko_bindings/sugar/ownership.rs
@@ -30,6 +30,15 @@ impl<T> From<Arc<T>> for Strong<T> {
}
}
+impl<T> From<Option<Arc<T>>> for Strong<T> {
+ fn from(arc: Option<Arc<T>>) -> Self {
+ match arc {
+ Some(arc) => arc.into(),
+ None => Self::null(),
+ }
+ }
+}
+
impl<GeckoType> Strong<GeckoType> {
#[inline]
/// Returns whether this reference is null.
diff --git a/servo/components/style/style_resolver.rs b/servo/components/style/style_resolver.rs
@@ -119,7 +119,7 @@ impl From<ResolvedElementStyles> for ElementStyles {
}
}
-fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
+pub(crate) fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
where
E: TElement,
F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs
@@ -1209,6 +1209,53 @@ impl Stylist {
)
}
+ /// Computes a fallback style lazily given the current and parent styles, and name.
+ pub fn resolve_position_try<E>(
+ &self,
+ style: &ComputedValues,
+ guards: &StylesheetGuards,
+ element: E,
+ name: &Atom,
+ ) -> Option<Arc<ComputedValues>>
+ where
+ E: TElement,
+ {
+ let fallback_rule = self.lookup_position_try(name, element)?;
+ let fallback_block = &fallback_rule.read_with(guards.author).block;
+ let pseudo = style
+ .pseudo()
+ .or_else(|| element.implemented_pseudo_element());
+ let inputs = {
+ let mut inputs = CascadeInputs::new_from_style(style);
+ // @position-try doesn't care about any :visited-dependent property.
+ inputs.visited_rules = None;
+ let rules = inputs.rules.as_ref().unwrap_or(self.rule_tree.root());
+ let mut important_rules_changed = false;
+ inputs.rules = self.rule_tree.update_rule_at_level(
+ CascadeLevel::PositionFallback,
+ LayerOrder::root(),
+ Some(fallback_block.borrow_arc()),
+ rules,
+ guards,
+ &mut important_rules_changed,
+ );
+ inputs
+ };
+ crate::style_resolver::with_default_parent_styles(element, |parent_style, layout_parent_style| {
+ Some(self.cascade_style_and_visited(
+ Some(element),
+ pseudo.as_ref(),
+ inputs,
+ guards,
+ parent_style,
+ layout_parent_style,
+ FirstLineReparenting::No,
+ /* rule_cache = */ None,
+ &mut RuleCacheConditions::default(),
+ ))
+ })
+ }
+
/// Computes a style using the given CascadeInputs. This can be used to
/// compute a style any time we know what rules apply and just need to use
/// the given parent styles.
@@ -1564,7 +1611,7 @@ impl Stylist {
/// Returns the registered `@position-try-rule` animation for the specified name.
#[inline]
- pub fn lookup_position_try<'a, E>(
+ fn lookup_position_try<'a, E>(
&'a self,
name: &Atom,
element: E,
diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs
@@ -4383,6 +4383,30 @@ pub unsafe extern "C" fn Servo_ComputedValues_GetForPageContent(
}
#[no_mangle]
+pub unsafe extern "C" fn Servo_ComputedValues_GetForPositionTry(
+ raw_data: &PerDocumentStyleData,
+ style: &ComputedValues,
+ element: &RawGeckoElement,
+ name: *const nsAtom,
+) -> Strong<ComputedValues> {
+ debug_assert!(!name.is_null());
+ let global_style_data = &*GLOBAL_STYLE_DATA;
+ let guard = global_style_data.shared_lock.read();
+ let guards = StylesheetGuards::same(&guard);
+ let element = GeckoElement(element);
+ let data = raw_data.borrow();
+ Atom::with(name, |name| {
+ data.stylist.resolve_position_try(
+ style,
+ &guards,
+ element,
+ name,
+ )
+ })
+ .into()
+}
+
+#[no_mangle]
pub unsafe extern "C" fn Servo_ComputedValues_GetForAnonymousBox(
parent_style_or_null: Option<&ComputedValues>,
pseudo: PseudoStyleType,
diff --git a/testing/web-platform/meta/css/css-anchor-position/anchor-center-fallback-transition-behavior.html.ini b/testing/web-platform/meta/css/css-anchor-position/anchor-center-fallback-transition-behavior.html.ini
@@ -1,3 +0,0 @@
-[anchor-center-fallback-transition-behavior.html]
- [anchor-center aligned bottom anchored element overflowing IMCB]
- expected: FAIL
diff --git a/testing/web-platform/meta/css/css-anchor-position/last-successful-change-try-rule.html.ini b/testing/web-platform/meta/css/css-anchor-position/last-successful-change-try-rule.html.ini
@@ -1,6 +1,6 @@
[last-successful-change-try-rule.html]
- [Starts rendering with --try]
+ [No successful position, keep --try]
expected: FAIL
- [No successful position, keep --try]
+ [No successful position, last successful invalidated by @position-try change]
expected: FAIL
diff --git a/testing/web-platform/meta/css/css-anchor-position/position-try-order-position-area.html.ini b/testing/web-platform/meta/css/css-anchor-position/position-try-order-position-area.html.ini
@@ -1,3 +1,47 @@
[position-try-order-position-area.html]
expected:
if (processor == "x86") and (os == "linux"): [OK, CRASH]
+ [--right, --left, --bottom, --top | --right]
+ expected: FAIL
+
+ [normal --right, --left, --bottom, --top | --right]
+ expected: FAIL
+
+ [normal --top, --left, --bottom, --right | --top]
+ expected: FAIL
+
+ [most-block-size --right, --left | --right]
+ expected: FAIL
+
+ [most-height --right, --left | --right]
+ expected: FAIL
+
+ [most-inline-size --bottom, --top | --bottom]
+ expected: FAIL
+
+ [most-width --bottom, --top | --bottom]
+ expected: FAIL
+
+ [most-inline-size --right, --left, --bottom, --top | --bottom]
+ expected: FAIL
+
+ [most-inline-size --right, --left, --top, --bottom | --top]
+ expected: FAIL
+
+ [most-block-size --bottom, --top, --right, --left | --right]
+ expected: FAIL
+
+ [most-block-size --bottom, --top, --left, --right | --left]
+ expected: FAIL
+
+ [most-inline-size --left-sweep, --bottom-sweep | --left-sweep]
+ expected: FAIL
+
+ [most-inline-size --bottom-sweep, --left-sweep | --bottom-sweep]
+ expected: FAIL
+
+ [most-block-size --left-sweep, --bottom-sweep | --left-sweep]
+ expected: FAIL
+
+ [most-inline-size --right-sweep, --left-sweep, --bottom-sweep, --top-sweep | --left-sweep]
+ expected: FAIL