use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{DeepCloneWithLock, Locked};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::stylesheets::CssRules;
use crate::values::CssUrl;
use cssparser::{BasicParseErrorKind, Parser, SourceLocation};
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
#[derive(Debug, ToShmem)]
pub struct DocumentRule {
pub condition: DocumentCondition,
pub rules: Arc<Locked<CssRules>>,
pub source_location: SourceLocation,
}
impl DocumentRule {
#[cfg(feature = "gecko")]
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
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_char(' ')?;
rule.to_css(guard, dest)?;
}
dest.write_str(" }")
}
}
impl DeepCloneWithLock for DocumentRule {
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
) -> 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))),
source_location: self.source_location.clone(),
}
}
}
#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
#[allow(missing_docs)]
pub enum MediaDocumentKind {
All,
Image,
Video,
}
#[derive(Clone, Debug, ToCss, ToShmem)]
pub enum DocumentMatchingFunction {
Url(CssUrl),
#[css(function)]
UrlPrefix(String),
#[css(function)]
Domain(String),
#[css(function)]
Regexp(String),
#[css(function)]
MediaDocument(MediaDocumentKind),
#[css(function)]
PlainTextDocument(()),
#[css(function)]
UnobservableDocument(()),
}
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 DocumentMatchingFunction {
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
return Ok(DocumentMatchingFunction::Url(url));
}
let location = input.current_source_location();
let function = input.expect_function()?.clone();
match_ignore_ascii_case! { &function,
"url-prefix" => {
parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
},
"domain" => {
parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
},
"regexp" => {
input.parse_nested_block(|input| {
Ok(DocumentMatchingFunction::Regexp(
input.expect_string()?.as_ref().to_owned(),
))
})
},
"media-document" => {
input.parse_nested_block(|input| {
let kind = MediaDocumentKind::parse(input)?;
Ok(DocumentMatchingFunction::MediaDocument(kind))
})
},
"plain-text-document" => {
input.parse_nested_block(|input| {
input.expect_exhausted()?;
Ok(DocumentMatchingFunction::PlainTextDocument(()))
})
},
"unobservable-document" => {
input.parse_nested_block(|input| {
input.expect_exhausted()?;
Ok(DocumentMatchingFunction::UnobservableDocument(()))
})
},
_ => {
Err(location.new_custom_error(
StyleParseErrorKind::UnexpectedFunction(function.clone())
))
},
}
}
#[cfg(feature = "gecko")]
pub fn evaluate(&self, device: &Device) -> bool {
use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
use nsstring::nsCStr;
let func = match *self {
DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
DocumentMatchingFunction::MediaDocument(_) => {
GeckoDocumentMatchingFunction::MediaDocument
},
DocumentMatchingFunction::PlainTextDocument(..) => {
GeckoDocumentMatchingFunction::PlainTextDocument
},
DocumentMatchingFunction::UnobservableDocument(..) => {
GeckoDocumentMatchingFunction::UnobservableDocument
},
};
let pattern = nsCStr::from(match *self {
DocumentMatchingFunction::Url(ref url) => url.as_str(),
DocumentMatchingFunction::UrlPrefix(ref pat) |
DocumentMatchingFunction::Domain(ref pat) |
DocumentMatchingFunction::Regexp(ref pat) => pat,
DocumentMatchingFunction::MediaDocument(kind) => match kind {
MediaDocumentKind::All => "all",
MediaDocumentKind::Image => "image",
MediaDocumentKind::Video => "video",
},
DocumentMatchingFunction::PlainTextDocument(()) |
DocumentMatchingFunction::UnobservableDocument(()) => "",
});
unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
}
#[cfg(not(feature = "gecko"))]
pub fn evaluate(&self, _: &Device) -> bool {
false
}
}
#[derive(Clone, Debug, ToCss, ToShmem)]
#[css(comma)]
pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
impl DocumentCondition {
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let conditions =
input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
let condition = DocumentCondition(conditions);
if !condition.allowed_in(context) {
return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into())));
}
Ok(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 {
if context.chrome_rules_enabled() {
return true;
}
if self.0.len() != 1 {
return false;
}
match self.0[0] {
DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
_ => false,
}
}
}