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