mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Bug 1331213: Implement the bulk of media query evaluation. r=heycam
Two main notes: * The nsStringBuffer bit goes untested, since it's only used on windows and there's no way I can test it, please review with care. * I haven't implemented yet the "enumerated" media queries. I'd want to do it as a follow-up, because I'm running out of time, and it requires some investigation. MozReview-Commit-ID: 1pBbzyIViPk Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
parent
197f21f3a7
commit
10a1b887d6
1 changed files with 201 additions and 21 deletions
|
@ -5,21 +5,23 @@
|
||||||
//! Gecko's media-query device and expression representation.
|
//! Gecko's media-query device and expression representation.
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use cssparser::{Parser, Token};
|
use cssparser::{CssStringWriter, Parser, Token};
|
||||||
use euclid::Size2D;
|
use euclid::Size2D;
|
||||||
use gecko_bindings::bindings;
|
use gecko_bindings::bindings;
|
||||||
|
use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsStringBuffer, nsresult};
|
||||||
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
|
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
|
||||||
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
|
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
|
||||||
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
use media_queries::MediaType;
|
use media_queries::MediaType;
|
||||||
use properties::ComputedValues;
|
use properties::ComputedValues;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::fmt;
|
use std::fmt::{self, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use style_traits::ToCss;
|
use style_traits::ToCss;
|
||||||
use style_traits::viewport::ViewportConstraints;
|
use style_traits::viewport::ViewportConstraints;
|
||||||
use values::{CSSFloat, specified};
|
use values::{CSSFloat, specified};
|
||||||
|
use values::computed::{self, ToComputedValue};
|
||||||
|
|
||||||
/// The `Device` in Gecko wraps a pres context, has a default values computed,
|
/// The `Device` in Gecko wraps a pres context, has a default values computed,
|
||||||
/// and contains all the viewport rule state.
|
/// and contains all the viewport rule state.
|
||||||
|
@ -114,7 +116,7 @@ impl ToCss for Expression {
|
||||||
nsMediaExpression_Range::eEqual => {},
|
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 }))?;
|
write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
|
||||||
|
|
||||||
if let Some(ref val) = self.value {
|
if let Some(ref val) = self.value {
|
||||||
|
@ -138,6 +140,14 @@ pub enum Resolution {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolution {
|
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, ()> {
|
fn parse(input: &mut Parser) -> Result<Self, ()> {
|
||||||
let (value, unit) = match try!(input.next()) {
|
let (value, unit) = match try!(input.next()) {
|
||||||
Token::Dimension(value, unit) => {
|
Token::Dimension(value, unit) => {
|
||||||
|
@ -167,6 +177,19 @@ impl ToCss for Resolution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// A value found or expected in a media expression.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MediaExpressionValue {
|
pub enum MediaExpressionValue {
|
||||||
|
@ -183,10 +206,73 @@ pub enum MediaExpressionValue {
|
||||||
IntRatio(u32, u32),
|
IntRatio(u32, u32),
|
||||||
/// A resolution.
|
/// A resolution.
|
||||||
Resolution(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),
|
Enumerated(u32),
|
||||||
/// An identifier.
|
/// 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 {
|
impl ToCss for MediaExpressionValue {
|
||||||
|
@ -204,9 +290,11 @@ impl ToCss for MediaExpressionValue {
|
||||||
write!(dest, "{}/{}", a, b)
|
write!(dest, "{}/{}", a, b)
|
||||||
},
|
},
|
||||||
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||||
MediaExpressionValue::Enumerated(..) |
|
MediaExpressionValue::Ident(ref ident) => {
|
||||||
MediaExpressionValue::Ident(..) => {
|
CssStringWriter::new(dest).write_str(ident)
|
||||||
// TODO(emilio)
|
}
|
||||||
|
MediaExpressionValue::Enumerated(..) => {
|
||||||
|
// TODO(emilio): Use the CSS keyword table.
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,26 +435,118 @@ impl Expression {
|
||||||
MediaExpressionValue::Resolution(Resolution::parse(input)?)
|
MediaExpressionValue::Resolution(Resolution::parse(input)?)
|
||||||
}
|
}
|
||||||
nsMediaFeature_ValueType::eEnumerated => {
|
nsMediaFeature_ValueType::eEnumerated => {
|
||||||
let index = unsafe {
|
// TODO(emilio): Use Gecko's CSS keyword table to parse
|
||||||
let _table = feature.mData.mKeywordTable.as_ref();
|
// this.
|
||||||
// TODO
|
return Err(())
|
||||||
0
|
|
||||||
};
|
|
||||||
MediaExpressionValue::Enumerated(index)
|
|
||||||
}
|
}
|
||||||
nsMediaFeature_ValueType::eIdent => {
|
nsMediaFeature_ValueType::eIdent => {
|
||||||
MediaExpressionValue::Ident(input.expect_ident()?.into())
|
MediaExpressionValue::Ident(input.expect_ident()?.into_owned())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expression::new(feature, value, range))
|
Ok(Expression::new(feature, Some(value), range))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this media query evaluates to true for the given
|
/// Returns whether this media query evaluates to true for the given device.
|
||||||
/// device.
|
pub fn matches(&self, device: &Device) -> bool {
|
||||||
pub fn matches(&self, _device: &Device) -> bool {
|
let mut css_value = nsCSSValue::null();
|
||||||
// TODO
|
let result = unsafe {
|
||||||
false
|
(self.feature.mGetter.unwrap())(device.pres_context,
|
||||||
|
self.feature,
|
||||||
|
&mut css_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
if result != nsresult::NS_OK {
|
||||||
|
// FIXME(emilio): This doesn't seem possible 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 {
|
||||||
|
use self::MediaExpressionValue::*;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
debug_assert!(self.range == nsMediaExpression_Range::eEqual ||
|
||||||
|
self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
|
||||||
|
"Whoops, wrong range");
|
||||||
|
|
||||||
|
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 unless 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!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
cmp == Ordering::Equal || match self.range {
|
||||||
|
nsMediaExpression_Range::eMin => cmp == Ordering::Less,
|
||||||
|
nsMediaExpression_Range::eEqual => false,
|
||||||
|
nsMediaExpression_Range::eMax => cmp == Ordering::Greater,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue