import_rule.rs (8531B)
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 9 use crate::media_queries::MediaList; 10 use crate::parser::{Parse, ParserContext}; 11 use crate::shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; 12 use crate::stylesheets::{ 13 layer_rule::LayerName, supports_rule::SupportsCondition, CssRule, CssRuleType, 14 StylesheetInDocument, 15 }; 16 use crate::values::CssUrl; 17 use cssparser::{Parser, SourceLocation}; 18 use std::fmt::{self, Write}; 19 use style_traits::{CssStringWriter, CssWriter, ToCss}; 20 use to_shmem::{SharedMemoryBuilder, ToShmem}; 21 22 #[cfg(feature = "gecko")] 23 type StyleSheet = crate::gecko::data::GeckoStyleSheet; 24 #[cfg(feature = "servo")] 25 type StyleSheet = ::servo_arc::Arc<crate::stylesheets::Stylesheet>; 26 27 /// A sheet that is held from an import rule. 28 #[derive(Debug)] 29 pub 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 41 impl 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 88 impl 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)] 110 pub 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)] 123 pub 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 131 impl 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)] 152 pub 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 171 impl 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 214 impl 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 222 impl 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 234 impl 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 }