Bug 115199 - CSS2 @page rule style support. r=dbaron
authorBrendan Dahl <bdahl@mozilla.com>
Fri, 31 Aug 2012 14:21:28 -0700
changeset 113332 2ea5d36b35a15f440c86bf825b1c3737234f4e59
parent 113331 31312fa1abdee6f8089c549f426602800ac73329
child 113333 3d61038df8830adbcb892d8a9de3ab35f4637f84
push id23869
push useremorley@mozilla.com
push dateThu, 15 Nov 2012 16:18:16 +0000
treeherdermozilla-central@a37525d304d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs115199
milestone19.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
Bug 115199 - CSS2 @page rule style support. r=dbaron
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/interfaces/css/nsIDOMCSSPageRule.idl
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.h
layout/style/nsCSSRuleProcessor.cpp
layout/style/nsCSSRuleProcessor.h
layout/style/nsCSSRules.cpp
layout/style/nsCSSRules.h
layout/style/nsStyleSet.cpp
layout/style/nsStyleSet.h
layout/style/test/Makefile.in
layout/style/test/test_page_parser.html
layout/style/ua.css
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -279,16 +279,17 @@
 #include "nsIDOMCSSCharsetRule.h"
 #include "nsIDOMCSSImportRule.h"
 #include "nsIDOMCSSMediaRule.h"
 #include "nsIDOMCSSFontFaceRule.h"
 #include "nsIDOMCSSMozDocumentRule.h"
 #include "nsIDOMCSSSupportsRule.h"
 #include "nsIDOMMozCSSKeyframeRule.h"
 #include "nsIDOMMozCSSKeyframesRule.h"
+#include "nsIDOMCSSPageRule.h"
 #include "nsIDOMCSSPrimitiveValue.h"
 #include "nsIDOMCSSStyleRule.h"
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsDOMCSSValueList.h"
 #define MOZ_GENERATED_EVENTS_INCLUDES
 #include "GeneratedEvents.h"
 #undef MOZ_GENERATED_EVENTS_INCLUDES
 #include "nsIDOMDeviceMotionEvent.h"
@@ -1610,16 +1611,19 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(TouchEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(MediaQueryList, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MutationObserver, nsDOMMutationObserverSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS |
                            nsIXPCScriptable::WANT_ADDPROPERTY)
   NS_DEFINE_CLASSINFO_DATA(MutationRecord, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
@@ -4319,16 +4323,20 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframeRule, nsIDOMMozCSSKeyframeRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframesRule, nsIDOMMozCSSKeyframesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(CSSPageRule, nsIDOMCSSPageRule)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSPageRule)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(MediaQueryList, nsIDOMMediaQueryList)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMediaQueryList)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MutationObserver, nsIDOMMutationObserver)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMutationObserver)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -473,16 +473,18 @@ DOMCI_CLASS(IDBOpenDBRequest)
 
 DOMCI_CLASS(Touch)
 DOMCI_CLASS(TouchList)
 DOMCI_CLASS(TouchEvent)
 
 DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
+DOMCI_CLASS(CSSPageRule)
+
 DOMCI_CLASS(MediaQueryList)
 
 DOMCI_CLASS(MutationObserver)
 DOMCI_CLASS(MutationRecord)
 
 #ifdef MOZ_B2G_RIL
 DOMCI_CLASS(Telephony)
 DOMCI_CLASS(TelephonyCall)
--- a/dom/interfaces/css/nsIDOMCSSPageRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSPageRule.idl
@@ -1,15 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(a6cf90bd-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(6126024d-d716-4ad8-bc53-24dd6d5846b1)]
 interface nsIDOMCSSPageRule : nsIDOMCSSRule
 {
-           attribute DOMString        selectorText;
+           //attribute DOMString        selectorText;
                                         // raises(DOMException) on setting
 
   readonly attribute nsIDOMCSSStyleDeclaration  style;
 };
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -395,22 +395,28 @@ protected:
                            PRUnichar aStopChar);
   bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
   bool ParseSelector(nsCSSSelectorList* aList, PRUnichar aPrevCombinator);
 
   enum {
     eParseDeclaration_InBraces       = 1 << 0,
     eParseDeclaration_AllowImportant = 1 << 1
   };
