style/stylesheets/
import_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//! The [`@import`][import] at-rule.
6//!
7//! [import]: https://drafts.csswg.org/css-cascade-3/#at-import
8
9use crate::media_queries::MediaList;
10use crate::parser::{Parse, ParserContext};
11use crate::shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
12use crate::stylesheets::{
13    layer_rule::LayerName, supports_rule::SupportsCondition, CssRule, CssRuleType,
14    StylesheetInDocument,
15};
16use crate::values::CssUrl;
17use cssparser::{Parser, SourceLocation};
18use std::fmt::{self, Write};
19use style_traits::{CssStringWriter, CssWriter, ToCss};
20use to_shmem::{SharedMemoryBuilder, ToShmem};
21
22/// A sheet that is held from an import rule.
23#[cfg(feature = "gecko")]
24#[derive(Debug)]
25pub enum ImportSheet {
26    /// A bonafide stylesheet.
27    Sheet(crate::gecko::data::GeckoStyleSheet),
28
29    /// An @import created while parsing off-main-thread, whose Gecko sheet has
30    /// yet to be created and attached.
31    Pending,
32
33    /// An @import created with a false <supports-condition>, so will never be fetched.
34    Refused,
35}
36
37#[cfg(feature = "gecko")]
38impl ImportSheet {
39    /// Creates a new ImportSheet from a GeckoStyleSheet.
40    pub fn new(sheet: crate::gecko::data::GeckoStyleSheet) -> Self {
41        ImportSheet::Sheet(sheet)
42    }
43
44    /// Creates a pending ImportSheet for a load that has not started yet.
45    pub fn new_pending() -> Self {
46        ImportSheet::Pending
47    }
48
49    /// Creates a refused ImportSheet for a load that will not happen.
50    pub fn new_refused() -> Self {
51        ImportSheet::Refused
52    }
53
54    /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
55    /// exists.
56    pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> {
57        match *self {
58            ImportSheet::Sheet(ref s) => {
59                debug_assert!(!s.hack_is_null());
60                if s.hack_is_null() {
61                    return None;
62                }
63                Some(s)
64            },
65            ImportSheet::Refused | ImportSheet::Pending => None,
66        }
67    }
68
69    /// Returns the media list for this import rule.
70    pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
71        self.as_sheet().and_then(|s| s.media(guard))
72    }
73
74    /// Returns the rule list for this import rule.
75    pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] {
76        match self.as_sheet() {
77            Some(s) => s.rules(guard),
78            None => &[],
79        }
80    }
81}
82
83#[cfg(feature = "gecko")]
84impl DeepCloneWithLock for ImportSheet {
85    fn deep_clone_with_lock(&self, _lock: &SharedRwLock, _guard: &SharedRwLockReadGuard) -> Self {
86        use crate::gecko::data::GeckoStyleSheet;
87        use crate::gecko_bindings::bindings;
88        match *self {
89            ImportSheet::Sheet(ref s) => {
90                let clone = unsafe { bindings::Gecko_StyleSheet_Clone(s.raw() as *const _) };
91                ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) })
92            },
93            ImportSheet::Pending => ImportSheet::Pending,
94            ImportSheet::Refused => ImportSheet::Refused,
95        }
96    }
97}
98
99/// A sheet that is held from an import rule.
100#[cfg(feature = "servo")]
101#[derive(Debug)]
102pub enum ImportSheet {
103    /// A bonafide stylesheet.
104    Sheet(::servo_arc::Arc<crate::stylesheets::Stylesheet>),
105
106    /// An @import created with a false <supports-condition>, so will never be fetched.
107    Refused,
108}
109
110#[cfg(feature = "servo")]
111impl ImportSheet {
112    /// Creates a new ImportSheet from a stylesheet.
113    pub fn new(sheet: ::servo_arc::Arc<crate::stylesheets::Stylesheet>) -> Self {
114        ImportSheet::Sheet(sheet)
115    }
116
117    /// Creates a refused ImportSheet for a load that will not happen.
118    pub fn new_refused() -> Self {
119        ImportSheet::Refused
120    }
121
122    /// Returns a reference to the stylesheet in this ImportSheet, if it exists.
123    pub fn as_sheet(&self) -> Option<&::servo_arc::Arc<crate::stylesheets::Stylesheet>> {
124        match *self {
125            ImportSheet::Sheet(ref s) => Some(s),
126            ImportSheet::Refused => None,
127        }
128    }
129
130    /// Returns the media list for this import rule.
131    pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
132        self.as_sheet().and_then(|s| s.media(guard))
133    }
134
135    /// Returns the rules for this import rule.
136    pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] {
137        match self.as_sheet() {
138            Some(s) => s.rules(guard),
139            None => &[],
140        }
141    }
142}
143
144#[cfg(feature = "servo")]
145impl DeepCloneWithLock for ImportSheet {
146    fn deep_clone_with_lock(&self, _lock: &SharedRwLock, _guard: &SharedRwLockReadGuard) -> Self {
147        match *self {
148            ImportSheet::Sheet(ref s) => {
149                use servo_arc::Arc;
150                ImportSheet::Sheet(Arc::new((&**s).clone()))
151            },
152            ImportSheet::Refused => ImportSheet::Refused,
153        }
154    }
155}
156
157/// The layer specified in an import rule (can be none, anonymous, or named).
158#[derive(Debug, Clone)]
159pub enum ImportLayer {
160    /// No layer specified
161    None,
162
163    /// Anonymous layer (`layer`)
164    Anonymous,
165
166    /// Named layer (`layer(name)`)
167    Named(LayerName),
168}
169
170/// The supports condition in an import rule.
171#[derive(Debug, Clone)]
172pub struct ImportSupportsCondition {
173    /// The supports condition.
174    pub condition: SupportsCondition,
175
176    /// If the import is enabled, from the result of the import condition.
177    pub enabled: bool,
178}
179
180impl ToCss for ImportLayer {
181    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
182    where
183        W: Write,
184    {
185        match *self {
186            ImportLayer::None => Ok(()),
187            ImportLayer::Anonymous => dest.write_str("layer"),
188            ImportLayer::Named(ref name) => {
189                dest.write_str("layer(")?;
190                name.to_css(dest)?;
191                dest.write_char(')')
192            },
193        }
194    }
195}
196
197/// The [`@import`][import] at-rule.
198///
199/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
200#[derive(Debug)]
201pub struct ImportRule {
202    /// The `<url>` this `@import` rule is loading.
203    pub url: CssUrl,
204
205    /// The stylesheet is always present. However, in the case of gecko async
206    /// parsing, we don't actually have a Gecko sheet at first, and so the
207    /// ImportSheet just has stub behavior until it appears.
208    pub stylesheet: ImportSheet,
209
210    /// A <supports-condition> for the rule.
211    pub supports: Option<ImportSupportsCondition>,
212
213    /// A `layer()` function name.
214    pub layer: ImportLayer,
215
216    /// The line and column of the rule's source code.
217    pub source_location: SourceLocation,
218}
219
220impl ImportRule {
221    /// Parses the layer() / layer / supports() part of the import header, as per
222    /// https://drafts.csswg.org/css-cascade-5/#at-import:
223    ///
224    ///     [ layer | layer(<layer-name>) ]?
225    ///     [ supports([ <supports-condition> | <declaration> ]) ]?
226    ///
227    /// We do this here so that the import preloader can look at this without having to parse the
228    /// whole import rule or parse the media query list or what not.
229    pub fn parse_layer_and_supports<'i, 't>(
230        input: &mut Parser<'i, 't>,
231        context: &mut ParserContext,
232    ) -> (ImportLayer, Option<ImportSupportsCondition>) {
233        let layer = if input
234            .try_parse(|input| input.expect_ident_matching("layer"))
235            .is_ok()
236        {
237            ImportLayer::Anonymous
238        } else {
239            input
240                .try_parse(|input| {
241                    input.expect_function_matching("layer")?;
242                    input
243                        .parse_nested_block(|input| LayerName::parse(context, input))
244                        .map(|name| ImportLayer::Named(name))
245                })
246                .ok()
247                .unwrap_or(ImportLayer::None)
248        };
249
250        let supports = input
251            .try_parse(SupportsCondition::parse_for_import)
252            .map(|condition| {
253                let enabled =
254                    context.nest_for_rule(CssRuleType::Style, |context| condition.eval(context));
255                ImportSupportsCondition { condition, enabled }
256            })
257            .ok();
258
259        (layer, supports)
260    }
261}
262
263impl ToShmem for ImportRule {
264    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
265        Err(String::from(
266            "ToShmem failed for ImportRule: cannot handle imported style sheets",
267        ))
268    }
269}
270
271impl DeepCloneWithLock for ImportRule {
272    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
273        ImportRule {
274            url: self.url.clone(),
275            stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard),
276            supports: self.supports.clone(),
277            layer: self.layer.clone(),
278            source_location: self.source_location.clone(),
279        }
280    }
281}
282
283impl ToCssWithGuard for ImportRule {
284    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
285        dest.write_str("@import ")?;
286        self.url.to_css(&mut CssWriter::new(dest))?;
287
288        if !matches!(self.layer, ImportLayer::None) {
289            dest.write_char(' ')?;
290            self.layer.to_css(&mut CssWriter::new(dest))?;
291        }
292
293        if let Some(ref supports) = self.supports {
294            dest.write_str(" supports(")?;
295            supports.condition.to_css(&mut CssWriter::new(dest))?;
296            dest.write_char(')')?;
297        }
298
299        if let Some(media) = self.stylesheet.media(guard) {
300            if !media.is_empty() {
301                dest.write_char(' ')?;
302                media.to_css(&mut CssWriter::new(dest))?;
303            }
304        }
305
306        dest.write_char(';')
307    }
308}