Bug 441469. Implement parsing of @font-face rules. r+sr=dbaron
authorZack Weinberg <zweinberg@mozilla.com>
Thu, 07 Aug 2008 19:15:40 -0400
changeset 16498 04432668e53e285ab44efc99da4d07f2c4bcaf50
parent 16497 eb6fda3be5bb2bb7472207486f652ba8b5b8164b
child 16499 92b4e1f43fbf1261147b7972880a6ec3ca08092c
child 16540 3318a1471a44d0d98ce215dbc999c5e2ab05cfed
push idunknown
push userunknown
push dateunknown
bugs441469
milestone1.9.1a2pre
Bug 441469. Implement parsing of @font-face rules. r+sr=dbaron
dom/locales/en-US/chrome/layout/css.properties
dom/public/nsDOMClassInfoID.h
dom/src/base/nsDOMClassInfo.cpp
layout/style/nsCSSDeclaration.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSProperty.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsCSSRules.cpp
layout/style/nsCSSRules.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/test/Makefile.in
layout/style/test/test_font_face_parser.html
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -37,18 +37,18 @@
 MimeNotCss=The stylesheet %1$S was not loaded because its MIME type, "%2$S", is not "text/css".
 MimeNotCssWarn=The stylesheet %1$S was loaded as CSS even though its MIME type, "%2$S", is not "text/css".
 
 PEUnexpEOF2=Unexpected end of file while searching for %1$S.
 PEParseRuleWSOnly=Whitespace-only string given to be parsed as rule.
 PEDeclDropped=Declaration dropped.
 PEDeclSkipped=Skipped to next declaration.
 PEUnknownProperty=Unknown property '%1$S'.
-PEPropertyParsingError=Error in parsing value for property '%1$S'.
-PEExpectEndProperty=Expected end of value for property but found '%1$S'.
+PEValueParsingError=Error in parsing value for '%1$S'.
+PEExpectEndValue=Expected end of value but found '%1$S'.
 PESkipAtRuleEOF=end of unknown at-rule
 PEUnknownAtRule=Unrecognized at-rule or error parsing at-rule '%1$S'.
 PECharsetRuleEOF=charset string in @charset rule
 PECharsetRuleNotString=Expected charset string but found '%1$S'.
 PEGatherMediaEOF=end of media list in @import or @media rule
 PEGatherMediaNotComma=Expected ',' in media list but found '%1$S'.
 PEGatherMediaNotIdent=Expected identifier in media list but found '%1$S'.
 PEImportNotURI=Expected URI in @import rule but found '%1$S'.
@@ -122,13 +122,15 @@ PEParseDeclarationDeclExpected=Expected 
 PEEndOfDeclEOF=end of declaration
 PEImportantEOF=important
 PEExpectedImportant=Expected 'important' but found '%1$S'.
 PEBadDeclEnd=Expected ';' to terminate declaration but found '%1$S'.
 PEBadDeclOrRuleEnd2=Expected ';' or '}' to terminate declaration but found '%1$S'.
 PEInaccessibleProperty2=Cannot specify value for internal property.
 PECommentEOF=end of comment
 SEUnterminatedString=Found unclosed string '%1$S'.
+PEFontDescExpected=Expected font descriptor but found '%1$S'.
+PEUnknownFontDesc=Unknown descriptor '%1$S' in @font-face rule.
 PEMQExpectedExpressionStart=Expected '(' to start media query expression but found '%1$S'.
 PEMQExpressionEOF=contents of media query expression
 PEMQExpectedFeatureName=Expected media feature name but found '%1$S'.
 PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'.
 PEMQExpectedFeatureValue=Found invalid value for media feature.
