Bug 115199 - CSS2 @page rule style support. r=dbaron
☠☠ backed out by 0a7efaf3ac7a ☠ ☠
authorBrendan Dahl <bdahl@mozilla.com>
Fri, 31 Aug 2012 14:21:28 -0700
changeset 117802 0b6b3d2856a44d7ab71f6cfe1783fe77fd76141f
parent 117801 30776e402787760e09265f5bf4d651ab5292aa28
child 117803 fd7a0ace6b0e5090c8874ddb0db2a85c032859d8
push idunknown
push userunknown
push dateunknown
reviewersdbaron
bugs115199
milestone19.0a1
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
@@ -280,16 +280,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"
@@ -1611,16 +1612,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)
 
@@ -4320,16 +4324,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
@@ -974,16 +974,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
 
@@ -1024,16 +1025,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 =
@@ -2676,16 +2678,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;
@@ -2974,21 +2994,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
@@ -3001,32 +3023,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) {
@@ -3069,17 +3093,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) &&
@@ -3170,16 +3199,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 RuleProcessorData;
 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;