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