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