document_rule.rs (11156B)
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::derives::*; 10 use crate::media_queries::Device; 11 use crate::parser::{Parse, ParserContext}; 12 use crate::shared_lock::{DeepCloneWithLock, Locked}; 13 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; 14 use crate::stylesheets::CssRules; 15 use crate::values::CssUrl; 16 use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, 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::{CssStringWriter, 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")] 37 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 { 45 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_char(' ')?; 51 rule.to_css(guard, dest)?; 52 } 53 dest.write_str(" }") 54 } 55 } 56 57 impl DeepCloneWithLock for DocumentRule { 58 /// Deep clones this DocumentRule. 59 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self { 60 let rules = self.rules.read_with(guard); 61 DocumentRule { 62 condition: self.condition.clone(), 63 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))), 64 source_location: self.source_location.clone(), 65 } 66 } 67 } 68 69 /// The kind of media document that the rule will match. 70 #[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)] 71 #[allow(missing_docs)] 72 pub enum MediaDocumentKind { 73 All, 74 Image, 75 Video, 76 } 77 78 /// A matching function for a `@document` rule's condition. 79 #[derive(Clone, Debug, ToCss, ToShmem)] 80 pub enum DocumentMatchingFunction { 81 /// Exact URL matching function. It evaluates to true whenever the 82 /// URL of the document being styled is exactly the URL given. 83 Url(CssUrl), 84 /// URL prefix matching function. It evaluates to true whenever the 85 /// URL of the document being styled has the argument to the 86 /// function as an initial substring (which is true when the two 87 /// strings are equal). When the argument is the empty string, 88 /// it evaluates to true for all documents. 89 #[css(function)] 90 UrlPrefix(String), 91 /// Domain matching function. It evaluates to true whenever the URL 92 /// of the document being styled has a host subcomponent and that 93 /// host subcomponent is exactly the argument to the ‘domain()’ 94 /// function or a final substring of the host component is a 95 /// period (U+002E) immediately followed by the argument to the 96 /// ‘domain()’ function. 97 #[css(function)] 98 Domain(String), 99 /// Regular expression matching function. It evaluates to true 100 /// whenever the regular expression matches the entirety of the URL 101 /// of the document being styled. 102 #[css(function)] 103 Regexp(String), 104 /// Matching function for a media document. 105 #[css(function)] 106 MediaDocument(MediaDocumentKind), 107 /// Matching function for a plain-text document. 108 #[css(function)] 109 PlainTextDocument(()), 110 /// Matching function for a document that can be observed by other content 111 /// documents. 112 #[css(function)] 113 UnobservableDocument(()), 114 } 115 116 macro_rules! parse_quoted_or_unquoted_string { 117 ($input:ident, $url_matching_function:expr) => { 118 $input.parse_nested_block(|input| { 119 let start = input.position(); 120 input 121 .parse_entirely(|input| { 122 let string = input.expect_string()?; 123 Ok($url_matching_function(string.as_ref().to_owned())) 124 }) 125 .or_else(|_: ParseError| { 126 while let Ok(_) = input.next() {} 127 Ok($url_matching_function(input.slice_from(start).to_string())) 128 }) 129 }) 130 }; 131 } 132 133 impl DocumentMatchingFunction { 134 /// Parse a URL matching function for a`@document` rule's condition. 135 pub fn parse<'i, 't>( 136 context: &ParserContext, 137 input: &mut Parser<'i, 't>, 138 ) -> Result<Self, ParseError<'i>> { 139 if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) { 140 return Ok(DocumentMatchingFunction::Url(url)); 141 } 142 143 let location = input.current_source_location(); 144 let function = input.expect_function()?.clone(); 145 match_ignore_ascii_case! { &function, 146 "url-prefix" => { 147 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix) 148 }, 149 "domain" => { 150 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain) 151 }, 152 "regexp" => { 153 input.parse_nested_block(|input| { 154 Ok(DocumentMatchingFunction::Regexp( 155 input.expect_string()?.as_ref().to_owned(), 156 )) 157 }) 158 }, 159 "media-document" => { 160 input.parse_nested_block(|input| { 161 let kind = MediaDocumentKind::parse(input)?; 162 Ok(DocumentMatchingFunction::MediaDocument(kind)) 163 }) 164 }, 165 166 "plain-text-document" => { 167 input.parse_nested_block(|input| { 168 input.expect_exhausted()?; 169 Ok(DocumentMatchingFunction::PlainTextDocument(())) 170 }) 171 }, 172 173 "unobservable-document" => { 174 input.parse_nested_block(|input| { 175 input.expect_exhausted()?; 176 Ok(DocumentMatchingFunction::UnobservableDocument(())) 177 }) 178 }, 179 180 _ => { 181 Err(location.new_custom_error( 182 StyleParseErrorKind::UnexpectedFunction(function.clone()) 183 )) 184 }, 185 } 186 } 187 188 #[cfg(feature = "gecko")] 189 /// Evaluate a URL matching function. 190 pub fn evaluate(&self, device: &Device) -> bool { 191 use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; 192 use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction; 193 use nsstring::nsCStr; 194 195 let func = match *self { 196 DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL, 197 DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix, 198 DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain, 199 DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp, 200 DocumentMatchingFunction::MediaDocument(_) => { 201 GeckoDocumentMatchingFunction::MediaDocument 202 }, 203 DocumentMatchingFunction::PlainTextDocument(..) => { 204 GeckoDocumentMatchingFunction::PlainTextDocument 205 }, 206 DocumentMatchingFunction::UnobservableDocument(..) => { 207 GeckoDocumentMatchingFunction::UnobservableDocument 208 }, 209 }; 210 211 let pattern = nsCStr::from(match *self { 212 DocumentMatchingFunction::Url(ref url) => url.as_str(), 213 DocumentMatchingFunction::UrlPrefix(ref pat) 214 | DocumentMatchingFunction::Domain(ref pat) 215 | DocumentMatchingFunction::Regexp(ref pat) => pat, 216 DocumentMatchingFunction::MediaDocument(kind) => match kind { 217 MediaDocumentKind::All => "all", 218 MediaDocumentKind::Image => "image", 219 MediaDocumentKind::Video => "video", 220 }, 221 DocumentMatchingFunction::PlainTextDocument(()) 222 | DocumentMatchingFunction::UnobservableDocument(()) => "", 223 }); 224 unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) } 225 } 226 227 #[cfg(not(feature = "gecko"))] 228 /// Evaluate a URL matching function. 229 pub fn evaluate(&self, _: &Device) -> bool { 230 false 231 } 232 } 233 234 /// A `@document` rule's condition. 235 /// 236 /// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document> 237 /// 238 /// The `@document` rule's condition is written as a comma-separated list of 239 /// URL matching functions, and the condition evaluates to true whenever any 240 /// one of those functions evaluates to true. 241 #[derive(Clone, Debug, ToCss, ToShmem)] 242 #[css(comma)] 243 pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>); 244 245 impl DocumentCondition { 246 /// Parse a document condition. 247 pub fn parse<'i, 't>( 248 context: &ParserContext, 249 input: &mut Parser<'i, 't>, 250 ) -> Result<Self, ParseError<'i>> { 251 let conditions = 252 input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?; 253 254 let condition = DocumentCondition(conditions); 255 if !condition.allowed_in(context) { 256 return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into()))); 257 } 258 Ok(condition) 259 } 260 261 /// Evaluate a document condition. 262 pub fn evaluate(&self, device: &Device) -> bool { 263 self.0 264 .iter() 265 .any(|url_matching_function| url_matching_function.evaluate(device)) 266 } 267 268 #[cfg(feature = "servo")] 269 fn allowed_in(&self, _: &ParserContext) -> bool { 270 false 271 } 272 273 #[cfg(feature = "gecko")] 274 fn allowed_in(&self, context: &ParserContext) -> bool { 275 if context.chrome_rules_enabled() { 276 return true; 277 } 278 279 // Allow a single url-prefix() for compatibility. 280 // 281 // See bug 1446470 and dependencies. 282 if self.0.len() != 1 { 283 return false; 284 } 285 286 // NOTE(emilio): This technically allows url-prefix("") too, but... 287 match self.0[0] { 288 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(), 289 _ => false, 290 } 291 } 292 }