style/stylesheets/
scope_rule.rs

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 [`@scope`][scope] rule.
6//!
7//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles
8
9use crate::applicable_declarations::ScopeProximity;
10use crate::dom::TElement;
11use crate::parser::ParserContext;
12use crate::selector_parser::{SelectorImpl, SelectorParser};
13use crate::shared_lock::{
14    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
15};
16use crate::str::CssStringWriter;
17use crate::stylesheets::CssRules;
18use crate::simple_buckets_map::SimpleBucketsMap;
19use cssparser::{Parser, SourceLocation, ToCss};
20#[cfg(feature = "gecko")]
21use malloc_size_of::{
22    MallocSizeOfOps, MallocUnconditionalShallowSizeOf, MallocUnconditionalSizeOf,
23};
24use selectors::context::{MatchingContext, QuirksMode};
25use selectors::matching::matches_selector;
26use selectors::parser::{Component, ParseRelative, Selector, SelectorList};
27use selectors::OpaqueElement;
28use servo_arc::Arc;
29use std::fmt::{self, Write};
30use style_traits::{CssWriter, ParseError};
31
32/// A scoped rule.
33#[derive(Debug, ToShmem)]
34pub struct ScopeRule {
35    /// Bounds at which this rule applies.
36    pub bounds: ScopeBounds,
37    /// The nested rules inside the block.
38    pub rules: Arc<Locked<CssRules>>,
39    /// The source position where this rule was found.
40    pub source_location: SourceLocation,
41}
42
43impl DeepCloneWithLock for ScopeRule {
44    fn deep_clone_with_lock(
45        &self,
46        lock: &SharedRwLock,
47        guard: &SharedRwLockReadGuard,
48    ) -> Self {
49        let rules = self.rules.read_with(guard);
50        Self {
51            bounds: self.bounds.clone(),
52            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
53            source_location: self.source_location.clone(),
54        }
55    }
56}
57
58impl ToCssWithGuard for ScopeRule {
59    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
60        dest.write_str("@scope")?;
61        {
62            let mut writer = CssWriter::new(dest);
63            if let Some(start) = self.bounds.start.as_ref() {
64                writer.write_str(" (")?;
65                start.to_css(&mut writer)?;
66                writer.write_char(')')?;
67            }
68            if let Some(end) = self.bounds.end.as_ref() {
69                writer.write_str(" to (")?;
70                end.to_css(&mut writer)?;
71                writer.write_char(')')?;
72            }
73        }
74        self.rules.read_with(guard).to_css_block(guard, dest)
75    }
76}
77
78impl ScopeRule {
79    /// Measure heap usage.
80    #[cfg(feature = "gecko")]
81    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
82        self.rules.unconditional_shallow_size_of(ops) +
83            self.rules.read_with(guard).size_of(guard, ops) +
84            self.bounds.size_of(ops)
85    }
86}
87
88/// Bounds of the scope.
89#[derive(Debug, Clone, ToShmem)]
90pub struct ScopeBounds {
91    /// Start of the scope.
92    pub start: Option<SelectorList<SelectorImpl>>,
93    /// End of the scope.
94    pub end: Option<SelectorList<SelectorImpl>>,
95}
96
97impl ScopeBounds {
98    #[cfg(feature = "gecko")]
99    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
100        fn bound_size_of(
101            bound: &Option<SelectorList<SelectorImpl>>,
102            ops: &mut MallocSizeOfOps,
103        ) -> usize {
104            bound
105                .as_ref()
106                .map(|list| list.unconditional_size_of(ops))
107                .unwrap_or(0)
108        }
109        bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops)
110    }
111}
112
113fn parse_scope<'a>(
114    context: &ParserContext,
115    input: &mut Parser<'a, '_>,
116    parse_relative: ParseRelative,
117    for_end: bool,
118) -> Result<Option<SelectorList<SelectorImpl>>, ParseError<'a>> {
119    input
120        .try_parse(|input| {
121            if for_end {
122                // scope-end not existing is valid.
123                if input.try_parse(|i| i.expect_ident_matching("to")).is_err() {
124                    return Ok(None);
125                }
126            }
127            let parens = input.try_parse(|i| i.expect_parenthesis_block());
128            if for_end {
129                // `@scope to {}` is NOT valid.
130                parens?;
131            } else if parens.is_err() {
132                // `@scope {}` is valid.
133                return Ok(None);
134            }
135            input.parse_nested_block(|input| {
136                if input.is_exhausted() {
137                    // `@scope () {}` is valid.
138                    return Ok(None);
139                }
140                let selector_parser = SelectorParser {
141                    stylesheet_origin: context.stylesheet_origin,
142                    namespaces: &context.namespaces,
143                    url_data: context.url_data,
144                    for_supports_rule: false,
145                };
146                let parse_relative = if for_end {
147                    ParseRelative::ForScope
148                } else {
149                    parse_relative
150                };
151                Ok(Some(SelectorList::parse_disallow_pseudo(
152                    &selector_parser,
153                    input,
154                    parse_relative,
155                )?))
156            })
157        })
158}
159
160impl ScopeBounds {
161    /// Parse a container condition.
162    pub fn parse<'a>(
163        context: &ParserContext,
164        input: &mut Parser<'a, '_>,
165        parse_relative: ParseRelative,
166    ) -> Result<Self, ParseError<'a>> {
167        let start = parse_scope(context, input, parse_relative, false)?;
168        let end = parse_scope(context, input, parse_relative, true)?;
169        Ok(Self { start, end })
170    }
171}
172
173/// Types of implicit scope root.
174#[derive(Debug, Copy, Clone, MallocSizeOf)]
175pub enum ImplicitScopeRoot {
176    /// This implicit scope root is in the light tree.
177    InLightTree(OpaqueElement),
178    /// This implicit scope root is the document element, regardless of which (light|shadow) tree
179    /// the element being matched is. This is the case for e.g. if you specified an implicit scope
180    /// within a user stylesheet.
181    DocumentElement,
182    /// The implicit scope root is in a constructed stylesheet - the scope root the element
183    /// under consideration's shadow root (If one exists).
184    Constructed,
185    /// This implicit scope root is in the shadow tree.
186    InShadowTree(OpaqueElement),
187    /// This implicit scope root is the shadow host of the stylesheet-containing shadow tree.
188    ShadowHost(OpaqueElement),
189}
190
191impl ImplicitScopeRoot {
192    /// Return true if this matches the shadow host.
193    pub fn matches_shadow_host(&self) -> bool {
194        match self {
195            Self::InLightTree(..) | Self::InShadowTree(..) | Self::DocumentElement => false,
196            Self::ShadowHost(..) | Self::Constructed => true,
197        }
198    }
199
200    /// Return the implicit scope root element.
201    pub fn element(&self, current_host: Option<OpaqueElement>) -> ImplicitScopeTarget {
202        match self {
203            Self::InLightTree(e) | Self::InShadowTree(e) | Self::ShadowHost(e) => {
204                ImplicitScopeTarget::Element(*e)
205            },
206            Self::Constructed | Self::DocumentElement => {
207                if matches!(self, Self::Constructed) {
208                    if let Some(host) = current_host {
209                        return ImplicitScopeTarget::Element(host);
210                    }
211                }
212                ImplicitScopeTarget::DocumentElement
213            },
214        }
215    }
216}
217
218/// Target of this implicit scope.
219pub enum ImplicitScopeTarget {
220    /// Target matches only the specified element.
221    Element(OpaqueElement),
222    /// Implicit scope whose target is the document element.
223    DocumentElement,
224}
225
226impl ImplicitScopeTarget {
227    /// Check if this element is the implicit scope.
228    fn check<E: TElement>(&self, element: E) -> bool {
229        match self {
230            Self::Element(e) => element.opaque() == *e,
231            Self::DocumentElement => element.is_root(),
232        }
233    }
234}
235
236/// Target of this scope.
237pub enum ScopeTarget<'a> {
238    /// Target matches an element matching the specified selector list.
239    Selector(&'a SelectorList<SelectorImpl>),
240    /// Target matches an implicit scope target.
241    Implicit(ImplicitScopeTarget),
242}
243
244impl<'a> ScopeTarget<'a> {
245    /// Check if the given element is the scope.
246    fn check<E: TElement>(
247        &self,
248        element: E,
249        scope: Option<OpaqueElement>,
250        scope_subject_map: &ScopeSubjectMap,
251        context: &mut MatchingContext<E::Impl>,
252    ) -> bool {
253        match self {
254            Self::Selector(list) => context.nest_for_scope_condition(scope, |context| {
255                if scope_subject_map.early_reject(element, context.quirks_mode()) {
256                    return false;
257                }
258                for selector in list.slice().iter() {
259                    if matches_selector(selector, 0, None, &element, context) {
260                        return true;
261                    }
262                }
263                false
264            }),
265            Self::Implicit(t) => t.check(element),
266        }
267    }
268}
269
270/// A scope root candidate.
271#[derive(Clone, Copy, Debug)]
272pub struct ScopeRootCandidate {
273    /// This candidate's scope root.
274    pub root: OpaqueElement,
275    /// Ancestor hop from the element under consideration to this scope root.
276    pub proximity: ScopeProximity,
277}
278
279/// Collect potential scope roots for a given element and its scope target.
280/// The check may not pass the ceiling, if specified.
281pub fn collect_scope_roots<E>(
282    element: E,
283    ceiling: Option<OpaqueElement>,
284    context: &mut MatchingContext<E::Impl>,
285    target: &ScopeTarget,
286    matches_shadow_host: bool,
287    scope_subject_map: &ScopeSubjectMap,
288) -> Vec<ScopeRootCandidate>
289where
290    E: TElement,
291{
292    let mut result = vec![];
293    let mut parent = Some(element);
294    let mut proximity = 0usize;
295    while let Some(p) = parent {
296        if ceiling == Some(p.opaque()) {
297            break;
298        }
299        if target.check(p, ceiling, scope_subject_map, context) {
300            result.push(ScopeRootCandidate {
301                root: p.opaque(),
302                proximity: ScopeProximity::new(proximity),
303            });
304            // Note that we can't really break here - we need to consider
305            // ALL scope roots to figure out whch one didn't end.
306        }
307        parent = p.parent_element();
308        proximity += 1;
309        // We we got to the top of the shadow tree - keep going
310        // if we may match the shadow host.
311        if parent.is_none() && matches_shadow_host {
312            parent = p.containing_shadow_host();
313        }
314    }
315    result
316}
317
318/// Given the scope-end selector, check if the element is outside of the scope.
319/// That is, check if any ancestor to the root matches the scope-end selector.
320pub fn element_is_outside_of_scope<E>(
321    selector: &Selector<E::Impl>,
322    element: E,
323    root: OpaqueElement,
324    context: &mut MatchingContext<E::Impl>,
325    root_may_be_shadow_host: bool,
326) -> bool
327where
328    E: TElement,
329{
330    let mut parent = Some(element);
331    context.nest_for_scope_condition(Some(root), |context| {
332        while let Some(p) = parent {
333            if matches_selector(selector, 0, None, &p, context) {
334                return true;
335            }
336            if p.opaque() == root {
337                // Reached the top, not lying outside of scope.
338                break;
339            }
340            parent = p.parent_element();
341            if parent.is_none() && root_may_be_shadow_host {
342                if let Some(host) = p.containing_shadow_host() {
343                    // Pretty much an edge case where user specified scope-start and -end of :host
344                    return host.opaque() == root;
345                }
346            }
347        }
348        return false;
349    })
350}
351
352/// A map containing simple selectors in subjects of scope selectors.
353/// This allows fast-rejecting scopes before running the full match.
354#[derive(Clone, Debug, Default, MallocSizeOf)]
355pub struct ScopeSubjectMap {
356    buckets: SimpleBucketsMap<()>,
357    any: bool,
358}
359
360impl ScopeSubjectMap {
361    /// Add the `<scope-start>` of a scope.
362    pub fn add_bound_start(&mut self, selectors: &SelectorList<SelectorImpl>, quirks_mode: QuirksMode) {
363        if self.add_selector_list(selectors, quirks_mode) {
364            self.any = true;
365        }
366    }
367
368    fn add_selector_list(&mut self, selectors: &SelectorList<SelectorImpl>, quirks_mode: QuirksMode) -> bool {
369        let mut is_any = false;
370        for selector in selectors.slice().iter() {
371            is_any = is_any || self.add_selector(selector, quirks_mode);
372        }
373        is_any
374    }
375
376    fn add_selector(&mut self, selector: &Selector<SelectorImpl>, quirks_mode: QuirksMode) -> bool {
377        let mut is_any = true;
378        let mut iter = selector.iter();
379        while let Some(c) = iter.next() {
380            let component_any = match c {
381                Component::Class(cls) => {
382                    match self.buckets.classes.try_entry(cls.0.clone(), quirks_mode) {
383                        Ok(e) => {
384                            e.or_insert(());
385                            false
386                        },
387                        Err(_) => true,
388                    }
389                },
390                Component::ID(id) => {
391                    match self.buckets.ids.try_entry(id.0.clone(), quirks_mode) {
392                        Ok(e) => {
393                            e.or_insert(());
394                            false
395                        },
396                        Err(_) => true,
397                    }
398                },
399                Component::LocalName(local_name) => {
400                    self.buckets.local_names.insert(local_name.lower_name.clone(), ());
401                    false
402                },
403                Component::Is(ref list) | Component::Where(ref list) => {
404                    self.add_selector_list(list, quirks_mode)
405                },
406                _ => true,
407            };
408
409            is_any = is_any && component_any;
410        }
411        is_any
412    }
413
414    /// Shrink the map as much as possible.
415    pub fn shrink_if_needed(&mut self) {
416        self.buckets.shrink_if_needed();
417    }
418
419    /// Clear the map.
420    pub fn clear(&mut self) {
421        self.buckets.clear();
422        self.any = false;
423    }
424
425    /// Could a given element possibly be a scope root?
426    fn early_reject<E: TElement>(&self, element: E, quirks_mode: QuirksMode) -> bool {
427        if self.any {
428            return false;
429        }
430
431        if let Some(id) = element.id() {
432            if self.buckets.ids.get(id, quirks_mode).is_some() {
433                return false;
434            }
435        }
436
437        let mut found = false;
438        element.each_class(|cls| {
439            if self.buckets.classes.get(cls, quirks_mode).is_some() {
440                found = true;
441            }
442        });
443        if found {
444            return false;
445        }
446
447        if self.buckets.local_names.get(element.local_name()).is_some() {
448            return false;
449        }
450
451        true
452    }
453}
454
455/// Determine if this selector list, when used as a scope bound selector, is considered trivial.
456pub fn scope_selector_list_is_trivial(list: &SelectorList<SelectorImpl>) -> bool {
457    fn scope_selector_is_trivial(selector: &Selector<SelectorImpl>) -> bool {
458        // A selector is trivial if:
459        // * There is no selector conditional on its siblings and/or descendant to match, and
460        // * There is no dependency on sibling relations, and
461        // * There's no ID selector in the selector. A more correct approach may be to ensure that
462        //   scoping roots of the style sharing candidates and targets have matching IDs, but that
463        //   requires re-plumbing what we pass around for scope roots.
464        let mut iter = selector.iter();
465        loop {
466            while let Some(c) = iter.next() {
467                match c {
468                    Component::ID(_) | Component::Nth(_) | Component::NthOf(_) | Component::Has(_) => return false,
469                    Component::Is(ref list) | Component::Where(ref list) | Component::Negation(ref list) =>
470                        if !scope_selector_list_is_trivial(list) {
471                            return false;
472                        }
473                    _ => (),
474                }
475            }
476
477            match iter.next_sequence() {
478                Some(c) => if c.is_sibling() {
479                    return false;
480                },
481                None => return true,
482            }
483        }
484    }
485
486    list.slice().iter().all(|s| scope_selector_is_trivial(s))
487}