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