1use crate::computed_value_flags::ComputedValueFlags;
10use crate::derives::*;
11use crate::dom::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::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
34
35#[derive(Debug, ToShmem)]
37pub struct ContainerRule {
38 pub condition: Arc<ContainerCondition>,
40 pub rules: Arc<Locked<CssRules>>,
42 pub source_location: SourceLocation,
44}
45
46impl ContainerRule {
47 pub fn query_condition(&self) -> Option<&QueryCondition> {
49 self.condition.condition.as_ref()
50 }
51
52 pub fn container_name(&self) -> &ContainerName {
54 &self.condition.name
55 }
56
57 #[cfg(feature = "gecko")]
59 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
60 self.rules.unconditional_shallow_size_of(ops)
62 + self.rules.read_with(guard).size_of(guard, ops)
63 }
64}
65
66impl DeepCloneWithLock for ContainerRule {
67 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
68 let rules = self.rules.read_with(guard);
69 Self {
70 condition: self.condition.clone(),
71 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
72 source_location: self.source_location.clone(),
73 }
74 }
75}
76
77impl ToCssWithGuard for ContainerRule {
78 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
79 dest.write_str("@container ")?;
80 {
81 let mut writer = CssWriter::new(dest);
82 if !self.condition.name.is_none() {
83 self.condition.name.to_css(&mut writer)?;
84 if self.condition.condition.is_some() {
85 writer.write_char(' ')?;
86 }
87 }
88 if let Some(ref condition) = self.condition.condition {
89 condition.to_css(&mut writer)?;
90 }
91 }
92 self.rules.read_with(guard).to_css_block(guard, dest)
93 }
94}
95
96#[derive(Debug, ToShmem, ToCss)]
98pub struct ContainerCondition {
99 #[css(skip_if = "ContainerName::is_none")]
100 name: ContainerName,
101 condition: Option<QueryCondition>,
102 #[css(skip)]
103 flags: FeatureFlags,
104}
105
106pub struct ContainerLookupResult<E> {
108 pub element: E,
110 pub info: ContainerInfo,
112 pub style: Arc<ComputedValues>,
114}
115
116fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
117 if ty_.intersects(ContainerType::SIZE) {
118 FeatureFlags::all_container_axes()
119 } else if ty_.intersects(ContainerType::INLINE_SIZE) {
120 let physical_axis = if wm.is_vertical() {
121 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
122 } else {
123 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
124 };
125 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
126 } else {
127 FeatureFlags::empty()
128 }
129}
130
131enum TraversalResult<T> {
132 InProgress,
133 StopTraversal,
134 Done(T),
135}
136
137fn traverse_container<E, F, R>(
138 mut e: E,
139 originating_element_style: Option<&ComputedValues>,
140 evaluator: F,
141) -> Option<(E, R)>
142where
143 E: TElement,
144 F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>,
145{
146 if originating_element_style.is_some() {
147 match evaluator(e, originating_element_style) {
148 TraversalResult::InProgress => {},
149 TraversalResult::StopTraversal => return None,
150 TraversalResult::Done(result) => return Some((e, result)),
151 }
152 }
153 while let Some(element) = e.traversal_parent() {
154 match evaluator(element, None) {
155 TraversalResult::InProgress => {},
156 TraversalResult::StopTraversal => return None,
157 TraversalResult::Done(result) => return Some((element, result)),
158 }
159 e = element;
160 }
161
162 None
163}
164
165impl ContainerCondition {
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(crate) 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 let (container, info) = match result {
289 Some(r) => (r.element, (r.info, r.style)),
290 None => {
291 return KleeneValue::False;
294 },
295 };
296 let size_query_container_lookup = ContainerSizeQuery::for_element(
299 container, None, false,
300 );
301 Context::for_container_query_evaluation(
302 stylist.device(),
303 Some(stylist),
304 Some(info),
305 size_query_container_lookup,
306 |context| {
307 let matches = condition.matches(context, &mut CustomMediaEvaluator::none());
308 if context
309 .style()
310 .flags()
311 .contains(ComputedValueFlags::USES_VIEWPORT_UNITS)
312 {
313 invalidation_flags
316 .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
317 }
318 matches
319 },
320 )
321 }
322}
323
324#[derive(Clone)]
326pub struct ContainerInfo {
327 size: Size2D<Option<Au>>,
328 wm: WritingMode,
329 inherited_style: Option<Arc<ComputedValues>>,
330}
331
332impl ContainerInfo {
333 fn size(&self) -> Option<Size2D<Au>> {
334 Some(Size2D::new(self.size.width?, self.size.height?))
335 }
336
337 pub fn inherited_style(&self) -> Option<&ComputedValues> {
339 self.inherited_style.as_deref()
340 }
341}
342
343fn eval_width(context: &Context) -> Option<CSSPixelLength> {
344 let info = context.container_info.as_ref()?;
345 Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
346}
347
348fn eval_height(context: &Context) -> Option<CSSPixelLength> {
349 let info = context.container_info.as_ref()?;
350 Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
351}
352
353fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
354 let info = context.container_info.as_ref()?;
355 Some(CSSPixelLength::new(
356 LogicalSize::from_physical(info.wm, info.size)
357 .inline?
358 .to_f32_px(),
359 ))
360}
361
362fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
363 let info = context.container_info.as_ref()?;
364 Some(CSSPixelLength::new(
365 LogicalSize::from_physical(info.wm, info.size)
366 .block?
367 .to_f32_px(),
368 ))
369}
370
371fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
372 let info = context.container_info.as_ref()?;
373 Some(Ratio::new(
374 info.size.width?.0 as f32,
375 info.size.height?.0 as f32,
376 ))
377}
378
379fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
380 let size = match context.container_info.as_ref().and_then(|info| info.size()) {
381 Some(size) => size,
382 None => return KleeneValue::Unknown,
383 };
384 KleeneValue::from(Orientation::eval(size, value))
385}
386
387pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
391 feature!(
392 atom!("width"),
393 AllowsRanges::Yes,
394 Evaluator::OptionalLength(eval_width),
395 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
396 ),
397 feature!(
398 atom!("height"),
399 AllowsRanges::Yes,
400 Evaluator::OptionalLength(eval_height),
401 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
402 ),
403 feature!(
404 atom!("inline-size"),
405 AllowsRanges::Yes,
406 Evaluator::OptionalLength(eval_inline_size),
407 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
408 ),
409 feature!(
410 atom!("block-size"),
411 AllowsRanges::Yes,
412 Evaluator::OptionalLength(eval_block_size),
413 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
414 ),
415 feature!(
416 atom!("aspect-ratio"),
417 AllowsRanges::Yes,
418 Evaluator::OptionalNumberRatio(eval_aspect_ratio),
419 FeatureFlags::from_bits_truncate(
422 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
423 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
424 ),
425 ),
426 feature!(
427 atom!("orientation"),
428 AllowsRanges::No,
429 keyword_evaluator!(eval_orientation, Orientation),
430 FeatureFlags::from_bits_truncate(
431 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
432 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
433 ),
434 ),
435];
436
437#[derive(Copy, Clone, Default)]
441pub struct ContainerSizeQueryResult {
442 width: Option<Au>,
443 height: Option<Au>,
444}
445
446impl ContainerSizeQueryResult {
447 fn get_viewport_size(context: &Context) -> Size2D<Au> {
448 use crate::values::specified::ViewportVariant;
449 context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
450 }
451
452 fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
453 LogicalSize::from_physical(
454 context.builder.writing_mode,
455 Self::get_viewport_size(context),
456 )
457 }
458
459 pub fn get_container_inline_size(&self, context: &Context) -> Au {
461 if context.builder.writing_mode.is_horizontal() {
462 if let Some(w) = self.width {
463 return w;
464 }
465 } else {
466 if let Some(h) = self.height {
467 return h;
468 }
469 }
470 Self::get_logical_viewport_size(context).inline
471 }
472
473 pub fn get_container_block_size(&self, context: &Context) -> Au {
475 if context.builder.writing_mode.is_horizontal() {
476 self.get_container_height(context)
477 } else {
478 self.get_container_width(context)
479 }
480 }
481
482 pub fn get_container_width(&self, context: &Context) -> Au {
484 if let Some(w) = self.width {
485 return w;
486 }
487 Self::get_viewport_size(context).width
488 }
489
490 pub fn get_container_height(&self, context: &Context) -> Au {
492 if let Some(h) = self.height {
493 return h;
494 }
495 Self::get_viewport_size(context).height
496 }
497
498 fn merge(self, new_result: Self) -> Self {
500 let mut result = self;
501 if let Some(width) = new_result.width {
502 result.width.get_or_insert(width);
503 }
504 if let Some(height) = new_result.height {
505 result.height.get_or_insert(height);
506 }
507 result
508 }
509
510 fn is_complete(&self) -> bool {
511 self.width.is_some() && self.height.is_some()
512 }
513}
514
515pub enum ContainerSizeQuery<'a> {
517 NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
519 Evaluated(ContainerSizeQueryResult),
521}
522
523impl<'a> ContainerSizeQuery<'a> {
524 fn evaluate_potential_size_container<E>(
525 e: E,
526 originating_element_style: Option<&ComputedValues>,
527 ) -> TraversalResult<ContainerSizeQueryResult>
528 where
529 E: TElement,
530 {
531 let data;
532 let style = match originating_element_style {
533 Some(s) => s,
534 None => {
535 data = match e.borrow_data() {
536 Some(d) => d,
537 None => return TraversalResult::InProgress,
538 };
539 &**data.styles.primary()
540 },
541 };
542 if !style
543 .flags
544 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
545 {
546 return TraversalResult::StopTraversal;
548 }
549
550 let wm = style.writing_mode;
551 let box_style = style.get_box();
552
553 let container_type = box_style.clone_container_type();
554 let size = e.query_container_size(&box_style.clone_display());
555 if container_type.intersects(ContainerType::SIZE) {
556 TraversalResult::Done(ContainerSizeQueryResult {
557 width: size.width,
558 height: size.height,
559 })
560 } else if container_type.intersects(ContainerType::INLINE_SIZE) {
561 if wm.is_horizontal() {
562 TraversalResult::Done(ContainerSizeQueryResult {
563 width: size.width,
564 height: None,
565 })
566 } else {
567 TraversalResult::Done(ContainerSizeQueryResult {
568 width: None,
569 height: size.height,
570 })
571 }
572 } else {
573 TraversalResult::InProgress
574 }
575 }
576
577 fn lookup<E>(
579 element: E,
580 originating_element_style: Option<&ComputedValues>,
581 ) -> ContainerSizeQueryResult
582 where
583 E: TElement + 'a,
584 {
585 match traverse_container(
586 element,
587 originating_element_style,
588 |e, originating_element_style| {
589 Self::evaluate_potential_size_container(e, originating_element_style)
590 },
591 ) {
592 Some((container, result)) => {
593 if result.is_complete() {
594 result
595 } else {
596 result.merge(Self::lookup(container, None))
598 }
599 },
600 None => ContainerSizeQueryResult::default(),
601 }
602 }
603
604 pub fn for_element<E>(
606 element: E,
607 known_parent_style: Option<&'a ComputedValues>,
608 is_pseudo: bool,
609 ) -> Self
610 where
611 E: TElement + 'a,
612 {
613 let parent;
614 let data;
615 let parent_style = match known_parent_style {
616 Some(s) => Some(s),
617 None => {
618 parent = match element.traversal_parent() {
620 Some(parent) => parent,
621 None => return Self::none(),
622 };
623 data = parent.borrow_data();
624 data.as_ref().map(|data| &**data.styles.primary())
625 },
626 };
627
628 let should_traverse = parent_style.map_or(true, |s| {
631 s.flags
632 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
633 });
634 if !should_traverse {
635 return Self::none();
636 }
637 return Self::NotEvaluated(Box::new(move || {
638 Self::lookup(element, if is_pseudo { known_parent_style } else { None })
639 }));
640 }
641
642 pub fn for_option_element<E>(
644 element: Option<E>,
645 known_parent_style: Option<&'a ComputedValues>,
646 is_pseudo: bool,
647 ) -> Self
648 where
649 E: TElement + 'a,
650 {
651 if let Some(e) = element {
652 Self::for_element(e, known_parent_style, is_pseudo)
653 } else {
654 Self::none()
655 }
656 }
657
658 pub fn none() -> Self {
660 ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
661 }
662
663 pub fn get(&mut self) -> ContainerSizeQueryResult {
665 match self {
666 Self::NotEvaluated(lookup) => {
667 *self = Self::Evaluated((lookup)());
668 match self {
669 Self::Evaluated(info) => *info,
670 _ => unreachable!("Just evaluated but not set?"),
671 }
672 },
673 Self::Evaluated(info) => *info,
674 }
675 }
676}