Bug 1365045 - Introduce nsCSSKeywordAndBoolTableEntry. r?heycam draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 24 Jul 2018 13:34:22 +0900
changeset 821854 e5e57b8d8093f1eefdcd1767dd6619cab900350c
parent 821277 a10e41860e7d2e0c92b2f8adc09d297378593d26
child 821855 613ddb710503e47ff33afe13dcf80077f34af30b
push id117202
push userbmo:hikezoe@mozilla.com
push dateTue, 24 Jul 2018 04:49:35 +0000
reviewersheycam
bugs1365045
milestone63.0a1
Bug 1365045 - Introduce nsCSSKeywordAndBoolTableEntry. r?heycam The values in the boolean context depend on its feature. For examples, in the case of prefers-reduced-motion 'no-preference' means false and 'reduced' mean true in the boolean context, whereas in the case of prefers-contrast 'no-preference' means false and other two values, 'high' and 'low' means true in the boolean context. To support it we introduce a child struct of nsCSSKTableEntry that has an additional field representing the value in the boolean context and use it when we have no specified value in the media feature (i.e. in the boolean context). MozReview-Commit-ID: 79HiW8l5ous
layout/style/ServoBindings.toml
layout/style/nsMediaFeatures.h
servo/components/style/gecko/media_queries.rs
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -272,16 +272,17 @@ whitelist-types = [
     "nsAttrName",
     "nsAttrValue",
     "nscolor",
     "nsChangeHint",
     "nsCSSCounterDesc",
     "nsCSSFontDesc",
     "nsCSSKTableEntry",
     "nsCSSKeyword",
+    "nsCSSKeywordAndBoolTableEntry",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
     "nsCSSProps",
     "nsCSSShadowArray",
     "nsCSSValue",
     "nsCSSValueList",
     "nsCSSValueList_heap",
     "nsCSSValuePair_heap",
--- a/layout/style/nsMediaFeatures.h
+++ b/layout/style/nsMediaFeatures.h
@@ -12,41 +12,64 @@
 #include <stdint.h>
 
 class nsAtom;
 class nsIDocument;
 struct nsCSSKTableEntry;
 class nsCSSValue;
 class nsStaticAtom;
 
+struct nsCSSKeywordAndBoolTableEntry : public nsCSSKTableEntry {
+  constexpr nsCSSKeywordAndBoolTableEntry(nsCSSKeyword aKeyword, int16_t aValue)
+    : nsCSSKTableEntry(aKeyword, aValue)
+    , mValueInBooleanContext(true)
+  {
+  }
+
+  template<typename T,
+           typename = typename std::enable_if<std::is_enum<T>::value>::type>
+  constexpr nsCSSKeywordAndBoolTableEntry(
+      nsCSSKeyword aKeyword,
+      T aValue,
+      bool aValueInBooleanContext)
+    : nsCSSKTableEntry(aKeyword, aValue)
+    , mValueInBooleanContext(aValueInBooleanContext)
+  {
+  }
+
+  bool mValueInBooleanContext;
+};
+
 struct nsMediaFeature;
 typedef void (*nsMediaFeatureValueGetter)(nsIDocument* aDocument,
                                           const nsMediaFeature* aFeature,
                                           nsCSSValue& aResult);
 
 struct nsMediaFeature
 {
   nsStaticAtom** mName; // extra indirection to point to nsGkAtoms members
 
   enum RangeType { eMinMaxAllowed, eMinMaxNotAllowed };
   RangeType mRangeType;
 
   enum ValueType {
     // All value types allow eCSSUnit_Null to indicate that no value
     // was given (in addition to the types listed below).
-    eLength,     // values are eCSSUnit_Pixel
-    eInteger,    // values are eCSSUnit_Integer
-    eFloat,      // values are eCSSUnit_Number
-    eBoolInteger,// values are eCSSUnit_Integer (0, -0, or 1 only)
-    eIntRatio,   // values are eCSSUnit_Array of two eCSSUnit_Integer
-    eResolution, // values are in eCSSUnit_Inch (for dpi),
-                 //   eCSSUnit_Pixel (for dppx), or
-                 //   eCSSUnit_Centimeter (for dpcm)
-    eEnumerated, // values are eCSSUnit_Enumerated (uses keyword table)
-    eIdent       // values are eCSSUnit_Ident
+    eLength,         // values are eCSSUnit_Pixel
+    eInteger,        // values are eCSSUnit_Integer
+    eFloat,          // values are eCSSUnit_Number
+    eBoolInteger,    // values are eCSSUnit_Integer (0, -0, or 1 only)
+    eIntRatio,       // values are eCSSUnit_Array of two eCSSUnit_Integer
+    eResolution,     // values are in eCSSUnit_Inch (for dpi),
+                     //   eCSSUnit_Pixel (for dppx), or
+                     //   eCSSUnit_Centimeter (for dpcm)
+    eEnumerated,     // values are eCSSUnit_Enumerated (uses keyword table)
+    eBoolEnumerated, // values are eCSSUnit_Enumerated (uses keyword and boolean
+                     // pair table)
+    eIdent           // values are eCSSUnit_Ident
     // Note that a number of pieces of code (both for parsing and
     // for matching of valueless expressions) assume that all numeric
     // value types cannot be negative.  The parsing code also does
     // not allow zeros in eIntRatio types.
   };
   ValueType mValueType;
 
   enum RequirementFlags : uint8_t {
@@ -68,16 +91,19 @@ struct nsMediaFeature
   union {
     // In static arrays, it's the first member that's initialized.  We
     // need that to be void* so we can initialize both other types.
     // This member should never be accessed by name.
     const void* mInitializer_;
     // If mValueType == eEnumerated:  const int32_t*: keyword table in
     //   the same format as the keyword tables in nsCSSProps.
     const nsCSSKTableEntry* mKeywordTable;
+    // If mValueType == eBoolEnumerated:  similar to the above but having a
+    // boolean field representing the value in Boolean context.
+    const nsCSSKeywordAndBoolTableEntry* mKeywordAndBoolTable;
     // If mGetter == GetSystemMetric (which implies mValueType ==
     //   eBoolInteger): nsAtom * const *, for the system metric.
     nsAtom * const * mMetric;
   } mData;
 
   // A function that returns the current value for this feature for a
   // given presentation.  If it returns eCSSUnit_Null, the feature is
   // not present.
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -11,16 +11,17 @@ use cssparser::{Parser, RGBA, Token};
 use euclid::Size2D;
 use euclid::TypedScale;
 use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
 use gecko_bindings::bindings;
 use gecko_bindings::structs;
 use gecko_bindings::structs::{nsCSSKTableEntry, nsCSSKeyword, nsCSSUnit, nsCSSValue};
 use gecko_bindings::structs::{nsMediaFeature, nsMediaFeature_RangeType};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsPresContext};
+use gecko_bindings::structs::nsCSSKeywordAndBoolTableEntry;
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use parser::{Parse, ParserContext};
 use properties::ComputedValues;
 use servo_arc::Arc;
 use std::fmt::{self, Write};
 use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
 use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
@@ -369,16 +370,19 @@ pub enum MediaExpressionValue {
     /// 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(i16),
+    /// Similar to the above Enumerated but the variant keyword table has an
+    /// additional field what the keyword value means in the Boolean Context.
+    BoolEnumerated(i16),
     /// An identifier.
     Ident(Atom),
 }
 
 impl MediaExpressionValue {
     fn from_css_value(
         for_expr: &MediaFeatureExpression,
         css_value: &nsCSSValue,
@@ -415,16 +419,20 @@ impl MediaExpressionValue {
                 Some(MediaExpressionValue::Resolution(Resolution::Dppx(
                     css_value.float_unchecked(),
                 )))
             },
             nsMediaFeature_ValueType::eEnumerated => {
                 let value = css_value.integer_unchecked() as i16;
                 Some(MediaExpressionValue::Enumerated(value))
             },
+            nsMediaFeature_ValueType::eBoolEnumerated => {
+                let value = css_value.integer_unchecked() as i16;
+                Some(MediaExpressionValue::BoolEnumerated(value))
+            },
             nsMediaFeature_ValueType::eIdent => {
                 debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_AtomIdent);
                 Some(MediaExpressionValue::Ident(unsafe {
                     Atom::from_raw(*css_value.mValue.mAtom.as_ref())
                 }))
             },
             nsMediaFeature_ValueType::eIntRatio => {
                 let array = unsafe { css_value.array_unchecked() };
@@ -452,35 +460,53 @@ impl MediaExpressionValue {
             MediaExpressionValue::IntRatio(a, b) => {
                 a.to_css(dest)?;
                 dest.write_char('/')?;
                 b.to_css(dest)
             },
             MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
             MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
             MediaExpressionValue::Enumerated(value) => unsafe {
-                use std::{slice, str};
-                use std::os::raw::c_char;
-
-                // NB: All the keywords on nsMediaFeatures are static,
-                // well-formed utf-8.
-                let mut length = 0;
-
-                let (keyword, _value) = find_in_table(
+                let keyword = find_in_table(
                     *for_expr.feature.mData.mKeywordTable.as_ref(),
                     |_kw, val| val == value,
+                    |e| e.keyword(),
+                ).expect("Value not found in the keyword table?");
+
+                MediaExpressionValue::keyword_to_css(keyword, dest)
+            },
+            MediaExpressionValue::BoolEnumerated(value) => unsafe {
+                let keyword = find_in_table(
+                    *for_expr.feature.mData.mKeywordAndBoolTable.as_ref(),
+                    |_kw, val| val == value,
+                    |e| e.keyword(),
                 ).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);
+                MediaExpressionValue::keyword_to_css(keyword, dest)
+            }
+        }
+    }
 
-                let string = str::from_utf8_unchecked(buffer);
+    fn keyword_to_css<W>(keyword: nsCSSKeyword, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: fmt::Write,
+    {
+        use std::{slice, str};
+        use std::os::raw::c_char;
 
-                dest.write_str(string)
-            },
+        // NB: All the keywords on nsMediaFeatures are static,
+        // well-formed utf-8.
+        let mut length = 0;
+        unsafe {
+            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 find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
 where
     F: FnMut(&'static nsMediaFeature) -> bool,
 {
@@ -491,33 +517,61 @@ where
                 return Some(&*features);
             }
             features = features.offset(1);
         }
     }
     None
 }
 
-unsafe fn find_in_table<F>(
-    mut current_entry: *const nsCSSKTableEntry,
-    mut f: F,
-) -> Option<(nsCSSKeyword, i16)>
+trait TableEntry {
+    fn value(&self) -> i16;
+    fn keyword(&self) -> nsCSSKeyword;
+}
+
+impl TableEntry for nsCSSKTableEntry {
+    fn value(&self) -> i16 {
+        self.mValue
+    }
+    fn keyword(&self) -> nsCSSKeyword {
+        self.mKeyword
+    }
+}
+
+impl TableEntry for nsCSSKeywordAndBoolTableEntry {
+    fn value(&self) -> i16 {
+        self._base.mValue
+    }
+    fn keyword(&self) -> nsCSSKeyword {
+        self._base.mKeyword
+    }
+}
+
+unsafe fn find_in_table<T, R, FindFunc, ResultFunc>(
+    current_entry: *const T,
+    find: FindFunc,
+    result_func: ResultFunc,
+) -> Option<R>
 where
-    F: FnMut(nsCSSKeyword, i16) -> bool,
+    T: TableEntry,
+    FindFunc: Fn(nsCSSKeyword, i16) -> bool,
+    ResultFunc: Fn(&T) -> R,
 {
+    let mut current_entry = current_entry;
+
     loop {
-        let value = (*current_entry).mValue;
-        let keyword = (*current_entry).mKeyword;
+        let value = (*current_entry).value();
+        let keyword = (*current_entry).keyword();
 
         if value == -1 {
             return None; // End of the table.
         }
 
-        if f(keyword, value) {
-            return Some((keyword, value));
+        if find(keyword, value) {
+            return Some(result_func(&*current_entry));
         }
 
         current_entry = current_entry.offset(1);
     }
 }
 
 fn parse_feature_value<'i, 't>(
     feature: &nsMediaFeature,
@@ -551,42 +605,63 @@ fn parse_feature_value<'i, 't>(
             input.expect_delim('/')?;
             let b = Integer::parse_positive(context, input)?;
             MediaExpressionValue::IntRatio(a.value() as u32, b.value() as u32)
         },
         nsMediaFeature_ValueType::eResolution => {
             MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
         },
         nsMediaFeature_ValueType::eEnumerated => {
-            let location = input.current_source_location();
-            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 nsCSSKTableEntry =
                 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(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
-                },
-            };
+            let value = parse_keyword(input, first_table_entry)?;
 
             MediaExpressionValue::Enumerated(value)
         },
+        nsMediaFeature_ValueType::eBoolEnumerated => {
+            let first_table_entry: *const nsCSSKeywordAndBoolTableEntry =
+                unsafe { *feature.mData.mKeywordAndBoolTable.as_ref() };
+
+            let value = parse_keyword(input, first_table_entry)?;
+
+            MediaExpressionValue::BoolEnumerated(value)
+        },
         nsMediaFeature_ValueType::eIdent => {
             MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
         },
     };
 
     Ok(value)
 }
 
+/// Parse a keyword and returns the corresponding i16 value.
+fn parse_keyword<'i, 't, T>(
+    input: &mut Parser<'i, 't>,
+    first_table_entry: *const T,
+) -> Result<i16, ParseError<'i>>
+where
+    T: TableEntry,
+{
+    let location = input.current_source_location();
+    let keyword = input.expect_ident()?;
+    let keyword = unsafe {
+        bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(), keyword.len() as u32)
+    };
+
+    let value = unsafe {
+        find_in_table(first_table_entry, |kw, _| kw == keyword, |e| e.value())
+    };
+
+    match value {
+        Some(value) => Ok(value),
+        None => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
+    }
+}
+
 /// Consumes an operation or a colon, or returns an error.
 fn consume_operation_or_colon(
     input: &mut Parser,
 ) -> Result<Option<Operator>, ()> {
     let first_delim = {
         let next_token = match input.next() {
             Ok(t) => t,
             Err(..) => return Err(()),
@@ -829,16 +904,26 @@ impl MediaFeatureExpression {
                 return match *actual_value {
                     BoolInteger(v) => v,
                     Integer(v) => v != 0,
                     Length(ref l) => computed::Context::for_media_query_evaluation(
                         device,
                         quirks_mode,
                         |context| l.to_computed_value(&context).px() != 0.,
                     ),
+                    BoolEnumerated(value) =>  {
+                        let value = unsafe {
+                            find_in_table(
+                                *self.feature.mData.mKeywordAndBoolTable.as_ref(),
+                                |_kw, val| val == value,
+                                |e| e.mValueInBooleanContext,
+                            )
+                        };
+                        value.expect("Value not found in the keyword table?")
+                    },
                     _ => true,
                 };
             },
         };
 
         // FIXME(emilio): Handle the possible floating point errors?
         let cmp = match (actual_value, required_value) {
             (&Length(ref one), &Length(ref other)) => {
@@ -875,16 +960,23 @@ impl MediaFeatureExpression {
             },
             (&Enumerated(one), &Enumerated(other)) => {
                 debug_assert_ne!(
                     self.feature.mRangeType,
                     nsMediaFeature_RangeType::eMinMaxAllowed
                 );
                 return one == other;
             },
+            (&BoolEnumerated(one), &BoolEnumerated(other)) => {
+                debug_assert_ne!(
+                    self.feature.mRangeType,
+                    nsMediaFeature_RangeType::eMinMaxAllowed
+                );
+                return one == other;
+            },
             _ => unreachable!(),
         };
 
         let range_or_op = match self.range_or_operator {
             Some(r) => r,
             None => return cmp == Ordering::Equal,
         };