tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

container_rule.rs (21205B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 //! A [`@container`][container] rule.
      6 //!
      7 //! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
      8 
      9 use crate::computed_value_flags::ComputedValueFlags;
     10 use crate::derives::*;
     11 use crate::dom::TElement;
     12 use crate::logical_geometry::{LogicalSize, WritingMode};
     13 use crate::parser::ParserContext;
     14 use crate::properties::ComputedValues;
     15 use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
     16 use crate::queries::values::Orientation;
     17 use crate::queries::{FeatureType, QueryCondition};
     18 use crate::shared_lock::{
     19    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
     20 };
     21 use crate::stylesheets::{CssRules, CustomMediaEvaluator};
     22 use crate::stylist::Stylist;
     23 use crate::values::computed::{CSSPixelLength, ContainerType, Context, Ratio};
     24 use crate::values::specified::ContainerName;
     25 use app_units::Au;
     26 use cssparser::{Parser, SourceLocation};
     27 use euclid::default::Size2D;
     28 #[cfg(feature = "gecko")]
     29 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
     30 use selectors::kleene_value::KleeneValue;
     31 use servo_arc::Arc;
     32 use std::fmt::{self, Write};
     33 use style_traits::{CssStringWriter, CssWriter, ParseError, ToCss};
     34 
     35 /// A container rule.
     36 #[derive(Debug, ToShmem)]
     37 pub struct ContainerRule {
     38    /// The container query and name.
     39    pub condition: Arc<ContainerCondition>,
     40    /// The nested rules inside the block.
     41    pub rules: Arc<Locked<CssRules>>,
     42    /// The source position where this rule was found.
     43    pub source_location: SourceLocation,
     44 }
     45 
     46 impl ContainerRule {
     47    /// Returns the query condition.
     48    pub fn query_condition(&self) -> &QueryCondition {
     49        &self.condition.condition
     50    }
     51 
     52    /// Returns the query name filter.
     53    pub fn container_name(&self) -> &ContainerName {
     54        &self.condition.name
     55    }
     56 
     57    /// Measure heap usage.
     58    #[cfg(feature = "gecko")]
     59    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
     60        // Measurement of other fields may be added later.
     61        self.rules.unconditional_shallow_size_of(ops)
     62            + self.rules.read_with(guard).size_of(guard, ops)
     63    }
     64 }
     65 
     66 impl 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 
     77 impl 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 /// A container condition and filter, combined.
     93 #[derive(Debug, ToShmem, ToCss)]
     94 pub struct ContainerCondition {
     95    #[css(skip_if = "ContainerName::is_none")]
     96    name: ContainerName,
     97    condition: QueryCondition,
     98    #[css(skip)]
     99    flags: FeatureFlags,
    100 }
    101 
    102 /// The result of a successful container query lookup.
    103 pub struct ContainerLookupResult<E> {
    104    /// The relevant container.
    105    pub element: E,
    106    /// The sizing / writing-mode information of the container.
    107    pub info: ContainerInfo,
    108    /// The style of the element.
    109    pub style: Arc<ComputedValues>,
    110 }
    111 
    112 fn 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 
    127 enum TraversalResult<T> {
    128    InProgress,
    129    StopTraversal,
    130    Done(T),
    131 }
    132 
    133 fn traverse_container<E, F, R>(
    134    mut e: E,
    135    originating_element_style: Option<&ComputedValues>,
    136    evaluator: F,
    137 ) -> Option<(E, R)>
    138 where
    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 
    161 impl ContainerCondition {
    162    /// Parse a container condition.
    163    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        // Filter by container-type.
    203        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        // Filter by container-name.
    210        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    /// Performs container lookup for a given element.
    227    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    /// Tries to match a container query condition for a given element.
    248    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        // Set up the lookup for the container in question, as the condition may be using container
    264        // query lengths.
    265        let size_query_container_lookup = ContainerSizeQuery::for_option_element(
    266            container, /* known_parent_style = */ None, /* is_pseudo = */ 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                    // TODO(emilio): Might need something similar to improve
    283                    // invalidation of font relative container-query lengths.
    284                    invalidation_flags
    285                        .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
    286                }
    287                matches
    288            },
    289        )
    290    }
    291 }
    292 
    293 /// Information needed to evaluate an individual container query.
    294 #[derive(Copy, Clone)]
    295 pub struct ContainerInfo {
    296    size: Size2D<Option<Au>>,
    297    wm: WritingMode,
    298 }
    299 
    300 impl ContainerInfo {
    301    fn size(&self) -> Option<Size2D<Au>> {
    302        Some(Size2D::new(self.size.width?, self.size.height?))
    303    }
    304 }
    305 
    306 fn 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 
    311 fn 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 
    316 fn 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 
    325 fn 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 
    334 fn 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 
    342 fn 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 
    350 /// https://drafts.csswg.org/css-contain-3/#container-features
    351 ///
    352 /// TODO: Support style queries, perhaps.
    353 pub 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        // XXX from_bits_truncate is const, but the pipe operator isn't, so this
    383        // works around it.
    384        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 /// Result of a container size query, signifying the hypothetical containment boundary in terms of physical axes.
    401 /// Defined by up to two size containers. Queries on logical axes are resolved with respect to the querying
    402 /// element's writing mode.
    403 #[derive(Copy, Clone, Default)]
    404 pub struct ContainerSizeQueryResult {
    405    width: Option<Au>,
    406    height: Option<Au>,
    407 }
    408 
    409 impl 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    /// Get the inline-size of the query container.
    423    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    /// Get the block-size of the query container.
    437    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    /// Get the width of the query container.
    446    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    /// Get the height of the query container.
    454    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    // Merge the result of a subsequent lookup, preferring the initial result.
    462    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 
    478 /// Unevaluated lazy container size query.
    479 pub enum ContainerSizeQuery<'a> {
    480    /// Query prior to evaluation.
    481    NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
    482    /// Cached evaluated result.
    483    Evaluated(ContainerSizeQueryResult),
    484 }
    485 
    486 impl<'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            // We know we won't find a size container.
    510            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    /// Find the query container size for a given element. Meant to be used as a callback for new().
    541    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                    // Traverse up from the found size container to see if we can get a complete containment.
    560                    result.merge(Self::lookup(container, None))
    561                }
    562            },
    563            None => ContainerSizeQueryResult::default(),
    564        }
    565    }
    566 
    567    /// Create a new instance of the container size query for given element, with a deferred lookup callback.
    568    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                // No need to bother if we're the top element.
    582                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        // If there's no style, such as being `display: none` or so, we still want to show a
    592        // correct computed value, so give it a try.
    593        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    /// Create a new instance, but with optional element.
    606    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    /// Create a query that evaluates to empty, for cases where container size query is not required.
    622    pub fn none() -> Self {
    623        ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
    624    }
    625 
    626    /// Get the result of the container size query, doing the lookup if called for the first time.
    627    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 }