1use crate::derives::*;
10use crate::device::Device;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{DeepCloneWithLock, Locked};
13use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
14use crate::stylesheets::CssRules;
15use crate::values::CssUrl;
16use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, Parser, SourceLocation};
17#[cfg(feature = "gecko")]
18use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
19use servo_arc::Arc;
20use std::fmt::{self, Write};
21use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
22
23#[derive(Debug, ToShmem)]
24pub struct DocumentRule {
26 pub condition: DocumentCondition,
28 pub rules: Arc<Locked<CssRules>>,
30 pub source_location: SourceLocation,
32}
33
34impl DocumentRule {
35 #[cfg(feature = "gecko")]
37 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
38 self.rules.unconditional_shallow_size_of(ops)
40 + self.rules.read_with(guard).size_of(guard, ops)
41 }
42}
43
44impl 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
57impl DeepCloneWithLock for DocumentRule {
58 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,
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
71#[allow(missing_docs)]
72pub enum MediaDocumentKind {
73 All,
74 Image,
75 Video,
76}
77
78#[derive(Clone, Debug, ToCss, ToShmem)]
80pub enum DocumentMatchingFunction {
81 Url(CssUrl),
84 #[css(function)]
90 UrlPrefix(String),
91 #[css(function)]
98 Domain(String),
99 #[css(function)]
103 Regexp(String),
104 #[css(function)]
106 MediaDocument(MediaDocumentKind),
107 #[css(function)]
109 PlainTextDocument(()),
110}
111
112macro_rules! parse_quoted_or_unquoted_string {
113 ($input:ident, $url_matching_function:expr) => {
114 $input.parse_nested_block(|input| {
115 let start = input.position();
116 input
117 .parse_entirely(|input| {
118 let string = input.expect_string()?;
119 Ok($url_matching_function(string.as_ref().to_owned()))
120 })
121 .or_else(|_: ParseError| {
122 while let Ok(_) = input.next() {}
123 Ok($url_matching_function(input.slice_from(start).to_string()))
124 })
125 })
126 };
127}
128
129impl DocumentMatchingFunction {
130 pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
132 if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
133 return Ok(DocumentMatchingFunction::Url(url));
134 }
135
136 let function = input.expect_function()?.clone();
137 match_ignore_ascii_case! { &function,
138 "url-prefix" => {
139 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
140 },
141 "domain" => {
142 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
143 },
144 "regexp" => {
145 input.parse_nested_block(|input| {
146 Ok(DocumentMatchingFunction::Regexp(
147 input.expect_string()?.as_ref().to_owned(),
148 ))
149 })
150 },
151 "media-document" => {
152 input.parse_nested_block(|input| {
153 let kind = MediaDocumentKind::parse(input)?;
154 Ok(DocumentMatchingFunction::MediaDocument(kind))
155 })
156 },
157
158 "plain-text-document" => {
159 input.parse_nested_block(|input| {
160 input.expect_exhausted()?;
161 Ok(DocumentMatchingFunction::PlainTextDocument(()))
162 })
163 },
164
165 _ => {
166 Err(ParseError::custom(
167 StyleParseErrorKind::UnexpectedFunction
168 ))
169 },
170 }
171 }
172
173 #[cfg(feature = "gecko")]
174 pub fn evaluate(&self, device: &Device) -> bool {
176 use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
177 use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
178 use nsstring::nsCStr;
179
180 let func = match *self {
181 DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
182 DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
183 DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
184 DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
185 DocumentMatchingFunction::MediaDocument(_) => {
186 GeckoDocumentMatchingFunction::MediaDocument
187 },
188 DocumentMatchingFunction::PlainTextDocument(..) => {
189 GeckoDocumentMatchingFunction::PlainTextDocument
190 },
191 };
192
193 let pattern = nsCStr::from(match *self {
194 DocumentMatchingFunction::Url(ref url) => url.as_str(),
195 DocumentMatchingFunction::UrlPrefix(ref pat)
196 | DocumentMatchingFunction::Domain(ref pat)
197 | DocumentMatchingFunction::Regexp(ref pat) => pat,
198 DocumentMatchingFunction::MediaDocument(kind) => match kind {
199 MediaDocumentKind::All => "all",
200 MediaDocumentKind::Image => "image",
201 MediaDocumentKind::Video => "video",
202 },
203 DocumentMatchingFunction::PlainTextDocument(()) => "",
204 });
205 unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
206 }
207
208 #[cfg(not(feature = "gecko"))]
209 pub fn evaluate(&self, _: &Device) -> bool {
211 false
212 }
213}
214
215#[derive(Clone, Debug, ToCss, ToShmem)]
223#[css(comma)]
224pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
225
226impl DocumentCondition {
227 pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
229 let conditions =
230 input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
231
232 let condition = DocumentCondition(conditions);
233 if !condition.allowed_in(context) {
234 return Err(ParseError::from_basic_kind(
235 BasicParseErrorKind::AtRuleInvalid,
236 ));
237 }
238 Ok(condition)
239 }
240
241 pub fn evaluate(&self, device: &Device) -> bool {
243 self.0
244 .iter()
245 .any(|url_matching_function| url_matching_function.evaluate(device))
246 }
247
248 #[cfg(feature = "servo")]
249 fn allowed_in(&self, _: &ParserContext) -> bool {
250 false
251 }
252
253 #[cfg(feature = "gecko")]
254 fn allowed_in(&self, context: &ParserContext) -> bool {
255 if context.chrome_rules_enabled() {
256 return true;
257 }
258
259 if self.0.len() != 1 {
263 return false;
264 }
265
266 match self.0[0] {
268 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
269 _ => false,
270 }
271 }
272}