1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 //! Gecko's media-query device and expression representation.
6
7 use app_units::AU_PER_PX;
8 use app_units::Au;
9 use context::QuirksMode;
10 use cssparser::{Parser, RGBA, Token, BasicParseErrorKind};
11 use euclid::Size2D;
12 use euclid::TypedScale;
13 use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
14 use gecko_bindings::bindings;
15 use gecko_bindings::structs;
16 use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit};
17 use gecko_bindings::structs::{nsMediaFeature, nsMediaFeature_ValueType, nsMediaFeature_RangeType};
18 use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned};
19 use media_queries::MediaType;
20 use parser::{Parse, ParserContext};
21 use properties::ComputedValues;
22 use servo_arc::Arc;
23 use std::fmt::{self, Write};
24 use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
25 use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
26 use string_cache::Atom;
27 use style_traits::{CSSPixel, CssWriter, DevicePixel};
28 use style_traits::{ToCss, ParseError, StyleParseErrorKind};
29 use style_traits::viewport::ViewportConstraints;
30 use stylesheets::Origin;
31 use values::{CSSFloat, CustomIdent, KeyframesName, serialize_atom_identifier};
32 use values::computed::{self, ToComputedValue};
33 use values::computed::font::FontSize;
34 use values::specified::{Integer, Length, Number};
35
36 /// The `Device` in Gecko wraps a pres context, has a default values computed,
37 /// and contains all the viewport rule state.
38 pub struct Device {
39 /// NB: The pres context lifetime is tied to the styleset, who owns the
40 /// stylist, and thus the `Device`, so having a raw pres context pointer
41 /// here is fine.
42 pres_context: RawGeckoPresContextOwned,
43 default_values: Arc<ComputedValues>,
44 /// The font size of the root element
45 /// This is set when computing the style of the root
46 /// element, and used for rem units in other elements.
47 ///
48 /// When computing the style of the root element, there can't be any
49 /// other style being computed at the same time, given we need the style of
50 /// the parent to compute everything else. So it is correct to just use
51 /// a relaxed atomic here.
52 root_font_size: AtomicIsize,
53 /// The body text color, stored as an `nscolor`, used for the "tables
54 /// inherit from body" quirk.
55 ///
56 /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
57 body_text_color: AtomicUsize,
58 /// Whether any styles computed in the document relied on the root font-size
59 /// by using rem units.
60 used_root_font_size: AtomicBool,
61 /// Whether any styles computed in the document relied on the viewport size
62 /// by using vw/vh/vmin/vmax units.
63 used_viewport_size: AtomicBool,
64 }
65
66 unsafe impl Sync for Device {}
67 unsafe impl Send for Device {}
68
69 impl Device {
70 /// Trivially constructs a new `Device`.
new(pres_context: RawGeckoPresContextOwned) -> Self71 pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
72 assert!(!pres_context.is_null());
73 Device {
74 pres_context,
75 default_values: ComputedValues::default_values(unsafe { &*pres_context }),
76 // FIXME(bz): Seems dubious?
77 root_font_size: AtomicIsize::new(FontSize::medium().size().0 as isize),
78 body_text_color: AtomicUsize::new(unsafe { &*pres_context }.mDefaultColor as usize),
79 used_root_font_size: AtomicBool::new(false),
80 used_viewport_size: AtomicBool::new(false),
81 }
82 }
83
84 /// Tells the device that a new viewport rule has been found, and stores the
85 /// relevant viewport constraints.
account_for_viewport_rule( &mut self, _constraints: &ViewportConstraints, )86 pub fn account_for_viewport_rule(
87 &mut self,
88 _constraints: &ViewportConstraints,
89 ) {
90 unreachable!("Gecko doesn't support @viewport");
91 }
92
93 /// Whether any animation name may be referenced from the style of any
94 /// element.
animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool95 pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool {
96 unsafe {
97 bindings::Gecko_AnimationNameMayBeReferencedFromStyle(
98 self.pres_context(),
99 name.as_atom().as_ptr(),
100 )
101 }
102 }
103
104 /// Returns the default computed values as a reference, in order to match
105 /// Servo.
default_computed_values(&self) -> &ComputedValues106 pub fn default_computed_values(&self) -> &ComputedValues {
107 &self.default_values
108 }
109
110 /// Returns the default computed values as an `Arc`.
default_computed_values_arc(&self) -> &Arc<ComputedValues>111 pub fn default_computed_values_arc(&self) -> &Arc<ComputedValues> {
112 &self.default_values
113 }
114
115 /// Get the font size of the root element (for rem)
root_font_size(&self) -> Au116 pub fn root_font_size(&self) -> Au {
117 self.used_root_font_size.store(true, Ordering::Relaxed);
118 Au::new(self.root_font_size.load(Ordering::Relaxed) as i32)
119 }
120
121 /// Set the font size of the root element (for rem)
set_root_font_size(&self, size: Au)122 pub fn set_root_font_size(&self, size: Au) {
123 self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
124 }
125
126 /// Sets the body text color for the "inherit color from body" quirk.
127 ///
128 /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
set_body_text_color(&self, color: RGBA)129 pub fn set_body_text_color(&self, color: RGBA) {
130 self.body_text_color.store(convert_rgba_to_nscolor(&color) as usize, Ordering::Relaxed)
131 }
132
133 /// Returns the body text color.
body_text_color(&self) -> RGBA134 pub fn body_text_color(&self) -> RGBA {
135 convert_nscolor_to_rgba(self.body_text_color.load(Ordering::Relaxed) as u32)
136 }
137
138 /// Gets the pres context associated with this document.
pres_context(&self) -> &nsPresContext139 pub fn pres_context(&self) -> &nsPresContext {
140 unsafe { &*self.pres_context }
141 }
142
143 /// Recreates the default computed values.
reset_computed_values(&mut self)144 pub fn reset_computed_values(&mut self) {
145 self.default_values = ComputedValues::default_values(self.pres_context());
146 }
147
148 /// Rebuild all the cached data.
rebuild_cached_data(&mut self)149 pub fn rebuild_cached_data(&mut self) {
150 self.reset_computed_values();
151 self.used_root_font_size.store(false, Ordering::Relaxed);
152 self.used_viewport_size.store(false, Ordering::Relaxed);
153 }
154
155 /// Returns whether we ever looked up the root font size of the Device.
used_root_font_size(&self) -> bool156 pub fn used_root_font_size(&self) -> bool {
157 self.used_root_font_size.load(Ordering::Relaxed)
158 }
159
160 /// Recreates all the temporary state that the `Device` stores.
161 ///
162 /// This includes the viewport override from `@viewport` rules, and also the
163 /// default computed values.
reset(&mut self)164 pub fn reset(&mut self) {
165 self.reset_computed_values();
166 }
167
168 /// Returns the current media type of the device.
media_type(&self) -> MediaType169 pub fn media_type(&self) -> MediaType {
170 // Gecko allows emulating random media with mIsEmulatingMedia and
171 // mMediaEmulated.
172 let context = self.pres_context();
173 let medium_to_use = if context.mIsEmulatingMedia() != 0 {
174 context.mMediaEmulated.mRawPtr
175 } else {
176 context.mMedium
177 };
178
179 MediaType(CustomIdent(Atom::from(medium_to_use)))
180 }
181
182 /// Returns the current viewport size in app units.
au_viewport_size(&self) -> Size2D<Au>183 pub fn au_viewport_size(&self) -> Size2D<Au> {
184 let area = &self.pres_context().mVisibleArea;
185 Size2D::new(Au(area.width), Au(area.height))
186 }
187
188 /// Returns the current viewport size in app units, recording that it's been
189 /// used for viewport unit resolution.
au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au>190 pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
191 self.used_viewport_size.store(true, Ordering::Relaxed);
192 self.au_viewport_size()
193 }
194
195 /// Returns whether we ever looked up the viewport size of the Device.
used_viewport_size(&self) -> bool196 pub fn used_viewport_size(&self) -> bool {
197 self.used_viewport_size.load(Ordering::Relaxed)
198 }
199
200 /// Returns the device pixel ratio.
device_pixel_ratio(&self) -> TypedScale<f32, CSSPixel, DevicePixel>201 pub fn device_pixel_ratio(&self) -> TypedScale<f32, CSSPixel, DevicePixel> {
202 let override_dppx = self.pres_context().mOverrideDPPX;
203 if override_dppx > 0.0 { return TypedScale::new(override_dppx); }
204 let au_per_dpx = self.pres_context().mCurAppUnitsPerDevPixel as f32;
205 let au_per_px = AU_PER_PX as f32;
206 TypedScale::new(au_per_px / au_per_dpx)
207 }
208
209 /// Returns whether document colors are enabled.
use_document_colors(&self) -> bool210 pub fn use_document_colors(&self) -> bool {
211 self.pres_context().mUseDocumentColors() != 0
212 }
213
214 /// Returns the default background color.
default_background_color(&self) -> RGBA215 pub fn default_background_color(&self) -> RGBA {
216 convert_nscolor_to_rgba(self.pres_context().mBackgroundColor)
217 }
218
219 /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText).
zoom_text(&self, size: Au) -> Au220 pub fn zoom_text(&self, size: Au) -> Au {
221 size.scale_by(self.pres_context().mEffectiveTextZoom)
222 }
223 /// Un-apply text zoom (see nsStyleFont::UnzoomText).
unzoom_text(&self, size: Au) -> Au224 pub fn unzoom_text(&self, size: Au) -> Au {
225 size.scale_by(1. / self.pres_context().mEffectiveTextZoom)
226 }
227 }
228
229 /// The kind of matching that should be performed on a media feature value.
230 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
231 pub enum Range {
232 /// At least the specified value.
233 Min,
234 /// At most the specified value.
235 Max,
236 /// Exactly the specified value.
237 Equal,
238 }
239
240 /// A expression for gecko contains a reference to the media feature, the value
241 /// the media query contained, and the range to evaluate.
242 #[derive(Clone, Debug)]
243 pub struct Expression {
244 feature: &'static nsMediaFeature,
245 value: Option<MediaExpressionValue>,
246 range: Range,
247 }
248
249 impl ToCss for Expression {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,250 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
251 where W: fmt::Write,
252 {
253 dest.write_str("(")?;
254
255 if (self.feature.mReqFlags & structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix) != 0 {
256 dest.write_str("-webkit-")?;
257 }
258 match self.range {
259 Range::Min => dest.write_str("min-")?,
260 Range::Max => dest.write_str("max-")?,
261 Range::Equal => {},
262 }
263
264 // NB: CssStringWriter not needed, feature names are under control.
265 write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
266
267 if let Some(ref val) = self.value {
268 dest.write_str(": ")?;
269 val.to_css(dest, self)?;
270 }
271
272 dest.write_str(")")
273 }
274 }
275
276 impl PartialEq for Expression {
eq(&self, other: &Expression) -> bool277 fn eq(&self, other: &Expression) -> bool {
278 self.feature.mName == other.feature.mName &&
279 self.value == other.value && self.range == other.range
280 }
281 }
282
283 /// A resolution.
284 #[derive(Clone, Debug, PartialEq, ToCss)]
285 pub enum Resolution {
286 /// Dots per inch.
287 #[css(dimension)]
288 Dpi(CSSFloat),
289 /// Dots per pixel.
290 #[css(dimension)]
291 Dppx(CSSFloat),
292 /// Dots per centimeter.
293 #[css(dimension)]
294 Dpcm(CSSFloat),
295 }
296
297 impl Resolution {
to_dpi(&self) -> CSSFloat298 fn to_dpi(&self) -> CSSFloat {
299 match *self {
300 Resolution::Dpi(f) => f,
301 Resolution::Dppx(f) => f * 96.0,
302 Resolution::Dpcm(f) => f * 2.54,
303 }
304 }
305
parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>306 fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
307 let location = input.current_source_location();
308 let (value, unit) = match *input.next()? {
309 Token::Dimension { value, ref unit, .. } => {
310 (value, unit)
311 },
312 ref t => return Err(location.new_unexpected_token_error(t.clone())),
313 };
314
315 if value <= 0. {
316 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
317 }
318
319 (match_ignore_ascii_case! { &unit,
320 "dpi" => Ok(Resolution::Dpi(value)),
321 "dppx" => Ok(Resolution::Dppx(value)),
322 "dpcm" => Ok(Resolution::Dpcm(value)),
323 _ => Err(())
324 }).map_err(|()| location.new_custom_error(StyleParseErrorKind::UnexpectedDimension(unit.clone())))
325 }
326 }
327
328 /// A value found or expected in a media expression.
329 ///
330 /// FIXME(emilio): How should calc() serialize in the Number / Integer /
331 /// BoolInteger / IntRatio case, as computed or as specified value?
332 ///
333 /// If the first, this would need to store the relevant values.
334 ///
335 /// See: https://github.com/w3c/csswg-drafts/issues/1968
336 #[derive(Clone, Debug, PartialEq)]
337 pub enum MediaExpressionValue {
338 /// A length.
339 Length(Length),
340 /// A (non-negative) integer.
341 Integer(u32),
342 /// A floating point value.
343 Float(CSSFloat),
344 /// A boolean value, specified as an integer (i.e., either 0 or 1).
345 BoolInteger(bool),
346 /// Two integers separated by '/', with optional whitespace on either side
347 /// of the '/'.
348 IntRatio(u32, u32),
349 /// A resolution.
350 Resolution(Resolution),
351 /// An enumerated value, defined by the variant keyword table in the
352 /// feature's `mData` member.
353 Enumerated(i16),
354 /// An identifier.
355 Ident(Atom),
356 }
357
358 impl MediaExpressionValue {
from_css_value(for_expr: &Expression, css_value: &nsCSSValue) -> Option<Self>359 fn from_css_value(for_expr: &Expression, css_value: &nsCSSValue) -> Option<Self> {
360 // NB: If there's a null value, that means that we don't support the
361 // feature.
362 if css_value.mUnit == nsCSSUnit::eCSSUnit_Null {
363 return None;
364 }
365
366 match for_expr.feature.mValueType {
367 nsMediaFeature_ValueType::eLength => {
368 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel);
369 let pixels = css_value.float_unchecked();
370 Some(MediaExpressionValue::Length(Length::from_px(pixels)))
371 }
372 nsMediaFeature_ValueType::eInteger => {
373 let i = css_value.integer_unchecked();
374 debug_assert!(i >= 0);
375 Some(MediaExpressionValue::Integer(i as u32))
376 }
377 nsMediaFeature_ValueType::eFloat => {
378 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Number);
379 Some(MediaExpressionValue::Float(css_value.float_unchecked()))
380 }
381 nsMediaFeature_ValueType::eBoolInteger => {
382 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Integer);
383 let i = css_value.integer_unchecked();
384 debug_assert!(i == 0 || i == 1);
385 Some(MediaExpressionValue::BoolInteger(i == 1))
386 }
387 nsMediaFeature_ValueType::eResolution => {
388 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel);
389 Some(MediaExpressionValue::Resolution(Resolution::Dppx(css_value.float_unchecked())))
390 }
391 nsMediaFeature_ValueType::eEnumerated => {
392 let value = css_value.integer_unchecked() as i16;
393 Some(MediaExpressionValue::Enumerated(value))
394 }
395 nsMediaFeature_ValueType::eIdent => {
396 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_AtomIdent);
397 Some(MediaExpressionValue::Ident(Atom::from(unsafe {
398 *css_value.mValue.mAtom.as_ref()
399 })))
400 }
401 nsMediaFeature_ValueType::eIntRatio => {
402 let array = unsafe { css_value.array_unchecked() };
403 debug_assert_eq!(array.len(), 2);
404 let first = array[0].integer_unchecked();
405 let second = array[1].integer_unchecked();
406
407 debug_assert!(first >= 0 && second >= 0);
408 Some(MediaExpressionValue::IntRatio(first as u32, second as u32))
409 }
410 }
411 }
412 }
413
414 impl MediaExpressionValue {
to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &Expression) -> fmt::Result where W: fmt::Write,415 fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &Expression) -> fmt::Result
416 where W: fmt::Write,
417 {
418 match *self {
419 MediaExpressionValue::Length(ref l) => l.to_css(dest),
420 MediaExpressionValue::Integer(v) => v.to_css(dest),
421 MediaExpressionValue::Float(v) => v.to_css(dest),
422 MediaExpressionValue::BoolInteger(v) => {
423 dest.write_str(if v { "1" } else { "0" })
424 },
425 MediaExpressionValue::IntRatio(a, b) => {
426 a.to_css(dest)?;
427 dest.write_char('/')?;
428 b.to_css(dest)
429 },
430 MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
431 MediaExpressionValue::Ident(ref ident) => {
432 serialize_atom_identifier(ident, dest)
433 }
434 MediaExpressionValue::Enumerated(value) => unsafe {
435 use std::{slice, str};
436 use std::os::raw::c_char;
437
438 // NB: All the keywords on nsMediaFeatures are static,
439 // well-formed utf-8.
440 let mut length = 0;
441
442 let (keyword, _value) =
443 find_in_table(*for_expr.feature.mData.mKeywordTable.as_ref(),
444 |_kw, val| val == value)
445 .expect("Value not found in the keyword table?");
446
447 let buffer: *const c_char =
448 bindings::Gecko_CSSKeywordString(keyword, &mut length);
449 let buffer =
450 slice::from_raw_parts(buffer as *const u8, length as usize);
451
452 let string = str::from_utf8_unchecked(buffer);
453
454 dest.write_str(string)
455 }
456 }
457 }
458 }
459
find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature> where F: FnMut(&'static nsMediaFeature) -> bool,460 fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
461 where
462 F: FnMut(&'static nsMediaFeature) -> bool,
463 {
464 unsafe {
465 let mut features = structs::nsMediaFeatures_features.as_ptr();
466 while !(*features).mName.is_null() {
467 if f(&*features) {
468 return Some(&*features);
469 }
470 features = features.offset(1);
471 }
472 }
473 None
474 }
475
find_in_table<F>( mut current_entry: *const nsCSSProps_KTableEntry, mut f: F, ) -> Option<(nsCSSKeyword, i16)> where F: FnMut(nsCSSKeyword, i16) -> bool476 unsafe fn find_in_table<F>(
477 mut current_entry: *const nsCSSProps_KTableEntry,
478 mut f: F,
479 ) -> Option<(nsCSSKeyword, i16)>
480 where
481 F: FnMut(nsCSSKeyword, i16) -> bool
482 {
483 loop {
484 let value = (*current_entry).mValue;
485 let keyword = (*current_entry).mKeyword;
486
487 if value == -1 {
488 return None; // End of the table.
489 }
490
491 if f(keyword, value) {
492 return Some((keyword, value));
493 }
494
495 current_entry = current_entry.offset(1);
496 }
497 }
498
parse_feature_value<'i, 't>( feature: &nsMediaFeature, feature_value_type: nsMediaFeature_ValueType, context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<MediaExpressionValue, ParseError<'i>>499 fn parse_feature_value<'i, 't>(
500 feature: &nsMediaFeature,
501 feature_value_type: nsMediaFeature_ValueType,
502 context: &ParserContext,
503 input: &mut Parser<'i, 't>,
504 ) -> Result<MediaExpressionValue, ParseError<'i>> {
505 let value = match feature_value_type {
506 nsMediaFeature_ValueType::eLength => {
507 let length = Length::parse_non_negative(context, input)?;
508 MediaExpressionValue::Length(length)
509 },
510 nsMediaFeature_ValueType::eInteger => {
511 let integer = Integer::parse_non_negative(context, input)?;
512 MediaExpressionValue::Integer(integer.value() as u32)
513 }
514 nsMediaFeature_ValueType::eBoolInteger => {
515 let integer = Integer::parse_non_negative(context, input)?;
516 let value = integer.value();
517 if value > 1 {
518 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
519 }
520 MediaExpressionValue::BoolInteger(value == 1)
521 }
522 nsMediaFeature_ValueType::eFloat => {
523 let number = Number::parse(context, input)?;
524 MediaExpressionValue::Float(number.get())
525 }
526 nsMediaFeature_ValueType::eIntRatio => {
527 let a = Integer::parse_positive(context, input)?;
528 input.expect_delim('/')?;
529 let b = Integer::parse_positive(context, input)?;
530 MediaExpressionValue::IntRatio(a.value() as u32, b.value() as u32)
531 }
532 nsMediaFeature_ValueType::eResolution => {
533 MediaExpressionValue::Resolution(Resolution::parse(input)?)
534 }
535 nsMediaFeature_ValueType::eEnumerated => {
536 let location = input.current_source_location();
537 let keyword = input.expect_ident()?;
538 let keyword = unsafe {
539 bindings::Gecko_LookupCSSKeyword(
540 keyword.as_bytes().as_ptr(),
541 keyword.len() as u32,
542 )
543 };
544
545 let first_table_entry: *const nsCSSProps_KTableEntry = unsafe {
546 *feature.mData.mKeywordTable.as_ref()
547 };
548
549 let value = match unsafe { find_in_table(first_table_entry, |kw, _| kw == keyword) } {
550 Some((_kw, value)) => value,
551 None => return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
552 };
553
554 MediaExpressionValue::Enumerated(value)
555 }
556 nsMediaFeature_ValueType::eIdent => {
557 MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
558 }
559 };
560
561 Ok(value)
562 }
563
564 impl Expression {
565 /// Trivially construct a new expression.
new( feature: &'static nsMediaFeature, value: Option<MediaExpressionValue>, range: Range, ) -> Self566 fn new(
567 feature: &'static nsMediaFeature,
568 value: Option<MediaExpressionValue>,
569 range: Range,
570 ) -> Self {
571 Self { feature, value, range }
572 }
573
574 /// Parse a media expression of the form:
575 ///
576 /// ```
577 /// (media-feature: media-value)
578 /// ```
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>579 pub fn parse<'i, 't>(
580 context: &ParserContext,
581 input: &mut Parser<'i, 't>,
582 ) -> Result<Self, ParseError<'i>> {
583 input.expect_parenthesis_block().map_err(|err|
584 err.location.new_custom_error(match err.kind {
585 BasicParseErrorKind::UnexpectedToken(t) => StyleParseErrorKind::ExpectedIdentifier(t),
586 _ => StyleParseErrorKind::UnspecifiedError,
587 })
588 )?;
589
590 input.parse_nested_block(|input| {
591 // FIXME: remove extra indented block when lifetimes are non-lexical
592 let feature;
593 let range;
594 {
595 let location = input.current_source_location();
596 let ident = input.expect_ident().map_err(|err|
597 err.location.new_custom_error(match err.kind {
598 BasicParseErrorKind::UnexpectedToken(t) => StyleParseErrorKind::ExpectedIdentifier(t),
599 _ => StyleParseErrorKind::UnspecifiedError,
600 })
601 )?;
602
603 let mut flags = 0;
604
605 if context.chrome_rules_enabled() ||
606 context.stylesheet_origin == Origin::UserAgent {
607 flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
608 }
609
610 let result = {
611 let mut feature_name = &**ident;
612
613 if unsafe { structs::StylePrefs_sWebkitPrefixedAliasesEnabled } &&
614 starts_with_ignore_ascii_case(feature_name, "-webkit-") {
615 feature_name = &feature_name[8..];
616 flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix;
617 if unsafe { structs::StylePrefs_sWebkitDevicePixelRatioEnabled } {
618 flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled;
619 }
620 }
621
622 let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
623 feature_name = &feature_name[4..];
624 Range::Min
625 } else if starts_with_ignore_ascii_case(feature_name, "max-") {
626 feature_name = &feature_name[4..];
627 Range::Max
628 } else {
629 Range::Equal
630 };
631
632 let atom = Atom::from(string_as_ascii_lowercase(feature_name));
633 match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) {
634 Some(f) => Ok((f, range)),
635 None => Err(()),
636 }
637 };
638
639 match result {
640 Ok((f, r)) => {
641 feature = f;
642 range = r;
643 }
644 Err(()) => {
645 return Err(location.new_custom_error(
646 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone())
647 ))
648 }
649 }
650
651 if (feature.mReqFlags & !flags) != 0 {
652 return Err(location.new_custom_error(
653 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone())
654 ))
655 }
656
657 if range != Range::Equal &&
658 feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
659 return Err(location.new_custom_error(
660 StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone())
661 ))
662 }
663 }
664
665 // If there's no colon, this is a media query of the form
666 // '(<feature>)', that is, there's no value specified.
667 //
668 // Gecko doesn't allow ranged expressions without a value, so just
669 // reject them here too.
670 if input.try(|i| i.expect_colon()).is_err() {
671 if range != Range::Equal {
672 return Err(input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue))
673 }
674 return Ok(Expression::new(feature, None, range));
675 }
676
677 let value = parse_feature_value(feature,
678 feature.mValueType,
679 context, input).map_err(|err|
680 err.location.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
681 )?;
682
683 Ok(Expression::new(feature, Some(value), range))
684 })
685 }
686
687 /// Returns whether this media query evaluates to true for the given device.
matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool688 pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
689 let mut css_value = nsCSSValue::null();
690 unsafe {
691 (self.feature.mGetter.unwrap())(
692 device.pres_context().mDocument.raw::<structs::nsIDocument>(),
693 self.feature,
694 &mut css_value,
695 )
696 };
697
698 let value = match MediaExpressionValue::from_css_value(self, &css_value) {
699 Some(v) => v,
700 None => return false,
701 };
702
703 self.evaluate_against(device, &value, quirks_mode)
704 }
705
evaluate_against( &self, device: &Device, actual_value: &MediaExpressionValue, quirks_mode: QuirksMode, ) -> bool706 fn evaluate_against(
707 &self,
708 device: &Device,
709 actual_value: &MediaExpressionValue,
710 quirks_mode: QuirksMode,
711 ) -> bool {
712 use self::MediaExpressionValue::*;
713 use std::cmp::Ordering;
714
715 debug_assert!(self.range == Range::Equal ||
716 self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
717 "Whoops, wrong range");
718
719 // http://dev.w3.org/csswg/mediaqueries3/#units
720 // em units are relative to the initial font-size.
721 let required_value = match self.value {
722 Some(ref v) => v,
723 None => {
724 // If there's no value, always match unless it's a zero length
725 // or a zero integer or boolean.
726 return match *actual_value {
727 BoolInteger(v) => v,
728 Integer(v) => v != 0,
729 Length(ref l) => {
730 computed::Context::for_media_query_evaluation(
731 device,
732 quirks_mode,
733 |context| l.to_computed_value(&context).px() != 0.,
734 )
735 },
736 _ => true,
737 }
738 }
739 };
740
741 // FIXME(emilio): Handle the possible floating point errors?
742 let cmp = match (required_value, actual_value) {
743 (&Length(ref one), &Length(ref other)) => {
744 computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
745 one.to_computed_value(&context).to_i32_au()
746 .cmp(&other.to_computed_value(&context).to_i32_au())
747 })
748 }
749 (&Integer(one), &Integer(ref other)) => one.cmp(other),
750 (&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other),
751 (&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(),
752 (&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => {
753 // Extend to avoid overflow.
754 (one_num as u64 * other_den as u64).cmp(
755 &(other_num as u64 * one_den as u64))
756 }
757 (&Resolution(ref one), &Resolution(ref other)) => {
758 let actual_dpi = unsafe {
759 if (*device.pres_context).mOverrideDPPX > 0.0 {
760 self::Resolution::Dppx((*device.pres_context).mOverrideDPPX)
761 .to_dpi()
762 } else {
763 other.to_dpi()
764 }
765 };
766
767 one.to_dpi().partial_cmp(&actual_dpi).unwrap()
768 }
769 (&Ident(ref one), &Ident(ref other)) => {
770 debug_assert_ne!(self.feature.mRangeType, nsMediaFeature_RangeType::eMinMaxAllowed);
771 return one == other;
772 }
773 (&Enumerated(one), &Enumerated(other)) => {
774 debug_assert_ne!(self.feature.mRangeType, nsMediaFeature_RangeType::eMinMaxAllowed);
775 return one == other;
776 }
777 _ => unreachable!(),
778 };
779
780 cmp == Ordering::Equal || match self.range {
781 Range::Min => cmp == Ordering::Less,
782 Range::Equal => false,
783 Range::Max => cmp == Ordering::Greater,
784 }
785 }
786 }
787