--- a/dom/public/nsDOMClassInfoID.h
+++ b/dom/public/nsDOMClassInfoID.h
@@ -423,16 +423,20 @@ enum nsDOMClassInfoID {
   // event used for cross-domain message-passing and for server-sent events in
   // HTML5
   eDOMClassInfo_MessageEvent_id,
 
   // Geolocation
   eDOMClassInfo_Geolocation_id,
   eDOMClassInfo_Geolocator_id,
 
+  // @font-face in CSS
+  eDOMClassInfo_CSSFontFaceRule_id,
+  eDOMClassInfo_CSSFontFaceStyleDecl_id,
+
   // WhatWG Video Element
 #if defined(MOZ_MEDIA)
   eDOMClassInfo_HTMLVideoElement_id,
   eDOMClassInfo_HTMLSourceElement_id,
   eDOMClassInfo_ProgressEvent_id,
   eDOMClassInfo_HTMLMediaError_id,
   eDOMClassInfo_HTMLAudioElement_id,
 #endif
--- a/dom/src/base/nsDOMClassInfo.cpp
+++ b/dom/src/base/nsDOMClassInfo.cpp
@@ -320,16 +320,17 @@
 #include "nsIDOMHTMLAudioElement.h"
 #include "nsIDOMProgressEvent.h"
 #endif
 #include "nsIDOMNSUIEvent.h"
 #include "nsIDOMCSS2Properties.h"
 #include "nsIDOMCSSCharsetRule.h"
 #include "nsIDOMCSSImportRule.h"
 #include "nsIDOMCSSMediaRule.h"
+#include "nsIDOMCSSFontFaceRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
 #include "nsIDOMCSSPrimitiveValue.h"
 #include "nsIDOMCSSStyleRule.h"
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsIDOMCSSValueList.h"
 #include "nsIDOMRange.h"
 #include "nsIDOMNSRange.h"
 #include "nsIDOMRangeException.h"
@@ -809,17 +810,17 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(StyleSheetList, nsDOMGenericSH,
                            ARRAY_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSStyleSheet, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSStyleDeclaration, nsCSSStyleDeclSH,
                            ARRAY_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(ComputedCSSStyleDeclaration, nsCSSStyleDeclSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+                           ARRAY_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(ROCSSPrimitiveValue, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   // Range classes
   NS_DEFINE_CLASSINFO_DATA(Range, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(Selection, nsDOMGenericSH,
                            DEFAULT_SCRIPTABLE_FLAGS)
@@ -1248,16 +1249,21 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(Geolocation, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
  
    NS_DEFINE_CLASSINFO_DATA(Geolocator, nsDOMGenericSH,
                             DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(CSSFontFaceRule, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(CSSFontFaceStyleDecl, nsCSSStyleDeclSH,
+                           ARRAY_SCRIPTABLE_FLAGS)
+
 #if defined(MOZ_MEDIA) 
   NS_DEFINE_CLASSINFO_DATA(HTMLVideoElement, nsHTMLElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLSourceElement, nsHTMLElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(ProgressEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLMediaError, nsDOMGenericSH,
@@ -3435,16 +3441,25 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN(Geolocation, nsIDOMGeolocation)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeolocation)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Geolocator, nsIDOMGeolocator)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeolocator)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(CSSFontFaceRule, nsIDOMCSSFontFaceRule)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFaceRule)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(CSSFontFaceStyleDecl,
+                                      nsIDOMCSSStyleDeclaration)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSStyleDeclaration)
+  DOM_CLASSINFO_MAP_END
+
 #if defined(MOZ_MEDIA)
   DOM_CLASSINFO_MAP_BEGIN(HTMLVideoElement, nsIDOMHTMLVideoElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLVideoElement)
     DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLSourceElement, nsIDOMHTMLSourceElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLSourceElement)
--- a/layout/style/nsCSSDeclaration.h
+++ b/layout/style/nsCSSDeclaration.h
@@ -145,31 +145,29 @@ public:
     }
     mOrder.Clear();
   }
 
 #ifdef DEBUG
   void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
 #endif
   
+  // return whether there was a value in |aValue| (i.e., it had a non-null unit)
+  static PRBool AppendCSSValueToString(nsCSSProperty aProperty,
+                                       const nsCSSValue& aValue,
+                                       nsAString& aResult);
+
 private:
   // Not implemented, and not supported.
   nsCSSDeclaration& operator=(const nsCSSDeclaration& aCopy);
   PRBool operator==(const nsCSSDeclaration& aCopy) const;
 
   static void AppendImportanceToString(PRBool aIsImportant, nsAString& aString);
   // return whether there was a value in |aValue| (i.e., it had a non-null unit)
   PRBool   AppendValueToString(nsCSSProperty aProperty, nsAString& aResult) const;
-public:
-  // return whether there was a value in |aValue| (i.e., it had a non-null unit)
-  static PRBool AppendCSSValueToString(nsCSSProperty aProperty,
-                                       const nsCSSValue& aValue,
-                                       nsAString& aResult);
-
-private:
   // May be called only for properties whose type is eCSSType_Value.
   nsresult GetValueOrImportantValue(nsCSSProperty aProperty, nsCSSValue& aValue) const;
 
   void   PropertyIsSet(PRInt32 & aPropertyIndex, PRInt32 aIndex, PRUint32 & aSet, PRUint32 aValue) const;
   PRBool TryBorderShorthand(nsAString & aString, PRUint32 aPropertiesSet,
                             PRInt32 aBorderTopWidth,
                             PRInt32 aBorderTopStyle,
                             PRInt32 aBorderTopColor,
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -289,17 +289,22 @@ protected:
                        void* aProcessData);
   PRBool ParseGroupRule(nsresult& aErrorCode, nsICSSGroupRule* aRule, RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseMediaRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseMozDocumentRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ParseNameSpaceRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData);
   PRBool ProcessNameSpace(nsresult& aErrorCode, const nsString& aPrefix, 
                           const nsString& aURLSpec, RuleAppendFunc aAppendFunc,
                           void* aProcessData);
+
   PRBool ParseFontFaceRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData);
+  PRBool ParseFontDescriptor(nsresult& aErrorCode, nsCSSFontFaceRule* aRule);
+  PRBool ParseFontDescriptorValue(nsresult& aErrorCode, nsCSSFontDesc aDescID,
+                                  nsCSSValue& aValue);
+
   PRBool ParsePageRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData);
 
   enum nsSelectorParsingStatus {
     // we have parsed a selector and we saw a token that cannot be part of a selector:
     eSelectorParsingStatus_Done,
     // we should continue parsing the selector:
     eSelectorParsingStatus_Continue,
     // same as "Done" but we did not find a selector:
@@ -411,17 +416,21 @@ protected:
   PRBool ParseContent(nsresult& aErrorCode);
   PRBool ParseCounterData(nsresult& aErrorCode,
                           nsCSSValuePairList** aResult,
                           nsCSSProperty aPropID);
   PRBool ParseCue(nsresult& aErrorCode);
   PRBool ParseCursor(nsresult& aErrorCode);
   PRBool ParseFont(nsresult& aErrorCode);
   PRBool ParseFontWeight(nsresult& aErrorCode, nsCSSValue& aValue);
+  PRBool ParseOneFamily(nsresult& aErrorCode, nsAString& aValue);
   PRBool ParseFamily(nsresult& aErrorCode, nsCSSValue& aValue);
+  PRBool ParseFontSrc(nsresult& aErrorCode, nsCSSValue& aValue);
+  PRBool ParseFontSrcFormat(nsresult& aErrorCode, nsTArray<nsCSSValue>& values);
+  PRBool ParseFontRanges(nsresult& aErrorCode, nsCSSValue& aValue);
   PRBool ParseListStyle(nsresult& aErrorCode);
   PRBool ParseMargin(nsresult& aErrorCode);
   PRBool ParseMarks(nsresult& aErrorCode, nsCSSValue& aValue);
   PRBool ParseOutline(nsresult& aErrorCode);
   PRBool ParseOverflow(nsresult& aErrorCode);
   PRBool ParsePadding(nsresult& aErrorCode);
   PRBool ParsePause(nsresult& aErrorCode);
   PRBool ParseQuotes(nsresult& aErrorCode);
@@ -1071,23 +1080,23 @@ CSSParserImpl::ParseProperty(const nsCSS
   aDeclaration->ExpandTo(&mData);
   nsresult result = NS_OK;
   PRBool parsedOK = ParseProperty(errorCode, aPropID);
   if (parsedOK && !GetToken(errorCode, PR_TRUE)) {
     TransferTempData(aDeclaration, aPropID, PR_FALSE, PR_FALSE, aChanged);
   } else {
     if (parsedOK) {
       // Junk at end of property value.
-      REPORT_UNEXPECTED_TOKEN(PEExpectEndProperty);
+      REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
     }
     NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
     const PRUnichar *params[] = {
       propName.get()
     };
-    REPORT_UNEXPECTED_P(PEPropertyParsingError, params);
+    REPORT_UNEXPECTED_P(PEValueParsingError, params);
     REPORT_UNEXPECTED(PEDeclDropped);
     OUTPUT_ERROR();
     ClearTempData(aPropID);
     NS_ASSERTION(errorCode != nsresult(-1), "-1 is no longer used for EOF");
     result = errorCode;
   }
   CLEAR_ERROR();
   
@@ -1301,17 +1310,17 @@ PRBool CSSParserImpl::ExpectEndProperty(
   }
   if ((eCSSToken_Symbol == mToken.mType) &&
       ((';' == mToken.mSymbol) || ('!' == mToken.mSymbol) || ('}' == mToken.mSymbol))) {
     // XXX need to verify that ! is only followed by "important [;|}]
     // XXX this requires a multi-token pushback buffer
     UngetToken();
     return PR_TRUE;
   }
-  REPORT_UNEXPECTED_TOKEN(PEExpectEndProperty);
+  REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
   UngetToken();
   return PR_FALSE;
 }
 
 
 nsSubstring* CSSParserImpl::NextIdent(nsresult& aErrorCode)
 {
   // XXX Error reporting?
@@ -1985,21 +1994,118 @@ PRBool CSSParserImpl::ProcessNameSpace(n
     if (!mNameSpaceMap) {
       mNameSpaceMap = mSheet->GetNameSpaceMap();
     }
   }
 
   return result;
 }
 
-PRBool CSSParserImpl::ParseFontFaceRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aData)
-{
-  // XXX not yet implemented
-  return PR_FALSE;
-}
+// font-face-rule: '@font-face' '{' font-description '}'
+// font-description: font-descriptor+
+PRBool
+CSSParserImpl::ParseFontFaceRule(nsresult& aErrorCode,
+                                 RuleAppendFunc aAppendFunc, void* aData)
+{
+  if (!ExpectSymbol(aErrorCode, '{', PR_TRUE))
+    return PR_FALSE;
+
+  nsRefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule());
+  if (!rule) {
+    aErrorCode = NS_ERROR_OUT_OF_MEMORY;
+    return PR_FALSE;
+  }
+
+  for (;;) {
+    if (!GetToken(aErrorCode, PR_TRUE)) {
+      REPORT_UNEXPECTED_EOF(PEFontFaceEOF);
+      break;
+    }
+    if (mToken.IsSymbol('}')) { // done!
+      UngetToken();
+      break;
+    }
+
+    // ignore extra semicolons
+    if (mToken.IsSymbol(';'))
+      continue;
+
+    if (!ParseFontDescriptor(aErrorCode, rule)) {
+      REPORT_UNEXPECTED(PEDeclSkipped);
+      OUTPUT_ERROR();
+      if (!SkipDeclaration(aErrorCode, PR_TRUE))
+        break;
+    }
+  }
+  if (!ExpectSymbol(aErrorCode, '}', PR_TRUE))
+    return PR_FALSE;
+  (*aAppendFunc)(rule, aData);
+  return PR_TRUE;
+}
+
+// font-descriptor: font-family-desc
+//                | font-style-desc
+//                | font-weight-desc
+//                | font-stretch-desc
+//                | font-src-desc
+//                | unicode-range-desc
+//
+// All font-*-desc productions follow the pattern
+//    IDENT ':' value ';'
+//
+// On entry to this function, mToken is the IDENT.
+
+PRBool
+CSSParserImpl::ParseFontDescriptor(nsresult& aErrorCode,
+                                   nsCSSFontFaceRule* aRule)
+{
+  if (eCSSToken_Ident != mToken.mType) {
+    REPORT_UNEXPECTED_TOKEN(PEFontDescExpected);
+    return PR_FALSE;
+  }
+
+  nsString descName = mToken.mIdent;
+  if (!ExpectSymbol(aErrorCode, ':', PR_TRUE)) {
+    REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
+    OUTPUT_ERROR();
+    return PR_FALSE;
+  }
+
+  nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName);
+  nsCSSValue value;
+
+  if (descID == eCSSFontDesc_UNKNOWN) {
+    if (NonMozillaVendorIdentifier(descName)) {
+      // silently skip other vendors' extensions
+      SkipDeclaration(aErrorCode, PR_TRUE);
+      return PR_TRUE;
+    } else {
+      const PRUnichar *params[] = {
+        descName.get()
+      };
+      REPORT_UNEXPECTED_P(PEUnknownFontDesc, params);
+      return PR_FALSE;
+    }
+  }
+
+  if (!ParseFontDescriptorValue(aErrorCode, descID, value)) {
+    const PRUnichar *params[] = {
+      descName.get()
+    };
+    REPORT_UNEXPECTED_P(PEValueParsingError, params);
+    return PR_FALSE;
+  }
+  
+  if (!ExpectEndProperty(aErrorCode))
+    return PR_FALSE;
+
+  aRule->SetDesc(descID, value);
+  return PR_TRUE;
+}
+
 
 PRBool CSSParserImpl::ParsePageRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aData)
 {
   // XXX not yet implemented
   return PR_FALSE;
 }
 
 void CSSParserImpl::SkipUntil(nsresult& aErrorCode, PRUnichar aStopSymbol)
@@ -3742,17 +3848,17 @@ CSSParserImpl::ParseDeclaration(nsresult
 
     return PR_FALSE;
   }
   if (! ParseProperty(aErrorCode, propID)) {
     // XXX Much better to put stuff in the value parsers instead...
     const PRUnichar *params[] = {
       propertyName.get()
     };
-    REPORT_UNEXPECTED_P(PEPropertyParsingError, params);
+    REPORT_UNEXPECTED_P(PEValueParsingError, params);
     REPORT_UNEXPECTED(PEDeclDropped);
     OUTPUT_ERROR();
     ClearTempData(propID);
     return PR_FALSE;
   }
   CLEAR_ERROR();
 
   // See if the declaration is followed by a "!important" declaration
@@ -4439,56 +4545,58 @@ PRBool CSSParserImpl::ParseAttr(nsresult
         aValue.SetStringValue(attr, eCSSUnit_Attr);
         return PR_TRUE;
       }
     }
   }
   return PR_FALSE;
 }
 
-PRBool CSSParserImpl::ParseURL(nsresult& aErrorCode, nsCSSValue& aValue)
+PRBool
+CSSParserImpl::ParseURL(nsresult& aErrorCode, nsCSSValue& aValue)
 {
   if (!mSheetPrincipal) {
     NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
                   "origin principal");
     return PR_FALSE;
   }
-  
-  if (ExpectSymbol(aErrorCode, '(', PR_FALSE)) {
-    if (! GetURLToken(aErrorCode)) {
-      return PR_FALSE;
-    }
-    nsCSSToken* tk = &mToken;
-    if ((eCSSToken_String == tk->mType) || (eCSSToken_URL == tk->mType)) {
-      // Translate url into an absolute url if the url is relative to
-      // the style sheet.
-      nsCOMPtr<nsIURI> uri;
-      NS_NewURI(getter_AddRefs(uri), tk->mIdent, nsnull, mBaseURL);
-      if (ExpectSymbol(aErrorCode, ')', PR_TRUE)) {
-        // Set a null value on failure.  Most failure cases should be
-        // NS_ERROR_MALFORMED_URI.
-        nsStringBuffer* buffer = nsCSSValue::BufferFromString(tk->mIdent);
-        if (NS_UNLIKELY(!buffer)) {
-          aErrorCode = NS_ERROR_OUT_OF_MEMORY;
-          return PR_FALSE;
-        }
-
-        nsCSSValue::URL *url =
-          new nsCSSValue::URL(uri, buffer, mSheetURL, mSheetPrincipal);
-        buffer->Release();
-        if (NS_UNLIKELY(!url)) {
-          aErrorCode = NS_ERROR_OUT_OF_MEMORY;
-          return PR_FALSE;
-        }
-        aValue.SetURLValue(url);
-        return PR_TRUE;
-      }
-    }
-  }
-  return PR_FALSE;
+
+  if (!ExpectSymbol(aErrorCode, '(', PR_FALSE))
+    return PR_FALSE;
+  if (!GetURLToken(aErrorCode))
+    return PR_FALSE;
+
+  nsCSSToken* tk = &mToken;
+  if (eCSSToken_String != tk->mType && eCSSToken_URL != tk->mType)
+    return PR_FALSE;
+
+  nsString url = tk->mIdent;
+  if (!ExpectSymbol(aErrorCode, ')', PR_TRUE))
+    return PR_FALSE;
+
+  // Translate url into an absolute url if the url is relative to the
+  // style sheet.
+  nsCOMPtr<nsIURI> uri;
+  NS_NewURI(getter_AddRefs(uri), url, nsnull, mBaseURL);
+
+  nsStringBuffer* buffer = nsCSSValue::BufferFromString(url);
+  if (NS_UNLIKELY(!buffer)) {
+    aErrorCode = NS_ERROR_OUT_OF_MEMORY;
+    return PR_FALSE;
+  }
+  nsCSSValue::URL *urlVal =
+    new nsCSSValue::URL(uri, buffer, mSheetURL, mSheetPrincipal);
+
+  buffer->Release();
+  if (NS_UNLIKELY(!urlVal)) {
+    aErrorCode = NS_ERROR_OUT_OF_MEMORY;
+    return PR_FALSE;
+  }
+  aValue.SetURLValue(urlVal);
+  return PR_TRUE;
 }
 
 PRInt32 CSSParserImpl::ParseChoice(nsresult& aErrorCode, nsCSSValue aValues[],
                                    const nsCSSProperty aPropIDs[], PRInt32 aNumIDs)
 {
   PRInt32 found = 0;
   nsAutoParseCompoundProperty compound(this);
 
@@ -5380,16 +5488,105 @@ PRBool CSSParserImpl::ParseSingleValuePr
   case eCSSProperty_z_index:
     return ParseVariant(aErrorCode, aValue, VARIANT_AHI, nsnull);
   }
   // explicitly do NOT have a default case to let the compiler
   // help find missing properties
   return PR_FALSE;
 }
 
