style/stylesheets/
rules_iterator.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//! An iterator over a list of rules.
6
7use crate::context::QuirksMode;
8use crate::device::Device;
9use crate::shared_lock::SharedRwLockReadGuard;
10use crate::stylesheets::{
11    CssRule, CssRuleRef, CustomMediaEvaluator, CustomMediaMap, DocumentRule, ImportRule, MediaRule,
12    SupportsRule,
13};
14use smallvec::SmallVec;
15use std::ops::Deref;
16use std::slice;
17
18/// An iterator over a list of rules.
19pub struct RulesIterator<'a, 'b, C, CMM>
20where
21    'b: 'a,
22    C: NestedRuleIterationCondition + 'static,
23    CMM: Deref<Target = CustomMediaMap>,
24{
25    device: &'a Device,
26    quirks_mode: QuirksMode,
27    custom_media: CMM,
28    guard: &'a SharedRwLockReadGuard<'b>,
29    stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
30    last_rule_had_children: bool,
31    _phantom: ::std::marker::PhantomData<C>,
32}
33
34impl<'a, 'b, C, CMM> RulesIterator<'a, 'b, C, CMM>
35where
36    'b: 'a,
37    C: NestedRuleIterationCondition + 'static,
38    CMM: Deref<Target = CustomMediaMap>,
39{
40    /// Returns the custom media map passed at construction.
41    pub fn custom_media(&mut self) -> &mut CMM {
42        &mut self.custom_media
43    }
44
45    /// Creates a new `RulesIterator` to iterate over `rules`.
46    pub fn new(
47        device: &'a Device,
48        quirks_mode: QuirksMode,
49        custom_media: CMM,
50        guard: &'a SharedRwLockReadGuard<'b>,
51        rules: slice::Iter<'a, CssRule>,
52    ) -> Self {
53        let mut stack = SmallVec::new();
54        stack.push(rules);
55        Self {
56            device,
57            quirks_mode,
58            custom_media,
59            guard,
60            stack,
61            last_rule_had_children: false,
62            _phantom: ::std::marker::PhantomData,
63        }
64    }
65
66    /// Skips all the remaining children of the last nested rule processed.
67    pub fn skip_children(&mut self) {
68        if self.last_rule_had_children {
69            self.stack.pop();
70            self.last_rule_had_children = false;
71        }
72    }
73
74    /// Returns the children of `rule`, and whether `rule` is effective.
75    pub fn children(
76        rule: &'a CssRule,
77        device: &'a Device,
78        quirks_mode: QuirksMode,
79        custom_media_map: &CustomMediaMap,
80        guard: &'a SharedRwLockReadGuard<'_>,
81        effective: &mut bool,
82    ) -> &'a [CssRule] {
83        *effective = true;
84        match *rule {
85            CssRule::Namespace(_)
86            | CssRule::FontFace(_)
87            | CssRule::CounterStyle(_)
88            | CssRule::CustomMedia(_)
89            | CssRule::Keyframes(_)
90            | CssRule::Margin(_)
91            | CssRule::Property(_)
92            | CssRule::LayerStatement(_)
93            | CssRule::FontFeatureValues(_)
94            | CssRule::FontPaletteValues(_)
95            | CssRule::NestedDeclarations(_)
96            | CssRule::PositionTry(_)
97            | CssRule::ViewTransition(_) => &[],
98            CssRule::Page(ref page_rule) => {
99                let page_rule = page_rule.read_with(guard);
100                let rules = page_rule.rules.read_with(guard);
101                rules.0.as_slice()
102            },
103            CssRule::Style(ref style_rule) => {
104                let style_rule = style_rule.read_with(guard);
105                match style_rule.rules.as_ref() {
106                    Some(r) => r.read_with(guard).0.as_slice(),
107                    None => &[],
108                }
109            },
110            CssRule::Import(ref import_rule) => {
111                let import_rule = import_rule.read_with(guard);
112                if !C::process_import(guard, device, quirks_mode, custom_media_map, import_rule) {
113                    *effective = false;
114                    return &[];
115                }
116                import_rule.stylesheet.rules(guard)
117            },
118            CssRule::Document(ref doc_rule) => {
119                if !C::process_document(guard, device, quirks_mode, doc_rule) {
120                    *effective = false;
121                    return &[];
122                }
123                doc_rule.rules.read_with(guard).0.as_slice()
124            },
125            CssRule::Container(ref container_rule) => {
126                container_rule.rules.read_with(guard).0.as_slice()
127            },
128            CssRule::Media(ref media_rule) => {
129                if !C::process_media(guard, device, quirks_mode, custom_media_map, media_rule) {
130                    *effective = false;
131                    return &[];
132                }
133                media_rule.rules.read_with(guard).0.as_slice()
134            },
135            CssRule::Supports(ref supports_rule) => {
136                if !C::process_supports(guard, device, quirks_mode, supports_rule) {
137                    *effective = false;
138                    return &[];
139                }
140                supports_rule.rules.read_with(guard).0.as_slice()
141            },
142            CssRule::LayerBlock(ref layer_rule) => layer_rule.rules.read_with(guard).0.as_slice(),
143            CssRule::Scope(ref rule) => rule.rules.read_with(guard).0.as_slice(),
144            CssRule::StartingStyle(ref rule) => rule.rules.read_with(guard).0.as_slice(),
145            CssRule::AppearanceBase(ref rule) => rule.rules.read_with(guard).0.as_slice(),
146        }
147    }
148}
149
150impl<'a, 'b, C, CMM> Iterator for RulesIterator<'a, 'b, C, CMM>
151where
152    'b: 'a,
153    C: NestedRuleIterationCondition + 'static,
154    CMM: Deref<Target = CustomMediaMap>,
155{
156    type Item = &'a CssRule;
157
158    fn next(&mut self) -> Option<Self::Item> {
159        self.last_rule_had_children = false;
160        while !self.stack.is_empty() {
161            let rule = {
162                let nested_iter = self.stack.last_mut().unwrap();
163                match nested_iter.next() {
164                    Some(r) => r,
165                    None => {
166                        self.stack.pop();
167                        continue;
168                    },
169                }
170            };
171
172            let mut effective = true;
173            let children = Self::children(
174                rule,
175                self.device,
176                self.quirks_mode,
177                &self.custom_media,
178                self.guard,
179                &mut effective,
180            );
181            if !effective {
182                continue;
183            }
184            if !children.is_empty() {
185                debug_assert_eq!(
186                    rule.children(self.guard).len(),
187                    children.len(),
188                    "Should agree with CssRule::children if effective"
189                );
190                self.last_rule_had_children = true;
191                self.stack.push(children.iter());
192            }
193            return Some(rule);
194        }
195
196        None
197    }
198}
199
200/// RulesIterator.
201pub trait NestedRuleIterationCondition {
202    /// Whether we should process the nested rules in a given `@import` rule.
203    fn process_import(
204        guard: &SharedRwLockReadGuard,
205        device: &Device,
206        quirks_mode: QuirksMode,
207        custom_media_map: &CustomMediaMap,
208        rule: &ImportRule,
209    ) -> bool;
210
211    /// Whether we should process the nested rules in a given `@media` rule.
212    fn process_media(
213        guard: &SharedRwLockReadGuard,
214        device: &Device,
215        quirks_mode: QuirksMode,
216        custom_media_map: &CustomMediaMap,
217        rule: &MediaRule,
218    ) -> bool;
219
220    /// Whether we should process the nested rules in a given `@-moz-document`
221    /// rule.
222    fn process_document(
223        guard: &SharedRwLockReadGuard,
224        device: &Device,
225        quirks_mode: QuirksMode,
226        rule: &DocumentRule,
227    ) -> bool;
228
229    /// Whether we should process the nested rules in a given `@supports` rule.
230    fn process_supports(
231        guard: &SharedRwLockReadGuard,
232        device: &Device,
233        quirks_mode: QuirksMode,
234        rule: &SupportsRule,
235    ) -> bool;
236}
237
238/// A struct that represents the condition that a rule applies to the document.
239pub struct EffectiveRules;
240
241impl EffectiveRules {
242    /// Returns whether a given rule is effective.
243    pub fn is_effective(
244        guard: &SharedRwLockReadGuard,
245        device: &Device,
246        quirks_mode: QuirksMode,
247        custom_media_map: &CustomMediaMap,
248        rule: &CssRuleRef,
249    ) -> bool {
250        match *rule {
251            CssRuleRef::Import(import_rule) => {
252                let import_rule = import_rule.read_with(guard);
253                Self::process_import(guard, device, quirks_mode, custom_media_map, import_rule)
254            },
255            CssRuleRef::Document(doc_rule) => {
256                Self::process_document(guard, device, quirks_mode, doc_rule)
257            },
258            CssRuleRef::Media(media_rule) => {
259                Self::process_media(guard, device, quirks_mode, custom_media_map, media_rule)
260            },
261            CssRuleRef::Supports(supports_rule) => {
262                Self::process_supports(guard, device, quirks_mode, supports_rule)
263            },
264            _ => true,
265        }
266    }
267}
268
269impl NestedRuleIterationCondition for EffectiveRules {
270    fn process_import(
271        guard: &SharedRwLockReadGuard,
272        device: &Device,
273        quirks_mode: QuirksMode,
274        custom_media_map: &CustomMediaMap,
275        rule: &ImportRule,
276    ) -> bool {
277        match rule.stylesheet.media(guard) {
278            Some(m) => m.evaluate(
279                device,
280                quirks_mode,
281                &mut CustomMediaEvaluator::new(custom_media_map, guard),
282            ),
283            None => true,
284        }
285    }
286
287    fn process_media(
288        guard: &SharedRwLockReadGuard,
289        device: &Device,
290        quirks_mode: QuirksMode,
291        custom_media_map: &CustomMediaMap,
292        rule: &MediaRule,
293    ) -> bool {
294        rule.media_queries.read_with(guard).evaluate(
295            device,
296            quirks_mode,
297            &mut CustomMediaEvaluator::new(custom_media_map, guard),
298        )
299    }
300
301    fn process_document(
302        _: &SharedRwLockReadGuard,
303        device: &Device,
304        _: QuirksMode,
305        rule: &DocumentRule,
306    ) -> bool {
307        rule.condition.evaluate(device)
308    }
309
310    fn process_supports(
311        _: &SharedRwLockReadGuard,
312        _: &Device,
313        _: QuirksMode,
314        rule: &SupportsRule,
315    ) -> bool {
316        rule.enabled
317    }
318}
319
320/// A filter that processes all the rules in a rule list.
321pub struct AllRules;
322
323impl NestedRuleIterationCondition for AllRules {
324    fn process_import(
325        _: &SharedRwLockReadGuard,
326        _: &Device,
327        _: QuirksMode,
328        _: &CustomMediaMap,
329        _: &ImportRule,
330    ) -> bool {
331        true
332    }
333
334    fn process_media(
335        _: &SharedRwLockReadGuard,
336        _: &Device,
337        _: QuirksMode,
338        _: &CustomMediaMap,
339        _: &MediaRule,
340    ) -> bool {
341        true
342    }
343
344    fn process_document(
345        _: &SharedRwLockReadGuard,
346        _: &Device,
347        _: QuirksMode,
348        _: &DocumentRule,
349    ) -> bool {
350        true
351    }
352
353    fn process_supports(
354        _: &SharedRwLockReadGuard,
355        _: &Device,
356        _: QuirksMode,
357        _: &SupportsRule,
358    ) -> bool {
359        true
360    }
361}
362
363/// An iterator over all the effective rules of a stylesheet.
364///
365/// NOTE: This iterator recurses into `@import` rules.
366pub type EffectiveRulesIterator<'a, 'b, CMM> = RulesIterator<'a, 'b, EffectiveRules, CMM>;
367
368impl<'a, 'b, CMM> EffectiveRulesIterator<'a, 'b, CMM>
369where
370    CMM: Deref<Target = CustomMediaMap>,
371{
372    /// Returns an iterator over the effective children of a rule, even if
373    /// `rule` itself is not effective.
374    pub fn effective_children(
375        device: &'a Device,
376        quirks_mode: QuirksMode,
377        custom_media_map: CMM,
378        guard: &'a SharedRwLockReadGuard<'b>,
379        rule: &'a CssRule,
380    ) -> Self {
381        let children = RulesIterator::<AllRules, CMM>::children(
382            rule,
383            device,
384            quirks_mode,
385            &custom_media_map,
386            guard,
387            &mut false,
388        );
389        EffectiveRulesIterator::new(
390            device,
391            quirks_mode,
392            custom_media_map,
393            guard,
394            children.iter(),
395        )
396    }
397}