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