+// nsFont::EnumerateFamilies callback for ParseFontDescriptorValue
+struct NS_STACK_CLASS ExtractFirstFamilyData {
+  nsAutoString mFamilyName;
+  PRBool mGood;
+  ExtractFirstFamilyData() : mFamilyName(), mGood(PR_FALSE) {}
+};
+
+static PRBool
+ExtractFirstFamily(const nsString& aFamily,
+                   PRBool aGeneric,
+                   void* aData)
+{
+  ExtractFirstFamilyData* realData = (ExtractFirstFamilyData*) aData;
+  if (aGeneric || realData->mFamilyName.Length() > 0) {
+    realData->mGood = PR_FALSE;
+    return PR_FALSE;
+  }
+  realData->mFamilyName.Assign(aFamily);
+  realData->mGood = PR_TRUE;
+  return PR_TRUE;
+}
+
+// font-descriptor: descriptor ':' value ';'
+// caller has advanced mToken to point at the descriptor
+PRBool
+CSSParserImpl::ParseFontDescriptorValue(nsresult& aErrorCode,
+                                        nsCSSFontDesc aDescID,
+                                        nsCSSValue& aValue)
+{
+  switch (aDescID) {
+    // These four are similar to the properties of the same name,
+    // possibly with more restrictions on the values they can take.
+  case eCSSFontDesc_Family: {
+    if (!ParseFamily(aErrorCode, aValue) ||
+        aValue.GetUnit() != eCSSUnit_String)
+      return PR_FALSE;
+
+    // the style parameters to the nsFont constructor are ignored,
+    // because it's only being used to call EnumerateFamilies
+    nsAutoString valueStr;
+    aValue.GetStringValue(valueStr);
+    nsFont font(valueStr, 0, 0, 0, 0, 0);
+    ExtractFirstFamilyData dat;
+
+    font.EnumerateFamilies(ExtractFirstFamily, (void*) &dat);
+    if (!dat.mGood)
+      return PR_FALSE;
+
+    aValue.SetStringValue(dat.mFamilyName, eCSSUnit_String);
+    return PR_TRUE;
+  }
+
+  case eCSSFontDesc_Style:
+    // property is VARIANT_HMK|VARIANT_SYSFONT
+    return ParseVariant(aErrorCode, aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
+                        nsCSSProps::kFontStyleKTable);
+
+  case eCSSFontDesc_Weight:
+    return (ParseFontWeight(aErrorCode, aValue) &&
+            aValue.GetUnit() != eCSSUnit_Inherit &&
+            aValue.GetUnit() != eCSSUnit_Initial &&
+            (aValue.GetUnit() != eCSSUnit_Enumerated ||
+             (aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
+              aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));
+
+  case eCSSFontDesc_Stretch:
+    // property is VARIANT_HMK|VARIANT_SYSFONT
+    return (ParseVariant(aErrorCode, aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
+                         nsCSSProps::kFontStretchKTable) &&
+            (aValue.GetUnit() != eCSSUnit_Enumerated ||
+             (aValue.GetIntValue() != NS_STYLE_FONT_STRETCH_WIDER &&
+              aValue.GetIntValue() != NS_STYLE_FONT_STRETCH_NARROWER)));
+
+    // These two are unique to @font-face and have their own special grammar.
+  case eCSSFontDesc_Src:
+    return ParseFontSrc(aErrorCode, aValue);
+
+  case eCSSFontDesc_UnicodeRange:
+    return ParseFontRanges(aErrorCode, aValue);
+
+  case eCSSFontDesc_UNKNOWN:
+  case eCSSFontDesc_COUNT:
+    NS_NOTREACHED("bad nsCSSFontDesc code");
+  }
+  // explicitly do NOT have a default case to let the compiler
+  // help find missing descriptors
+  return PR_FALSE;
+}
+
 void
 CSSParserImpl::InitBoxPropsAsPhysical(const nsCSSProperty *aSourceProperties)
 {
   nsCSSValue physical(NS_BOXPROP_SOURCE_PHYSICAL, eCSSUnit_Enumerated);
   for (const nsCSSProperty *prop = aSourceProperties;
        *prop != eCSSProperty_UNKNOWN; ++prop) {
     AppendValue(*prop, physical);
   }
@@ -6419,95 +6616,208 @@ PRBool CSSParserImpl::ParseFontWeight(ns
         return PR_FALSE;
       }
     }
     return PR_TRUE;
   }
   return PR_FALSE;
 }
 
