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::stylesheets::CssRules;
14use crate::values::CssUrl;
15use cssparser::{BasicParseErrorKind, Parser, SourceLocation};
16#[cfg(feature = "gecko")]
17use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
18use servo_arc::Arc;
19use std::fmt::{self, Write};
20use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
21
22#[derive(Debug, ToShmem)]
23pub struct DocumentRule {
25 pub condition: DocumentCondition,
27 pub rules: Arc<Locked<CssRules>>,
29 pub source_location: SourceLocation,
31}
32
33impl DocumentRule {
34 #[cfg(feature = "gecko")]
36 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
37 self.rules.unconditional_shallow_size_of(ops)
39 + self.rules.read_with(guard).size_of(guard, ops)
40 }
41}
42
43impl ToCssWithGuard for DocumentRule {
44 fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
45 dest.write_str("@-moz-document ")?;
46 self.condition.to_css(&mut CssWriter::new(dest))?;
47 dest.write_str(" {")?;
48 for rule in self.rules.read_with(guard).0.iter() {
49 dest.write_char(' ')?;
50 rule.to_css(guard, dest)?;
51 }
52 dest.write_str(" }")
53 }
54}
55
56impl DeepCloneWithLock for DocumentRule {
57 fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
59 let rules = self.rules.read_with(guard);
60 DocumentRule {
61 condition: self.condition.clone(),
62 rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
63 source_location: self.source_location.clone(),
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
70#[allow(missing_docs)]
71pub enum MediaDocumentKind {
72 All,
73 Image,
74 Video,
75}
76
77#[derive(Clone, Debug, ToCss, ToShmem)]
79pub enum DocumentMatchingFunction {
80 Url(CssUrl),
83 #[css(function)]
89 UrlPrefix(String),
90 #[css(function)]
97 Domain(String),
98 #[css(function)]
102 Regexp(String),
103 #[css(function)]
105 MediaDocument(MediaDocumentKind),
106 #[css(function)]
108 PlainTextDocument(()),
109 #[css(function)]
112 UnobservableDocument(()),
113}
114
115macro_rules! parse_quoted_or_unquoted_string {
116 ($input:ident, $url_matching_function:expr) => {
117 $input.parse_nested_block(|input| {
118 let start = input.position();
119 input
120 .parse_entirely(|input| {
121 let string = input.expect_string()?;
122 Ok($url_matching_function(string.as_ref().to_owned()))
123 })
124 .or_else(|_: ParseError| {
125 while let Ok(_) = input.next() {}
126 Ok($url_matching_function(input.slice_from(start).to_string()))
127 })
128 })
129 };
130}
131
132impl DocumentMatchingFunction {
133 pub fn parse<'i, 't>(
135 context: &ParserContext,
136 input: &mut Parser<'i, 't>,
137 ) -> Result<Self, ParseError<'i>> {
138 if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
139 return Ok(DocumentMatchingFunction::Url(url));
140 }
141
142 let location = input.current_source_location();
143 let function = input.expect_function()?.clone();
144 match_ignore_ascii_case! { &function,
145 "url-prefix" => {
146 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
147 },
148 "domain" => {
149 parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
150 },
151 "regexp" => {
152 input.parse_nested_block(|input| {
153 Ok(DocumentMatchingFunction::Regexp(
154 input.expect_string()?.as_ref().to_owned(),
155 ))
156 })
157 },
158 "media-document" => {
159 input.parse_nested_block(|input| {
160 let kind = MediaDocumentKind::parse(input)?;
161 Ok(DocumentMatchingFunction::MediaDocument(kind))
162 })
163 },
164
165 "plain-text-document" => {
166 input.parse_nested_block(|input| {
167 input.expect_exhausted()?;
168 Ok(DocumentMatchingFunction::PlainTextDocument(()))
169 })
170 },
171
172 "unobservable-document" => {
173 input.parse_nested_block(|input| {
174 input.expect_exhausted()?;
175 Ok(DocumentMatchingFunction::UnobservableDocument(()))
176 })
177 },
178
179 _ => {
180 Err(location.new_custom_error(
181 StyleParseErrorKind::UnexpectedFunction(function.clone())
182 ))
183 },
184 }
185 }
186
187 #[cfg(feature = "gecko")]
188 pub fn evaluate(&self, device: &Device) -> bool {
190 use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
191 use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
192 use nsstring::nsCStr;
193
194 let func = match *self {
195 DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
196 DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
197 DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
198 DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
199 DocumentMatchingFunction::MediaDocument(_) => {
200 GeckoDocumentMatchingFunction::MediaDocument
201 },
202 DocumentMatchingFunction::PlainTextDocument(..) => {
203 GeckoDocumentMatchingFunction::PlainTextDocument
204 },
205 DocumentMatchingFunction::UnobservableDocument(..) => {
206 GeckoDocumentMatchingFunction::UnobservableDocument
207 },
208 };
209
210 let pattern = nsCStr::from(match *self {
211 DocumentMatchingFunction::Url(ref url) => url.as_str(),
212 DocumentMatchingFunction::UrlPrefix(ref pat)
213 | DocumentMatchingFunction::Domain(ref pat)
214 | DocumentMatchingFunction::Regexp(ref pat) => pat,
215 DocumentMatchingFunction::MediaDocument(kind) => match kind {
216 MediaDocumentKind::All => "all",
217 MediaDocumentKind::Image => "image",
218 MediaDocumentKind::Video => "video",
219 },
220 DocumentMatchingFunction::PlainTextDocument(())
221 | DocumentMatchingFunction::UnobservableDocument(()) => "",
222 });
223 unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
224 }
225
226 #[cfg(not(feature = "gecko"))]
227 pub fn evaluate(&self, _: &Device) -> bool {
229 false
230 }
231}
232
233#[derive(Clone, Debug, ToCss, ToShmem)]
241#[css(comma)]
242pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
243
244impl DocumentCondition {
245 pub fn parse<'i, 't>(
247 context: &ParserContext,
248 input: &mut Parser<'i, 't>,
249 ) -> Result<Self, ParseError<'i>> {
250 let conditions =
251 input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
252
253 let condition = DocumentCondition(conditions);
254 if !condition.allowed_in(context) {
255 return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into())));
256 }
257 Ok(condition)
258 }
259
260 pub fn evaluate(&self, device: &Device) -> bool {
262 self.0
263 .iter()
264 .any(|url_matching_function| url_matching_function.evaluate(device))
265 }
266
267 #[cfg(feature = "servo")]
268 fn allowed_in(&self, _: &ParserContext) -> bool {
269 false
270 }
271
272 #[cfg(feature = "gecko")]
273 fn allowed_in(&self, context: &ParserContext) -> bool {
274 if context.chrome_rules_enabled() {
275 return true;
276 }
277
278 if self.0.len() != 1 {
282 return false;
283 }
284
285 match self.0[0] {
287 DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
288 _ => false,
289 }
290 }
291}