Skip to main content

style/stylesheets/
container_rule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! A [`@container`][container] rule.
6//!
7//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
8
9use crate::computed_value_flags::ComputedValueFlags;
10use crate::dom::{AttributeTracker, TElement};
11use crate::logical_geometry::{LogicalSize, WritingMode};
12use crate::parser::ParserContext;
13use crate::properties::ComputedValues;
14use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
15use crate::queries::values::Orientation;
16use crate::queries::{FeatureType, QueryCondition};
17use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
18use crate::shared_lock::{
19    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
20};
21use crate::stylesheets::{CssRules, CustomMediaEvaluator};
22use crate::stylist::Stylist;
23use crate::values::computed::{CSSPixelLength, ContainerType, Context, Ratio};
24use crate::values::specified::ContainerName;
25use crate::{derives::*, LocalName};
26use app_units::Au;
27use cssparser::{Parser, SourceLocation};
28use euclid::default::Size2D;
29#[cfg(feature = "gecko")]
30use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
31use selectors::kleene_value::KleeneValue;
32use selectors::matching::ElementSelectorFlags;
33use servo_arc::Arc;
34use std::fmt::{self, Write};
35use style_traits::arc_slice::ArcSlice;
36use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
37
38/// A container rule.
39#[derive(Debug, ToShmem)]
40pub struct ContainerRule {
41    /// The container queries and name.
42    pub conditions: ContainerConditions,
43    /// The nested rules inside the block.
44    pub rules: Arc<Locked<CssRules>>,
45    /// The source position where this rule was found.
46    pub source_location: SourceLocation,
47}
48
49impl ContainerRule {
50    /// Measure heap usage.
51    #[cfg(feature = "gecko")]
52    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
53        // Measurement of other fields may be added later.
54        self.rules.unconditional_shallow_size_of(ops)
55            + self.rules.read_with(guard).size_of(guard, ops)
56    }
57}
58
59impl DeepCloneWithLock for ContainerRule {
60    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
61        let rules = self.rules.read_with(guard);
62        Self {
63            conditions: self.conditions.clone(),
64            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
65            source_location: self.source_location,
66        }
67    }
68}
69
70impl ToCssWithGuard for ContainerRule {
71    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
72        dest.write_str("@container ")?;
73        {
74            let mut writer = CssWriter::new(dest);
75            self.conditions.to_css(&mut writer)?;
76        }
77        self.rules.read_with(guard).to_css_block(guard, dest)
78    }
79}
80
81/// Contains all container conditions for a container rule.
82///
83/// https://drafts.csswg.org/css-conditional-5/#container-rule
84#[derive(Clone, Debug, ToCss, ToShmem)]
85#[css(comma)]
86pub struct ContainerConditions(#[css(iterable)] pub ArcSlice<ContainerCondition>);
87
88/// A set of attribute names used for invalidation of attr() inside
89/// style container queries.
90pub type AttrReferenceSet = PrecomputedHashSet<LocalName>;
91
92/// Whether this container uses attr() references and if so whether or not this
93/// is a named or unnamed container query. Used for determining which kind of
94/// restyle hint we need when traversing invalidation.
95#[derive(Debug, Clone, Copy, MallocSizeOf, PartialEq, Eq, PartialOrd, Ord)]
96#[repr(C)]
97pub enum ContainerAttributeDependencyKind {
98    /// This container does not use attr() references.
99    None = 0,
100    /// This unnamed container uses attr() references.
101    UnnamedContainer = 1,
102    /// This named container uses attr() references.
103    NamedContainer = 2,
104}
105
106impl ContainerAttributeDependencyKind {
107    /// A named container that uses attr needs to invalidate all descendants that
108    /// are affected by style container queries. If we only have an unnamed
109    /// container or no attribute dependencies we keep looking for a condition that
110    /// would require us to invalidate more.
111    pub fn element_container_dependency_kind<E: TElement>(
112        element: E,
113        local_name: &LocalName,
114        stylist: &Stylist,
115    ) -> Self {
116        let mut name_kind = ContainerAttributeDependencyKind::None;
117        stylist.any_applicable_rule_data(element, |data| {
118            let value = data.might_have_attribute_dependency_in_container(local_name);
119            name_kind = std::cmp::max(name_kind, value);
120            name_kind == ContainerAttributeDependencyKind::NamedContainer
121        });
122        name_kind
123    }
124}
125
126/// A container condition and filter, combined.
127#[derive(Debug, ToShmem, ToCss)]
128pub struct ContainerCondition {
129    #[css(skip_if = "ContainerName::is_none")]
130    name: ContainerName,
131    condition: Option<QueryCondition>,
132    #[css(skip)]
133    flags: FeatureFlags,
134    #[css(skip)]
135    attributes_referenced: AttrReferenceSet,
136}
137
138/// The result of a successful container query lookup.
139pub struct ContainerLookupResult<E> {
140    /// The relevant container.
141    pub element: E,
142    /// The sizing / writing-mode information of the container.
143    pub info: ContainerInfo,
144    /// The style of the element.
145    pub style: Arc<ComputedValues>,
146}
147
148fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
149    if ty_.intersects(ContainerType::SIZE) {
150        FeatureFlags::all_container_axes()
151    } else if ty_.intersects(ContainerType::INLINE_SIZE) {
152        let physical_axis = if wm.is_vertical() {
153            FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
154        } else {
155            FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
156        };
157        FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
158    } else {
159        FeatureFlags::empty()
160    }
161}
162
163enum TraversalResult<T> {
164    InProgress,
165    StopTraversal,
166    Done(T),
167}
168
169fn traverse_container<E, F, R>(
170    mut e: E,
171    originating_element_style: Option<&ComputedValues>,
172    evaluator: F,
173) -> Option<(E, R)>
174where
175    E: TElement,
176    F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>,
177{
178    if originating_element_style.is_some() {
179        match evaluator(e, originating_element_style) {
180            TraversalResult::InProgress => {},
181            TraversalResult::StopTraversal => return None,
182            TraversalResult::Done(result) => return Some((e, result)),
183        }
184    }
185    while let Some(element) = e.traversal_parent() {
186        match evaluator(element, None) {
187            TraversalResult::InProgress => {},
188            TraversalResult::StopTraversal => return None,
189            TraversalResult::Done(result) => return Some((element, result)),
190        }
191        e = element;
192    }
193
194    None
195}
196
197impl ContainerCondition {
198    /// Get the name of this condition.
199    #[inline]
200    pub fn name(&self) -> &ContainerName {
201        &self.name
202    }
203    /// Get the query condition of this condition
204    #[inline]
205    pub fn query_condition(&self) -> Option<&QueryCondition> {
206        self.condition.as_ref()
207    }
208    /// Parse a container condition.
209    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
210        let name = input
211            .try_parse(|input| ContainerName::parse_for_query(context, input))
212            .ok()
213            .unwrap_or_else(ContainerName::none);
214        let condition = input
215            .try_parse(|input| QueryCondition::parse(context, input, FeatureType::Container))
216            .ok();
217        if condition.is_none() && name.is_none() {
218            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
219        }
220        let mut attributes_referenced = AttrReferenceSet::default();
221        if let Some(c) = condition.as_ref() {
222            c.collect_attribute_references(&mut attributes_referenced)
223        }
224        let flags = condition
225            .as_ref()
226            .map_or(FeatureFlags::empty(), |c| c.cumulative_flags());
227        Ok(Self {
228            name,
229            condition,
230            flags,
231            attributes_referenced,
232        })
233    }
234
235    fn valid_container_info<E>(
236        &self,
237        potential_container: E,
238        originating_element_style: Option<&ComputedValues>,
239    ) -> TraversalResult<ContainerLookupResult<E>>
240    where
241        E: TElement,
242    {
243        let data;
244        let style = match originating_element_style {
245            Some(s) => s,
246            None => {
247                data = match potential_container.borrow_data() {
248                    Some(d) => d,
249                    None => return TraversalResult::InProgress,
250                };
251                &**data.styles.primary()
252            },
253        };
254        let wm = style.writing_mode;
255        let box_style = style.get_box();
256
257        // Filter by container-type.
258        let container_type = box_style.clone_container_type();
259        let available_axes = container_type_axes(container_type, wm);
260        if !available_axes.contains(self.flags.container_axes()) {
261            return TraversalResult::InProgress;
262        }
263
264        // Filter by container-name.
265        let container_name = box_style.clone_container_name();
266        for filter_name in self.name.0.iter() {
267            if !container_name.0.contains(filter_name) {
268                return TraversalResult::InProgress;
269            }
270        }
271
272        let size = potential_container.query_container_size(&box_style.clone_display());
273        let style = style.to_arc();
274        TraversalResult::Done(ContainerLookupResult {
275            element: potential_container,
276            info: ContainerInfo {
277                size,
278                wm,
279                inherited_style: {
280                    potential_container.traversal_parent().and_then(|parent| {
281                        parent
282                            .borrow_data()
283                            .and_then(|data| data.styles.get_primary().cloned())
284                    })
285                },
286            },
287            style,
288        })
289    }
290
291    /// Performs container lookup for a given element.
292    pub fn find_container<E>(
293        &self,
294        e: E,
295        originating_element_style: Option<&ComputedValues>,
296    ) -> Option<ContainerLookupResult<E>>
297    where
298        E: TElement,
299    {
300        traverse_container(
301            e,
302            originating_element_style,
303            |element, originating_element_style| {
304                self.valid_container_info(element, originating_element_style)
305            },
306        )
307        .map(|(_, result)| result)
308    }
309
310    /// Tries to match a container query condition for a given element.
311    pub fn matches<E>(
312        &self,
313        stylist: &Stylist,
314        element: E,
315        originating_element_style: Option<&ComputedValues>,
316        invalidation_flags: &mut ComputedValueFlags,
317    ) -> KleeneValue
318    where
319        E: TElement,
320    {
321        let result = self.find_container(element, originating_element_style);
322        let condition = match self.condition {
323            Some(ref c) => c,
324            None => {
325                // Condition-less container query (name only): matches if a
326                // named container was found.
327                return KleeneValue::from(result.is_some());
328            },
329        };
330        // We have to tag the invalidation flags here because style container
331        // query matching may return early if we cannot find a suitable
332        // container element right now. However, we must also consider the case
333        // when an ancestor becomes a container and we have to invalidate this
334        // element from not matching to matching.
335        if self.flags.contains(FeatureFlags::STYLE) {
336            invalidation_flags.insert(ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY);
337        }
338        let (container, info) = match result {
339            Some(r) => (r.element, (r.info, r.style)),
340            None => {
341                // If we did not find the named (or any) container,
342                // the query must fail to match.
343                return KleeneValue::False;
344            },
345        };
346        // Set up the lookup for the container in question, as the condition may be using container
347        // query lengths.
348        let size_query_container_lookup = ContainerSizeQuery::for_element(
349            container, /* known_parent_style = */ None, /* is_pseudo = */ false,
350        );
351        let mut attribute_tracker = AttributeTracker::new(&container);
352        Context::for_container_query_evaluation(
353            stylist.device(),
354            Some(stylist),
355            Some(info),
356            size_query_container_lookup,
357            &container,
358            |context| {
359                let matches = condition.matches(
360                    context,
361                    &mut CustomMediaEvaluator::none(),
362                    &mut attribute_tracker,
363                );
364                let flags = context.style().flags();
365                if flags.contains(ComputedValueFlags::USES_VIEWPORT_UNITS) {
366                    // TODO(emilio): Might need something similar to improve
367                    // invalidation of font relative container-query lengths.
368                    invalidation_flags
369                        .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
370                }
371                if flags.contains(ComputedValueFlags::USES_FONT_OR_WM_RELATIVE_UNITS) {
372                    invalidation_flags.insert(
373                        ComputedValueFlags::USES_FONT_OR_WM_RELATIVE_UNITS_ON_CONTAINER_QUERIES,
374                    );
375                }
376                if flags.intersects(ComputedValueFlags::tree_counting_function_flags()) {
377                    // Container query usage of sibling-index() and sibling-count() requires
378                    // redoing selector matches on sibling changes. Although this is not itself
379                    // a selector, the HAS_SLOW_SELECTOR flag is reused here because it has the
380                    // required invalidation behavior.
381                    container.apply_selector_flags(ElementSelectorFlags::HAS_SLOW_SELECTOR);
382                }
383                matches
384            },
385        )
386    }
387
388    /// Get the attribute functions referenced by this condition, if any.
389    pub fn get_attributes_referenced(&self) -> &AttrReferenceSet {
390        &self.attributes_referenced
391    }
392
393    /// Insert the attribute dependencies and attribute dependency kind into
394    /// the attribute_dependencies set and the attr_function_dependencies map.
395    pub fn insert_attribute_references_in_dependency_map(
396        &self,
397        kind_map: &mut PrecomputedHashMap<LocalName, ContainerAttributeDependencyKind>,
398        attribute_dependencies: &mut AttrReferenceSet,
399    ) {
400        let name_kind = if self.name.is_none() {
401            ContainerAttributeDependencyKind::UnnamedContainer
402        } else {
403            ContainerAttributeDependencyKind::NamedContainer
404        };
405        for attr in self.get_attributes_referenced() {
406            kind_map
407                .entry(attr.clone())
408                .and_modify(|v| {
409                    if *v == ContainerAttributeDependencyKind::UnnamedContainer {
410                        *v = name_kind
411                    }
412                })
413                .or_insert(name_kind);
414            attribute_dependencies.insert(attr.clone());
415        }
416    }
417}
418
419/// Information needed to evaluate an individual container query.
420#[derive(Clone)]
421pub struct ContainerInfo {
422    size: Size2D<Option<Au>>,
423    wm: WritingMode,
424    inherited_style: Option<Arc<ComputedValues>>,
425}
426
427impl ContainerInfo {
428    fn size(&self) -> Option<Size2D<Au>> {
429        Some(Size2D::new(self.size.width?, self.size.height?))
430    }
431
432    /// Get a reference to the container's inherited style, if any.
433    pub fn inherited_style(&self) -> Option<&ComputedValues> {
434        self.inherited_style.as_deref()
435    }
436}
437
438fn eval_width(context: &Context) -> Option<CSSPixelLength> {
439    let info = context.container_info.as_ref()?;
440    Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
441}
442
443fn eval_height(context: &Context) -> Option<CSSPixelLength> {
444    let info = context.container_info.as_ref()?;
445    Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
446}
447
448fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
449    let info = context.container_info.as_ref()?;
450    Some(CSSPixelLength::new(
451        LogicalSize::from_physical(info.wm, info.size)
452            .inline?
453            .to_f32_px(),
454    ))
455}
456
457fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
458    let info = context.container_info.as_ref()?;
459    Some(CSSPixelLength::new(
460        LogicalSize::from_physical(info.wm, info.size)
461            .block?
462            .to_f32_px(),
463    ))
464}
465
466fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
467    let info = context.container_info.as_ref()?;
468    Some(Ratio::new(
469        info.size.width?.0 as f32,
470        info.size.height?.0 as f32,
471    ))
472}
473
474fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
475    let size = match context.container_info.as_ref().and_then(|info| info.size()) {
476        Some(size) => size,
477        None => return KleeneValue::Unknown,
478    };
479    KleeneValue::from(Orientation::eval(size, value))
480}
481
482/// https://drafts.csswg.org/css-contain-3/#container-features
483///
484/// TODO: Support style queries, perhaps.
485pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
486    feature!(
487        atom!("width"),
488        AllowsRanges::Yes,
489        Evaluator::OptionalLength(eval_width),
490        FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
491    ),
492    feature!(
493        atom!("height"),
494        AllowsRanges::Yes,
495        Evaluator::OptionalLength(eval_height),
496        FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
497    ),
498    feature!(
499        atom!("inline-size"),
500        AllowsRanges::Yes,
501        Evaluator::OptionalLength(eval_inline_size),
502        FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
503    ),
504    feature!(
505        atom!("block-size"),
506        AllowsRanges::Yes,
507        Evaluator::OptionalLength(eval_block_size),
508        FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
509    ),
510    feature!(
511        atom!("aspect-ratio"),
512        AllowsRanges::Yes,
513        Evaluator::OptionalNumberRatio(eval_aspect_ratio),
514        // XXX from_bits_truncate is const, but the pipe operator isn't, so this
515        // works around it.
516        FeatureFlags::from_bits_truncate(
517            FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
518                | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
519        ),
520    ),
521    feature!(
522        atom!("orientation"),
523        AllowsRanges::No,
524        keyword_evaluator!(eval_orientation, Orientation),
525        FeatureFlags::from_bits_truncate(
526            FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
527                | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
528        ),
529    ),
530];
531
532/// Result of a container size query, signifying the hypothetical containment boundary in terms of physical axes.
533/// Defined by up to two size containers. Queries on logical axes are resolved with respect to the querying
534/// element's writing mode.
535#[derive(Copy, Clone, Default)]
536pub struct ContainerSizeQueryResult {
537    width: Option<Au>,
538    height: Option<Au>,
539}
540
541impl ContainerSizeQueryResult {
542    fn get_viewport_size(context: &Context) -> Size2D<Au> {
543        use crate::values::specified::ViewportVariant;
544        context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
545    }
546
547    fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
548        LogicalSize::from_physical(
549            context.builder.writing_mode,
550            Self::get_viewport_size(context),
551        )
552    }
553
554    /// Get the inline-size of the query container.
555    pub fn get_container_inline_size(&self, context: &Context) -> Au {
556        if context.builder.writing_mode.is_horizontal() {
557            if let Some(w) = self.width {
558                return w;
559            }
560        } else if let Some(h) = self.height {
561            return h;
562        }
563        Self::get_logical_viewport_size(context).inline
564    }
565
566    /// Get the block-size of the query container.
567    pub fn get_container_block_size(&self, context: &Context) -> Au {
568        if context.builder.writing_mode.is_horizontal() {
569            self.get_container_height(context)
570        } else {
571            self.get_container_width(context)
572        }
573    }
574
575    /// Get the width of the query container.
576    pub fn get_container_width(&self, context: &Context) -> Au {
577        if let Some(w) = self.width {
578            return w;
579        }
580        Self::get_viewport_size(context).width
581    }
582
583    /// Get the height of the query container.
584    pub fn get_container_height(&self, context: &Context) -> Au {
585        if let Some(h) = self.height {
586            return h;
587        }
588        Self::get_viewport_size(context).height
589    }
590
591    // Merge the result of a subsequent lookup, preferring the initial result.
592    fn merge(self, new_result: Self) -> Self {
593        let mut result = self;
594        if let Some(width) = new_result.width {
595            result.width.get_or_insert(width);
596        }
597        if let Some(height) = new_result.height {
598            result.height.get_or_insert(height);
599        }
600        result
601    }
602
603    fn is_complete(&self) -> bool {
604        self.width.is_some() && self.height.is_some()
605    }
606}
607
608/// Unevaluated lazy container size query.
609pub enum ContainerSizeQuery<'a> {
610    /// Query prior to evaluation.
611    NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
612    /// Cached evaluated result.
613    Evaluated(ContainerSizeQueryResult),
614}
615
616impl<'a> ContainerSizeQuery<'a> {
617    fn evaluate_potential_size_container<E>(
618        e: E,
619        originating_element_style: Option<&ComputedValues>,
620    ) -> TraversalResult<ContainerSizeQueryResult>
621    where
622        E: TElement,
623    {
624        let data;
625        let style = match originating_element_style {
626            Some(s) => s,
627            None => {
628                data = match e.borrow_data() {
629                    Some(d) => d,
630                    None => return TraversalResult::InProgress,
631                };
632                &**data.styles.primary()
633            },
634        };
635        if !style
636            .flags
637            .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
638        {
639            // We know we won't find a size container.
640            return TraversalResult::StopTraversal;
641        }
642
643        let wm = style.writing_mode;
644        let box_style = style.get_box();
645
646        let container_type = box_style.clone_container_type();
647        let size = e.query_container_size(&box_style.clone_display());
648        if container_type.intersects(ContainerType::SIZE) {
649            TraversalResult::Done(ContainerSizeQueryResult {
650                width: size.width,
651                height: size.height,
652            })
653        } else if container_type.intersects(ContainerType::INLINE_SIZE) {
654            if wm.is_horizontal() {
655                TraversalResult::Done(ContainerSizeQueryResult {
656                    width: size.width,
657                    height: None,
658                })
659            } else {
660                TraversalResult::Done(ContainerSizeQueryResult {
661                    width: None,
662                    height: size.height,
663                })
664            }
665        } else {
666            TraversalResult::InProgress
667        }
668    }
669
670    /// Find the query container size for a given element. Meant to be used as a callback for new().
671    fn lookup<E>(
672        element: E,
673        originating_element_style: Option<&ComputedValues>,
674    ) -> ContainerSizeQueryResult
675    where
676        E: TElement + 'a,
677    {
678        match traverse_container(
679            element,
680            originating_element_style,
681            |e, originating_element_style| {
682                Self::evaluate_potential_size_container(e, originating_element_style)
683            },
684        ) {
685            Some((container, result)) => {
686                if result.is_complete() {
687                    result
688                } else {
689                    // Traverse up from the found size container to see if we can get a complete containment.
690                    result.merge(Self::lookup(container, None))
691                }
692            },
693            None => ContainerSizeQueryResult::default(),
694        }
695    }
696
697    /// Create a new instance of the container size query for given element, with a deferred lookup callback.
698    pub fn for_element<E>(
699        element: E,
700        known_parent_style: Option<&'a ComputedValues>,
701        is_pseudo: bool,
702    ) -> Self
703    where
704        E: TElement + 'a,
705    {
706        let parent;
707        let data;
708        let parent_style = match known_parent_style {
709            Some(s) => Some(s),
710            None => {
711                // No need to bother if we're the top element.
712                parent = match element.traversal_parent() {
713                    Some(parent) => parent,
714                    None => return Self::none(),
715                };
716                data = parent.borrow_data();
717                data.as_ref().map(|data| &**data.styles.primary())
718            },
719        };
720
721        // If there's no style, such as being `display: none` or so, we still want to show a
722        // correct computed value, so give it a try.
723        let should_traverse = parent_style.is_none_or(|s| {
724            s.flags
725                .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
726        });
727        if !should_traverse {
728            return Self::none();
729        }
730        Self::NotEvaluated(Box::new(move || {
731            Self::lookup(element, if is_pseudo { known_parent_style } else { None })
732        }))
733    }
734
735    /// Create a new instance, but with optional element.
736    pub fn for_option_element<E>(
737        element: Option<E>,
738        known_parent_style: Option<&'a ComputedValues>,
739        is_pseudo: bool,
740    ) -> Self
741    where
742        E: TElement + 'a,
743    {
744        if let Some(e) = element {
745            Self::for_element(e, known_parent_style, is_pseudo)
746        } else {
747            Self::none()
748        }
749    }
750
751    /// Create a query that evaluates to empty, for cases where container size query is not required.
752    pub fn none() -> Self {
753        ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
754    }
755
756    /// Get the result of the container size query, doing the lookup if called for the first time.
757    pub fn get(&mut self) -> ContainerSizeQueryResult {
758        match self {
759            Self::NotEvaluated(lookup) => {
760                *self = Self::Evaluated((lookup)());
761                match self {
762                    Self::Evaluated(info) => *info,
763                    _ => unreachable!("Just evaluated but not set?"),
764                }
765            },
766            Self::Evaluated(info) => *info,
767        }
768    }
769}