-PRBool CSSParserImpl::ParseFamily(nsresult& aErrorCode, nsCSSValue& aValue)
-{
+PRBool CSSParserImpl::ParseOneFamily(nsresult& aErrorCode, nsAString& aFamily)
+{
+  if (!GetToken(aErrorCode, PR_TRUE))
+    return PR_FALSE;
+
   nsCSSToken* tk = &mToken;
-  nsAutoString family;
-  PRBool firstOne = PR_TRUE;
-  for (;;) {
-    if (!GetToken(aErrorCode, PR_TRUE)) {
-      break;
-    }
-    if (eCSSToken_Ident == tk->mType) {
-      if (firstOne) {
-        nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent);
-        if (keyword == eCSSKeyword_inherit) {
-          aValue.SetInheritValue();
-          return PR_TRUE;
-        }
-        if (keyword == eCSSKeyword__moz_initial) {
-          aValue.SetInitialValue();
-          return PR_TRUE;
-        }
-        if (keyword == eCSSKeyword__moz_use_system_font &&
-            !IsParsingCompoundProperty()) {
-          aValue.SetSystemFontValue();
-          return PR_TRUE;
-        }
-      }
-      else {
-        family.Append(PRUnichar(','));
-      }
-      family.Append(tk->mIdent);
-      for (;;) {
-        if (!GetToken(aErrorCode, PR_FALSE)) {
+
+  if (eCSSToken_Ident == tk->mType) {
+    aFamily.Append(tk->mIdent);
+    for (;;) {
+      if (!GetToken(aErrorCode, PR_FALSE))
+        break;
+
+      if (eCSSToken_Ident == tk->mType) {
+        aFamily.Append(tk->mIdent);
+      } else if (eCSSToken_WhiteSpace == tk->mType) {
+        // Lookahead one token and drop whitespace if we are ending the
+        // font name.
+        if (!GetToken(aErrorCode, PR_TRUE))
           break;
-        }
-        if (eCSSToken_Ident == tk->mType) {
-          family.Append(tk->mIdent);
-        } else if (eCSSToken_WhiteSpace == tk->mType) {
-          // Lookahead one token and drop whitespace if we ending the
-          // font name.
-          if (!GetToken(aErrorCode, PR_TRUE)) {
-            break;
-          }
-          if (eCSSToken_Ident != tk->mType) {
-            UngetToken();
-            break;
-          }
-          UngetToken();
-          family.Append(PRUnichar(' '));
-        } else {
-          UngetToken();
+
+        UngetToken();
+        if (eCSSToken_Ident == tk->mType)
+          aFamily.Append(PRUnichar(' '));
+        else
           break;
-        }
-      }
-      firstOne = PR_FALSE;
-    } else if (eCSSToken_String == tk->mType) {
-      if (!firstOne) {
-        family.Append(PRUnichar(','));
-      }
-      family.Append(tk->mSymbol); // replace the quotes
-      family.Append(tk->mIdent); // XXX What if it had escaped quotes?
-      family.Append(tk->mSymbol);
-      firstOne = PR_FALSE;
-    } else if (eCSSToken_Symbol == tk->mType) {
-      if (',' != tk->mSymbol) {
+      } else {
         UngetToken();
         break;
       }
-    } else {
-      UngetToken();
+    }
+    return PR_TRUE;
+
+  } else if (eCSSToken_String == tk->mType) {
+    aFamily.Append(tk->mSymbol); // replace the quotes
+    aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes?
+    aFamily.Append(tk->mSymbol);
+    return PR_TRUE;
+
+  } else {
+    UngetToken();
+    return PR_FALSE;
+  }
+}
+
+PRBool CSSParserImpl::ParseFamily(nsresult& aErrorCode, nsCSSValue& aValue)
+{
+  if (!GetToken(aErrorCode, PR_TRUE))
+    return PR_FALSE;
+
+  if (eCSSToken_Ident == mToken.mType) {
+    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+    if (keyword == eCSSKeyword_inherit) {
+      aValue.SetInheritValue();
+      return PR_TRUE;
+    }
+    if (keyword == eCSSKeyword__moz_initial) {
+      aValue.SetInitialValue();
+      return PR_TRUE;
+    }
+    if (keyword == eCSSKeyword__moz_use_system_font &&
+        !IsParsingCompoundProperty()) {
+      aValue.SetSystemFontValue();
+      return PR_TRUE;
+    }
+  }
+
+  UngetToken();
+
+  nsAutoString family;
+  for (;;) {
+    if (!ParseOneFamily(aErrorCode, family))
+      return PR_FALSE;
+
+    if (!ExpectSymbol(aErrorCode, ',', PR_TRUE))
       break;
-    }
-  }
+
+    family.Append(PRUnichar(','));
+  }
+    
   if (family.IsEmpty()) {
     return PR_FALSE;
   }
   aValue.SetStringValue(family, eCSSUnit_String);
   return PR_TRUE;
 }
 
+// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
+// uri-src: uri [ 'format(' string ( ',' string )* ')' ]
+// local-src: 'local(' ( string | ident ) ')'
+
+PRBool
+CSSParserImpl::ParseFontSrc(nsresult& aErrorCode, nsCSSValue& aValue)
+{
+  // could we maybe turn nsCSSValue::Array into nsTArray<nsCSSValue>?
+  nsTArray<nsCSSValue> values;
+  nsCSSValue cur;
+  for (;;) {
+    if (!GetToken(aErrorCode, PR_TRUE))
+      break;
+
+    if (mToken.mType == eCSSToken_Function &&
+        mToken.mIdent.LowerCaseEqualsLiteral("url")) {
+      if (!ParseURL(aErrorCode, cur))
+        return PR_FALSE;
+      values.AppendElement(cur);
+      if (!ParseFontSrcFormat(aErrorCode, values))
+        return PR_FALSE;
+
+    } else if (mToken.mType == eCSSToken_Function &&
+               mToken.mIdent.LowerCaseEqualsLiteral("local")) {
+      // css3-fonts does not specify a formal grammar for local().
+      // The text permits both unquoted identifiers and quoted
+      // strings.  We resolve this ambiguity in the spec by
+      // assuming that the appropriate production is a single
+      // <family-name>, possibly surrounded by whitespace.
+
+      nsAutoString family;
+      if (!ExpectSymbol(aErrorCode, '(', PR_FALSE))
+        return PR_FALSE;
+      if (!ParseOneFamily(aErrorCode, family))
+        return PR_FALSE;
+      if (!ExpectSymbol(aErrorCode, ')', PR_TRUE))
+        return PR_FALSE;
+
+      // the style parameters to the nsFont constructor are ignored,
+      // because it's only being used to call EnumerateFamilies
+      nsFont font(family, 0, 0, 0, 0, 0);
+      ExtractFirstFamilyData dat;
+
+      font.EnumerateFamilies(ExtractFirstFamily, (void*) &dat);
+      if (!dat.mGood)
+        return PR_FALSE;
+
+      cur.SetStringValue(dat.mFamilyName, eCSSUnit_Local_Font);
+      values.AppendElement(cur);
+    } else {
+      return PR_FALSE;
+    }
+
+    if (!ExpectSymbol(aErrorCode, ',', PR_TRUE))
+      break;
+  }
+
+  nsRefPtr<nsCSSValue::Array> srcVals
+    = nsCSSValue::Array::Create(values.Length());
+  if (!srcVals)
+    return PR_FALSE;
+
+  PRUint32 i;
+  for (i = 0; i < values.Length(); i++)
+    srcVals->Item(i) = values[i];
+  aValue.SetArrayValue(srcVals, eCSSUnit_Array);
+  return PR_TRUE;
+}
+
+PRBool
+CSSParserImpl::ParseFontSrcFormat(nsresult& aErrorCode,
+                                  nsTArray<nsCSSValue> & values)
+{
+  if (!GetToken(aErrorCode, PR_TRUE))
+    return PR_TRUE; // EOF harmless here
+  if (mToken.mType != eCSSToken_Function ||
+      !mToken.mIdent.LowerCaseEqualsLiteral("format")) {
+    UngetToken();
+    return PR_TRUE;
+  }
+  if (!ExpectSymbol(aErrorCode, '(', PR_FALSE))
+    return PR_FALSE;
+
+  do {
+    if (!GetToken(aErrorCode, PR_TRUE))
+      return PR_FALSE;
+
+    if (mToken.mType != eCSSToken_String)
+      return PR_FALSE;
+
+    nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format);
+    values.AppendElement(cur);
+  } while (ExpectSymbol(aErrorCode, ',', PR_TRUE));
+
+  return ExpectSymbol(aErrorCode, ')', PR_TRUE);
+}
+
+// font-ranges: urange ( ',' urange )*
+PRBool
+CSSParserImpl::ParseFontRanges(nsresult& aErrorCode, nsCSSValue& aValue)
+{
+  // not currently implemented (bug 443976)
+  return PR_FALSE;
+}
+
 PRBool CSSParserImpl::ParseListStyle(nsresult& aErrorCode)
 {
   const PRInt32 numProps = 3;
   static const nsCSSProperty listStyleIDs[] = {
     eCSSProperty_list_style_type,
     eCSSProperty_list_style_position,
     eCSSProperty_list_style_image
   };
--- a/layout/style/nsCSSProperty.h
+++ b/layout/style/nsCSSProperty.h
@@ -70,9 +70,24 @@ enum nsCSSProperty {
 enum nsCSSType {
   eCSSType_Value,
   eCSSType_Rect,
   eCSSType_ValuePair,
   eCSSType_ValueList,
   eCSSType_ValuePairList
 };
 
+// The "descriptors" that can appear in a @font-face rule.
+// They have the syntax of properties but different value rules.
+// Keep in sync with kCSSRawFontDescs in nsCSSProps.cpp and
+// nsCSSFontFaceStyleDecl::Fields in nsCSSRules.cpp.
+enum nsCSSFontDesc {
+  eCSSFontDesc_UNKNOWN = -1,
+  eCSSFontDesc_Family,
+  eCSSFontDesc_Style,
+  eCSSFontDesc_Weight,
+  eCSSFontDesc_Stretch,
+  eCSSFontDesc_Src,
+  eCSSFontDesc_UnicodeRange,
+  eCSSFontDesc_COUNT
+};
+
 #endif /* nsCSSProperty_h___ */
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -63,49 +63,83 @@ const char* const kCSSRawProperties[] = 
 #define CSS_PROP_SHORTHAND(name_, id_, method_) #name_,
 #include "nsCSSPropList.h"
 #undef CSS_PROP_SHORTHAND
 };
 
 
 static PRInt32 gTableRefCount;
 static nsStaticCaseInsensitiveNameTable* gPropertyTable;
+static nsStaticCaseInsensitiveNameTable* gFontDescTable;
+
+// Keep in sync with enum nsCSSFontDesc in nsCSSProperty.h.
+static const char* const kCSSRawFontDescs[] = {
+  "font-family",
+  "font-style",
+  "font-weight",
+  "font-stretch",
+  "src",
+  "unicode-range"
+};
 
 void
 nsCSSProps::AddRefTable(void) 
 {
   if (0 == gTableRefCount++) {
     NS_ASSERTION(!gPropertyTable, "pre existing array!");
+    NS_ASSERTION(!gFontDescTable, "pre existing array!");
+
     gPropertyTable = new nsStaticCaseInsensitiveNameTable();
     if (gPropertyTable) {
 #ifdef DEBUG
     {
       // let's verify the table...
       for (PRInt32 index = 0; index < eCSSProperty_COUNT; ++index) {
         nsCAutoString temp1(kCSSRawProperties[index]);
         nsCAutoString temp2(kCSSRawProperties[index]);
         ToLowerCase(temp1);
-        NS_ASSERTION(temp1.Equals(temp2), "upper case char in table");
-        NS_ASSERTION(-1 == temp1.FindChar('_'), "underscore char in table");
+        NS_ASSERTION(temp1.Equals(temp2), "upper case char in prop table");
+        NS_ASSERTION(-1 == temp1.FindChar('_'), "underscore char in prop table");
       }
     }
 #endif      
-      gPropertyTable->Init(kCSSRawProperties, eCSSProperty_COUNT); 
+      gPropertyTable->Init(kCSSRawProperties, eCSSProperty_COUNT);
+    }
+
+    gFontDescTable = new nsStaticCaseInsensitiveNameTable();
+    if (gFontDescTable) {
+#ifdef DEBUG
+    {
+      // let's verify the table...
+      for (PRInt32 index = 0; index < eCSSFontDesc_COUNT; ++index) {
+        nsCAutoString temp1(kCSSRawFontDescs[index]);
+        nsCAutoString temp2(kCSSRawFontDescs[index]);
+        ToLowerCase(temp1);
+        NS_ASSERTION(temp1.Equals(temp2), "upper case char in desc table");
+        NS_ASSERTION(-1 == temp1.FindChar('_'), "underscore char in desc table");
+      }
+    }
+#endif      
+      gFontDescTable->Init(kCSSRawFontDescs, eCSSFontDesc_COUNT); 
     }
   }
 }
 
 void
 nsCSSProps::ReleaseTable(void) 
 {
   if (0 == --gTableRefCount) {
     if (gPropertyTable) {
       delete gPropertyTable;
       gPropertyTable = nsnull;
     }
+    if (gFontDescTable) {
+      delete gFontDescTable;
+      gFontDescTable = nsnull;
+    }
   }
 }
 
 struct CSSPropertyAlias {
   char name[sizeof("-moz-outline-offset")];
   nsCSSProperty id;
 };
 
@@ -155,28 +189,54 @@ nsCSSProps::LookupProperty(const nsAStri
       if (nsCRT::strcasecmp(prop.get(), alias->name) == 0) {
         res = alias->id;
         break;
       }
   }
   return res;
 }
 
+nsCSSFontDesc 
+nsCSSProps::LookupFontDesc(const nsACString& aFontDesc)
+{
+  NS_ASSERTION(gFontDescTable, "no lookup table, needs addref");
+  return nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
+}
+
+nsCSSFontDesc 
+nsCSSProps::LookupFontDesc(const nsAString& aFontDesc)
+{
+  NS_ASSERTION(gFontDescTable, "no lookup table, needs addref");
+  return nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
+}
+
 const nsAFlatCString& 
 nsCSSProps::GetStringValue(nsCSSProperty aProperty)
 {
   NS_ASSERTION(gPropertyTable, "no lookup table, needs addref");
   if (gPropertyTable) {
     return gPropertyTable->GetStringValue(PRInt32(aProperty));
   } else {
     static nsDependentCString sNullStr("");
     return sNullStr;
   }
 }
 
