--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -1,30 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Gecko's media-query device and expression representation.
use app_units::Au;
-use cssparser::{Parser, Token};
+use cssparser::{CssStringWriter, Parser, Token};
use euclid::Size2D;
+use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsStringBuffer, nsresult};
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
use gecko_bindings::structs::RawGeckoPresContextOwned;
use gecko_bindings::bindings;
use media_queries::MediaType;
use properties::ComputedValues;
use std::ascii::AsciiExt;
-use std::fmt;
+use std::fmt::{self, Write};
use std::sync::Arc;
use string_cache::Atom;
use style_traits::ToCss;
use style_traits::viewport::ViewportConstraints;
use values::{CSSFloat, specified};
+use values::computed::{self, ToComputedValue};
/// The `Device` in Gecko wraps a pres context, has a default values computed,
/// and contains all the viewport rule state.
pub struct Device {
/// NB: The pres context lifetime is tied to the styleset, who owns the
/// stylist, and thus the `Device`, so having a raw pres context pointer
/// here is fine.
pres_context: RawGeckoPresContextOwned,
@@ -108,17 +110,17 @@ impl ToCss for Expression {
{
dest.write_str("(")?;
match self.range {
nsMediaExpression_Range::eMin => dest.write_str("min-")?,
nsMediaExpression_Range::eMax => dest.write_str("max-")?,
nsMediaExpression_Range::eEqual => {},
}
- // NB: CSSStringWriter not needed, feature names are under control.
+ // NB: CssStringWriter not needed, feature names are under control.
write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
if let Some(ref val) = self.value {
dest.write_str(": ")?;
val.to_css(dest)?;
}
dest.write_str(")")
@@ -132,16 +134,24 @@ pub enum Resolution {
Dpi(CSSFloat),
/// Dots per pixel.
Dppx(CSSFloat),
/// Dots per centimeter.
Dpcm(CSSFloat),
}
impl Resolution {
+ fn to_dpi(&self) -> CSSFloat {
+ match *self {
+ Resolution::Dpi(f) => f,
+ Resolution::Dppx(f) => f * 96.0,
+ Resolution::Dpcm(f) => f * 2.54,
+ }
+ }
+
fn parse(input: &mut Parser) -> Result<Self, ()> {
let (value, unit) = match try!(input.next()) {
Token::Dimension(value, unit) => {
(value.value, unit)
},
_ => return Err(()),
};
@@ -161,36 +171,112 @@ impl ToCss for Resolution {
match *self {
Resolution::Dpi(v) => write!(dest, "{}dpi", v),
Resolution::Dppx(v) => write!(dest, "{}dppx", v),
Resolution::Dpcm(v) => write!(dest, "{}dpcm", v),
}
}
}
+unsafe fn string_from_ns_string_buffer(buffer: *const nsStringBuffer) -> String {
+ use std::slice;
+ debug_assert!(!buffer.is_null());
+ let data = buffer.offset(1) as *const u16;
+ let mut length = 0;
+ let mut iter = data;
+ while *iter != 0 {
+ length += 1;
+ iter = iter.offset(1);
+ }
+ String::from_utf16_lossy(slice::from_raw_parts(data, length))
+}
+
/// A value found or expected in a media expression.
#[derive(Debug, Clone)]
pub enum MediaExpressionValue {
/// A length.
Length(specified::Length),
/// A (non-negative) integer.
Integer(u32),
/// A floating point value.
Float(CSSFloat),
/// A boolean value, specified as an integer (i.e., either 0 or 1).
BoolInteger(bool),
/// Two integers separated by '/', with optional whitespace on either side
/// of the '/'.
IntRatio(u32, u32),
/// A resolution.
Resolution(Resolution),
- /// An enumerated index into the variant keyword table.
+ /// An enumerated value, defined by the variant keyword table in the
+ /// feature's `mData` member.
Enumerated(u32),
/// An identifier.
- Ident(Atom),
+ ///
+ /// TODO(emilio): Maybe atomize?
+ Ident(String),
+}
+
+impl MediaExpressionValue {
+ fn from_css_value(for_expr: &Expression, css_value: &nsCSSValue) -> Option<Self> {
+ // NB: If there's a null value, that means that we don't support the
+ // feature.
+ if css_value.mUnit == nsCSSUnit::eCSSUnit_Null {
+ return None;
+ }
+
+ match for_expr.feature.mValueType {
+ nsMediaFeature_ValueType::eLength => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Pixel);
+ let pixels = css_value.float_unchecked();
+ Some(MediaExpressionValue::Length(
+ specified::Length::Absolute(Au::from_f32_px(pixels))))
+ }
+ nsMediaFeature_ValueType::eInteger => {
+ let i = css_value.integer_unchecked();
+ debug_assert!(i >= 0);
+ Some(MediaExpressionValue::Integer(i as u32))
+ }
+ nsMediaFeature_ValueType::eFloat => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Number);
+ Some(MediaExpressionValue::Float(css_value.float_unchecked()))
+ }
+ nsMediaFeature_ValueType::eBoolInteger => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Integer);
+ let i = css_value.integer_unchecked();
+ debug_assert!(i == 0 || i == 1);
+ Some(MediaExpressionValue::BoolInteger(i == 1))
+ }
+ nsMediaFeature_ValueType::eResolution => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Inch);
+ Some(MediaExpressionValue::Resolution(Resolution::Dpi(css_value.float_unchecked())))
+ }
+ nsMediaFeature_ValueType::eEnumerated => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Enumerated);
+ let value = css_value.integer_unchecked();
+ debug_assert!(value >= 0);
+ Some(MediaExpressionValue::Enumerated(value as u32))
+ }
+ nsMediaFeature_ValueType::eIdent => {
+ debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Ident);
+ let string = unsafe {
+ string_from_ns_string_buffer(*css_value.mValue.mString.as_ref())
+ };
+ Some(MediaExpressionValue::Ident(string))
+ }
+ nsMediaFeature_ValueType::eIntRatio => {
+ let array = unsafe { css_value.array_unchecked() };
+ debug_assert_eq!(array.len(), 2);
+ let first = array[0].integer_unchecked();
+ let second = array[1].integer_unchecked();
+
+ debug_assert!(first >= 0 && second >= 0);
+ Some(MediaExpressionValue::IntRatio(first as u32, second as u32))
+ }
+ }
+ }
}
impl ToCss for MediaExpressionValue {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
match *self {
MediaExpressionValue::Length(ref l) => l.to_css(dest),
@@ -198,19 +284,21 @@ impl ToCss for MediaExpressionValue {
MediaExpressionValue::Float(v) => write!(dest, "{}", v),
MediaExpressionValue::BoolInteger(v) => {
dest.write_str(if v { "1" } else { "0" })
},
MediaExpressionValue::IntRatio(a, b) => {
write!(dest, "{}/{}", a, b)
},
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
- MediaExpressionValue::Enumerated(..) |
- MediaExpressionValue::Ident(..) => {
- // TODO(emilio)
+ MediaExpressionValue::Ident(ref ident) => {
+ CssStringWriter::new(dest).write_str(ident)
+ }
+ MediaExpressionValue::Enumerated(..) => {
+ // TODO(emilio): Use the CSS keyword table.
unimplemented!()
}
}
}
}
fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
string.len() > prefix.len() &&
@@ -341,31 +429,121 @@ impl Expression {
return Err(())
}
MediaExpressionValue::IntRatio(a as u32, b as u32)
}
nsMediaFeature_ValueType::eResolution => {
MediaExpressionValue::Resolution(Resolution::parse(input)?)
}
nsMediaFeature_ValueType::eEnumerated => {
- let index = unsafe {
- let _table = feature.mData.mKeywordTable.as_ref();
- // TODO
- 0
- };
- MediaExpressionValue::Enumerated(index)
+ // TODO(emilio): Use Gecko's CSS keyword table to parse
+ // this.
+ return Err(())
}
nsMediaFeature_ValueType::eIdent => {
MediaExpressionValue::Ident(input.expect_ident()?.into())
}
};
- Ok(Expression::new(feature, value, range))
+ Ok(Expression::new(feature, Some(value), range))
})
}
- /// Returns whether this media query evaluates to true for the given
- /// device.
- pub fn matches(&self, _device: &Device) -> bool {
- // TODO
- false
+ /// Returns whether this media query evaluates to true for the given device.
+ pub fn matches(&self, device: &Device) -> bool {
+ let mut css_value = nsCSSValue::null();
+ let result = unsafe {
+ (self.feature.mGetter.unwrap())(device.pres_context,
+ self.feature,
+ &mut css_value)
+ };
+
+ if result != nsresult::NS_OK {
+ // FIXME(emilio): This doesn't seem posible from reading gecko code,
+ // probably we should just clean up that function and return void.
+ error!("Media feature getter errored: {:?}, {:?}",
+ result, Atom::from(unsafe { *self.feature.mName }));
+ return false;
+ }
+
+ let value = match MediaExpressionValue::from_css_value(self, &css_value) {
+ Some(v) => v,
+ None => return false,
+ };
+
+ self.evaluate_against(device, &value)
+ }
+
+ fn evaluate_against(&self,
+ device: &Device,
+ actual_value: &MediaExpressionValue)
+ -> bool {
+ debug_assert!(self.range == nsMediaExpression_Range::eEqual ||
+ self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
+ "Whoops, wrong range");
+ use self::MediaExpressionValue::*;
+ use std::cmp::Ordering;
+
+ let default_values = device.default_values();
+
+ // http://dev.w3.org/csswg/mediaqueries3/#units
+ // em units are relative to the initial font-size.
+ let context = computed::Context {
+ is_root_element: false,
+ viewport_size: device.au_viewport_size(),
+ inherited_style: default_values,
+ // This cloning business is kind of dumb.... It's because Context
+ // insists on having an actual ComputedValues inside itself.
+ style: default_values.clone(),
+ font_metrics_provider: None
+ };
+
+ let required_value = match self.value {
+ Some(ref v) => v,
+ None => {
+ // If there's no value, always match except it's a zero length
+ // or a zero integer or boolean.
+ return match *actual_value {
+ BoolInteger(v) => v,
+ Integer(v) => v != 0,
+ Length(ref l) => l.to_computed_value(&context) != Au(0),
+ _ => true,
+ }
+ }
+ };
+
+ // FIXME(emilio): Handle the possible floating point errors?
+ let cmp = match (required_value, actual_value) {
+ (&Length(ref one), &Length(ref other)) => {
+ one.to_computed_value(&context)
+ .cmp(&other.to_computed_value(&context))
+ }
+ (&Integer(one), &Integer(ref other)) => one.cmp(other),
+ (&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other),
+ (&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(),
+ (&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => {
+ (one_num * other_den).partial_cmp(&(other_num * one_den)).unwrap()
+ }
+ (&Resolution(ref one), &Resolution(ref other)) => {
+ // FIXME(emilio): The pres context may override the DPPX of the
+ // `other` resolution, we need to look at that here, but I'm
+ // skipping that for now (we should check if bindgen can
+ // generate nsPresContext correctly now).
+ one.to_dpi().partial_cmp(&other.to_dpi()).unwrap()
+ }
+ (&Ident(ref one), &Ident(ref other)) => {
+ debug_assert!(self.feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed);
+ return one == other;
+ }
+ (&Enumerated(..), &Enumerated(..)) => {
+ // TODO(emilio)
+ unimplemented!();
+ }
+ _ => unreachable!(),
+ };
+
+ match self.range {
+ nsMediaExpression_Range::eMin => cmp == Ordering::Less,
+ nsMediaExpression_Range::eEqual => cmp == Ordering::Equal,
+ nsMediaExpression_Range::eMax => cmp == Ordering::Greater,
+ }
}
}