1use 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#[derive(Debug, ToShmem)]
40pub struct ContainerRule {
41 pub conditions: ContainerConditions,
43 pub rules: Arc<Locked<CssRules>>,
45 pub source_location: SourceLocation,
47}
48
49impl ContainerRule {
50 #[cfg(feature = "gecko")]
52 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
53 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#[derive(Clone, Debug, ToCss, ToShmem)]
85#[css(comma)]
86pub struct ContainerConditions(#[css(iterable)] pub ArcSlice<ContainerCondition>);
87
88pub type AttrReferenceSet = PrecomputedHashSet<LocalName>;
91
92#[derive(Debug, Clone, Copy, MallocSizeOf, PartialEq, Eq, PartialOrd, Ord)]
96#[repr(C)]
97pub enum ContainerAttributeDependencyKind {
98 None = 0,
100 UnnamedContainer = 1,
102 NamedContainer = 2,
104}
105
106impl ContainerAttributeDependencyKind {
107 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#[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
138pub struct ContainerLookupResult<E> {
140 pub element: E,
142 pub info: ContainerInfo,
144 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 #[inline]
200 pub fn name(&self) -> &ContainerName {
201 &self.name
202 }
203 #[inline]
205 pub fn query_condition(&self) -> Option<&QueryCondition> {
206 self.condition.as_ref()
207 }
208 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 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 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 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 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 return KleeneValue::from(result.is_some());
328 },
329 };
330 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 return KleeneValue::False;
344 },
345 };
346 let size_query_container_lookup = ContainerSizeQuery::for_element(
349 container, None, 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 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.apply_selector_flags(ElementSelectorFlags::HAS_SLOW_SELECTOR);
382 }
383 matches
384 },
385 )
386 }
387
388 pub fn get_attributes_referenced(&self) -> &AttrReferenceSet {
390 &self.attributes_referenced
391 }
392
393 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#[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 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
482pub 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 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#[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 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 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 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 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 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
608pub enum ContainerSizeQuery<'a> {
610 NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
612 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 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 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 result.merge(Self::lookup(container, None))
691 }
692 },
693 None => ContainerSizeQueryResult::default(),
694 }
695 }
696
697 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 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 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 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 pub fn none() -> Self {
753 ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
754 }
755
756 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}