+const nsAFlatCString& 
+nsCSSProps::GetStringValue(nsCSSFontDesc aFontDescID)
+{
+  NS_ASSERTION(gFontDescTable, "no lookup table, needs addref");
+  if (gFontDescTable) {
+    return gFontDescTable->GetStringValue(PRInt32(aFontDescID));
+  } else {
+    static nsDependentCString sNullStr("");
+    return sNullStr;
+  }
+}
+
 
 /***************************************************************************/
 
 const PRInt32 nsCSSProps::kAppearanceKTable[] = {
   eCSSKeyword_none,                   NS_THEME_NONE,
   eCSSKeyword_button,                 NS_THEME_BUTTON,
   eCSSKeyword_radio,                  NS_THEME_RADIO,
   eCSSKeyword_checkbox,               NS_THEME_CHECKBOX,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -60,18 +60,23 @@ public:
   static nsCSSProperty LookupProperty(const nsACString& aProperty);
 
   static inline PRBool IsShorthand(nsCSSProperty aProperty) {
     NS_ASSERTION(0 <= aProperty && aProperty < eCSSProperty_COUNT,
                  "out of range");
     return (aProperty >= eCSSProperty_COUNT_no_shorthands);
   }
 
+  // Same but for @font-face descriptors
+  static nsCSSFontDesc LookupFontDesc(const nsAString& aProperty);
+  static nsCSSFontDesc LookupFontDesc(const nsACString& aProperty);
+
   // Given a property enum, get the string value
   static const nsAFlatCString& GetStringValue(nsCSSProperty aProperty);
+  static const nsAFlatCString& GetStringValue(nsCSSFontDesc aFontDesc);
 
   // Given a CSS Property and a Property Enum Value
   // Return back a const nsString& representation of the 
   // value. Return back nullstr if no value is found
   static const nsAFlatCString& LookupPropertyValue(nsCSSProperty aProperty, PRInt32 aValue);
 
   // Get a color name for a predefined color value like buttonhighlight or activeborder
   // Sets the aStr param to the name of the propertyID
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -34,43 +34,48 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* rules in a CSS stylesheet other than style rules (e.g., @import rules) */
 
 #include "nsCSSRules.h"
+#include "nsCSSValue.h"
 #include "nsICSSImportRule.h"
 #include "nsICSSNameSpaceRule.h"
 
 #include "nsString.h"
 #include "nsIAtom.h"
 #include "nsIURL.h"
 
 #include "nsCSSRule.h"
+#include "nsCSSProps.h"
 #include "nsICSSStyleSheet.h"
 
 #include "nsCOMPtr.h"
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsIDOMCSSRule.h"
 #include "nsIDOMCSSImportRule.h"
 #include "nsIDOMCSSMediaRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
 #include "nsIDOMCSSCharsetRule.h"
+#include "nsIDOMCSSStyleDeclaration.h"
 #include "nsIMediaList.h"
 #include "nsIDOMMediaList.h"
 #include "nsIDOMCSSRuleList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 
 #include "nsContentUtils.h"
 #include "nsStyleConsts.h"
 #include "nsDOMError.h"
+#include "nsStyleUtil.h"
+#include "nsCSSDeclaration.h"
 
 #define IMPL_STYLE_RULE_INHERIT(_class, super) \
 NS_IMETHODIMP _class::GetStyleSheet(nsIStyleSheet*& aSheet) const { return super::GetStyleSheet(aSheet); }  \
 NS_IMETHODIMP _class::SetStyleSheet(nsICSSStyleSheet* aSheet) { return super::SetStyleSheet(aSheet); }  \
 NS_IMETHODIMP _class::SetParentRule(nsICSSGroupRule* aRule) { return super::SetParentRule(aRule); }  \
 NS_IMETHODIMP _class::GetDOMRule(nsIDOMCSSRule** aDOMRule) { return CallQueryInterface(this, aDOMRule); }  \
 NS_IMETHODIMP _class::MapRuleInfoInto(nsRuleData* aRuleData) { return NS_OK; } 
 
@@ -1008,17 +1013,17 @@ NS_IMETHODIMP
 nsCSSMediaRule::GetMedia(nsIDOMMediaList* *aMedia)
 {
   NS_ENSURE_ARG_POINTER(aMedia);
   if (!mMedia) {
     *aMedia = nsnull;
     return NS_OK;
   }
 
-  return CallQueryInterface(mMedia, aMedia);
+  return CallQueryInterface(mMedia.get(), aMedia);
 }
 
 NS_IMETHODIMP
 nsCSSMediaRule::GetCssRules(nsIDOMCSSRuleList* *aRuleList)
 {
   return nsCSSGroupRule::GetCssRules(aRuleList);
 }
 
@@ -1446,8 +1451,429 @@ NS_IMETHODIMP
 CSSNameSpaceRuleImpl::GetParentRule(nsIDOMCSSRule** aParentRule)
 {
   if (mParentRule) {
     return mParentRule->GetDOMRule(aParentRule);
   }
   *aParentRule = nsnull;
   return NS_OK;
 }
+
+// -------------------------------------------
+// nsCSSFontFaceStyleDecl and related routines
+//
+
+// A src: descriptor is represented as an array value; each entry in
+// the array can be eCSSUnit_URL, eCSSUnit_Local_Font, or
+// eCSSUnit_Font_Format.  Blocks of eCSSUnit_Font_Format may appear
+// only after one of the first two.  (css3-fonts only contemplates
+// annotating URLs with formats, but we handle the general case.)
+static void
+SerializeFontSrc(const nsCSSValue& src, nsAString & aResult NS_OUTPARAM)
+{
+  NS_PRECONDITION(src.GetUnit() == eCSSUnit_Null ||
+                  src.GetUnit() == eCSSUnit_Array,
+                  "improper value unit for src:");
+  aResult.Truncate();
+  if (src.GetUnit() != eCSSUnit_Array)
+    return;
+
+  const nsCSSValue::Array& sources = *src.GetArrayValue();
+  PRUint32 i = 0;
+
+  while (i < sources.Count()) {
+    nsAutoString formats;
+
+    if (sources[i].GetUnit() == eCSSUnit_URL) {
+      nsDependentString url(sources[i].GetOriginalURLValue());
+      nsAutoString escapedUrl;
+      nsStyleUtil::EscapeCSSString(url, escapedUrl);
+      aResult.AppendLiteral("url(\"");
+      aResult.Append(escapedUrl);
+      aResult.AppendLiteral("\")");
+    } else if (sources[i].GetUnit() == eCSSUnit_Local_Font) {
+      nsDependentString local(sources[i].GetStringBufferValue());
+      nsAutoString escapedLocal;
+      nsStyleUtil::EscapeCSSString(local, escapedLocal);
+      aResult.AppendLiteral("local(\"");
+      aResult.Append(escapedLocal);
+      aResult.AppendLiteral("\")");
+    } else {
+      NS_NOTREACHED("entry in src: descriptor with improper unit");
+      i++;
+      continue;
+    }
+
+    i++;
+    formats.Truncate();
+    while (i < sources.Count() &&
+           sources[i].GetUnit() == eCSSUnit_Font_Format) {
+      formats.Append('"');
+      formats.Append(sources[i].GetStringBufferValue());
+      formats.AppendLiteral("\", ");
+      i++;
+    }
+    if (formats.Length() > 0) {
+      formats.Truncate(formats.Length() - 2); // remove the last comma
+      aResult.AppendLiteral(" format(");
+      aResult.Append(formats);
+      aResult.Append(')');
+    }
+    aResult.AppendLiteral(", ");
+  }
+  aResult.Truncate(aResult.Length() - 2); // remove the last comma-space
+}
+
+// Mapping from nsCSSFontDesc codes to nsCSSFontFaceStyleDecl fields.
+// Keep this in sync with enum nsCSSFontDesc in nsCSSProperty.h.
+nsCSSValue nsCSSFontFaceStyleDecl::* const
+nsCSSFontFaceStyleDecl::Fields[] = {
+    &nsCSSFontFaceStyleDecl::mFamily,
+    &nsCSSFontFaceStyleDecl::mStyle,
+    &nsCSSFontFaceStyleDecl::mWeight,
+    &nsCSSFontFaceStyleDecl::mStretch,
+    &nsCSSFontFaceStyleDecl::mSrc,
+    &nsCSSFontFaceStyleDecl::mUnicodeRange
+};
+
+// QueryInterface implementation for nsCSSFontFaceStyleDecl
+NS_INTERFACE_MAP_BEGIN(nsCSSFontFaceStyleDecl)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleDeclaration)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CSSFontFaceStyleDecl)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(nsCSSFontFaceStyleDecl, ContainingRule())
+NS_IMPL_RELEASE_USING_AGGREGATOR(nsCSSFontFaceStyleDecl, ContainingRule())
+
+// helper for string GetPropertyValue and RemovePropertyValue
+nsresult
+nsCSSFontFaceStyleDecl::GetPropertyValue(nsCSSFontDesc aFontDescID,
+                                         nsAString & aResult NS_OUTPARAM) const
+{
+  NS_ENSURE_ARG_RANGE(aFontDescID, eCSSFontDesc_UNKNOWN,
+                      eCSSFontDesc_COUNT - 1);
+
+  aResult.Truncate();
+  if (aFontDescID == eCSSFontDesc_UNKNOWN)
+    return NS_OK;
+
+  const nsCSSValue& val = this->*nsCSSFontFaceStyleDecl::Fields[aFontDescID];
+
+  switch (aFontDescID) {
+  case eCSSFontDesc_Family: {
+      // we don't use AppendCSSValueToString here because it doesn't
+      // canonicalize the way we want, and anyway it's overkill when
+      // we know we have eCSSUnit_String
+      nsDependentString family(val.GetStringBufferValue());
+      nsAutoString escapedFamily;
+      nsStyleUtil::EscapeCSSString(family, escapedFamily);
+      aResult.Append('"');
+      aResult.Append(escapedFamily);
+      aResult.Append('"');
+      return NS_OK;
+    }
+
+  case eCSSFontDesc_Style:
+    nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_font_style, val,
+                                             aResult);
+    return NS_OK;
+
+  case eCSSFontDesc_Weight:
+    nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_font_weight, val,
+                                             aResult);
+    return NS_OK;
+    
+  case eCSSFontDesc_Stretch:
+    nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_font_stretch, val,
+                                             aResult);
+    return NS_OK;
+
+  case eCSSFontDesc_Src:
+    SerializeFontSrc(val, aResult);
+    return NS_OK;
+
+  case eCSSFontDesc_UnicodeRange:
+    // these are not implemented, so always return an empty string
+    return NS_OK;
+
+  case eCSSFontDesc_UNKNOWN:
+  case eCSSFontDesc_COUNT:
+    ;
+  }
+  NS_NOTREACHED("nsCSSFontFaceStyleDecl::GetPropertyValue: "
+                "out-of-range value got to the switch");
+  return NS_ERROR_INVALID_ARG;
+}
+
+
+// attribute DOMString cssText;
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetCssText(nsAString & aCssText)
+{
+  nsAutoString descStr;
+
+  aCssText.Truncate();
+  for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+       id < eCSSFontDesc_COUNT;
+       id = nsCSSFontDesc(id + 1)) {
+    if ((this->*nsCSSFontFaceStyleDecl::Fields[id]).GetUnit()
+          != eCSSUnit_Null &&
+        NS_SUCCEEDED(GetPropertyValue(id, descStr))) {
+      NS_ASSERTION(descStr.Length() > 0,
+                   "GetCssText: non-null unit, empty property value");
+      aCssText.AppendLiteral("  ");
+      aCssText.AppendASCII(nsCSSProps::GetStringValue(id).get());
+      aCssText.AppendLiteral(": ");
+      aCssText.Append(descStr);
+      aCssText.AppendLiteral(";\n");
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::SetCssText(const nsAString & aCssText)
+{
+  return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+// DOMString getPropertyValue (in DOMString propertyName);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyValue(const nsAString & propertyName,
+                                         nsAString & aResult NS_OUTPARAM)
+{
+  return GetPropertyValue(nsCSSProps::LookupFontDesc(propertyName), aResult);
+}
+
+// nsIDOMCSSValue getPropertyCSSValue (in DOMString propertyName);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyCSSValue(const nsAString & propertyName,
+                                            nsIDOMCSSValue **aResult NS_OUTPARAM)
+{
+  // ??? nsDOMCSSDeclaration returns null/NS_OK, but that seems wrong.
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// DOMString removeProperty (in DOMString propertyName) raises (DOMException);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::RemoveProperty(const nsAString & propertyName,
+                                       nsAString & aResult NS_OUTPARAM)
+{
+  nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(propertyName);
+  NS_ASSERTION(descID >= eCSSFontDesc_UNKNOWN &&
+               descID < eCSSFontDesc_COUNT,
+               "LookupFontDesc returned value out of range");
+
+  if (descID == eCSSFontDesc_UNKNOWN) {
+    aResult.Truncate();
+  } else {
+    nsresult rv = GetPropertyValue(descID, aResult);
+    NS_ENSURE_SUCCESS(rv, rv);
+    (this->*nsCSSFontFaceStyleDecl::Fields[descID]).Reset();
+  }
+  return NS_OK;
+}
+
+// DOMString getPropertyPriority (in DOMString propertyName);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetPropertyPriority(const nsAString & propertyName,
+                                            nsAString & aResult NS_OUTPARAM)
+{
+  // font descriptors do not have priorities at present
+  aResult.Truncate();
+  return NS_OK;
+}
+
+// void setProperty (in DOMString propertyName, in DOMString value,
+//                   in DOMString priority)  raises (DOMException);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::SetProperty(const nsAString & propertyName,
+                                    const nsAString & value,
+                                    const nsAString & priority)
+{
+  return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+// readonly attribute unsigned long length;
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetLength(PRUint32 *aLength)
+{
+  PRUint32 len = 0;
+  for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+       id < eCSSFontDesc_COUNT;
+       id = nsCSSFontDesc(id + 1))
+    if ((this->*nsCSSFontFaceStyleDecl::Fields[id]).GetUnit() != eCSSUnit_Null)
+      len++;
+
+  *aLength = len;
+  return NS_OK;
+}
+
+// DOMString item (in unsigned long index);
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::Item(PRUint32 index, nsAString & aResult NS_OUTPARAM)
+ {
+  PRInt32 nset = -1;
+  for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+       id < eCSSFontDesc_COUNT;
+       id = nsCSSFontDesc(id + 1)) {
+    if ((this->*nsCSSFontFaceStyleDecl::Fields[id]).GetUnit()
+        != eCSSUnit_Null) {
+      nset++;
+      if (nset == PRInt32(index)) {
+        aResult.AssignASCII(nsCSSProps::GetStringValue(id).get());
+        return NS_OK;
+      }
+    }
+  }
+  aResult.Truncate();
+  return NS_OK;
+}
+
+// readonly attribute nsIDOMCSSRule parentRule;
+NS_IMETHODIMP
+nsCSSFontFaceStyleDecl::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+  return ContainingRule()->GetDOMRule(aParentRule);
+}
+
+
+// -------------------------------------------
+// nsCSSFontFaceRule
+// 
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::Clone(nsICSSRule*& aClone) const
+{
+  nsCSSFontFaceRule* clone = new nsCSSFontFaceRule(*this);
+  if (clone) {
+    return CallQueryInterface(clone, &aClone);
+  }
+  aClone = nsnull;
+  return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsCSSFontFaceRule, nsCSSRule)
+NS_IMPL_RELEASE_INHERITED(nsCSSFontFaceRule, nsCSSRule)
+
+// QueryInterface implementation for nsCSSFontFaceRule
+NS_INTERFACE_MAP_BEGIN(nsCSSFontFaceRule)
+  NS_INTERFACE_MAP_ENTRY(nsICSSRule)
+  NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSFontFaceRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICSSRule)
+  NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CSSFontFaceRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT(nsCSSFontFaceRule, nsCSSRule)
+
+#ifdef DEBUG
+NS_IMETHODIMP
+nsCSSFontFaceRule::List(FILE* out, PRInt32 aIndent) const
+{
+  nsCString baseInd, descInd;
+  for (PRInt32 indent = aIndent; --indent >= 0; ) {
+    baseInd.AppendLiteral("  ");
+    descInd.AppendLiteral("  ");
+  }
+  descInd.AppendLiteral("  ");
+
+  nsString descStr;
+
+  fprintf(out, "%s@font-face {\n", baseInd.get());
+  for (nsCSSFontDesc id = nsCSSFontDesc(eCSSFontDesc_UNKNOWN + 1);
+       id < eCSSFontDesc_COUNT;
+       id = nsCSSFontDesc(id + 1))
+    if ((mDecl.*nsCSSFontFaceStyleDecl::Fields[id]).GetUnit()
+        != eCSSUnit_Null) {
+      if (NS_FAILED(mDecl.GetPropertyValue(id, descStr)))
+        descStr.AssignLiteral("#<serialization error>");
+      else if (descStr.Length() == 0)
+        descStr.AssignLiteral("#<serialization missing>");
+      fprintf(out, "%s%s: %s\n",
+              descInd.get(), nsCSSProps::GetStringValue(id).get(),
+              NS_ConvertUTF16toUTF8(descStr).get());
+    }
+  fprintf(out, "%s}\n", baseInd.get());
+  return NS_OK;
+}
+#endif
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetType(PRInt32& aType) const
+{
+  aType = nsICSSRule::FONT_FACE_RULE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetType(PRUint16* aType)
+{
+  *aType = nsIDOMCSSRule::FONT_FACE_RULE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetCssText(nsAString& aCssText)
+{
+  nsAutoString propText;
+  mDecl.GetCssText(propText);
+
+  aCssText.AssignLiteral("@font-face {\n");
+  aCssText.Append(propText);
+  aCssText.Append('}');
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::SetCssText(const nsAString& aCssText)
+{
+  return NS_ERROR_NOT_IMPLEMENTED; // bug 443978
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+  if (mSheet) {
+    return CallQueryInterface(mSheet, aSheet);
+  }
+  *aSheet = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+  if (mParentRule) {
+    return mParentRule->GetDOMRule(aParentRule);
+  }
+  *aParentRule = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSFontFaceRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+  return CallQueryInterface(&mDecl, aStyle);
+}
+
+// Arguably these should forward to nsCSSFontFaceStyleDecl methods.
+void
+nsCSSFontFaceRule::SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue)
+{
+  NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
+                  aDescID < eCSSFontDesc_COUNT,
+                  "aDescID out of range in nsCSSFontFaceRule::SetDesc");
+
+  mDecl.*nsCSSFontFaceStyleDecl::Fields[aDescID] = aValue;
+}
+
+void
+nsCSSFontFaceRule::GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue)
+{
+  NS_PRECONDITION(aDescID > eCSSFontDesc_UNKNOWN &&
+                  aDescID < eCSSFontDesc_COUNT,
+                  "aDescID out of range in nsCSSFontFaceRule::GetDesc");
+
+  aValue = mDecl.*nsCSSFontFaceStyleDecl::Fields[aDescID];
+}
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -40,22 +40,23 @@
 
 /* rules in a CSS stylesheet other than style rules (e.g., @import rules) */
 
 #ifndef nsCSSRules_h_
 #define nsCSSRules_h_
 
 #include "nsCSSRule.h"
 #include "nsICSSGroupRule.h"
