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(&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#[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 #[css(function)]
113 UnobservableDocument(()),
114}
115
116macro_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
133impl DocumentMatchingFunction {
134 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 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 pub fn evaluate(&self, _: &Device) -> bool {
230 false
231 }
232}
233
234#[derive(Clone, Debug, ToCss, ToShmem)]
242#[css(comma)]
243pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
244
245impl DocumentCondition {
246 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 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 if self.0.len() != 1 {
283 return false;
284 }
285
286 match self.0[0] {
288 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
289 _ => false,
290 }
291 }
292}