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