-#include "nsCOMPtr.h"
-#include "nsAutoPtr.h"
-#include "nsCOMArray.h"
 #include "nsIDOMCSSMediaRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
-#include "nsString.h"
+#include "nsIDOMCSSFontFaceRule.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsAutoPtr.h"
+#include "nsCSSProperty.h"
+#include "nsCSSValue.h"
 
 class CSSGroupRuleRuleListImpl;
 class nsMediaList;
 
 #define DECL_STYLE_RULE_INHERIT  \
 NS_IMETHOD GetStyleSheet(nsIStyleSheet*& aSheet) const; \
 NS_IMETHOD SetStyleSheet(nsICSSStyleSheet* aSheet); \
 NS_IMETHOD SetParentRule(nsICSSGroupRule* aRule); \
@@ -137,17 +138,17 @@ public:
   // rest of nsICSSGroupRule interface
   NS_IMETHOD_(PRBool) UseForPresentation(nsPresContext* aPresContext,
                                          nsMediaQueryResultCacheKey& aKey);
 
   // @media rule methods
   nsresult SetMedia(nsMediaList* aMedia);
   
 protected:
-  nsCOMPtr<nsMediaList> mMedia;
+  nsRefPtr<nsMediaList> mMedia;
 };
 
 class nsCSSDocumentRule : public nsCSSGroupRule,
                           public nsIDOMCSSMozDocumentRule
 {
 public:
   nsCSSDocumentRule(void);
   nsCSSDocumentRule(const nsCSSDocumentRule& aCopy);
@@ -196,10 +197,86 @@ public:
   };
 
   void SetURLs(URL *aURLs) { mURLs = aURLs; }
 
 protected:
   nsAutoPtr<URL> mURLs; // linked list of |struct URL| above.
 };
 
