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::str::CssStringWriter;
21use crate::stylesheets::CssRules;
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::{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(
68 &self,
69 lock: &SharedRwLock,
70 guard: &SharedRwLockReadGuard,
71 ) -> Self {
72 let rules = self.rules.read_with(guard);
73 Self {
74 condition: self.condition.clone(),
75 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
76 source_location: self.source_location.clone(),
77 }
78 }
79}
80
81impl ToCssWithGuard for ContainerRule {
82 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
83 dest.write_str("@container ")?;
84 {
85 let mut writer = CssWriter::new(dest);
86 if !self.condition.name.is_none() {
87 self.condition.name.to_css(&mut writer)?;
88 writer.write_char(' ')?;
89 }
90 self.condition.condition.to_css(&mut writer)?;
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: 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 match ty_ {
118 ContainerType::Size => FeatureFlags::all_container_axes(),
119 ContainerType::InlineSize => {
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 },
127 ContainerType::Normal => 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 = QueryCondition::parse(context, input, FeatureType::Container)?;
176 let flags = condition.cumulative_flags();
177 Ok(Self {
178 name,
179 condition,
180 flags,
181 })
182 }
183
184 fn valid_container_info<E>(
185 &self,
186 potential_container: E,
187 originating_element_style: Option<&ComputedValues>,
188 ) -> TraversalResult<ContainerLookupResult<E>>
189 where
190 E: TElement,
191 {
192 let data;
193 let style = match originating_element_style {
194 Some(s) => s,
195 None => {
196 data = match potential_container.borrow_data() {
197 Some(d) => d,
198 None => return TraversalResult::InProgress,
199 };
200 &**data.styles.primary()
201 },
202 };
203 let wm = style.writing_mode;
204 let box_style = style.get_box();
205
206 let container_type = box_style.clone_container_type();
208 let available_axes = container_type_axes(container_type, wm);
209 if !available_axes.contains(self.flags.container_axes()) {
210 return TraversalResult::InProgress;
211 }
212
213 let container_name = box_style.clone_container_name();
215 for filter_name in self.name.0.iter() {
216 if !container_name.0.contains(filter_name) {
217 return TraversalResult::InProgress;
218 }
219 }
220
221 let size = potential_container.query_container_size(&box_style.clone_display());
222 let style = style.to_arc();
223 TraversalResult::Done(ContainerLookupResult {
224 element: potential_container,
225 info: ContainerInfo { size, wm },
226 style,
227 })
228 }
229
230 pub fn find_container<E>(
232 &self,
233 e: E,
234 originating_element_style: Option<&ComputedValues>,
235 ) -> Option<ContainerLookupResult<E>>
236 where
237 E: TElement,
238 {
239 match traverse_container(
240 e,
241 originating_element_style,
242 |element, originating_element_style| {
243 self.valid_container_info(element, originating_element_style)
244 },
245 ) {
246 Some((_, result)) => Some(result),
247 None => None,
248 }
249 }
250
251 pub(crate) fn matches<E>(
253 &self,
254 stylist: &Stylist,
255 element: E,
256 originating_element_style: Option<&ComputedValues>,
257 invalidation_flags: &mut ComputedValueFlags,
258 ) -> KleeneValue
259 where
260 E: TElement,
261 {
262 let result = self.find_container(element, originating_element_style);
263 let (container, info) = match result {
264 Some(r) => (Some(r.element), Some((r.info, r.style))),
265 None => (None, None),
266 };
267 let size_query_container_lookup = ContainerSizeQuery::for_option_element(
270 container, None, false,
271 );
272 Context::for_container_query_evaluation(
273 stylist.device(),
274 Some(stylist),
275 info,
276 size_query_container_lookup,
277 |context| {
278 let matches = self.condition.matches(context);
279 if context
280 .style()
281 .flags()
282 .contains(ComputedValueFlags::USES_VIEWPORT_UNITS)
283 {
284 invalidation_flags
287 .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
288 }
289 matches
290 },
291 )
292 }
293}
294
295#[derive(Copy, Clone)]
297pub struct ContainerInfo {
298 size: Size2D<Option<Au>>,
299 wm: WritingMode,
300}
301
302impl ContainerInfo {
303 fn size(&self) -> Option<Size2D<Au>> {
304 Some(Size2D::new(self.size.width?, self.size.height?))
305 }
306}
307
308fn eval_width(context: &Context) -> Option<CSSPixelLength> {
309 let info = context.container_info.as_ref()?;
310 Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
311}
312
313fn eval_height(context: &Context) -> Option<CSSPixelLength> {
314 let info = context.container_info.as_ref()?;
315 Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
316}
317
318fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
319 let info = context.container_info.as_ref()?;
320 Some(CSSPixelLength::new(
321 LogicalSize::from_physical(info.wm, info.size)
322 .inline?
323 .to_f32_px(),
324 ))
325}
326
327fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
328 let info = context.container_info.as_ref()?;
329 Some(CSSPixelLength::new(
330 LogicalSize::from_physical(info.wm, info.size)
331 .block?
332 .to_f32_px(),
333 ))
334}
335
336fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
337 let info = context.container_info.as_ref()?;
338 Some(Ratio::new(
339 info.size.width?.0 as f32,
340 info.size.height?.0 as f32,
341 ))
342}
343
344fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
345 let size = match context.container_info.as_ref().and_then(|info| info.size()) {
346 Some(size) => size,
347 None => return KleeneValue::Unknown,
348 };
349 KleeneValue::from(Orientation::eval(size, value))
350}
351
352pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
356 feature!(
357 atom!("width"),
358 AllowsRanges::Yes,
359 Evaluator::OptionalLength(eval_width),
360 FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
361 ),
362 feature!(
363 atom!("height"),
364 AllowsRanges::Yes,
365 Evaluator::OptionalLength(eval_height),
366 FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
367 ),
368 feature!(
369 atom!("inline-size"),
370 AllowsRanges::Yes,
371 Evaluator::OptionalLength(eval_inline_size),
372 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
373 ),
374 feature!(
375 atom!("block-size"),
376 AllowsRanges::Yes,
377 Evaluator::OptionalLength(eval_block_size),
378 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
379 ),
380 feature!(
381 atom!("aspect-ratio"),
382 AllowsRanges::Yes,
383 Evaluator::OptionalNumberRatio(eval_aspect_ratio),
384 FeatureFlags::from_bits_truncate(
387 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() |
388 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
389 ),
390 ),
391 feature!(
392 atom!("orientation"),
393 AllowsRanges::No,
394 keyword_evaluator!(eval_orientation, Orientation),
395 FeatureFlags::from_bits_truncate(
396 FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() |
397 FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
398 ),
399 ),
400];
401
402#[derive(Copy, Clone, Default)]
406pub struct ContainerSizeQueryResult {
407 width: Option<Au>,
408 height: Option<Au>,
409}
410
411impl ContainerSizeQueryResult {
412 fn get_viewport_size(context: &Context) -> Size2D<Au> {
413 use crate::values::specified::ViewportVariant;
414 context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
415 }
416
417 fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
418 LogicalSize::from_physical(
419 context.builder.writing_mode,
420 Self::get_viewport_size(context),
421 )
422 }
423
424 pub fn get_container_inline_size(&self, context: &Context) -> Au {
426 if context.builder.writing_mode.is_horizontal() {
427 if let Some(w) = self.width {
428 return w;
429 }
430 } else {
431 if let Some(h) = self.height {
432 return h;
433 }
434 }
435 Self::get_logical_viewport_size(context).inline
436 }
437
438 pub fn get_container_block_size(&self, context: &Context) -> Au {
440 if context.builder.writing_mode.is_horizontal() {
441 self.get_container_height(context)
442 } else {
443 self.get_container_width(context)
444 }
445 }
446
447 pub fn get_container_width(&self, context: &Context) -> Au {
449 if let Some(w) = self.width {
450 return w;
451 }
452 Self::get_viewport_size(context).width
453 }
454
455 pub fn get_container_height(&self, context: &Context) -> Au {
457 if let Some(h) = self.height {
458 return h;
459 }
460 Self::get_viewport_size(context).height
461 }
462
463 fn merge(self, new_result: Self) -> Self {
465 let mut result = self;
466 if let Some(width) = new_result.width {
467 result.width.get_or_insert(width);
468 }
469 if let Some(height) = new_result.height {
470 result.height.get_or_insert(height);
471 }
472 result
473 }
474
475 fn is_complete(&self) -> bool {
476 self.width.is_some() && self.height.is_some()
477 }
478}
479
480pub enum ContainerSizeQuery<'a> {
482 NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
484 Evaluated(ContainerSizeQueryResult),
486}
487
488impl<'a> ContainerSizeQuery<'a> {
489 fn evaluate_potential_size_container<E>(
490 e: E,
491 originating_element_style: Option<&ComputedValues>,
492 ) -> TraversalResult<ContainerSizeQueryResult>
493 where
494 E: TElement,
495 {
496 let data;
497 let style = match originating_element_style {
498 Some(s) => s,
499 None => {
500 data = match e.borrow_data() {
501 Some(d) => d,
502 None => return TraversalResult::InProgress,
503 };
504 &**data.styles.primary()
505 },
506 };
507 if !style
508 .flags
509 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
510 {
511 return TraversalResult::StopTraversal;
513 }
514
515 let wm = style.writing_mode;
516 let box_style = style.get_box();
517
518 let container_type = box_style.clone_container_type();
519 let size = e.query_container_size(&box_style.clone_display());
520 match container_type {
521 ContainerType::Size => TraversalResult::Done(ContainerSizeQueryResult {
522 width: size.width,
523 height: size.height,
524 }),
525 ContainerType::InlineSize => {
526 if wm.is_horizontal() {
527 TraversalResult::Done(ContainerSizeQueryResult {
528 width: size.width,
529 height: None,
530 })
531 } else {
532 TraversalResult::Done(ContainerSizeQueryResult {
533 width: None,
534 height: size.height,
535 })
536 }
537 },
538 ContainerType::Normal => TraversalResult::InProgress,
539 }
540 }
541
542 fn lookup<E>(
544 element: E,
545 originating_element_style: Option<&ComputedValues>,
546 ) -> ContainerSizeQueryResult
547 where
548 E: TElement + 'a,
549 {
550 match traverse_container(
551 element,
552 originating_element_style,
553 |e, originating_element_style| {
554 Self::evaluate_potential_size_container(e, originating_element_style)
555 },
556 ) {
557 Some((container, result)) => {
558 if result.is_complete() {
559 result
560 } else {
561 result.merge(Self::lookup(container, None))
563 }
564 },
565 None => ContainerSizeQueryResult::default(),
566 }
567 }
568
569 pub fn for_element<E>(
571 element: E,
572 known_parent_style: Option<&'a ComputedValues>,
573 is_pseudo: bool,
574 ) -> Self
575 where
576 E: TElement + 'a,
577 {
578 let parent;
579 let data;
580 let parent_style = match known_parent_style {
581 Some(s) => Some(s),
582 None => {
583 parent = match element.traversal_parent() {
585 Some(parent) => parent,
586 None => return Self::none(),
587 };
588 data = parent.borrow_data();
589 data.as_ref().map(|data| &**data.styles.primary())
590 },
591 };
592
593 let should_traverse = parent_style.map_or(true, |s| {
596 s.flags
597 .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
598 });
599 if !should_traverse {
600 return Self::none();
601 }
602 return Self::NotEvaluated(Box::new(move || {
603 Self::lookup(element, if is_pseudo { known_parent_style } else { None })
604 }));
605 }
606
607 pub fn for_option_element<E>(
609 element: Option<E>,
610 known_parent_style: Option<&'a ComputedValues>,
611 is_pseudo: bool,
612 ) -> Self
613 where
614 E: TElement + 'a,
615 {
616 if let Some(e) = element {
617 Self::for_element(e, known_parent_style, is_pseudo)
618 } else {
619 Self::none()
620 }
621 }
622
623 pub fn none() -> Self {
625 ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
626 }
627
628 pub fn get(&mut self) -> ContainerSizeQueryResult {
630 match self {
631 Self::NotEvaluated(lookup) => {
632 *self = Self::Evaluated((lookup)());
633 match self {
634 Self::Evaluated(info) => *info,
635 _ => unreachable!("Just evaluated but not set?"),
636 }
637 },
638 Self::Evaluated(info) => *info,
639 }
640 }
641}