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, 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) -> &QueryCondition {
49 &self.condition.condition
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 writer.write_char(' ')?;
85 }
86 self.condition.condition.to_css(&mut writer)?;
87 }
88 self.rules.read_with(guard).to_css_block(guard, dest)
89 }
90}
91
92#[derive(Debug, ToShmem, ToCss)]
94pub struct ContainerCondition {
95 #[css(skip_if = "ContainerName::is_none")]
96 name: ContainerName,
97 condition: QueryCondition,
98 #[css(skip)]
99 flags: FeatureFlags,
100}
101
102pub struct ContainerLookupResult<E> {
104 pub element: E,
106 pub info: ContainerInfo,
108 pub style: Arc<ComputedValues>,
110}
111
112fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
113 if ty_.intersects(ContainerType::SIZE) {
114 FeatureFlags::all_container_axes()
115 } else if ty_.intersects(ContainerType::INLINE_SIZE) {
116 let physical_axis = if wm.is_vertical() {
117 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
118 } else {
119 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
120 };
121 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
122 } else {
123 FeatureFlags::empty()
124 }
125}
126
127enum TraversalResult<T> {
128 InProgress,
129 StopTraversal,
130 Done(T),
131}
132
133fn traverse_container<E, F, R>(
134 mut e: E,
135 originating_element_style: Option<&ComputedValues>,
136 evaluator: F,
137) -> Option<(E, R)>
138where
139 E: TElement,
140 F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>,
141{
142 if originating_element_style.is_some() {
143 match evaluator(e, originating_element_style) {
144 TraversalResult::InProgress => {},
145 TraversalResult::StopTraversal => return None,
146 TraversalResult::Done(result) => return Some((e, result)),
147 }
148 }
149 while let Some(element) = e.traversal_parent() {
150 match evaluator(element, None) {
151 TraversalResult::InProgress => {},
152 TraversalResult::StopTraversal => return None,
153 TraversalResult::Done(result) => return Some((element, result)),
154 }
155 e = element;
156 }
157
158 None
159}
160
161impl ContainerCondition {
162 pub fn parse<'a>(
164 context: &ParserContext,
165 input: &mut Parser<'a, '_>,
166 ) -> Result<Self, ParseError<'a>> {
167 let name = input
168 .try_parse(|input| ContainerName::parse_for_query(context, input))
169 .ok()
170 .unwrap_or_else(ContainerName::none);
171 let condition = QueryCondition::parse(context, input, FeatureType::Container)?;
172 let flags = condition.cumulative_flags();
173 Ok(Self {
174 name,
175 condition,
176 flags,
177 })
178 }
179
180 fn valid_container_info<E>(
181 &self,
182 potential_container: E,
183 originating_element_style: Option<&ComputedValues>,
184 ) -> TraversalResult<ContainerLookupResult<E>>
185 where
186 E: TElement,
187 {
188 let data;
189 let style = match originating_element_style {
190 Some(s) => s,
191 None => {
192 data = match potential_container.borrow_data() {
193 Some(d) => d,
194 None => return TraversalResult::InProgress,
195 };
196 &**data.styles.primary()
197 },
198 };
199 let wm = style.writing_mode;
200 let box_style = style.get_box();
201
202 let container_type = box_style.clone_container_type();
204 let available_axes = container_type_axes(container_type, wm);
205 if !available_axes.contains(self.flags.container_axes()) {
206 return TraversalResult::InProgress;
207 }
208
209 let container_name = box_style.clone_container_name();
211 for filter_name in self.name.0.iter() {
212 if !container_name.0.contains(filter_name) {
213 return TraversalResult::InProgress;
214 }
215 }
216
217 let size = potential_container.query_container_size(&box_style.clone_display());
218 let style = style.to_arc();
219 TraversalResult::Done(ContainerLookupResult {
220 element: potential_container,
221 info: ContainerInfo { size, wm },
222 style,
223 })
224 }
225
226 pub fn find_container<E>(
228 &self,
229 e: E,
230 originating_element_style: Option<&ComputedValues>,
231 ) -> Option<ContainerLookupResult<E>>
232 where
233 E: TElement,
234 {
235 match traverse_container(
236 e,
237 originating_element_style,
238 |element, originating_element_style| {
239 self.valid_container_info(element, originating_element_style)
240 },
241 ) {
242 Some((_, result)) => Some(result),
243 None => None,
244 }
245 }
246
247 pub(crate) fn matches<E>(
249 &self,
250 stylist: &Stylist,
251 element: E,
252 originating_element_style: Option<&ComputedValues>,
253 invalidation_flags: &mut ComputedValueFlags,
254 ) -> KleeneValue
255 where
256 E: TElement,
257 {
258 let result = self.find_container(element, originating_element_style);
259 let (container, info) = match result {
260 Some(r) => (Some(r.element), Some((r.info, r.style))),
261 None => (None, None),
262 };
263 let size_query_container_lookup = ContainerSizeQuery::for_option_element(
266 container, None, false,
267 );
268 Context::for_container_query_evaluation(
269 stylist.device(),
270 Some(stylist),
271 info,
272 size_query_container_lookup,
273 |context| {
274 let matches = self
275 .condition
276 .matches(context, &mut CustomMediaEvaluator::none());
277 if context
278 .style()
279 .flags()
280 .contains(ComputedValueFlags::USES_VIEWPORT_UNITS)
281 {
282 invalidation_flags
285 .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
286 }
287 matches
288 },
289 )
290 }
291}
292
293#[derive(Copy, Clone)]
295pub struct ContainerInfo {
296 size: Size2D<Option<Au>>,
297 wm: WritingMode,
298}
299
300impl ContainerInfo {
301 fn size(&self) -> Option<Size2D<Au>> {
302 Some(Size2D::new(self.size.width?, self.size.height?))
303 }
304}
305
306fn eval_width(context: &Context) -> Option<CSSPixelLength> {
307 let info = context.container_info.as_ref()?;
308 Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
309}
310
311fn eval_height(context: &Context) -> Option<CSSPixelLength> {
312 let info = context.container_info.as_ref()?;
313 Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
314}
315
316fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
317 let info = context.container_info.as_ref()?;
318 Some(CSSPixelLength::new(
319 LogicalSize::from_physical(info.wm, info.size)
320 .inline?
321 .to_f32_px(),
322 ))
323}
324
325fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
326 let info = context.container_info.as_ref()?;
327 Some(CSSPixelLength::new(
328 LogicalSize::from_physical(info.wm, info.size)
329 .block?
330 .to_f32_px(),
331 ))
332}
333
334fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
335 let info = context.container_info.as_ref()?;
336 Some(Ratio::new(
337 info.size.width?.0 as f32,
338 info.size.height?.0 as f32,
339 ))
340}
341
342fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
343 let size = match context.container_info.as_ref().and_then(|info| info.size()) {
344 Some(size) => size,
345 None => return KleeneValue::Unknown,
346 };
347 KleeneValue::from(Orientation::eval(size, value))
348}
349
350pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
354 feature!(
355 atom!("width"),
356 AllowsRanges::Yes,
357 Evaluator::OptionalLength(eval_width),
358 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
359 ),
360 feature!(
361 atom!("height"),
362 AllowsRanges::Yes,
363 Evaluator::OptionalLength(eval_height),
364 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
365 ),
366 feature!(
367 atom!("inline-size"),
368 AllowsRanges::Yes,
369 Evaluator::OptionalLength(eval_inline_size),
370 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
371 ),
372 feature!(
373 atom!("block-size"),
374 AllowsRanges::Yes,
375 Evaluator::OptionalLength(eval_block_size),
376 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
377 ),
378 feature!(
379 atom!("aspect-ratio"),
380 AllowsRanges::Yes,
381 Evaluator::OptionalNumberRatio(eval_aspect_ratio),
382 FeatureFlags::from_bits_truncate(
385 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
386 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
387 ),
388 ),
389 feature!(
390 atom!("orientation"),
391 AllowsRanges::No,
392 keyword_evaluator!(eval_orientation, Orientation),
393 FeatureFlags::from_bits_truncate(
394 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits()
395 | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
396 ),
397 ),
398];
399
400#[derive(Copy, Clone, Default)]
404pub struct ContainerSizeQueryResult {
405 width: Option<Au>,
406 height: Option<Au>,
407}
408
409impl ContainerSizeQueryResult {
410 fn get_viewport_size(context: &Context) -> Size2D<Au> {
411 use crate::values::specified::ViewportVariant;
412 context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
413 }
414
415 fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
416 LogicalSize::from_physical(
417 context.builder.writing_mode,
418 Self::get_viewport_size(context),
419 )
420 }
421
422 pub fn get_container_inline_size(&self, context: &Context) -> Au {
424 if context.builder.writing_mode.is_horizontal() {
425 if let Some(w) = self.width {
426 return w;
427 }
428 } else {
429 if let Some(h) = self.height {
430 return h;
431 }
432 }
433 Self::get_logical_viewport_size(context).inline
434 }
435
436 pub fn get_container_block_size(&self, context: &Context) -> Au {
438 if context.builder.writing_mode.is_horizontal() {
439 self.get_container_height(context)
440 } else {
441 self.get_container_width(context)
442 }
443 }
444
445 pub fn get_container_width(&self, context: &Context) -> Au {
447 if let Some(w) = self.width {
448 return w;
449 }
450 Self::get_viewport_size(context).width
451 }
452
453 pub fn get_container_height(&self, context: &Context) -> Au {
455 if let Some(h) = self.height {
456 return h;
457 }
458 Self::get_viewport_size(context).height
459 }
460
461 fn merge(self, new_result: Self) -> Self {
463 let mut result = self;
464 if let Some(width) = new_result.width {
465 result.width.get_or_insert(width);
466 }
467 if let Some(height) = new_result.height {
468 result.height.get_or_insert(height);
469 }
470 result
471 }
472
473 fn is_complete(&self) -> bool {
474 self.width.is_some() && self.height.is_some()
475 }
476}
477
478pub enum ContainerSizeQuery<'a> {
480 NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
482 Evaluated(ContainerSizeQueryResult),
484}
485
486impl<'a> ContainerSizeQuery<'a> {
487 fn evaluate_potential_size_container<E>(
488 e: E,
489 originating_element_style: Option<&ComputedValues>,
490 ) -> TraversalResult<ContainerSizeQueryResult>
491 where
492 E: TElement,
493 {
494 let data;
495 let style = match originating_element_style {
496 Some(s) => s,
497 None => {
498 data = match e.borrow_data() {
499 Some(d) => d,
500 None => return TraversalResult::InProgress,
501 };
502 &**data.styles.primary()
503 },
504 };
505 if !style
506 .flags
507 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
508 {
509 return TraversalResult::StopTraversal;
511 }
512
513 let wm = style.writing_mode;
514 let box_style = style.get_box();
515
516 let container_type = box_style.clone_container_type();
517 let size = e.query_container_size(&box_style.clone_display());
518 if container_type.intersects(ContainerType::SIZE) {
519 TraversalResult::Done(ContainerSizeQueryResult {
520 width: size.width,
521 height: size.height,
522 })
523 } else if container_type.intersects(ContainerType::INLINE_SIZE) {
524 if wm.is_horizontal() {
525 TraversalResult::Done(ContainerSizeQueryResult {
526 width: size.width,
527 height: None,
528 })
529 } else {
530 TraversalResult::Done(ContainerSizeQueryResult {
531 width: None,
532 height: size.height,
533 })
534 }
535 } else {
536 TraversalResult::InProgress
537 }
538 }
539
540 fn lookup<E>(
542 element: E,
543 originating_element_style: Option<&ComputedValues>,
544 ) -> ContainerSizeQueryResult
545 where
546 E: TElement + 'a,
547 {
548 match traverse_container(
549 element,
550 originating_element_style,
551 |e, originating_element_style| {
552 Self::evaluate_potential_size_container(e, originating_element_style)
553 },
554 ) {
555 Some((container, result)) => {
556 if result.is_complete() {
557 result
558 } else {
559 result.merge(Self::lookup(container, None))
561 }
562 },
563 None => ContainerSizeQueryResult::default(),
564 }
565 }
566
567 pub fn for_element<E>(
569 element: E,
570 known_parent_style: Option<&'a ComputedValues>,
571 is_pseudo: bool,
572 ) -> Self
573 where
574 E: TElement + 'a,
575 {
576 let parent;
577 let data;
578 let parent_style = match known_parent_style {
579 Some(s) => Some(s),
580 None => {
581 parent = match element.traversal_parent() {
583 Some(parent) => parent,
584 None => return Self::none(),
585 };
586 data = parent.borrow_data();
587 data.as_ref().map(|data| &**data.styles.primary())
588 },
589 };
590
591 let should_traverse = parent_style.map_or(true, |s| {
594 s.flags
595 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
596 });
597 if !should_traverse {
598 return Self::none();
599 }
600 return Self::NotEvaluated(Box::new(move || {
601 Self::lookup(element, if is_pseudo { known_parent_style } else { None })
602 }));
603 }
604
605 pub fn for_option_element<E>(
607 element: Option<E>,
608 known_parent_style: Option<&'a ComputedValues>,
609 is_pseudo: bool,
610 ) -> Self
611 where
612 E: TElement + 'a,
613 {
614 if let Some(e) = element {
615 Self::for_element(e, known_parent_style, is_pseudo)
616 } else {
617 Self::none()
618 }
619 }
620
621 pub fn none() -> Self {
623 ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
624 }
625
626 pub fn get(&mut self) -> ContainerSizeQueryResult {
628 match self {
629 Self::NotEvaluated(lookup) => {
630 *self = Self::Evaluated((lookup)());
631 match self {
632 Self::Evaluated(info) => *info,
633 _ => unreachable!("Just evaluated but not set?"),
634 }
635 },
636 Self::Evaluated(info) => *info,
637 }
638 }
639}