Bug 1356074: Implement keyword-valued media queries. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 13 Apr 2017 12:24:02 +0800
changeset 562511 3cc26a449a4a7b0ad8982007ad7e1c757746417c
parent 562510 0d3dcc54598162dc6c9fba37b34590b0787ae818
child 562512 14a1b0c8abe88fcaea5994b824d3f6656b8188bb
push id54042
push userbmo:emilio+bugs@crisal.io
push dateFri, 14 Apr 2017 02:24:34 +0000
reviewersheycam
bugs1356074
milestone55.0a1
Bug 1356074: Implement keyword-valued media queries. r?heycam MozReview-Commit-ID: 1oskYLM11us
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
servo/components/style/gecko/media_queries.rs
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1725,16 +1725,35 @@ Gecko_LoadStyleSheet(css::Loader* aLoade
 }
 
 const nsMediaFeature*
 Gecko_GetMediaFeatures()
 {
   return nsMediaFeatures::features;
 }
 
+nsCSSKeyword
+Gecko_LookupCSSKeyword(const uint8_t* aString, uint32_t aLength)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsDependentCSubstring keyword(reinterpret_cast<const char*>(aString), aLength);
+  return nsCSSKeywords::LookupKeyword(keyword);
+}
+
+const char*
+Gecko_CSSKeywordString(nsCSSKeyword aKeyword, uint32_t* aLength)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aLength);
+  const nsAFlatCString& value = nsCSSKeywords::GetStringValue(aKeyword);
+  *aLength = value.Length();
+  return value.get();
+}
+
 nsCSSFontFaceRule*
 Gecko_CSSFontFaceRule_Create()
 {
   RefPtr<nsCSSFontFaceRule> rule = new nsCSSFontFaceRule(0, 0);
   return rule.forget().take();
 }
 
 void
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -417,16 +417,18 @@ GeckoFontMetrics Gecko_GetFontMetrics(Ra
                                       bool is_vertical,
                                       const nsStyleFont* font,
                                       nscoord font_size,
                                       bool use_user_font_set);
 void InitializeServo();
 void ShutdownServo();
 
 const nsMediaFeature* Gecko_GetMediaFeatures();
+nsCSSKeyword Gecko_LookupCSSKeyword(const uint8_t* string, uint32_t len);
+const char* Gecko_CSSKeywordString(nsCSSKeyword keyword, uint32_t* len);
 
 // Font face rule
 // Creates and returns a new (already-addrefed) nsCSSFontFaceRule object.
 nsCSSFontFaceRule* Gecko_CSSFontFaceRule_Create();
 void Gecko_CSSFontFaceRule_GetCssText(const nsCSSFontFaceRule* rule, nsAString* result);
 NS_DECL_FFI_REFCOUNTING(nsCSSFontFaceRule, CSSFontFaceRule);
 
 RawGeckoElementBorrowedOrNull Gecko_GetBody(RawGeckoPresContextBorrowed pres_context);
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -4,17 +4,17 @@
 
 //! Gecko's media-query device and expression representation.
 
 use app_units::Au;
 use cssparser::{CssStringWriter, Parser, Token};
 use euclid::Size2D;
 use font_metrics::get_metrics_provider_for_product;
 use gecko_bindings::bindings;
-use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsStringBuffer};
+use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit, nsStringBuffer};
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::ComputedValues;
 use std::ascii::AsciiExt;
 use std::fmt::{self, Write};
@@ -133,17 +133,17 @@ impl ToCss for Expression {
             nsMediaExpression_Range::eEqual => {},
         }
 
         // 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)?;
+            val.to_css(dest, self)?;
         }
 
         dest.write_str(")")
     }
 }
 
 impl PartialEq for Expression {
     fn eq(&self, other: &Expression) -> bool {
@@ -227,17 +227,17 @@ pub enum MediaExpressionValue {
     BoolInteger(bool),
     /// Two integers separated by '/', with optional whitespace on either side
     /// of the '/'.
     IntRatio(u32, u32),
     /// A resolution.
     Resolution(Resolution),
     /// An enumerated value, defined by the variant keyword table in the
     /// feature's `mData` member.
-    Enumerated(u32),
+    Enumerated(i16),
     /// An identifier.
     ///
     /// TODO(emilio): Maybe atomize?
     Ident(String),
 }
 
 impl MediaExpressionValue {
     fn from_css_value(for_expr: &Expression, css_value: &nsCSSValue) -> Option<Self> {
@@ -268,20 +268,18 @@ impl MediaExpressionValue {
                 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))
+                let value = css_value.integer_unchecked() as i16;
+                Some(MediaExpressionValue::Enumerated(value))
             }
             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))
             }
