servo: Merge #17739 - Suppress CSS parser errors for vendor-prefixed properties (from jdm:no-vendor-prefixed-errors); r=emilio
authorJosh Matthews <josh@joshmatthews.net>
Mon, 17 Jul 2017 07:10:11 -0700
changeset 369174 7eceab6da87599e367c0d4dfd3d3167841f0f6a9
parent 369173 07976bd4a36ca50b15a2b40004f917e6e0bdddce
child 369175 6d6a00964ace50cada854687e0c9064b19a2f187
push id32192
push userkwierso@gmail.com
push dateTue, 18 Jul 2017 00:01:01 +0000
treeherdermozilla-central@efc0b1525edb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17739 - Suppress CSS parser errors for vendor-prefixed properties (from jdm:no-vendor-prefixed-errors); r=emilio This matches the behaviour of Gecko's CSS parser. - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #17736 - [X] There are tests for these changes Source-Repo: https://github.com/servo/servo Source-Revision: 38f4ae80c4b456b89ee33543c8c6699501696c9c
servo/components/layout_thread/lib.rs
servo/components/script/dom/cssstyledeclaration.rs
servo/components/style/properties/declaration_block.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/stylesheets/keyframes_rule.rs
servo/components/style_traits/lib.rs
servo/ports/geckolib/error_reporter.rs
servo/tests/unit/style/stylesheets.rs
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -128,17 +128,16 @@ use style::selector_parser::SnapshotMap;
 use style::servo::restyle_damage::{REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, REPOSITION, STORE_OVERFLOW};
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
 use style::stylearc::Arc as StyleArc;
 use style::stylesheets::{Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets};
 use style::stylist::{ExtraStyleData, Stylist};
 use style::thread_state;
 use style::timer::Timer;
 use style::traversal::{DomTraversal, TraversalDriver, TraversalFlags};
