1use crate::computed_value_flags::ComputedValueFlags;
10use crate::derives::*;
11use crate::dom::{AttributeTracker, TElement};
12use crate::logical_geometry::{LogicalSize, WritingMode};
13use crate::parser::ParserContext;
14use crate::properties::ComputedValues;
15use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
16use crate::queries::values::Orientation;
17use crate::queries::{FeatureType, QueryCondition};
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 app_units::Au;
26use cssparser::{Parser, SourceLocation};
27use euclid::default::Size2D;
28#[cfg(feature = "gecko")]
29use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
30use selectors::kleene_value::KleeneValue;
31use selectors::matching::ElementSelectorFlags;
32use servo_arc::Arc;
33use std::fmt::{self, Write};
34use style_traits::arc_slice::ArcSlice;
35use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
36
37#[derive(Debug, ToShmem)]
39pub struct ContainerRule {
40 pub conditions: ContainerConditions,
42 pub rules: Arc<Locked<CssRules>>,
44 pub source_location: SourceLocation,
46}
47
48impl ContainerRule {
49 #[cfg(feature = "gecko")]
51 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
52 self.rules.unconditional_shallow_size_of(ops)
54 + self.rules.read_with(guard).size_of(guard, ops)
55 }
56}
57
58impl DeepCloneWithLock for ContainerRule {
59 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
60 let rules = self.rules.read_with(guard);
61 Self {
62 conditions: self.conditions.clone(),
63 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
64 source_location: self.source_location.clone(),
65 }
66 }
67}
68
69impl ToCssWithGuard for ContainerRule {
70 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
71 dest.write_str("@container ")?;
72 {
73 let mut writer = CssWriter::new(dest);
74 self.conditions.to_css(&mut writer)?;
75 }
76 self.rules.read_with(guard).to_css_block(guard, dest)
77 }
78}
79
80#[derive(Clone, Debug, ToCss, ToShmem)]
84#[css(comma)]
85pub struct ContainerConditions(#[css(iterable)] pub ArcSlice<ContainerCondition>);
86
87#[derive(Debug, ToShmem, ToCss)]
89pub struct ContainerCondition {
90 #[css(skip_if = "ContainerName::is_none")]
91 name: ContainerName,
92 condition: Option<QueryCondition>,
93 #[css(skip)]
94 flags: FeatureFlags,
95}
96
97pub struct ContainerLookupResult<E> {
99 pub element: E,
101 pub info: ContainerInfo,
103 pub style: Arc<ComputedValues>,
105}
106
107fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
108 if ty_.intersects(ContainerType::SIZE) {
109 FeatureFlags::all_container_axes()
110 } else if ty_.intersects(ContainerType::INLINE_SIZE) {
111 let physical_axis = if wm.is_vertical() {
112 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
113 } else {
114 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
115 };
116 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
117 } else {
118 FeatureFlags::empty()
119 }
120}
121
122enum TraversalResult<T> {
123 InProgress,
124 StopTraversal,
125 Done(T),
126}
127
128fn traverse_container<E, F, R>(
129 mut e: E,
130 originating_element_style: Option<&ComputedValues>,
131 evaluator: F,
132) -> Option<(E, R)>
133where
134 E: TElement,
135 F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>,
136{
137 if originating_element_style.is_some() {
138 match evaluator(e, originating_element_style) {
139 TraversalResult::InProgress => {},
140 TraversalResult::StopTraversal => return None,
141 TraversalResult::Done(result) => return Some((e, result)),
142 }
143 }
144 while let Some(element) = e.traversal_parent() {
145 match evaluator(element, None) {
146 TraversalResult::InProgress => {},
147 TraversalResult::StopTraversal => return None,
148 TraversalResult::Done(result) => return Some((element, result)),
149 }
150 e = element;
151 }
152
153 None
154}
155
156impl ContainerCondition {
157 #[inline]
159 pub fn name(&self) -> &ContainerName {
160 &self.name
161 }
162 #[inline]
164 pub fn query_condition(&self) -> Option<&QueryCondition> {
165 self.condition.as_ref()
166 }
167 pub fn parse<'a>(
169 context: &ParserContext,
170 input: &mut Parser<'a, '_>,
171 ) -> Result<Self, ParseError<'a>> {
172 let name = input
173 .try_parse(|input| ContainerName::parse_for_query(context, input))
174 .ok()
175 .unwrap_or_else(ContainerName::none);
176 let condition = input
177 .try_parse(|input| QueryCondition::parse(context, input, FeatureType::Container))
178 .ok();
179 if condition.is_none() && name.is_none() {
180 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
181 }
182 let flags = condition
183 .as_ref()
184 .map_or(FeatureFlags::empty(), |c| c.cumulative_flags());
185 Ok(Self {
186 name,
187 condition,
188 flags,
189 })
190 }
191
192 fn valid_container_info<E>(
193 &self,
194 potential_container: E,
195 originating_element_style: Option<&ComputedValues>,
196 ) -> TraversalResult<ContainerLookupResult<E>>
197 where
198 E: TElement,
199 {
200 let data;
201 let style = match originating_element_style {
202 Some(s) => s,
203 None => {
204 data = match potential_container.borrow_data() {
205 Some(d) => d,
206 None => return TraversalResult::InProgress,
207 };
208 &**data.styles.primary()
209 },
210 };
211 let wm = style.writing_mode;
212 let box_style = style.get_box();
213
214 let container_type = box_style.clone_container_type();
216 let available_axes = container_type_axes(container_type, wm);
217 if !available_axes.contains(self.flags.container_axes()) {
218 return TraversalResult::InProgress;
219 }
220
221 let container_name = box_style.clone_container_name();
223 for filter_name in self.name.0.iter() {
224 if !container_name.0.contains(filter_name) {
225 return TraversalResult::InProgress;
226 }
227 }
228
229 let size = potential_container.query_container_size(&box_style.clone_display());
230 let style = style.to_arc();
231 TraversalResult::Done(ContainerLookupResult {
232 element: potential_container,
233 info: ContainerInfo {
234 size,
235 wm,
236 inherited_style: {
237 potential_container.traversal_parent().and_then(|parent| {
238 parent
239 .borrow_data()
240 .and_then(|data| data.styles.get_primary().cloned())
241 })
242 },
243 },
244 style,
245 })
246 }
247
248 pub fn find_container<E>(
250 &self,
251 e: E,
252 originating_element_style: Option<&ComputedValues>,
253 ) -> Option<ContainerLookupResult<E>>
254 where
255 E: TElement,
256 {
257 match traverse_container(
258 e,
259 originating_element_style,
260 |element, originating_element_style| {
261 self.valid_container_info(element, originating_element_style)
262 },
263 ) {
264 Some((_, result)) => Some(result),
265 None => None,
266 }
267 }
268
269 pub fn matches<E>(
271 &self,
272 stylist: &Stylist,
273 element: E,
274 originating_element_style: Option<&ComputedValues>,
275 invalidation_flags: &mut ComputedValueFlags,
276 ) -> KleeneValue
277 where
278 E: TElement,
279 {
280 let result = self.find_container(element, originating_element_style);
281 let condition = match self.condition {
282 Some(ref c) => c,
283 None => {
284 return KleeneValue::from(result.is_some());
287 },
288 };
289 if self.flags.contains(FeatureFlags::STYLE) {
295 invalidation_flags.insert(ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY);
296 }
297 let (container, info) = match result {
298 Some(r) => (r.element, (r.info, r.style)),
299 None => {
300 return KleeneValue::False;
303 },
304 };
305 let size_query_container_lookup = ContainerSizeQuery::for_element(
308 container, None, false,
309 );
310 let mut attribute_tracker = AttributeTracker::new(&container);
311 Context::for_container_query_evaluation(
312 stylist.device(),
313 Some(stylist),
314 Some(info),
315 size_query_container_lookup,
316 &container,
317 |context| {
318 let matches = condition.matches(
319 context,
320 &mut CustomMediaEvaluator::none(),
321 &mut attribute_tracker,
322 );
323 let flags = context.style().flags();
324 if flags.contains(ComputedValueFlags::USES_VIEWPORT_UNITS) {
325 invalidation_flags
328 .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
329 }
330 if flags.contains(ComputedValueFlags::USES_FONT_RELATIVE_UNITS) {
331 invalidation_flags
332 .insert(ComputedValueFlags::USES_FONT_RELATIVE_UNITS_ON_CONTAINER_QUERIES);
333 }
334 if flags.intersects(ComputedValueFlags::tree_counting_function_flags()) {
335 container.apply_selector_flags(ElementSelectorFlags::HAS_SLOW_SELECTOR);
340 }
341 matches
342 },
343 )
344 }
345}
346
347#[derive(Clone)]
349pub struct ContainerInfo {
350 size: Size2D<Option<Au>>,
351 wm: WritingMode,
352 inherited_style: Option<Arc<ComputedValues>>,
353}
354
355impl ContainerInfo {
356 fn size(&self) -> Option<Size2D<Au>> {
357 Some(Size2D::new(self.size.width?, self.size.height?))
358 }
359
360 pub fn inherited_style(&self) -> Option<&ComputedValues> {
362 self.inherited_style.as_deref()
363 }
364}
365
366fn eval_width(context: &Context) -> Option<CSSPixelLength> {
367 let info = context.container_info.as_ref()?;
368 Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
369}
370
371fn eval_height(context: &Context) -> Option<CSSPixelLength> {
372 let info = context.container_info.as_ref()?;
373 Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
374}
375
376fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
377 let info = context.container_info.as_ref()?;
378 Some(CSSPixelLength::new(
379 LogicalSize::from_physical(info.wm, info.size)
380 .inline?
381 .to_f32_px(),
382 ))
383}
384
385fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
386 let info = context.container_info.as_ref()?;
387 Some(CSSPixelLength::new(
388 LogicalSize::from_physical(info.wm, info.size)
389 .block?
390 .to_f32_px(),
391 ))
392}
393
394fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
395 let info = context.container_info.as_ref()?;
396 Some(Ratio::new(
397 info.size.width?.0 as f32,
398 info.size.height?.0 as f32,
399 ))
400}
401
402fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
403 let size = match context.container_info.as_ref().and_then(|info| info.size()) {
404 Some(size) => size,
405 None => return KleeneValue::Unknown,
406 };
407 KleeneValue::from(Orientation::eval(size, value))
408}
409
410pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
414 feature!(
415 atom!("width"),
416 AllowsRanges::Yes,
417 Evaluator::OptionalLength(eval_width),
418 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
419 ),
420 feature!(
421 atom!("height"),
422 AllowsRanges::Yes,
423 Evaluator::OptionalLength(eval_height),
424 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
425 ),
426 feature!(
427 atom!("inline-size"),
428 AllowsRanges::Yes,
429 Evaluator::OptionalLength(eval_inline_size),
430 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
431 ),
432 feature!(
433 atom!("block-size"),
434 AllowsRanges::Yes,
435 Evaluator::OptionalLength(eval_block_size),
436 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
437 ),
438 feature!(
439 atom!("aspect-ratio"),
440 AllowsRanges::Yes,
441 Evaluator::OptionalNumberRatio(eval_aspect_ratio),
442 FeatureFlags::from_bits_truncate(
445 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
446 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
447 ),
448 ),
449 feature!(
450 atom!("orientation"),
451 AllowsRanges::No,
452 keyword_evaluator!(eval_orientation, Orientation),
453 FeatureFlags::from_bits_truncate(
454 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
455 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
456 ),
457 ),
458];
459
460#[derive(Copy, Clone, Default)]
464pub struct ContainerSizeQueryResult {
465 width: Option<Au>,
466 height: Option<Au>,
467}
468
469impl ContainerSizeQueryResult {
470 fn get_viewport_size(context: &Context) -> Size2D<Au> {
471 use crate::values::specified::ViewportVariant;
472 context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
473 }
474
475 fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
476 LogicalSize::from_physical(
477 context.builder.writing_mode,
478 Self::get_viewport_size(context),
479 )
480 }
481
482 pub fn get_container_inline_size(&self, context: &Context) -> Au {
484 if context.builder.writing_mode.is_horizontal() {
485 if let Some(w) = self.width {
486 return w;
487 }
488 } else {
489 if let Some(h) = self.height {
490 return h;
491 }
492 }
493 Self::get_logical_viewport_size(context).inline
494 }
495
496 pub fn get_container_block_size(&self, context: &Context) -> Au {
498 if context.builder.writing_mode.is_horizontal() {
499 self.get_container_height(context)
500 } else {
501 self.get_container_width(context)
502 }
503 }
504
505 pub fn get_container_width(&self, context: &Context) -> Au {
507 if let Some(w) = self.width {
508 return w;
509 }
510 Self::get_viewport_size(context).width
511 }
512
513 pub fn get_container_height(&self, context: &Context) -> Au {
515 if let Some(h) = self.height {
516 return h;
517 }
518 Self::get_viewport_size(context).height
519 }
520
521 fn merge(self, new_result: Self) -> Self {
523 let mut result = self;
524 if let Some(width) = new_result.width {
525 result.width.get_or_insert(width);
526 }
527 if let Some(height) = new_result.height {
528 result.height.get_or_insert(height);
529 }
530 result
531 }
532
533 fn is_complete(&self) -> bool {
534 self.width.is_some() && self.height.is_some()
535 }
536}
537
538pub enum ContainerSizeQuery<'a> {
540 NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
542 Evaluated(ContainerSizeQueryResult),
544}
545
546impl<'a> ContainerSizeQuery<'a> {
547 fn evaluate_potential_size_container<E>(
548 e: E,
549 originating_element_style: Option<&ComputedValues>,
550 ) -> TraversalResult<ContainerSizeQueryResult>
551 where
552 E: TElement,
553 {
554 let data;
555 let style = match originating_element_style {
556 Some(s) => s,
557 None => {
558 data = match e.borrow_data() {
559 Some(d) => d,
560 None => return TraversalResult::InProgress,
561 };
562 &**data.styles.primary()
563 },
564 };
565 if !style
566 .flags
567 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
568 {
569 return TraversalResult::StopTraversal;
571 }
572
573 let wm = style.writing_mode;
574 let box_style = style.get_box();
575
576 let container_type = box_style.clone_container_type();
577 let size = e.query_container_size(&box_style.clone_display());
578 if container_type.intersects(ContainerType::SIZE) {
579 TraversalResult::Done(ContainerSizeQueryResult {
580 width: size.width,
581 height: size.height,
582 })
583 } else if container_type.intersects(ContainerType::INLINE_SIZE) {
584 if wm.is_horizontal() {
585 TraversalResult::Done(ContainerSizeQueryResult {
586 width: size.width,
587 height: None,
588 })
589 } else {
590 TraversalResult::Done(ContainerSizeQueryResult {
591 width: None,
592 height: size.height,
593 })
594 }
595 } else {
596 TraversalResult::InProgress
597 }
598 }
599
600 fn lookup<E>(
602 element: E,
603 originating_element_style: Option<&ComputedValues>,
604 ) -> ContainerSizeQueryResult
605 where
606 E: TElement + 'a,
607 {
608 match traverse_container(
609 element,
610 originating_element_style,
611 |e, originating_element_style| {
612 Self::evaluate_potential_size_container(e, originating_element_style)
613 },
614 ) {
615 Some((container, result)) => {
616 if result.is_complete() {
617 result
618 } else {
619 result.merge(Self::lookup(container, None))
621 }
622 },
623 None => ContainerSizeQueryResult::default(),
624 }
625 }
626
627 pub fn for_element<E>(
629 element: E,
630 known_parent_style: Option<&'a ComputedValues>,
631 is_pseudo: bool,
632 ) -> Self
633 where
634 E: TElement + 'a,
635 {
636 let parent;
637 let data;
638 let parent_style = match known_parent_style {
639 Some(s) => Some(s),
640 None => {
641 parent = match element.traversal_parent() {
643 Some(parent) => parent,
644 None => return Self::none(),
645 };
646 data = parent.borrow_data();
647 data.as_ref().map(|data| &**data.styles.primary())
648 },
649 };
650
651 let should_traverse = parent_style.map_or(true, |s| {
654 s.flags
655 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
656 });
657 if !should_traverse {
658 return Self::none();
659 }
660 return Self::NotEvaluated(Box::new(move || {
661 Self::lookup(element, if is_pseudo { known_parent_style } else { None })
662 }));
663 }
664
665 pub fn for_option_element<E>(
667 element: Option<E>,
668 known_parent_style: Option<&'a ComputedValues>,
669 is_pseudo: bool,
670 ) -> Self
671 where
672 E: TElement + 'a,
673 {
674 if let Some(e) = element {
675 Self::for_element(e, known_parent_style, is_pseudo)
676 } else {
677 Self::none()
678 }
679 }
680
681 pub fn none() -> Self {
683 ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
684 }
685
686 pub fn get(&mut self) -> ContainerSizeQueryResult {
688 match self {
689 Self::NotEvaluated(lookup) => {
690 *self = Self::Evaluated((lookup)());
691 match self {
692 Self::Evaluated(info) => *info,
693 _ => unreachable!("Just evaluated but not set?"),
694 }
695 },
696 Self::Evaluated(info) => *info,
697 }
698 }
699}