@@ -293,37 +291,55 @@ impl MediaExpressionValue {
 
                 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
+impl MediaExpressionValue {
+    fn to_css<W>(&self, dest: &mut W, for_expr: &Expression) -> fmt::Result
         where W: fmt::Write,
     {
         match *self {
             MediaExpressionValue::Length(ref l) => l.to_css(dest),
             MediaExpressionValue::Integer(v) => write!(dest, "{}", v),
             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::Ident(ref ident) => {
                 CssStringWriter::new(dest).write_str(ident)
             }
-            MediaExpressionValue::Enumerated(..) => {
-                // TODO(emilio): Use the CSS keyword table.
-                unimplemented!()
+            MediaExpressionValue::Enumerated(value) => unsafe {
+                use std::os::raw::c_char;
+                use std::{slice, str};
+
+                // NB: All the keywords on nsMediaFeatures are static,
+                // well-formed utf-8.
+                let mut length = 0;
+
+                let (keyword, _value) =
+                    find_in_table(*for_expr.feature.mData.mKeywordTable.as_ref(),
+                                  |_kw, val| val == value)
+                        .expect("Value not found in the keyword table?");
+
+                let buffer: *const c_char =
+                    bindings::Gecko_CSSKeywordString(keyword, &mut length);
+                let buffer =
+                    slice::from_raw_parts(buffer as *const u8, length as usize);
+
+                let string = str::from_utf8_unchecked(buffer);
+
+                dest.write_str(string)
             }
         }
     }
 }
 
 fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
     string.len() > prefix.len() &&
       string[0..prefix.len()].eq_ignore_ascii_case(prefix)
@@ -345,16 +361,37 @@ fn find_feature<F>(mut f: F) -> Option<&
             }
             features = features.offset(1);
         }
     }
 
     None
 }
 
+unsafe fn find_in_table<F>(mut current_entry: *const nsCSSProps_KTableEntry,
+                           mut f: F)
+                           -> Option<(nsCSSKeyword, i16)>
+    where F: FnMut(nsCSSKeyword, i16) -> bool
+{
+    loop {
+        let value = (*current_entry).mValue;
+        let keyword = (*current_entry).mKeyword;
+
+        if value == -1 {
+            return None; // End of the table.
+        }
+
+        if f(keyword, value) {
+            return Some((keyword, value));
+        }
+
+        current_entry = current_entry.offset(1);
+    }
+}
+
 impl Expression {
     /// Trivially construct a new expression.
     fn new(feature: &'static nsMediaFeature,
            value: Option<MediaExpressionValue>,
            range: nsMediaExpression_Range) -> Self {
         Expression {
             feature: feature,
             value: value,
@@ -454,19 +491,35 @@ impl Expression {
                         return Err(())
                     }
                     MediaExpressionValue::IntRatio(a as u32, b as u32)
                 }
                 nsMediaFeature_ValueType::eResolution => {
                     MediaExpressionValue::Resolution(Resolution::parse(input)?)
                 }
                 nsMediaFeature_ValueType::eEnumerated => {
-                    // TODO(emilio): Use Gecko's CSS keyword table to parse
-                    // this.
-                    return Err(())
+                    let keyword = input.expect_ident()?;
+                    let keyword = unsafe {
+                        bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(),
+                                                         keyword.len() as u32)
+                    };
+
+                    let first_table_entry: *const nsCSSProps_KTableEntry = unsafe {
+                        *feature.mData.mKeywordTable.as_ref()
+                    };
+
+                    let value =
+                        match unsafe { find_in_table(first_table_entry, |kw, _| kw == keyword) } {
+                            Some((_kw, value)) => {
+                                value
+                            }
+                            None => return Err(()),
+                        };
+
+                    MediaExpressionValue::Enumerated(value)
                 }
                 nsMediaFeature_ValueType::eIdent => {
                     MediaExpressionValue::Ident(input.expect_ident()?.into_owned())
                 }
             };
 
             Ok(Expression::new(feature, Some(value), range))
         })
@@ -555,19 +608,19 @@ impl Expression {
                 };
 
                 one.to_dpi().partial_cmp(&actual_dpi).unwrap()
             }
             (&Ident(ref one), &Ident(ref other)) => {
                 debug_assert!(self.feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed);
                 return one == other;
             }
-            (&Enumerated(..), &Enumerated(..)) => {
-                // TODO(emilio)
-                unimplemented!();
+            (&Enumerated(one), &Enumerated(other)) => {
+                debug_assert!(self.feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed);
+                return one == other;
             }
             _ => unreachable!(),
         };
 
         cmp == Ordering::Equal || match self.range {
             nsMediaExpression_Range::eMin => cmp == Ordering::Less,
             nsMediaExpression_Range::eEqual => false,
             nsMediaExpression_Range::eMax => cmp == Ordering::Greater,