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 //! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) 6 //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4. 7 //! We implement the prefixed `@-moz-document`. 8 9 use crate::media_queries::Device; 10 use crate::parser::{Parse, ParserContext}; 11 use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; 12 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; 13 use crate::str::CssStringWriter; 14 use crate::stylesheets::CssRules; 15 use crate::values::CssUrl; 16 use cssparser::{Parser, SourceLocation}; 17 #[cfg(feature = "gecko")] 18 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; 19 use servo_arc::Arc; 20 use std::fmt::{self, Write}; 21 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 22 23 #[derive(Debug, ToShmem)] 24 /// A @-moz-document rule 25 pub struct DocumentRule { 26 /// The parsed condition 27 pub condition: DocumentCondition, 28 /// Child rules 29 pub rules: Arc<Locked<CssRules>>, 30 /// The line and column of the rule's source code. 31 pub source_location: SourceLocation, 32 } 33 34 impl DocumentRule { 35 /// Measure heap usage. 36 #[cfg(feature = "gecko")] size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize37 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { 38 // Measurement of other fields may be added later. 39 self.rules.unconditional_shallow_size_of(ops) + 40 self.rules.read_with(guard).size_of(guard, ops) 41 } 42 } 43 44 impl ToCssWithGuard for DocumentRule { to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result45 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { 46 dest.write_str("@-moz-document ")?; 47 self.condition.to_css(&mut CssWriter::new(dest))?; 48 dest.write_str(" {")?; 49 for rule in self.rules.read_with(guard).0.iter() { 50 dest.write_str(" ")?; 51 rule.to_css(guard, dest)?; 52 } 53 dest.write_str(" }") 54 } 55 } 56 57 impl DeepCloneWithLock for DocumentRule { 58 /// Deep clones this DocumentRule. deep_clone_with_lock( &self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard, params: &DeepCloneParams, ) -> Self59 fn deep_clone_with_lock( 60 &self, 61 lock: &SharedRwLock, 62 guard: &SharedRwLockReadGuard, 63 params: &DeepCloneParams, 64 ) -> Self { 65 let rules = self.rules.read_with(guard); 66 DocumentRule { 67 condition: self.condition.clone(), 68 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), 69 source_location: self.source_location.clone(), 70 } 71 } 72 } 73 74 /// The kind of media document that the rule will match. 75 #[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)] 76 #[allow(missing_docs)] 77 pub enum MediaDocumentKind { 78 All, 79 Plugin, 80 Image, 81 Video, 82 } 83 84 /// A matching function for a `@document` rule's condition. 85 #[derive(Clone, Debug, ToCss, ToShmem)] 86 pub enum DocumentMatchingFunction { 87 /// Exact URL matching function. It evaluates to true whenever the 88 /// URL of the document being styled is exactly the URL given. 89 Url(CssUrl), 90 /// URL prefix matching function. It evaluates to true whenever the 91 /// URL of the document being styled has the argument to the 92 /// function as an initial substring (which is true when the two 93 /// strings are equal). When the argument is the empty string, 94 /// it evaluates to true for all documents. 95 #[css(function)] 96 UrlPrefix(String), 97 /// Domain matching function. It evaluates to true whenever the URL 98 /// of the document being styled has a host subcomponent and that 99 /// host subcomponent is exactly the argument to the ‘domain()’ 100 /// function or a final substring of the host component is a 101 /// period (U+002E) immediately followed by the argument to the 102 /// ‘domain()’ function. 103 #[css(function)] 104 Domain(String), 105 /// Regular expression matching function. It evaluates to true 106 /// whenever the regular expression matches the entirety of the URL 107 /// of the document being styled. 108 #[css(function)] 109 Regexp(String), 110 /// Matching function for a media document. 111 #[css(function)] 112 MediaDocument(MediaDocumentKind), 113 } 114 115 macro_rules! parse_quoted_or_unquoted_string { 116 ($input:ident, $url_matching_function:expr) => { 117 $input.parse_nested_block(|input| { 118 let start = input.position(); 119 input 120 .parse_entirely(|input| { 121 let string = input.expect_string()?; 122 Ok($url_matching_function(string.as_ref().to_owned())) 123 }) 124 .or_else(|_: ParseError| { 125 while let Ok(_) = input.next() {} 126 Ok($url_matching_function(input.slice_from(start).to_string())) 127 }) 128 }) 129 }; 130 } 131 132 impl DocumentMatchingFunction { 133 /// Parse a URL matching function for a`@document` rule's condition. parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>134 pub fn parse<'i, 't>( 135 context: &ParserContext, 136 input: &mut Parser<'i, 't>, 137 ) -> Result<Self, ParseError<'i>> { 138 if let Ok(url) = input.try(|input| CssUrl::parse(context, input)) { 139 return Ok(DocumentMatchingFunction::Url(url)); 140 } 141 142 let location = input.current_source_location(); 143 let function = input.expect_function()?.clone(); 144 match_ignore_ascii_case! { &function, 145 "url-prefix" => { 146 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix) 147 }, 148 "domain" => { 149 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain) 150 }, 151 "regexp" => { 152 input.parse_nested_block(|input| { 153 Ok(DocumentMatchingFunction::Regexp( 154 input.expect_string()?.as_ref().to_owned(), 155 )) 156 }) 157 }, 158 "media-document" => { 159 input.parse_nested_block(|input| { 160 let kind = MediaDocumentKind::parse(input)?; 161 Ok(DocumentMatchingFunction::MediaDocument(kind)) 162 }) 163 }, 164 _ => { 165 Err(location.new_custom_error( 166 StyleParseErrorKind::UnexpectedFunction(function.clone()) 167 )) 168 }, 169 } 170 } 171 172 #[cfg(feature = "gecko")] 173 /// Evaluate a URL matching function. evaluate(&self, device: &Device) -> bool174 pub fn evaluate(&self, device: &Device) -> bool { 175 use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; 176 use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction; 177 use nsstring::nsCStr; 178 179 let func = match *self { 180 DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL, 181 DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix, 182 DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain, 183 DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp, 184 DocumentMatchingFunction::MediaDocument(_) => { 185 GeckoDocumentMatchingFunction::MediaDocument 186 }, 187 }; 188 189 let pattern = nsCStr::from(match *self { 190 DocumentMatchingFunction::Url(ref url) => url.as_str(), 191 DocumentMatchingFunction::UrlPrefix(ref pat) | 192 DocumentMatchingFunction::Domain(ref pat) | 193 DocumentMatchingFunction::Regexp(ref pat) => pat, 194 DocumentMatchingFunction::MediaDocument(kind) => match kind { 195 MediaDocumentKind::All => "all", 196 MediaDocumentKind::Image => "image", 197 MediaDocumentKind::Plugin => "plugin", 198 MediaDocumentKind::Video => "video", 199 }, 200 }); 201 unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) } 202 } 203 204 #[cfg(not(feature = "gecko"))] 205 /// Evaluate a URL matching function. evaluate(&self, _: &Device) -> bool206 pub fn evaluate(&self, _: &Device) -> bool { 207 false 208 } 209 } 210 211 /// A `@document` rule's condition. 212 /// 213 /// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document> 214 /// 215 /// The `@document` rule's condition is written as a comma-separated list of 216 /// URL matching functions, and the condition evaluates to true whenever any 217 /// one of those functions evaluates to true. 218 #[css(comma)] 219 #[derive(Clone, Debug, ToCss, ToShmem)] 220 pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>); 221 222 impl DocumentCondition { 223 /// Parse a document condition. parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>224 pub fn parse<'i, 't>( 225 context: &ParserContext, 226 input: &mut Parser<'i, 't>, 227 ) -> Result<Self, ParseError<'i>> { 228 let conditions = 229 input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?; 230 231 let condition = DocumentCondition(conditions); 232 if !condition.allowed_in(context) { 233 return Err( 234 input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule( 235 "-moz-document".into(), 236 )), 237 ); 238 } 239 Ok(condition) 240 } 241 242 /// Evaluate a document condition. evaluate(&self, device: &Device) -> bool243 pub fn evaluate(&self, device: &Device) -> bool { 244 self.0 245 .iter() 246 .any(|url_matching_function| url_matching_function.evaluate(device)) 247 } 248 249 #[cfg(feature = "servo")] allowed_in(&self, _: &ParserContext) -> bool250 fn allowed_in(&self, _: &ParserContext) -> bool { 251 false 252 } 253 254 #[cfg(feature = "gecko")] allowed_in(&self, context: &ParserContext) -> bool255 fn allowed_in(&self, context: &ParserContext) -> bool { 256 use crate::stylesheets::Origin; 257 use static_prefs::pref; 258 259 if context.stylesheet_origin != Origin::Author { 260 return true; 261 } 262 263 if pref!("layout.css.moz-document.content.enabled") { 264 return true; 265 } 266 267 // Allow a single url-prefix() for compatibility. 268 // 269 // See bug 1446470 and dependencies. 270 if self.0.len() != 1 { 271 return false; 272 } 273 274 // NOTE(emilio): This technically allows url-prefix("") too, but... 275 match self.0[0] { 276 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(), 277 _ => false, 278 } 279 } 280 } 281