+// A nsCSSFontFaceStyleDecl is always embedded in a nsCSSFontFaceRule.
+class nsCSSFontFaceRule;
+class nsCSSFontFaceStyleDecl : public nsIDOMCSSStyleDeclaration
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMCSSSTYLEDECLARATION
+
+  nsresult GetPropertyValue(nsCSSFontDesc aFontDescID,
+                            nsAString & aResult NS_OUTPARAM) const;
+
+protected:
+  friend class nsCSSFontFaceRule;
+  nsCSSValue mFamily;
+  nsCSSValue mStyle;
+  nsCSSValue mWeight;
+  nsCSSValue mStretch;
+  nsCSSValue mSrc;
+  nsCSSValue mUnicodeRange;
+
+  static nsCSSValue nsCSSFontFaceStyleDecl::* const Fields[];  
+  inline nsCSSFontFaceRule* ContainingRule();
+  inline const nsCSSFontFaceRule* ContainingRule() const;
+
+private:
+  // NOT TO BE IMPLEMENTED
+  // This object cannot be allocated on its own, only as part of
+  // nsCSSFontFaceRule.
+  void* operator new(size_t size) CPP_THROW_NEW;
+};
+
+class nsCSSFontFaceRule : public nsCSSRule,
+                          public nsICSSRule,
+                          public nsIDOMCSSFontFaceRule
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // nsIStyleRule methods
+#ifdef DEBUG
+  NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;
+#endif
+
+  // nsICSSRule methods
+  DECL_STYLE_RULE_INHERIT
+
+  NS_IMETHOD GetType(PRInt32& aType) const;
+  NS_IMETHOD Clone(nsICSSRule*& aClone) const;
+
+  // nsIDOMCSSRule interface
+  NS_DECL_NSIDOMCSSRULE
+
+  // nsIDOMCSSFontFaceRule interface
+  NS_DECL_NSIDOMCSSFONTFACERULE
+
+  void SetDesc(nsCSSFontDesc aDescID, nsCSSValue const & aValue);
+  void GetDesc(nsCSSFontDesc aDescID, nsCSSValue & aValue);
+
+protected:
+  friend class nsCSSFontFaceStyleDecl;
+  nsCSSFontFaceStyleDecl mDecl;
+};
+
+inline nsCSSFontFaceRule*
+nsCSSFontFaceStyleDecl::ContainingRule()
+{
+  return reinterpret_cast<nsCSSFontFaceRule*>
+    (reinterpret_cast<char*>(this) - offsetof(nsCSSFontFaceRule, mDecl));
+}
+
+inline const nsCSSFontFaceRule*
+nsCSSFontFaceStyleDecl::ContainingRule() const
+{
+  return reinterpret_cast<const nsCSSFontFaceRule*>
+    (reinterpret_cast<const char*>(this) - offsetof(nsCSSFontFaceRule, mDecl));
+}
 
 #endif /* !defined(nsCSSRules_h_) */
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -75,18 +75,18 @@ nsCSSValue::nsCSSValue(float aValue, nsC
     mUnit = eCSSUnit_Null;
     mValue.mInt = 0;
   }
 }
 
 nsCSSValue::nsCSSValue(const nsString& aValue, nsCSSUnit aUnit)
   : mUnit(aUnit)
 {
-  NS_ASSERTION((eCSSUnit_String <= aUnit) && (aUnit <= eCSSUnit_Attr), "not a string value");
-  if ((eCSSUnit_String <= aUnit) && (aUnit <= eCSSUnit_Attr)) {
+  NS_ASSERTION(UnitHasStringValue(), "not a string value");
+  if (UnitHasStringValue()) {
     mValue.mString = BufferFromString(aValue);
     if (NS_UNLIKELY(!mValue.mString)) {
       // XXXbz not much we can do here; just make sure that our promise of a
       // non-null mValue.mString holds for string units.
       mUnit = eCSSUnit_Null;
     }
   }
   else {
@@ -128,17 +128,17 @@ nsCSSValue::nsCSSValue(const nsCSSValue&
   : mUnit(aCopy.mUnit)
 {
   if (mUnit <= eCSSUnit_Dummy) {
     // nothing to do, but put this important case first
   }
   else if (eCSSUnit_Percent <= mUnit) {
     mValue.mFloat = aCopy.mValue.mFloat;
   }
-  else if (eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Attr) {
+  else if (UnitHasStringValue()) {
     mValue.mString = aCopy.mValue.mString;
     mValue.mString->AddRef();
   }
   else if (eCSSUnit_Integer <= mUnit && mUnit <= eCSSUnit_EnumColor) {
     mValue.mInt = aCopy.mValue.mInt;
   }
   else if (eCSSUnit_Color == mUnit) {
     mValue.mColor = aCopy.mValue.mColor;
@@ -170,17 +170,17 @@ nsCSSValue& nsCSSValue::operator=(const 
 }
 
 PRBool nsCSSValue::operator==(const nsCSSValue& aOther) const
 {
   if (mUnit == aOther.mUnit) {
     if (mUnit <= eCSSUnit_Dummy) {
       return PR_TRUE;
     }
-    else if ((eCSSUnit_String <= mUnit) && (mUnit <= eCSSUnit_Attr)) {
+    else if (UnitHasStringValue()) {
       return (NS_strcmp(GetBufferValue(mValue.mString),
                         GetBufferValue(aOther.mValue.mString)) == 0);
     }
     else if ((eCSSUnit_Integer <= mUnit) && (mUnit <= eCSSUnit_EnumColor)) {
       return mValue.mInt == aOther.mValue.mInt;
     }
     else if (eCSSUnit_Color == mUnit) {
       return mValue.mColor == aOther.mValue.mColor;
@@ -242,17 +242,17 @@ nscoord nsCSSValue::GetLengthTwips() con
       break;
     }
   }
   return 0;
 }
 
 void nsCSSValue::DoReset()
 {
-  if (eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Attr) {
+  if (UnitHasStringValue()) {
     mValue.mString->Release();
   } else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Counters) {
     mValue.mArray->Release();
   } else if (eCSSUnit_URL == mUnit) {
     mValue.mURL->Release();
   } else if (eCSSUnit_Image == mUnit) {
     mValue.mImage->Release();
   }
@@ -286,27 +286,28 @@ void nsCSSValue::SetFloatValue(float aVa
     mUnit = aUnit;
     mValue.mFloat = aValue;
   }
 }
 
 void nsCSSValue::SetStringValue(const nsString& aValue,
                                 nsCSSUnit aUnit)
 {
-  NS_ASSERTION((eCSSUnit_String <= aUnit) && (aUnit <= eCSSUnit_Attr), "not a string unit");
   Reset();
-  if ((eCSSUnit_String <= aUnit) && (aUnit <= eCSSUnit_Attr)) {
-    mUnit = aUnit;
+  mUnit = aUnit;
+  NS_ASSERTION(UnitHasStringValue(), "not a string unit");
+  if (UnitHasStringValue()) {
     mValue.mString = BufferFromString(aValue);
     if (NS_UNLIKELY(!mValue.mString)) {
       // XXXbz not much we can do here; just make sure that our promise of a
       // non-null mValue.mString holds for string units.
       mUnit = eCSSUnit_Null;
     }
-  }
+  } else
+    mUnit = eCSSUnit_Null;
 }
 
 void nsCSSValue::SetColorValue(nscolor aValue)
 {
   Reset();
   mUnit = eCSSUnit_Color;
   mValue.mColor = aValue;
 }
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -61,16 +61,18 @@ enum nsCSSUnit {
   eCSSUnit_Initial      = 3,      // (n/a) value is default UA value
   eCSSUnit_None         = 4,      // (n/a) value is none
   eCSSUnit_Normal       = 5,      // (n/a) value is normal (algorithmic, different than auto)
   eCSSUnit_System_Font  = 6,      // (n/a) value is -moz-use-system-font
   eCSSUnit_Dummy        = 7,      // (n/a) a fake but specified value, used
                                   //       only in temporary values
   eCSSUnit_String       = 10,     // (PRUnichar*) a string value
   eCSSUnit_Attr         = 11,     // (PRUnichar*) a attr(string) value
+  eCSSUnit_Local_Font   = 12,     // (PRUnichar*) a local font name
+  eCSSUnit_Font_Format  = 13,     // (PRUnichar*) a font format name
   eCSSUnit_Array        = 20,     // (nsCSSValue::Array*) a list of values
   eCSSUnit_Counter      = 21,     // (nsCSSValue::Array*) a counter(string,[string]) value
   eCSSUnit_Counters     = 22,     // (nsCSSValue::Array*) a counters(string,string[,string]) value
   eCSSUnit_URL          = 30,     // (nsCSSValue::URL*) value
   eCSSUnit_Image        = 31,     // (nsCSSValue::Image*) value
   eCSSUnit_Integer      = 50,     // (int) simple value
   eCSSUnit_Enumerated   = 51,     // (int) value has enumerated meaning
   eCSSUnit_EnumColor    = 80,     // (int) enumerated color (kColorKTable)
@@ -156,27 +158,30 @@ public:
 
   PRBool operator!=(const nsCSSValue& aOther) const
   {
     return !(*this == aOther);
   }
 
   nsCSSUnit GetUnit() const { return mUnit; }
   PRBool    IsLengthUnit() const
-    { return PRBool((eCSSUnit_Inch <= mUnit) && (mUnit <= eCSSUnit_Pixel)); }
+    { return eCSSUnit_Inch <= mUnit && mUnit <= eCSSUnit_Pixel; }
   PRBool    IsFixedLengthUnit() const  
-    { return PRBool((eCSSUnit_Inch <= mUnit) && (mUnit <= eCSSUnit_Cicero)); }
+    { return eCSSUnit_Inch <= mUnit && mUnit <= eCSSUnit_Cicero; }
   PRBool    IsRelativeLengthUnit() const  
-    { return PRBool((eCSSUnit_EM <= mUnit) && (mUnit <= eCSSUnit_Pixel)); }
+    { return eCSSUnit_EM <= mUnit && mUnit <= eCSSUnit_Pixel; }
   PRBool    IsAngularUnit() const  
-    { return PRBool((eCSSUnit_Degree <= mUnit) && (mUnit <= eCSSUnit_Radian)); }
+    { return eCSSUnit_Degree <= mUnit && mUnit <= eCSSUnit_Radian; }
   PRBool    IsFrequencyUnit() const  
-    { return PRBool((eCSSUnit_Hertz <= mUnit) && (mUnit <= eCSSUnit_Kilohertz)); }
+    { return eCSSUnit_Hertz <= mUnit && mUnit <= eCSSUnit_Kilohertz; }
   PRBool    IsTimeUnit() const  
-    { return PRBool((eCSSUnit_Seconds <= mUnit) && (mUnit <= eCSSUnit_Milliseconds)); }
+    { return eCSSUnit_Seconds <= mUnit && mUnit <= eCSSUnit_Milliseconds; }
+
+  PRBool    UnitHasStringValue() const
+    { return eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Font_Format; }
 
   PRInt32 GetIntValue() const
   {
     NS_ASSERTION(mUnit == eCSSUnit_Integer || mUnit == eCSSUnit_Enumerated ||
                  mUnit == eCSSUnit_EnumColor,
                  "not an int value");
     return mValue.mInt;
   }
@@ -190,28 +195,26 @@ public:
   float GetFloatValue() const
   {
     NS_ASSERTION(eCSSUnit_Number <= mUnit, "not a float value");
     return mValue.mFloat;
   }
 
   nsAString& GetStringValue(nsAString& aBuffer) const
   {
-    NS_ASSERTION(eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Attr,
-                 "not a string value");
+    NS_ASSERTION(UnitHasStringValue(), "not a string value");
     aBuffer.Truncate();
     PRUint32 len = NS_strlen(GetBufferValue(mValue.mString));
     mValue.mString->ToString(len, aBuffer);
     return aBuffer;
   }
 
   const PRUnichar* GetStringBufferValue() const
   {
-    NS_ASSERTION(eCSSUnit_String <= mUnit && mUnit <= eCSSUnit_Attr,
-                 "not a string value");
+    NS_ASSERTION(UnitHasStringValue(), "not a string value");
     return GetBufferValue(mValue.mString);
   }
 
   nscolor GetColorValue() const
   {
     NS_ASSERTION((mUnit == eCSSUnit_Color), "not a color value");
     return mValue.mColor;
   }
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -94,16 +94,17 @@ GARBAGE += css_properties.js
 		test_bug401046.html \
 		test_bug405818.html \
 		test_bug412901.html \
 		test_bug437915.html \
 		test_cascade.html \
 		test_compute_data_with_start_struct.html \
 		test_css_eof_handling.html \
 		test_dont_use_document_colors.html \
+		test_font_face_parser.html \
 		test_inherit_computation.html \
 		test_inherit_storage.html \
 		test_initial_computation.html \
 		test_initial_storage.html \
 		test_media_queries.html \
 		test_media_queries_dynamic_xbl.html \
 		test_of_type_selectors.xhtml \
 		test_parse_rule.html \
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_font_face_parser.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML><html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=441469 -->
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8">
+  <title>Test of @font-face parser</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@font-face parsing (<a
+  target="_blank"
+  href="https://bugzilla.mozilla.org/show_bug.cgi?id=441469"
+>bug 441469</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+  function _(b) { return "@font-face { " + b + " }"; };
+  var testset = [
+    // Complete nonsense - shouldn't make a font-face rule at all.
+    { rule: "@font-face;" },
+    { rule: "font-face { }" },
+    { rule: "@fontface { }" },
+    { rule: "@namespace foo url(http://example.com/foo);" },
+
+    // Empty rule.
+    { rule: "@font-face { }",   d: {} },
+    { rule: "@font-face {",     d: {} },
+    { rule: "@font-face { ; }", d: {}, noncanonical: true },
+
+    // Correct font-family.
+    { rule: _("font-family: \"Mouse\";"), d: {"font-family" : "\"Mouse\""} },
+    { rule: _("font-family: \"Mouse\""),  d: {"font-family" : "\"Mouse\""},
+      noncanonical: true },
+    { rule: _("font-family: Mouse;"),  d: {"font-family" : "\"Mouse\"" },
+      noncanonical: true },
+    { rule: _("font-family: Mouse"),  d: {"font-family" : "\"Mouse\"" },
+      noncanonical: true },
+
+    // Correct but unusual font-family.
+    { rule: _("font-family: Hoefler Text;"), 
+      d: {"font-family" : "\"Hoefler Text\""},
+      noncanonical: true },
+
+    // Incorrect font-family.
+    { rule: _("font-family:"),        d: {} },
+    { rule: _("font-family \"Mouse\""), d: {} },
+    { rule: _("font-family: *"),      d: {} },
+    { rule: _("font-family: Mouse, Rat"), d: {} },
+    { rule: _("font-family: sans-serif"), d: {} },
+
+    // Correct font-style.
+    { rule: _("font-style: normal;"),  d: {"font-style" : "normal"} },
+    { rule: _("font-style: italic;"),  d: {"font-style" : "italic"} },
+    { rule: _("font-style: oblique;"),  d: {"font-style" : "oblique"} },
+
+    // Correct font-weight.
+    { rule: _("font-weight: 100;"),  d: {"font-weight" : "100"} },
+    { rule: _("font-weight: 200;"),  d: {"font-weight" : "200"} },
+    { rule: _("font-weight: 300;"),  d: {"font-weight" : "300"} },
+    { rule: _("font-weight: 400;"),  d: {"font-weight" : "400"} },
+    { rule: _("font-weight: 500;"),  d: {"font-weight" : "500"} },
+    { rule: _("font-weight: 600;"),  d: {"font-weight" : "600"} },
+    { rule: _("font-weight: 700;"),  d: {"font-weight" : "700"} },
+    { rule: _("font-weight: 800;"),  d: {"font-weight" : "800"} },
+    { rule: _("font-weight: 900;"),  d: {"font-weight" : "900"} },
+    { rule: _("font-weight: normal;"),  d: {"font-weight" : "normal"} },
+    { rule: _("font-weight: bold;"),    d: {"font-weight" : "bold"} },
+
+    // Incorrect font-weight.
+    { rule: _("font-weight: bolder;"),  d: {} },
+    { rule: _("font-weight: lighter;"), d: {} },
+
+    // Correct font-stretch.
+    { rule: _("font-stretch: ultra-condensed;"),
+      d: {"font-stretch" : "ultra-condensed"} },
+    { rule: _("font-stretch: extra-condensed;"),
+      d: {"font-stretch" : "extra-condensed"} },
+    { rule: _("font-stretch: condensed;"),
+      d: {"font-stretch" : "condensed"} },
+    { rule: _("font-stretch: semi-condensed;"),
+      d: {"font-stretch" : "semi-condensed"} },
+    { rule: _("font-stretch: normal;"),
+      d: {"font-stretch" : "normal"} },
+    { rule: _("font-stretch: semi-expanded;"),
+      d: {"font-stretch" : "semi-expanded"} },
+    { rule: _("font-stretch: expanded;"),
+      d: {"font-stretch" : "expanded"} },
+    { rule: _("font-stretch: extra-expanded;"),
+      d: {"font-stretch" : "extra-expanded"} },
+    { rule: _("font-stretch: ultra-expanded;"),
+      d: {"font-stretch" : "ultra-expanded"} },
+
+    // Incorrect font-stretch.
+    { rule: _("font-stretch: wider;"),  d: {} },
+    { rule: _("font-stretch: narrower;"), d: {} },
+
+    // Correct src:
+    { rule: _("src: url(\"/fonts/Mouse\");"),
+      d: { "src" : "url(\"/fonts/Mouse\")" } },
+    { rule: _("src: url(/fonts/Mouse);"),
+      d: { "src" : "url(\"/fonts/Mouse\")" }, noncanonical: true },
+
+    { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\");"),
+      d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\")" } },
+    { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\");"),
+      d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\")" } },
+
+    { rule: _("src: url(\"/fonts/Mouse\"), url(\"/fonts/Rat\");"),
+      d: { "src" : "url(\"/fonts/Mouse\"), url(\"/fonts/Rat\")" } },
+
+    { rule: _("src: local(Mouse), url(\"/fonts/Mouse\");"),
+      d: { "src" : "local(\"Mouse\"), url(\"/fonts/Mouse\")" },
+      noncanonical: true },
+
+    { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\");"),
+      d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\")" } },
+
+    { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\");"),
+      d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\")" } },
+
+    // Correct but unusual src:
+    { rule: _("src: local(Hoefler Text);"), 
+      d: {"src" : "local(\"Hoefler Text\")"}, noncanonical: true },
+
+    // Incorrect src:
+    { rule: _("src: \"/fonts/Mouse\";"), d: {} },
+    { rule: _("src: /fonts/Mouse;"), d: {} },
+    { rule: _("src: url(\"/fonts/Mouse\") format(truetype);"), d: {} },
+    { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\",opentype);"), d: {} },
+    { rule: _("src: local(*);"), d: {} },
+    { rule: _("src: format(\"truetype\");"), d: {} },
+    { rule: _("src: local(Mouse) format(\"truetype\");"), d: {} },
+    { rule: _("src: local(Mouse, Rat);"), d: {} },
+    { rule: _("src: local(sans-serif);"), d: {} }
+
+    // unicode-range is not implemented (bug 443976).
+    // tests for that omitted for now.
+  ];
+
+  var display = document.getElementById("display");
+  var sheet = document.styleSheets[1];
+
+  for (var curTest = 0; curTest < testset.length; curTest++) {
+    try {
+      while(sheet.cssRules.length > 0)
+        sheet.deleteRule(0);
+      sheet.insertRule(testset[curTest].rule, 0);
+    } catch (e if e instanceof DOMException) {
+      ok(e.code == DOMException.SYNTAX_ERR
+         && !('d' in testset[curTest]),
+         testset[curTest].rule + " syntax error thrown", e);
+    } catch (e) {
+      ok(false, testset[curTest].rule, "During prep: " + e);
+    }
+
+    try {
+      if (testset[curTest].d) {
+        is(sheet.cssRules.length, 1,
+           testset[curTest].rule + " rule count");
+           is(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+           testset[curTest].rule + " rule type");
+
+        var d = testset[curTest].d;
+        var s = sheet.cssRules[0].style;
+        var n = 0;
+
+        // everything is set that should be
+        for (var name in d) {
+          is(s.getPropertyValue(name), d[name],
+             testset[curTest].rule + " (prop " + name + ")");
+          n++;
+        }
+        // nothing else is set
+        is(s.length, n, testset[curTest].rule + "prop count");
+        for (var i = 0; i < s.length; i++) {
+          ok(s[i] in d, testset[curTest].rule,
+             "Unexpected item #" + i + ": " + s[i]);
+        }
+
+        // round-tripping of cssText
+        // this is a strong test; it's okay if the exact serialization
+        // changes in the future
+        if (n && !testset[curTest].noncanonical) {
+          is(sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "),
+             testset[curTest].rule,
+             testset[curTest].rule + " rule text");
+        }
+      } else {
+        if (sheet.cssRules.length == 0) {
+          is(sheet.cssRules.length, 0,
+             testset[curTest].rule + " rule count (0)");
+        } else {
+          is(sheet.cssRules.length, 1,
+             testset[curTest].rule + " rule count (1 non-fontface)");
+          isnot(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
+                testset[curTest].rule + " rule type (1 non-fontface)");
+        }
+      }
+    } catch (e) {
+      ok(false, testset[curTest].rule, "During test: " + e);
+    }
+  }
+</script>
+</body>
+</html>