1use crate::media_queries::Device;
10use crate::parser::{Parse, ParserContext};
11use crate::shared_lock::{DeepCloneWithLock, Locked};
12use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
13use crate::str::CssStringWriter;
14use crate::stylesheets::CssRules;
15use crate::values::CssUrl;
16use cssparser::{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::{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(
60 &self,
61 lock: &SharedRwLock,
62 guard: &SharedRwLockReadGuard,
63 ) -> Self {
64 let rules = self.rules.read_with(guard);
65 DocumentRule {
66 condition: self.condition.clone(),
67 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
68 source_location: self.source_location.clone(),
69 }
70 }
71}
72
73#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
75#[allow(missing_docs)]
76pub enum MediaDocumentKind {
77 All,
78 Image,
79 Video,
80}
81
82#[derive(Clone, Debug, ToCss, ToShmem)]
84pub enum DocumentMatchingFunction {
85 Url(CssUrl),
88 #[css(function)]
94 UrlPrefix(String),
95 #[css(function)]
102 Domain(String),
103 #[css(function)]
107 Regexp(String),
108 #[css(function)]
110 MediaDocument(MediaDocumentKind),
111 #[css(function)]
113 PlainTextDocument(()),
114 #[css(function)]
117 UnobservableDocument(()),
118}
119
120macro_rules! parse_quoted_or_unquoted_string {
121 ($input:ident, $url_matching_function:expr) => {
122 $input.parse_nested_block(|input| {
123 let start = input.position();
124 input
125 .parse_entirely(|input| {
126 let string = input.expect_string()?;
127 Ok($url_matching_function(string.as_ref().to_owned()))
128 })
129 .or_else(|_: ParseError| {
130 while let Ok(_) = input.next() {}
131 Ok($url_matching_function(input.slice_from(start).to_string()))
132 })
133 })
134 };
135}
136
137impl DocumentMatchingFunction {
138 pub fn parse<'i, 't>(
140 context: &ParserContext,
141 input: &mut Parser<'i, 't>,
142 ) -> Result<Self, ParseError<'i>> {
143 if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
144 return Ok(DocumentMatchingFunction::Url(url));
145 }
146
147 let location = input.current_source_location();
148 let function = input.expect_function()?.clone();
149 match_ignore_ascii_case! { &function,
150 "url-prefix" => {
151 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
152 },
153 "domain" => {
154 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
155 },
156 "regexp" => {
157 input.parse_nested_block(|input| {
158 Ok(DocumentMatchingFunction::Regexp(
159 input.expect_string()?.as_ref().to_owned(),
160 ))
161 })
162 },
163 "media-document" => {
164 input.parse_nested_block(|input| {
165 let kind = MediaDocumentKind::parse(input)?;
166 Ok(DocumentMatchingFunction::MediaDocument(kind))
167 })
168 },
169
170 "plain-text-document" => {
171 input.parse_nested_block(|input| {
172 input.expect_exhausted()?;
173 Ok(DocumentMatchingFunction::PlainTextDocument(()))
174 })
175 },
176
177 "unobservable-document" => {
178 input.parse_nested_block(|input| {
179 input.expect_exhausted()?;
180 Ok(DocumentMatchingFunction::UnobservableDocument(()))
181 })
182 },
183
184 _ => {
185 Err(location.new_custom_error(
186 StyleParseErrorKind::UnexpectedFunction(function.clone())
187 ))
188 },
189 }
190 }
191
192 #[cfg(feature = "gecko")]
193 pub fn evaluate(&self, device: &Device) -> bool {
195 use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
196 use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
197 use nsstring::nsCStr;
198
199 let func = match *self {
200 DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
201 DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
202 DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
203 DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
204 DocumentMatchingFunction::MediaDocument(_) => {
205 GeckoDocumentMatchingFunction::MediaDocument
206 },
207 DocumentMatchingFunction::PlainTextDocument(..) => {
208 GeckoDocumentMatchingFunction::PlainTextDocument
209 },
210 DocumentMatchingFunction::UnobservableDocument(..) => {
211 GeckoDocumentMatchingFunction::UnobservableDocument
212 },
213 };
214
215 let pattern = nsCStr::from(match *self {
216 DocumentMatchingFunction::Url(ref url) => url.as_str(),
217 DocumentMatchingFunction::UrlPrefix(ref pat) |
218 DocumentMatchingFunction::Domain(ref pat) |
219 DocumentMatchingFunction::Regexp(ref pat) => pat,
220 DocumentMatchingFunction::MediaDocument(kind) => match kind {
221 MediaDocumentKind::All => "all",
222 MediaDocumentKind::Image => "image",
223 MediaDocumentKind::Video => "video",
224 },
225 DocumentMatchingFunction::PlainTextDocument(()) |
226 DocumentMatchingFunction::UnobservableDocument(()) => "",
227 });
228 unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
229 }
230
231 #[cfg(not(feature = "gecko"))]
232 pub fn evaluate(&self, _: &Device) -> bool {
234 false
235 }
236}
237
238#[derive(Clone, Debug, ToCss, ToShmem)]
246#[css(comma)]
247pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
248
249impl DocumentCondition {
250 pub fn parse<'i, 't>(
252 context: &ParserContext,
253 input: &mut Parser<'i, 't>,
254 ) -> Result<Self, ParseError<'i>> {
255 let conditions =
256 input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
257
258 let condition = DocumentCondition(conditions);
259 if !condition.allowed_in(context) {
260 return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into())));
261 }
262 Ok(condition)
263 }
264
265 pub fn evaluate(&self, device: &Device) -> bool {
267 self.0
268 .iter()
269 .any(|url_matching_function| url_matching_function.evaluate(device))
270 }
271
272 #[cfg(feature = "servo")]
273 fn allowed_in(&self, _: &ParserContext) -> bool {
274 false
275 }
276
277 #[cfg(feature = "gecko")]
278 fn allowed_in(&self, context: &ParserContext) -> bool {
279 if context.chrome_rules_enabled() {
280 return true;
281 }
282
283 if self.0.len() != 1 {
287 return false;
288 }
289
290 match self.0[0] {
292 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
293 _ => false,
294 }
295 }
296}