-use style::values::CompactCowStr;
 
 /// Information needed by the layout thread.
 pub struct LayoutThread {
     /// The ID of the pipeline that we belong to.
     id: PipelineId,
 
     /// The ID of the top-level browsing context that we belong to.
     top_level_browsing_context_id: TopLevelBrowsingContextId,
@@ -709,17 +708,17 @@ impl LayoutThread {
                 self.create_layout_thread(info)
             }
             Msg::SetFinalUrl(final_url) => {
                 self.url = final_url;
             },
             Msg::RegisterPaint(name, mut properties, painter) => {
                 debug!("Registering the painter");
                 let properties = properties.drain(..)
-                    .filter_map(|name| PropertyId::parse(CompactCowStr::from(&*name)).ok().map(|id| (name.clone(), id)))
+                    .filter_map(|name| PropertyId::parse(&*name).ok().map(|id| (name.clone(), id)))
                     .filter(|&(_, ref id)| id.as_shorthand().is_err())
                     .collect();
                 let registered_painter = RegisteredPainter {
                     name: name.clone(),
                     properties: properties,
                     painter: painter,
                 };
                 self.registered_painters.write()
--- a/servo/components/script/dom/cssstyledeclaration.rs
+++ b/servo/components/script/dom/cssstyledeclaration.rs
@@ -291,28 +291,28 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
     fn Item(&self, index: u32) -> DOMString {
         self.IndexedGetter(index).unwrap_or_default()
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
     fn GetPropertyValue(&self, property: DOMString) -> DOMString {
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property
             return DOMString::new()
         };
         self.get_property_value(id)
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
     fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property
             return DOMString::new()
         };
 
         self.owner.with_block(|pdb| {
             if pdb.property_priority(&id).important() {
@@ -326,34 +326,34 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
     fn SetProperty(&self,
                    property: DOMString,
                    value: DOMString,
                    priority: DOMString)
                    -> ErrorResult {
         // Step 3
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unknown property
             return Ok(())
         };
         self.set_property(id, value, priority)
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority
     fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
         // Step 2 & 3
-        let id = match PropertyId::parse(property.into()) {
+        let id = match PropertyId::parse(&property) {
             Ok(id) => id,
             Err(..) => return Ok(()), // Unkwown property
         };
 
         // Step 4
         let importance = match &*priority {
             "" => Importance::Normal,
             p if p.eq_ignore_ascii_case("important") => Importance::Important,
@@ -375,17 +375,17 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
     fn RemoveProperty(&self, property: DOMString) -> Fallible<DOMString> {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property, cannot be there to remove.
             return Ok(DOMString::new())
         };
 
         let mut string = String::new();
         self.owner.mutate_associated_block(|mut pdb, mut changed| {
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A property declaration block.
 
 #![deny(missing_docs)]
 
 use context::QuirksMode;
 use cssparser::{DeclarationListParser, parse_important, ParserInput, CompactCowStr};
-use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter};
+use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter, ParseError as CssParseError};
 use error_reporting::{ParseErrorReporter, ContextualParseError};
 use parser::{ParserContext, log_css_error};
 use properties::animated_properties::AnimationValue;
 use selectors::parser::SelectorParseError;
 use shared_lock::Locked;
 use smallvec::SmallVec;
 use std::fmt;
 use std::slice::Iter;
@@ -930,23 +930,38 @@ struct PropertyDeclarationParser<'a, 'b:
 
 /// Default methods reject all at rules.
 impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Prelude = ();
     type AtRule = Importance;
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 }
 
+/// Based on NonMozillaVendorIdentifier from Gecko's CSS parser.
+fn is_non_mozilla_vendor_identifier(name: &str) -> bool {
+    (name.starts_with("-") && !name.starts_with("-moz-")) ||
+        name.starts_with("_")
+}
+
 impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Declaration = Importance;
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
     fn parse_value<'t>(&mut self, name: CompactCowStr<'i>, input: &mut Parser<'i, 't>)
                        -> Result<Importance, ParseError<'i>> {
-        let id = PropertyId::parse(name)?;
+        let id = match PropertyId::parse(&name) {
+            Ok(id) => id,
+            Err(()) => {
+                return Err(if is_non_mozilla_vendor_identifier(&name) {
+                    PropertyDeclarationParseError::UnknownVendorProperty
+                } else {
+                    PropertyDeclarationParseError::UnknownProperty(name)
+                }.into());
+            }
+        };
         input.parse_until_before(Delimiter::Bang, |input| {
             PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
                 .map_err(|e| e.into())
         })?;
         let importance = match input.try(parse_important) {
             Ok(()) => Importance::Important,
             Err(_) => Importance::Normal,
         };
@@ -971,16 +986,25 @@ pub fn parse_property_declaration_list(c
     let mut iter = DeclarationListParser::new(input, parser);
     while let Some(declaration) = iter.next() {
         match declaration {
             Ok(importance) => {
                 block.extend(iter.parser.declarations.drain(), importance);
             }
             Err(err) => {
                 iter.parser.declarations.clear();
+
+                // If the unrecognized property looks like a vendor-specific property,
+                // silently ignore it instead of polluting the error output.
+                if let CssParseError::Custom(SelectorParseError::Custom(
+                    StyleParseError::PropertyDeclaration(
+                        PropertyDeclarationParseError::UnknownVendorProperty))) = err.error {
+                    continue;
+                }
+
                 let pos = err.span.start;
                 let error = ContextualParseError::UnsupportedPropertyDeclaration(
                     iter.input.slice(err.span), err.error);
                 log_css_error(iter.input, pos, error, &context);
             }
         }
     }
     block
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2866,17 +2866,17 @@ fn static_assert() {
                     } else if feature.0 == atom!("transform") {
                         self.gecko.mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM as u8;
                     }
 
                     unsafe {
                         Gecko_AppendWillChange(&mut self.gecko, feature.0.as_ptr());
                     }
 
-                    if let Ok(prop_id) = PropertyId::parse(feature.0.to_string().into()) {
+                    if let Ok(prop_id) = PropertyId::parse(&feature.0.to_string()) {
                         match prop_id.as_shorthand() {
                             Ok(shorthand) => {
                                 for longhand in shorthand.longhands() {
                                     self.gecko.mWillChangeBitField |=
                                         will_change_bitfield_from_prop_flags(longhand);
                                 }
                             },
                             Err(longhand_or_custom) => {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -15,17 +15,17 @@ use std::collections::HashSet;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
 use stylearc::{Arc, UniqueArc};
 
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::RGBA;
 use cssparser::{Parser, TokenSerializationType, serialize_identifier};
-use cssparser::{ParserInput, CompactCowStr};
+use cssparser::ParserInput;
 #[cfg(feature = "servo")] use euclid::SideOffsets2D;
 use computed_values;
 use context::QuirksMode;
 use error_reporting::NullReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
@@ -505,17 +505,17 @@ impl LonghandId {
     fn parse_value<'i, 't>(&self, context: &ParserContext, input: &mut Parser<'i, 't>)
                            -> Result<PropertyDeclaration, ParseError<'i>> {
         match *self {
             % for property in data.longhands:
                 LonghandId::${property.camel_case} => {
                     % if not property.derived_from:
                         longhands::${property.ident}::parse_declared(context, input)
                     % else:
-                        Err(PropertyDeclarationParseError::UnknownProperty.into())
+                        Err(PropertyDeclarationParseError::UnknownProperty("${property.ident}".into()).into())
                     % endif
                 }
             % endfor
         }
     }
 
     /// If this is a logical property, return the corresponding physical one in the given writing mode.
     /// Otherwise, return unchanged.
@@ -987,18 +987,18 @@ impl ToCss for PropertyId {
         }
     }
 }
 
 impl PropertyId {
     /// Returns a given property from the string `s`.
     ///
     /// Returns Err(()) for unknown non-custom properties
-    pub fn parse<'i>(property_name: CompactCowStr<'i>) -> Result<Self, ParseError<'i>> {
-        if let Ok(name) = ::custom_properties::parse_name(&property_name) {
+    pub fn parse(property_name: &str) -> Result<Self, ()> {
+        if let Ok(name) = ::custom_properties::parse_name(property_name) {
             return Ok(PropertyId::Custom(::custom_properties::Name::from(name)))
         }
 
         // FIXME(https://github.com/rust-lang/rust/issues/33156): remove this enum and use PropertyId
         // when stable Rust allows destructors in statics.
         pub enum StaticId {
             Longhand(LonghandId),
             Shorthand(ShorthandId),
@@ -1009,20 +1009,20 @@ impl PropertyId {
                     % for property in properties:
                         % for name in [property.name] + property.alias:
                             "${name}" => StaticId::${kind}(${kind}Id::${property.camel_case}),
                         % endfor
                     % endfor
                 % endfor
             }
         }
-        match static_id(&property_name) {
+        match static_id(property_name) {
             Some(&StaticId::Longhand(id)) => Ok(PropertyId::Longhand(id)),
             Some(&StaticId::Shorthand(id)) => Ok(PropertyId::Shorthand(id)),
-            None => Err(StyleParseError::UnknownProperty(property_name).into()),
+            None => Err(()),
         }
     }
 
     /// Returns a property id from Gecko's nsCSSPropertyID.
     #[cfg(feature = "gecko")]
     #[allow(non_upper_case_globals)]
     pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
         use gecko_bindings::structs::*;
@@ -1096,17 +1096,17 @@ impl PropertyId {
                 let mut s = String::new();
                 write!(&mut s, "--{}", name).unwrap();
                 s.into()
             }
         }
     }
 
     fn check_allowed_in(&self, rule_type: CssRuleType, stylesheet_origin: Origin)
-                        -> Result<(), PropertyDeclarationParseError> {
+                        -> Result<(), PropertyDeclarationParseError<'static>> {
         let id: NonCustomPropertyId;
         match *self {
             // Custom properties are allowed everywhere
             PropertyId::Custom(_) => return Ok(()),
 
             PropertyId::Shorthand(shorthand_id) => id = shorthand_id.into(),
             PropertyId::Longhand(longhand_id) => id = longhand_id.into(),
         }
@@ -1181,17 +1181,17 @@ impl PropertyId {
 
         if INTERNAL.contains(id) {
             if stylesheet_origin != Origin::UserAgent {
                 if EXPERIMENTAL.contains(id) {
                     if !passes_pref_check() {
                         return Err(PropertyDeclarationParseError::ExperimentalProperty);
                     }
                 } else {
-                    return Err(PropertyDeclarationParseError::UnknownProperty);
+                    return Err(PropertyDeclarationParseError::UnknownProperty(self.name().into()));
                 }
             }
         } else {
             if EXPERIMENTAL.contains(id) && !passes_pref_check() {
                 return Err(PropertyDeclarationParseError::ExperimentalProperty);
             }
         }
 
@@ -1457,19 +1457,19 @@ impl PropertyDeclaration {
     /// https://drafts.csswg.org/css-animations/#keyframes
     /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
     /// > except those defined in this specification,
     /// > but does accept the `animation-play-state` property and interprets it specially.
     ///
     /// This will not actually parse Importance values, and will always set things
     /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
     /// we only set them here so that we don't have to reallocate
-    pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
-                      id: PropertyId, context: &ParserContext, input: &mut Parser)
-                      -> Result<(), PropertyDeclarationParseError> {
+    pub fn parse_into<'i, 't>(declarations: &mut SourcePropertyDeclaration,
+                              id: PropertyId, context: &ParserContext, input: &mut Parser<'i, 't>)
+                              -> Result<(), PropertyDeclarationParseError<'i>> {
         assert!(declarations.is_empty());
         let rule_type = context.rule_type();
         debug_assert!(rule_type == CssRuleType::Keyframe ||
                       rule_type == CssRuleType::Page ||
                       rule_type == CssRuleType::Style,
                       "Declarations are only expected inside a keyframe, page, or style rule.");
         id.check_allowed_in(rule_type, context.stylesheet_origin)?;
         match id {
--- a/servo/components/style/stylesheets/keyframes_rule.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -12,16 +12,17 @@ use properties::{Importance, PropertyDec
 use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::animated_properties::AnimatableLonghand;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
 use selectors::parser::SelectorParseError;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
 use std::fmt;
 use style_traits::{PARSING_MODE_DEFAULT, ToCss, ParseError, StyleParseError};
+use style_traits::PropertyDeclarationParseError;
 use stylearc::Arc;
 use stylesheets::{CssRuleType, StylesheetContents};
 use stylesheets::rule_parser::VendorPrefix;
 use values::KeyframesName;
 
 /// A [`@keyframes`][keyframes] rule.
 ///
 /// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
@@ -527,17 +528,18 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for Ke
 }
 
 impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeDeclarationParser<'a, 'b> {
     type Declaration = ();
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
     fn parse_value<'t>(&mut self, name: CompactCowStr<'i>, input: &mut Parser<'i, 't>)
                        -> Result<(), ParseError<'i>> {
-        let id = PropertyId::parse(name.into())?;
+        let id = PropertyId::parse(&name)
+            .map_err(|()| PropertyDeclarationParseError::UnknownProperty(name))?;
         match PropertyDeclaration::parse_into(self.declarations, id, self.context, input) {
             Ok(()) => {
                 // In case there is still unparsed text in the declaration, we should roll back.
                 input.expect_exhausted().map_err(|e| e.into())
             }
             Err(_e) => Err(StyleParseError::UnspecifiedError.into())
         }
     }
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -86,17 +86,17 @@ pub enum StyleParseError<'i> {
     BadStringInDeclarationValueBlock(CompactCowStr<'i>),
     /// Unexpected closing parenthesis in a DVB.
     UnbalancedCloseParenthesisInDeclarationValueBlock,
     /// Unexpected closing bracket in a DVB.
     UnbalancedCloseSquareBracketInDeclarationValueBlock,
     /// Unexpected closing curly bracket in a DVB.
     UnbalancedCloseCurlyBracketInDeclarationValueBlock,
     /// A property declaration parsing error.
-    PropertyDeclaration(PropertyDeclarationParseError),
+    PropertyDeclaration(PropertyDeclarationParseError<'i>),
     /// A property declaration value had input remaining after successfully parsing.
     PropertyDeclarationValueNotExhausted,
     /// An unexpected dimension token was encountered.
     UnexpectedDimension(CompactCowStr<'i>),
     /// A media query using a ranged expression with no value was encountered.
     RangedExpressionWithNoValue,
     /// A function was encountered that was not expected.
     UnexpectedFunction(CompactCowStr<'i>),
@@ -107,25 +107,25 @@ pub enum StyleParseError<'i> {
     /// Unexpected @charset rule encountered.
     UnexpectedCharsetRule,
     /// Unsupported @ rule
     UnsupportedAtRule(CompactCowStr<'i>),
     /// A placeholder for many sources of errors that require more specific variants.
     UnspecifiedError,
     /// An unexpected token was found within a namespace rule.
     UnexpectedTokenWithinNamespace(Token<'i>),
-    /// An unknown CSS property was encountered.
-    UnknownProperty(CompactCowStr<'i>),
 }
 
 /// The result of parsing a property declaration.
 #[derive(Eq, PartialEq, Clone, Debug)]
-pub enum PropertyDeclarationParseError {
+pub enum PropertyDeclarationParseError<'i> {
     /// The property declaration was for an unknown property.
-    UnknownProperty,
+    UnknownProperty(CompactCowStr<'i>),
+    /// An unknown vendor-specific identifier was encountered.
+    UnknownVendorProperty,
     /// The property declaration was for a disabled experimental property.
     ExperimentalProperty,
     /// The property declaration contained an invalid value.
     InvalidValue(String),
     /// The declaration contained an animation property, and we were parsing
     /// this as a keyframe block (so that property should be ignored).
     ///
     /// See: https://drafts.csswg.org/css-animations/#keyframes
@@ -135,18 +135,18 @@ pub enum PropertyDeclarationParseError {
 }
 
 impl<'a> From<StyleParseError<'a>> for ParseError<'a> {
     fn from(this: StyleParseError<'a>) -> Self {
         cssparser::ParseError::Custom(SelectorParseError::Custom(this))
     }
 }
 
-impl<'a> From<PropertyDeclarationParseError> for ParseError<'a> {
-    fn from(this: PropertyDeclarationParseError) -> Self {
+impl<'a> From<PropertyDeclarationParseError<'a>> for ParseError<'a> {
+    fn from(this: PropertyDeclarationParseError<'a>) -> Self {
         cssparser::ParseError::Custom(SelectorParseError::Custom(StyleParseError::PropertyDeclaration(this)))
     }
 }
 
 bitflags! {
     /// The mode to use when parsing values.
     pub flags ParsingMode: u8 {
         /// In CSS, lengths must have units, except for zero values, where the unit can be omitted.
--- a/servo/ports/geckolib/error_reporter.rs
+++ b/servo/ports/geckolib/error_reporter.rs
@@ -228,17 +228,18 @@ impl<'a> ErrorHelpers<'a> for Contextual
 
             (_, CssParseError::Custom(SelectorParseError::UnexpectedIdent(ident))) =>
                 ErrorString::Ident(ident),
 
             (_, CssParseError::Custom(SelectorParseError::ExpectedNamespace(namespace))) =>
                 ErrorString::Ident(namespace),
 
             (_, CssParseError::Custom(SelectorParseError::Custom(
-                StyleParseError::UnknownProperty(property)))) =>
+                StyleParseError::PropertyDeclaration(
+                    PropertyDeclarationParseError::UnknownProperty(property))))) =>
                 ErrorString::Ident(property),
 
             (_, CssParseError::Custom(SelectorParseError::Custom(
                 StyleParseError::UnexpectedTokenWithinNamespace(token)))) =>
                 ErrorString::UnexpectedToken(token),
 
             (s, _)  => ErrorString::Snippet(s)
         }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -311,21 +311,48 @@ fn test_report_error_stylesheet() {
     let media = Arc::new(lock.wrap(MediaList::empty()));
     Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
                          None, &error_reporter, QuirksMode::NoQuirks, 5u64);
 
     let mut errors = errors.lock().unwrap();
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'invalid: true;', \
-                Custom(UnknownProperty(\"invalid\"))", error.message);
+                Custom(PropertyDeclaration(UnknownProperty(\"invalid\")))", error.message);
     assert_eq!(9, error.line);
     assert_eq!(8, error.column);
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'display: invalid;', \
                 Custom(PropertyDeclaration(InvalidValue(\"display\")))", error.message);
     assert_eq!(8, error.line);
     assert_eq!(8, error.column);
 
     // testing for the url
     assert_eq!(url, error.url);
 }
+
+#[test]
+fn test_no_report_unrecognized_vendor_properties() {
+    let css = r"
+    div {
+        -o-background-color: red;
+        _background-color: red;
+        -moz-background-color: red;
+    }
+    ";
+    let url = ServoUrl::parse("about::test").unwrap();
+    let error_reporter = CSSInvalidErrorReporterTest::new();
+
+    let errors = error_reporter.errors.clone();
+
+    let lock = SharedRwLock::new();
+    let media = Arc::new(lock.wrap(MediaList::empty()));
+    Stylesheet::from_str(css, url, Origin::UserAgent, media, lock,
+                         None, &error_reporter, QuirksMode::NoQuirks, 0u64);
+
+    let mut errors = errors.lock().unwrap();
+    let error = errors.pop().unwrap();
+    assert_eq!("Unsupported property declaration: '-moz-background-color: red;', \
+                Custom(PropertyDeclaration(UnknownProperty(\"-moz-background-color\")))",
+               error.message);
+    assert!(errors.is_empty());
+}