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