-
-  css::Declaration* ParseDeclarationBlock(uint32_t aFlags);
+  enum nsCSSContextType {
+    eCSSContext_General,
+    eCSSContext_Page
+  };
+
+  css::Declaration* ParseDeclarationBlock(uint32_t aFlags,
+                                          nsCSSContextType aContext = eCSSContext_General);
   bool ParseDeclaration(css::Declaration* aDeclaration,
                         uint32_t aFlags,
                         bool aMustCallValueAppended,
-                        bool* aChanged);
+                        bool* aChanged,
+                        nsCSSContextType aContext = eCSSContext_General);
 
   bool ParseProperty(nsCSSProperty aPropID);
   bool ParsePropertyByFunction(nsCSSProperty aPropID);
   bool ParseSingleValueProperty(nsCSSValue& aValue,
                                   nsCSSProperty aPropID);
 
   enum PriorityParsingStatus {
     ePriority_None,
@@ -2256,23 +2262,16 @@ CSSParserImpl::ParseFontDescriptor(nsCSS
     return false;
 
   aRule->SetDesc(descID, value);
   return true;
 }
 
 
 bool
-CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
-{
-  // XXX not yet implemented
-  return false;
-}
-
-bool
 CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
 {
   if (!GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
     return false;
   }
 
   if (mToken.mType != eCSSToken_Ident) {
@@ -2298,30 +2297,49 @@ CSSParserImpl::ParseKeyframesRule(RuleAp
       SkipRuleSet(true);
     }
   }
 
   (*aAppendFunc)(rule, aData);
   return true;
 }
 
+bool
+CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
+{
+  // TODO: There can be page selectors after @page such as ":first", ":left".
+  uint32_t parseFlags = eParseDeclaration_InBraces |
+                        eParseDeclaration_AllowImportant;
+  nsAutoPtr<css::Declaration> declaration(
+                                ParseDeclarationBlock(parseFlags,
+                                                      eCSSContext_Page));
+  if (!declaration) {
+    return false;
+  }
+
+  // Takes ownership of declaration.
+  nsRefPtr<nsCSSPageRule> rule = new nsCSSPageRule(declaration);
+
+  (*aAppendFunc)(rule, aData);
+  return true;
+}
+
 already_AddRefed<nsCSSKeyframeRule>
 CSSParserImpl::ParseKeyframeRule()
 {
   InfallibleTArray<float> selectorList;
   if (!ParseKeyframeSelectorList(selectorList)) {
     REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
     return nullptr;
   }
 
   // Ignore !important in keyframe rules
   uint32_t parseFlags = eParseDeclaration_InBraces;
   nsAutoPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
   if (!declaration) {
-    REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
     return nullptr;
   }
 
   // Takes ownership of declaration, and steals contents of selectorList.
   nsRefPtr<nsCSSKeyframeRule> rule =
     new nsCSSKeyframeRule(selectorList, declaration);
 
   return rule.forget();
@@ -3887,33 +3905,33 @@ CSSParserImpl::ParseSelector(nsCSSSelect
     selector->mClassList = pseudoElementArgs.forget();
     selector->SetPseudoType(pseudoElementType);
   }
 
   return true;
 }
 
 css::Declaration*
-CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags)
+CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
 {
   bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
 
   if (checkForBraces) {
     if (!ExpectSymbol('{', true)) {
       REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
       OUTPUT_ERROR();
       return nullptr;
     }
   }
   css::Declaration* declaration = new css::Declaration();
   mData.AssertInitialState();
   if (declaration) {
     for (;;) {
       bool changed;
-      if (!ParseDeclaration(declaration, aFlags, true, &changed)) {
+      if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
         if (!SkipDeclaration(checkForBraces)) {
           break;
         }
         if (checkForBraces) {
           if (ExpectSymbol('}', true)) {
             break;
           }
         }
@@ -4294,18 +4312,22 @@ CSSParserImpl::ParseTreePseudoElement(ns
 #endif
 
 //----------------------------------------------------------------------
 
 bool
 CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
                                 uint32_t aFlags,
                                 bool aMustCallValueAppended,
-                                bool* aChanged)
-{
+                                bool* aChanged,
+                                nsCSSContextType aContext)
+{
+  NS_PRECONDITION(aContext == eCSSContext_General ||
+                  aContext == eCSSContext_Page,
+                  "Must be page or general context");
   bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
 
   mTempData.AssertInitialState();
 
   // Get property name
   nsCSSToken* tk = &mToken;
   nsAutoString propertyName;
   for (;;) {
@@ -4339,17 +4361,19 @@ CSSParserImpl::ParseDeclaration(css::Dec
     // Not a declaration...
     UngetToken();
     return false;
   }
 
   // Map property name to its ID and then parse the property
   nsCSSProperty propID = nsCSSProps::LookupProperty(propertyName,
                                                     nsCSSProps::eEnabled);
-  if (eCSSProperty_UNKNOWN == propID) { // unknown property
+  if (eCSSProperty_UNKNOWN == propID ||
+     (aContext == nsCSSContextType::eCSSContext_Page &&
+      !nsCSSProps::PropHasFlags(propID, CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
     if (!NonMozillaVendorIdentifier(propertyName)) {
       const PRUnichar *params[] = {
         propertyName.get()
       };
       REPORT_UNEXPECTED_P(PEUnknownProperty, params);
       REPORT_UNEXPECTED(PEDeclDropped);
       OUTPUT_ERROR();
     }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1914,171 +1914,186 @@ CSS_PROP_LIST(
     kListStyleKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_SHORTHAND(
     margin,
     margin,
     Margin,
     CSS_PROPERTY_PARSE_FUNCTION |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "")
 CSS_PROP_MARGIN(
     margin-bottom,
     margin_bottom,
     MarginBottom,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
         CSS_PROPERTY_STORES_CALC |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC,
     nullptr,
     offsetof(nsStyleMargin, mMargin),
     eStyleAnimType_Sides_Bottom)
 CSS_PROP_SHORTHAND(
     -moz-margin-end,
     margin_end,
     CSS_PROP_DOMPROP_PREFIXED(MarginEnd),
-    CSS_PROPERTY_PARSE_FUNCTION,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "")
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_MARGIN(
     margin-end-value,
     margin_end_value,
     MarginEndValue,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_STORES_CALC,
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC, // for internal use
     nullptr,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 #endif
 CSS_PROP_SHORTHAND(
     margin-left,
     margin_left,
     MarginLeft,
     CSS_PROPERTY_PARSE_FUNCTION |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "")
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_MARGIN(
     margin-left-value,
     margin_left_value,
     MarginLeftValue,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
         CSS_PROPERTY_REPORT_OTHER_NAME |
-        CSS_PROPERTY_STORES_CALC,
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC, // for internal use
     nullptr,
     offsetof(nsStyleMargin, mMargin),
     eStyleAnimType_Sides_Left)
 CSS_PROP_MARGIN(
     margin-left-ltr-source,
     margin_left_ltr_source,
     MarginLeftLTRSource,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_DIRECTIONAL_SOURCE,
+        CSS_PROPERTY_DIRECTIONAL_SOURCE |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     0,
     kBoxPropSourceKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_MARGIN(
     margin-left-rtl-source,
     margin_left_rtl_source,
     MarginLeftRTLSource,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_DIRECTIONAL_SOURCE,
+        CSS_PROPERTY_DIRECTIONAL_SOURCE |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     0,
     kBoxPropSourceKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 #endif
 CSS_PROP_SHORTHAND(
     margin-right,
     margin_right,
     MarginRight,
     CSS_PROPERTY_PARSE_FUNCTION |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "")
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_MARGIN(
     margin-right-value,
     margin_right_value,
     MarginRightValue,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
         CSS_PROPERTY_REPORT_OTHER_NAME |
-        CSS_PROPERTY_STORES_CALC,
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC, // for internal use
     nullptr,
     offsetof(nsStyleMargin, mMargin),
     eStyleAnimType_Sides_Right)
 CSS_PROP_MARGIN(
     margin-right-ltr-source,
     margin_right_ltr_source,
     MarginRightLTRSource,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_DIRECTIONAL_SOURCE,
+        CSS_PROPERTY_DIRECTIONAL_SOURCE |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     0,
     kBoxPropSourceKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_MARGIN(
     margin-right-rtl-source,
     margin_right_rtl_source,
     MarginRightRTLSource,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_DIRECTIONAL_SOURCE,
+        CSS_PROPERTY_DIRECTIONAL_SOURCE |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     0,
     kBoxPropSourceKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 #endif
 CSS_PROP_SHORTHAND(
     -moz-margin-start,
     margin_start,
     CSS_PROP_DOMPROP_PREFIXED(MarginStart),
-    CSS_PROPERTY_PARSE_FUNCTION,
+    CSS_PROPERTY_PARSE_FUNCTION |
+    CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "")
 #ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_MARGIN(
     margin-start-value,
     margin_start_value,
     MarginStartValue,
     CSS_PROPERTY_PARSE_INACCESSIBLE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
-        CSS_PROPERTY_STORES_CALC,
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC, // for internal use
     nullptr,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 #endif
 CSS_PROP_MARGIN(
     margin-top,
     margin_top,
     MarginTop,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER |
         CSS_PROPERTY_STORES_CALC |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_APPLIES_TO_PAGE_RULE,
     "",
     VARIANT_AHLP | VARIANT_CALC,
     nullptr,
     offsetof(nsStyleMargin, mMargin),
     eStyleAnimType_Sides_Top)
 CSS_PROP_CONTENT(
     marker-offset,
     marker_offset,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -99,16 +99,19 @@ MOZ_STATIC_ASSERT((CSS_PROPERTY_PARSE_PR
 #define CSS_PROPERTY_UNITLESS_LENGTH_QUIRK        (1<<16)
 
 // Is this property (which must be a shorthand) really an alias?
 #define CSS_PROPERTY_IS_ALIAS                     (1<<17)
 
 // Does the property apply to ::-moz-placeholder?
 #define CSS_PROPERTY_APPLIES_TO_PLACEHOLDER       (1<<18)
 
+// This property is allowed in an @page rule.
+#define CSS_PROPERTY_APPLIES_TO_PAGE_RULE         (1<<19)
+
 /**
  * Types of animatable values.
  */
 enum nsStyleAnimType {
   // requires a custom implementation in
   // nsStyleAnimation::ExtractComputedValue
   eStyleAnimType_Custom,
 
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -973,16 +973,17 @@ struct RuleCascadeData {
   PLDHashTable             mAttributeSelectors;
   PLDHashTable             mAnonBoxRules;
 #ifdef MOZ_XUL
   PLDHashTable             mXULTreeRules;
 #endif
 
   nsTArray<nsFontFaceRuleContainer> mFontFaceRules;
   nsTArray<nsCSSKeyframesRule*> mKeyframesRules;
+  nsTArray<nsCSSPageRule*> mPageRules;
 
   // Looks up or creates the appropriate list in |mAttributeSelectors|.
   // Returns null only on allocation failure.
   nsTArray<nsCSSSelector*>* AttributeListFor(nsIAtom* aAttribute);
 
   nsMediaQueryResultCacheKey mCacheKey;
   RuleCascadeData*  mNext; // for a different medium
 
@@ -1023,16 +1024,17 @@ RuleCascadeData::SizeOfIncludingThis(nsM
                                         SizeOfRuleHashTableEntry, aMallocSizeOf);
 #ifdef MOZ_XUL
   n += PL_DHashTableSizeOfExcludingThis(&mXULTreeRules,
                                         SizeOfRuleHashTableEntry, aMallocSizeOf);
 #endif
 
   n += mFontFaceRules.SizeOfExcludingThis(aMallocSizeOf);
   n += mKeyframesRules.SizeOfExcludingThis(aMallocSizeOf);
+  n += mPageRules.SizeOfExcludingThis(aMallocSizeOf);
 
   return n;
 }
 
 nsTArray<nsCSSSelector*>*
 RuleCascadeData::AttributeListFor(nsIAtom* aAttribute)
 {
   AtomSelectorEntry *entry =
@@ -2674,16 +2676,34 @@ nsCSSRuleProcessor::AppendKeyframesRules
   if (cascade) {
     if (!aArray.AppendElements(cascade->mKeyframesRules))
       return false;
   }
   
   return true;
 }
 
+// Append all the currently-active page rules to aArray.  Return
+// true for success and false for failure.
+bool
+nsCSSRuleProcessor::AppendPageRules(
+                              nsPresContext* aPresContext,
+                              nsTArray<nsCSSPageRule*>& aArray)
+{
+  RuleCascadeData* cascade = GetRuleCascade(aPresContext);
+
+  if (cascade) {
+    if (!aArray.AppendElements(cascade->mPageRules)) {
+      return false;
+    }
+  }
+  
+  return true;
+}
+
 nsresult
 nsCSSRuleProcessor::ClearRuleCascades()
 {
   // We rely on our caller (perhaps indirectly) to do something that
   // will rebuild style data and the user font set (either
   // nsIPresShell::ReconstructStyleData or
   // nsPresContext::RebuildAllStyleData).
   RuleCascadeData *data = mRuleCascades;
@@ -2972,21 +2992,23 @@ static PLDHashTableOps gRulesByWeightOps
     PL_DHashFinalizeStub,
     InitWeightEntry
 };
 
 struct CascadeEnumData {
   CascadeEnumData(nsPresContext* aPresContext,
                   nsTArray<nsFontFaceRuleContainer>& aFontFaceRules,
                   nsTArray<nsCSSKeyframesRule*>& aKeyframesRules,
+                  nsTArray<nsCSSPageRule*>& aPageRules,
                   nsMediaQueryResultCacheKey& aKey,
                   uint8_t aSheetType)
     : mPresContext(aPresContext),
       mFontFaceRules(aFontFaceRules),
       mKeyframesRules(aKeyframesRules),
+      mPageRules(aPageRules),
       mCacheKey(aKey),
       mSheetType(aSheetType)
   {
     if (!PL_DHashTableInit(&mRulesByWeight, &gRulesByWeightOps, nullptr,
                           sizeof(RuleByWeightEntry), 64))
       mRulesByWeight.ops = nullptr;
 
     // Initialize our arena
@@ -2999,32 +3021,34 @@ struct CascadeEnumData {
     if (mRulesByWeight.ops)
       PL_DHashTableFinish(&mRulesByWeight);
     PL_FinishArenaPool(&mArena);
   }
 
   nsPresContext* mPresContext;
   nsTArray<nsFontFaceRuleContainer>& mFontFaceRules;
   nsTArray<nsCSSKeyframesRule*>& mKeyframesRules;
+  nsTArray<nsCSSPageRule*>& mPageRules;
   nsMediaQueryResultCacheKey& mCacheKey;
   PLArenaPool mArena;
   // Hooray, a manual PLDHashTable since nsClassHashtable doesn't
   // provide a getter that gives me a *reference* to the value.
   PLDHashTable mRulesByWeight; // of PerWeightDataListItem linked lists
   uint8_t mSheetType;
 };
 
 /*
  * This enumerates style rules in a sheet (and recursively into any
  * grouping rules) in order to:
  *  (1) add any style rules, in order, into data->mRulesByWeight (for
  *      the primary CSS cascade), where they are separated by weight
  *      but kept in order per-weight, and
  *  (2) add any @font-face rules, in order, into data->mFontFaceRules.
  *  (3) add any @keyframes rules, in order, into data->mKeyframesRules.
+ *  (4) add any @page rules, in order, into data->mPageRules.
  */
 static bool
 CascadeRuleEnumFunc(css::Rule* aRule, void* aData)
 {
   CascadeEnumData* data = (CascadeEnumData*)aData;
   int32_t type = aRule->GetType();
 
   if (css::Rule::STYLE_RULE == type) {
@@ -3067,17 +3091,22 @@ CascadeRuleEnumFunc(css::Rule* aRule, vo
   }
   else if (css::Rule::KEYFRAMES_RULE == type) {
     nsCSSKeyframesRule *keyframesRule =
       static_cast<nsCSSKeyframesRule*>(aRule);
     if (!data->mKeyframesRules.AppendElement(keyframesRule)) {
       return false;
     }
   }
-
+  else if (css::Rule::PAGE_RULE == type) {
+    nsCSSPageRule* pageRule = static_cast<nsCSSPageRule*>(aRule);
+    if (!data->mPageRules.AppendElement(pageRule)) {
+      return false;
+    }
+  }
   return true;
 }
 
 /* static */ bool
 nsCSSRuleProcessor::CascadeSheet(nsCSSStyleSheet* aSheet, CascadeEnumData* aData)
 {
   if (aSheet->IsApplicable() &&
       aSheet->UseForPresentation(aData->mPresContext, aData->mCacheKey) &&
@@ -3168,16 +3197,17 @@ nsCSSRuleProcessor::RefreshRuleCascade(n
 
   if (mSheets.Length() != 0) {
     nsAutoPtr<RuleCascadeData> newCascade(
       new RuleCascadeData(aPresContext->Medium(),
                           eCompatibility_NavQuirks == aPresContext->CompatibilityMode()));
     if (newCascade) {
       CascadeEnumData data(aPresContext, newCascade->mFontFaceRules,
                            newCascade->mKeyframesRules,
+                           newCascade->mPageRules,
                            newCascade->mCacheKey,
                            mSheetType);
       if (!data.mRulesByWeight.ops)
         return; /* out of memory */
 
       for (uint32_t i = 0; i < mSheets.Length(); ++i) {
         if (!CascadeSheet(mSheets.ElementAt(i), &data))
           return; /* out of memory */
--- a/layout/style/nsCSSRuleProcessor.h
+++ b/layout/style/nsCSSRuleProcessor.h
@@ -116,16 +116,19 @@ public:
   // Append all the currently-active font face rules to aArray.  Return
   // true for success and false for failure.
   bool AppendFontFaceRules(nsPresContext* aPresContext,
                            nsTArray<nsFontFaceRuleContainer>& aArray);
 
   bool AppendKeyframesRules(nsPresContext* aPresContext,
                             nsTArray<nsCSSKeyframesRule*>& aArray);
 
+  bool AppendPageRules(nsPresContext* aPresContext,
+                       nsTArray<nsCSSPageRule*>& aArray);
+
 #ifdef DEBUG
   void AssertQuirksChangeOK() {
     NS_ASSERTION(!mRuleCascades, "can't toggle quirks style sheet without "
                                  "clearing rule cascades");
   }
 #endif
 
 #ifdef XP_WIN
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -31,16 +31,17 @@
 #include "nsStyleConsts.h"
 #include "nsError.h"
 #include "nsStyleUtil.h"
 #include "mozilla/css/Declaration.h"
 #include "nsCSSParser.h"
 #include "nsPrintfCString.h"
 #include "nsDOMClassInfoID.h"
 #include "mozilla/dom/CSSStyleDeclarationBinding.h"
+#include "StyleRule.h"
 
 namespace css = mozilla::css;
 
 #define IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(class_, super_) \
   /* virtual */ nsIDOMCSSRule* class_::GetDOMRule()               \
   { return this; }                                                \
   /* virtual */ nsIDOMCSSRule* class_::GetExistingDOMRule()       \
   { return this; }
@@ -2321,16 +2322,222 @@ nsCSSKeyframesRule::SizeOfIncludingThis(
 
   // Measurement of the following members may be added later if DMD finds it is
   // worthwhile:
   // - mName
 
   return n;
 }
 
+// -------------------------------------------
+// nsCSSPageStyleDeclaration
+//
+
+nsCSSPageStyleDeclaration::nsCSSPageStyleDeclaration(nsCSSPageRule* aRule)
+  : mRule(aRule)
+{
+}
+
+nsCSSPageStyleDeclaration::~nsCSSPageStyleDeclaration()
+{
+  NS_ASSERTION(!mRule, "DropReference not called.");
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCSSPageStyleDeclaration)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCSSPageStyleDeclaration)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsCSSPageStyleDeclaration)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSPageStyleDeclaration)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
+
+css::Declaration*
+nsCSSPageStyleDeclaration::GetCSSDeclaration(bool aAllocate)
+{
+  if (mRule) {
+    return mRule->Declaration();
+  } else {
+    return nullptr;
+  }
+}
+
+void
+nsCSSPageStyleDeclaration::GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv)
+{
+  GetCSSParsingEnvironmentForRule(mRule, aCSSParseEnv);
+}
+
+NS_IMETHODIMP
+nsCSSPageStyleDeclaration::GetParentRule(nsIDOMCSSRule** aParent)
+{
+  NS_ENSURE_ARG_POINTER(aParent);
+
+  NS_IF_ADDREF(*aParent = mRule);
+  return NS_OK;
+}
+
+nsresult
+nsCSSPageStyleDeclaration::SetCSSDeclaration(css::Declaration* aDecl)
+{
+  NS_ABORT_IF_FALSE(aDecl, "must be non-null");
+  mRule->ChangeDeclaration(aDecl);
+  return NS_OK;
+}
+
+nsIDocument*
+nsCSSPageStyleDeclaration::DocToUpdate()
+{
+  return nullptr;
+}
+
+nsINode*
+nsCSSPageStyleDeclaration::GetParentObject()
+{
+  return mRule ? mRule->GetDocument() : nullptr;
+}
+
+// -------------------------------------------
+// nsCSSPageRule
+//
+
+nsCSSPageRule::nsCSSPageRule(const nsCSSPageRule& aCopy)
+  // copy everything except our reference count and mDOMDeclaration
+  : Rule(aCopy)
+  , mDeclaration(new css::Declaration(*aCopy.mDeclaration))
+{
+}
+
+nsCSSPageRule::~nsCSSPageRule()
+{
+  if (mDOMDeclaration) {
+    mDOMDeclaration->DropReference();
+  }
+}
+
+/* virtual */ already_AddRefed<css::Rule>
+nsCSSPageRule::Clone() const
+{
+  nsRefPtr<css::Rule> clone = new nsCSSPageRule(*this);
+  return clone.forget();
+}
+
+NS_IMPL_ADDREF(nsCSSPageRule)
+NS_IMPL_RELEASE(nsCSSPageRule)
+
+DOMCI_DATA(CSSPageRule, nsCSSPageRule)
+
+// QueryInterface implementation for nsCSSPageRule
+NS_INTERFACE_MAP_BEGIN(nsCSSPageRule)
+  NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSPageRule)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStyleRule)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSPageRule)
+NS_INTERFACE_MAP_END
+
+IMPL_STYLE_RULE_INHERIT_GET_DOM_RULE_WEAK(nsCSSPageRule, Rule)
+
+#ifdef DEBUG
+void
+nsCSSPageRule::List(FILE* out, int32_t aIndent) const
+{
+  // FIXME: WRITE ME
+}
+#endif
+
+/* virtual */ int32_t
+nsCSSPageRule::GetType() const
+{
+  return Rule::PAGE_RULE;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetType(uint16_t* aType)
+{
+  *aType = nsIDOMCSSRule::PAGE_RULE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetCssText(nsAString& aCssText)
+{
+  aCssText.AppendLiteral("@page { ");
+  nsAutoString tmp;
+  mDeclaration->ToString(tmp);
+  aCssText.Append(tmp);
+  aCssText.AppendLiteral(" }");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::SetCssText(const nsAString& aCssText)
+{
+  // FIXME: implement???
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetParentStyleSheet(nsIDOMCSSStyleSheet** aSheet)
+{
+  return Rule::GetParentStyleSheet(aSheet);
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetParentRule(nsIDOMCSSRule** aParentRule)
+{
+  return Rule::GetParentRule(aParentRule);
+}
+
+css::ImportantRule*
+nsCSSPageRule::GetImportantRule()
+{
+  if (!mDeclaration->HasImportantData()) {
+    return nullptr;
+  }
+  if (!mImportantRule) {
+    mImportantRule = new css::ImportantRule(mDeclaration);
+  }
+  return mImportantRule;
+}
+
+/* virtual */ void
+nsCSSPageRule::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+  mDeclaration->MapNormalRuleInfoInto(aRuleData);
+}
+
+NS_IMETHODIMP
+nsCSSPageRule::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
+{
+  if (!mDOMDeclaration) {
+    mDOMDeclaration = new nsCSSPageStyleDeclaration(this);
+  }
+  NS_ADDREF(*aStyle = mDOMDeclaration);
+  return NS_OK;
+}
+
+void
+nsCSSPageRule::ChangeDeclaration(css::Declaration* aDeclaration)
+{
+  mImportantRule = nullptr;
+  mDeclaration = aDeclaration;
+
+  nsCSSStyleSheet* sheet = GetStyleSheet();
+  if (sheet) {
+    sheet->SetModifiedByChildRule();
+  }
+}
+
+/* virtual */ size_t
+nsCSSPageRule::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
+{
+  return aMallocSizeOf(this);
+}
+
 namespace mozilla {
 
 CSSSupportsRule::CSSSupportsRule(bool aConditionMet,
                                  const nsString& aCondition)
   : mUseGroup(aConditionMet),
     mCondition(aCondition)
 {
 }
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -23,22 +23,18 @@
 #include "nsICSSRuleList.h"
 #include "nsAutoPtr.h"
 #include "nsCSSProperty.h"
 #include "nsCSSValue.h"
 #include "nsIDOMCSSCharsetRule.h"
 #include "nsTArray.h"
 #include "nsDOMCSSDeclaration.h"
 #include "Declaration.h"
-
-namespace mozilla {
-namespace css {
-class StyleRule;
-}
-}
+#include "nsIDOMCSSPageRule.h"
+#include "StyleRule.h"
 
 class nsMediaList;
 
 namespace mozilla {
 namespace css {
 
 class MediaRule MOZ_FINAL : public GroupRule,
                             public nsIDOMCSSMediaRule
@@ -428,16 +424,89 @@ public:
   virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const MOZ_OVERRIDE;
 
 private:
   uint32_t FindRuleIndexForKey(const nsAString& aKey);
 
   nsString                                   mName;
 };
 
+class nsCSSPageRule;
+
+class nsCSSPageStyleDeclaration MOZ_FINAL : public nsDOMCSSDeclaration
+{
+public:
+  nsCSSPageStyleDeclaration(nsCSSPageRule *aRule);
+  virtual ~nsCSSPageStyleDeclaration();
+
+  NS_IMETHOD GetParentRule(nsIDOMCSSRule **aParent) MOZ_OVERRIDE;
+  void DropReference() { mRule = nullptr; }
+  virtual mozilla::css::Declaration* GetCSSDeclaration(bool aAllocate) MOZ_OVERRIDE;
+  virtual nsresult SetCSSDeclaration(mozilla::css::Declaration* aDecl) MOZ_OVERRIDE;
+  virtual void GetCSSParsingEnvironment(CSSParsingEnvironment& aCSSParseEnv) MOZ_OVERRIDE;
+  virtual nsIDocument* DocToUpdate() MOZ_OVERRIDE;
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsCSSPageStyleDeclaration,
+                                                         nsICSSDeclaration)
+
+  virtual nsINode *GetParentObject();
+
+protected:
+  // This reference is not reference-counted. The rule object tells us
+  // when it's about to go away.
+  nsCSSPageRule *mRule;
+};
+
+class nsCSSPageRule MOZ_FINAL : public mozilla::css::Rule,
+                                public nsIDOMCSSPageRule
+{
+public:
+  // WARNING: Steals the contents of aDeclaration
+  nsCSSPageRule(nsAutoPtr<mozilla::css::Declaration> aDeclaration)
+    : mDeclaration(aDeclaration),
+      mImportantRule(nullptr)
+  {
+  }
+private:
+  nsCSSPageRule(const nsCSSPageRule& aCopy);
+  ~nsCSSPageRule();
+public:
+  NS_DECL_ISUPPORTS
+
+  // nsIStyleRule methods
+#ifdef DEBUG
+  virtual void List(FILE* out = stdout, int32_t aIndent = 0) const;
+#endif
+
+  // Rule methods
+  DECL_STYLE_RULE_INHERIT
+  virtual int32_t GetType() const;
+  virtual already_AddRefed<mozilla::css::Rule> Clone() const;
+
+  // nsIDOMCSSRule interface
+  NS_DECL_NSIDOMCSSRULE
+
+  // nsIDOMCSSPageRule interface
+  NS_DECL_NSIDOMCSSPAGERULE
+
+  mozilla::css::Declaration* Declaration()   { return mDeclaration; }
+
+  void ChangeDeclaration(mozilla::css::Declaration* aDeclaration);
+
+  mozilla::css::ImportantRule* GetImportantRule();
+
+  virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const;
+private:
+  nsAutoPtr<mozilla::css::Declaration>    mDeclaration;
+  // lazily created when needed:
+  nsRefPtr<nsCSSPageStyleDeclaration>     mDOMDeclaration;
+  nsRefPtr<mozilla::css::ImportantRule>   mImportantRule;
+};
+
 namespace mozilla {
 
 class CSSSupportsRule : public css::GroupRule,
                         public nsIDOMCSSSupportsRule
 {
 public:
   CSSSupportsRule(bool aConditionMet, const nsString& aCondition);
   CSSSupportsRule(const CSSSupportsRule& aCopy);
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -1160,16 +1160,34 @@ nsStyleSet::ResolveAnonymousBoxStyle(nsI
     NS_PRECONDITION(isAnonBox, "Unexpected pseudo");
 #endif
 
   nsRuleWalker ruleWalker(mRuleTree);
   AnonBoxRuleProcessorData data(PresContext(), aPseudoTag, &ruleWalker);
   FileRules(EnumRulesMatching<AnonBoxRuleProcessorData>, &data, nullptr,
             &ruleWalker);
 
+  if (aPseudoTag == nsCSSAnonBoxes::pageContent) {
+    // Add any @page rules that are specified.
+    nsTArray<nsCSSPageRule*> rules;
+    nsTArray<css::ImportantRule*> importantRules;
+    nsPresContext* presContext = PresContext();
+    presContext->StyleSet()->AppendPageRules(presContext, rules);
+    for (uint32_t i = 0, i_end = rules.Length(); i != i_end; ++i) {
+      ruleWalker.Forward(rules[i]);
+      css::ImportantRule* importantRule = rules[i]->GetImportantRule();
+      if (importantRule) {
+        importantRules.AppendElement(importantRule);
+      }
+    }
+    for (uint32_t i = 0, i_end = importantRules.Length(); i != i_end; ++i) {
+      ruleWalker.Forward(importantRules[i]);
+    }
+  }
+
   return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr,
                     false, false,
                     aPseudoTag, nsCSSPseudoElements::ePseudo_AnonBox,
                     false, nullptr);
 }
 
 #ifdef MOZ_XUL
 already_AddRefed<nsStyleContext>
@@ -1237,16 +1255,31 @@ nsStyleSet::AppendKeyframesRules(nsPresC
     nsCSSRuleProcessor *ruleProc = static_cast<nsCSSRuleProcessor*>
                                     (mRuleProcessors[gCSSSheetTypes[i]].get());
     if (ruleProc && !ruleProc->AppendKeyframesRules(aPresContext, aArray))
       return false;
   }
   return true;
 }
 
+bool
+nsStyleSet::AppendPageRules(nsPresContext* aPresContext,
+                            nsTArray<nsCSSPageRule*>& aArray)
+{
+  NS_ENSURE_FALSE(mInShutdown, false);
+
+  for (uint32_t i = 0; i < NS_ARRAY_LENGTH(gCSSSheetTypes); ++i) {
+    nsCSSRuleProcessor* ruleProc = static_cast<nsCSSRuleProcessor*>
+                                    (mRuleProcessors[gCSSSheetTypes[i]].get());
+    if (ruleProc && !ruleProc->AppendPageRules(aPresContext, aArray))
+      return false;
+  }
+  return true;
+}
+
 void
 nsStyleSet::BeginShutdown(nsPresContext* aPresContext)
 {
   mInShutdown = 1;
   mRoots.Clear(); // no longer valid, since we won't keep it up to date
 }
 
 void
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -24,16 +24,17 @@
 #include "nsIStyleRule.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSAnonBoxes.h"
 #include "mozilla/Attributes.h"
 
 class nsIURI;
 class nsCSSFontFaceRule;
 class nsCSSKeyframesRule;
+class nsCSSPageRule;
 class nsRuleWalker;
 struct ElementDependentRuleProcessorData;
 struct TreeMatchContext;
 
 class nsEmptyStyleRule MOZ_FINAL : public nsIStyleRule
 {
   NS_DECL_ISUPPORTS
   virtual void MapRuleInfoInto(nsRuleData* aRuleData);
@@ -147,16 +148,21 @@ class nsStyleSet
   bool AppendFontFaceRules(nsPresContext* aPresContext,
                              nsTArray<nsFontFaceRuleContainer>& aArray);
 
   // Append all the currently-active keyframes rules to aArray.  Return
   // true for success and false for failure.
   bool AppendKeyframesRules(nsPresContext* aPresContext,
                               nsTArray<nsCSSKeyframesRule*>& aArray);
 
+  // Append all the currently-active page rules to aArray.  Return
+  // true for success and false for failure.
+  bool AppendPageRules(nsPresContext* aPresContext,
+                       nsTArray<nsCSSPageRule*>& aArray);
+
   // Begin ignoring style context destruction, to avoid lots of unnecessary
   // work on document teardown.
   void BeginShutdown(nsPresContext* aPresContext);
 
   // Free all of the data associated with this style set.
   void Shutdown(nsPresContext* aPresContext);
 
   // Notification that a style context is being destroyed.
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -182,16 +182,17 @@ MOCHITEST_FILES =	test_acid3_test46.html
 		ccd.sjs \
 		visited-pref-iframe.html \
 		visited-lying-inner.html \
 		visited_image_loading.sjs \
 		visited_image_loading_frame.html \
 		visited_image_loading_frame_empty.html \
 		test_load_events_on_stylesheets.html \
 		test_bug721136.html \
+		test_page_parser.html \
 		test_bug732153.html \
 		test_bug732209.html \
 		bug732209-css.sjs \
 		test_bug795520.html \
 		viewport_units_iframe.html \
 		$(NULL)
 
 ifdef MOZ_FLEXBOX
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_page_parser.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 -->
+<head>
+  <meta charset="UTF-8">
+  <title>Test of @page parser</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<p>@page parsing (<a
+  target="_blank"
+  href="https://bugzilla.mozilla.org/show_bug.cgi?id=115199"
+>bug 115199</a>)</p>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+<script class="testbody" type="text/javascript">
+  function _(b) { return "@page { " + b + " }"; };
+
+  var testset = [
+    // CSS 2.1 only allows margin properties in the page rule.
+
+    // Check a bad property.
+    { rule: "position: absolute;" },
+
+    // Check good properties.
+    // NOTE: The margin-*-value and margin-*-source properties are not really
+    // expected and will need to be removed once bug 241234 is addressed.
+    { rule: _("margin: 2in;"), expected: {
+      "margin-top": "2in",
+      "margin-right-value": "2in",
+      "margin-bottom": "2in",
+      "margin-left-value": "2in",
+      "margin-left-ltr-source": "physical",
+      "margin-left-rtl-source": "physical",
+      "margin-right-ltr-source": "physical",
+      "margin-right-rtl-source": "physical"
+    }},
+    { rule: _("margin-top: 2in;"), expected: {"margin-top": "2in"}},
+    { rule: _("margin-left: 2in;"), expected: {
+      "margin-left-value": "2in",
+      "margin-left-ltr-source": "physical",
+      "margin-left-rtl-source": "physical",
+    }},
+    { rule: _("margin-bottom: 2in;"), expected: {"margin-bottom": "2in"}},
+    { rule: _("margin-right: 2in;"), expected: {
+      "margin-right-value": "2in",
+      "margin-right-ltr-source": "physical",
+      "margin-right-rtl-source": "physical",
+    }}
+  ];
+
+  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) {
+      ok(e.name == "SyntaxError"
+         && e instanceof DOMException
+         && e.code == DOMException.SYNTAX_ERR
+         && !('expected' in testset[curTest]),
+         testset[curTest].rule + " syntax error thrown", e);
+    }
+
+    try {
+      if (testset[curTest].expected) {
+        is(sheet.cssRules.length, 1,
+           testset[curTest].rule + " rule count");
+           is(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+           testset[curTest].rule + " rule type");
+
+        var expected = testset[curTest].expected;
+        var s = sheet.cssRules[0].style;
+        var n = 0;
+
+        // everything is set that should be
+        for (var name in expected) {
+          is(s.getPropertyValue(name), expected[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 expected, testset[curTest].rule,
+             "Unexpected item #" + i + ": " + s[i]);
+        }
+      } 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-page)");
+          isnot(sheet.cssRules[0].type, CSSRule.PAGE_RULE,
+                testset[curTest].rule + " rule type (1 non-page)");
+        }
+      }
+    } catch (e) {
+      ok(false, testset[curTest].rule, "During test: " + e);
+    }
+  }
+</script>
+</body>
+</html>
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -188,16 +188,17 @@
   display: block !important;
   background: white;
   box-shadow: 5px 5px 8px #202020;
   margin: 0.125in 0.25in;
 }
 
 *|*::-moz-pagecontent {
   display: block !important;
+  margin: auto;
 }
 
 *|*::-moz-pagebreak {
   display: block !important;
 }
 
 *|*::-moz-anonymous-positioned-block {
   display: block !important;