/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4. //! We implement the prefixed `@-moz-document`. use cssparser::{Parser, SourceLocation}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use media_queries::Device; use parser::{Parse, ParserContext}; use servo_arc::Arc; use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; use shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use std::fmt::{self, Write}; use str::CssStringWriter; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use stylesheets::CssRules; use values::CssUrl; #[derive(Debug)] /// A @-moz-document rule pub struct DocumentRule { /// The parsed condition pub condition: DocumentCondition, /// Child rules pub rules: Arc>, /// The line and column of the rule's source code. pub source_location: SourceLocation, } impl DocumentRule { /// Measure heap usage. #[cfg(feature = "gecko")] pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { // Measurement of other fields may be added later. self.rules.unconditional_shallow_size_of(ops) + self.rules.read_with(guard).size_of(guard, ops) } } impl ToCssWithGuard for DocumentRule { fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { dest.write_str("@-moz-document ")?; self.condition.to_css(&mut CssWriter::new(dest))?; dest.write_str(" {")?; for rule in self.rules.read_with(guard).0.iter() { dest.write_str(" ")?; rule.to_css(guard, dest)?; } dest.write_str(" }") } } impl DeepCloneWithLock for DocumentRule { /// Deep clones this DocumentRule. fn deep_clone_with_lock( &self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard, params: &DeepCloneParams, ) -> Self { let rules = self.rules.read_with(guard); DocumentRule { condition: self.condition.clone(), rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), source_location: self.source_location.clone(), } } } /// A URL matching function for a `@document` rule's condition. #[derive(Clone, Debug, ToCss)] pub enum UrlMatchingFunction { /// Exact URL matching function. It evaluates to true whenever the /// URL of the document being styled is exactly the URL given. Url(CssUrl), /// URL prefix matching function. It evaluates to true whenever the /// URL of the document being styled has the argument to the /// function as an initial substring (which is true when the two /// strings are equal). When the argument is the empty string, /// it evaluates to true for all documents. #[css(function)] UrlPrefix(String), /// Domain matching function. It evaluates to true whenever the URL /// of the document being styled has a host subcomponent and that /// host subcomponent is exactly the argument to the ‘domain()’ /// function or a final substring of the host component is a /// period (U+002E) immediately followed by the argument to the /// ‘domain()’ function. #[css(function)] Domain(String), /// Regular expression matching function. It evaluates to true /// whenever the regular expression matches the entirety of the URL /// of the document being styled. #[css(function)] Regexp(String), } macro_rules! parse_quoted_or_unquoted_string { ($input:ident, $url_matching_function:expr) => { $input.parse_nested_block(|input| { let start = input.position(); input .parse_entirely(|input| { let string = input.expect_string()?; Ok($url_matching_function(string.as_ref().to_owned())) }) .or_else(|_: ParseError| { while let Ok(_) = input.next() {} Ok($url_matching_function(input.slice_from(start).to_string())) }) }) }; } impl UrlMatchingFunction { /// Parse a URL matching function for a`@document` rule's condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if input .try(|input| input.expect_function_matching("url-prefix")) .is_ok() { return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix); } if input .try(|input| input.expect_function_matching("domain")) .is_ok() { return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain); } if input .try(|input| input.expect_function_matching("regexp")) .is_ok() { return input.parse_nested_block(|input| { Ok(UrlMatchingFunction::Regexp( input.expect_string()?.as_ref().to_owned(), )) }); } let url = CssUrl::parse(context, input)?; Ok(UrlMatchingFunction::Url(url)) } #[cfg(feature = "gecko")] /// Evaluate a URL matching function. pub fn evaluate(&self, device: &Device) -> bool { use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction; use nsstring::nsCStr; let func = match *self { UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL, UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix, UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain, UrlMatchingFunction::Regexp(_) => GeckoUrlMatchingFunction::eRegExp, }; let pattern = nsCStr::from(match *self { UrlMatchingFunction::Url(ref url) => url.as_str(), UrlMatchingFunction::UrlPrefix(ref pat) | UrlMatchingFunction::Domain(ref pat) | UrlMatchingFunction::Regexp(ref pat) => pat, }); unsafe { Gecko_DocumentRule_UseForPresentation(device.pres_context(), &*pattern, func) } } #[cfg(not(feature = "gecko"))] /// Evaluate a URL matching function. pub fn evaluate(&self, _: &Device) -> bool { false } } /// A `@document` rule's condition. /// /// /// /// The `@document` rule's condition is written as a comma-separated list of /// URL matching functions, and the condition evaluates to true whenever any /// one of those functions evaluates to true. #[css(comma)] #[derive(Clone, Debug, ToCss)] pub struct DocumentCondition(#[css(iterable)] Vec); impl DocumentCondition { /// Parse a document condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let conditions = input.parse_comma_separated(|input| UrlMatchingFunction::parse(context, input))?; let condition = DocumentCondition(conditions); if !condition.allowed_in(context) { return Err( input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule( "-moz-document".into(), )), ); } Ok(condition) } /// Evaluate a document condition. pub fn evaluate(&self, device: &Device) -> bool { self.0 .iter() .any(|url_matching_function| url_matching_function.evaluate(device)) } #[cfg(feature = "servo")] fn allowed_in(&self, _: &ParserContext) -> bool { false } #[cfg(feature = "gecko")] fn allowed_in(&self, context: &ParserContext) -> bool { use gecko_bindings::structs; use stylesheets::Origin; if context.stylesheet_origin != Origin::Author { return true; } if unsafe { structs::StaticPrefs_sVarCache_layout_css_moz_document_content_enabled } { return true; } if !unsafe { structs::StaticPrefs_sVarCache_layout_css_moz_document_url_prefix_hack_enabled } { return false; } // Allow a single url-prefix() for compatibility. // // See bug 1446470 and dependencies. if self.0.len() != 1 { return false; } // NOTE(emilio): This technically allows url-prefix("") too, but... match self.0[0] { UrlMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(), _ => false, } } }