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