diff -rU5 a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp
--- a/layout/build/nsLayoutStatics.cpp	2018-03-04 21:20:10.903070400 +0800
+++ b/layout/build/nsLayoutStatics.cpp	2018-03-30 11:20:44.527254100 +0800
@@ -28,10 +28,11 @@
 #include "nsFrame.h"
 #include "nsGlobalWindow.h"
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "nsLayoutStylesheetCache.h"
+#include "mozilla/RuleProcessorCache.h"
 #include "nsRange.h"
 #include "nsRegion.h"
 #include "nsRepeatService.h"
 #include "nsFloatManager.h"
 #include "nsSprocketLayout.h"
@@ -357,10 +358,11 @@
   nsTextFragment::Shutdown();
 
   nsAttrValue::Shutdown();
   nsContentUtils::Shutdown();
   nsLayoutStylesheetCache::Shutdown();
+  RuleProcessorCache::Shutdown();
 
   ShutdownJSEnvironment();
   nsGlobalWindow::ShutDown();
   nsDOMClassInfo::ShutDown();
   nsListControlFrame::Shutdown();
diff -rU5 a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp
--- a/layout/style/CSSStyleSheet.cpp	2018-03-04 21:20:28.729834100 +0800
+++ b/layout/style/CSSStyleSheet.cpp	2018-03-30 11:16:53.813457100 +0800
@@ -15,10 +15,11 @@
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/MediaListBinding.h"
 #include "mozilla/css/NameSpaceRule.h"
 #include "mozilla/css/GroupRule.h"
 #include "mozilla/css/ImportRule.h"
+#include "nsCSSRules.h"
 #include "nsIMediaList.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsString.h"
@@ -41,10 +42,11 @@
 #include "nsMediaFeatures.h"
 #include "nsDOMClassInfoID.h"
 #include "mozilla/Likely.h"
 #include "mozilla/dom/CSSStyleSheetBinding.h"
 #include "nsComponentManagerUtils.h"
+#include "mozilla/RuleProcessorCache.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // -------------------------------
@@ -328,10 +330,96 @@
   }
 
   return true;
 }
 
+bool
+nsDocumentRuleResultCacheKey::AddMatchingRule(css::DocumentRule* aRule)
+{
+  MOZ_ASSERT(!mFinalized);
+  return mMatchingRules.AppendElement(aRule);
+}
+
+void
+nsDocumentRuleResultCacheKey::Finalize()
+{
+  mMatchingRules.Sort();
+#ifdef DEBUG
+  mFinalized = true;
+#endif
+}
+
+bool
+nsDocumentRuleResultCacheKey::Matches(
+                       nsPresContext* aPresContext,
+                       const nsTArray<css::DocumentRule*>& aRules) const
+{
+  MOZ_ASSERT(mFinalized);
+
+  // First check that aPresContext matches all the rules listed in
+  // mMatchingRules.
+  for (css::DocumentRule* rule : mMatchingRules) {
+    if (!rule->UseForPresentation(aPresContext)) {
+      return false;
+    }
+  }
+
+  // Then check that all the rules in aRules that aren't also in
+  // mMatchingRules do not match.
+
+  // pointer to matching rules
+  auto pm     = mMatchingRules.begin();
+  auto pm_end = mMatchingRules.end();
+
+  // pointer to all rules
+  auto pr     = aRules.begin();
+  auto pr_end = aRules.end();
+
+  // mMatchingRules and aRules are both sorted by their pointer values,
+  // so we can iterate over the two lists simultaneously.
+  while (pr < pr_end) {
+    while (pm < pm_end && *pm < *pr) ++pm;
+    if (pm >= pm_end || *pm != *pr) {
+      if ((*pr)->UseForPresentation(aPresContext)) {
+        return false;
+      }
+    }
+    ++pr;
+  }
+  return true;
+}
+
+#ifdef DEBUG
+void
+nsDocumentRuleResultCacheKey::List(FILE* aOut, int32_t aIndent) const
+{
+  for (css::DocumentRule* rule : mMatchingRules) {
+    nsCString str;
+
+    for (int32_t i = 0; i < aIndent; i++) {
+      str.AppendLiteral("  ");
+    }
+    str.AppendLiteral("{ ");
+
+    nsString condition;
+    rule->GetConditionText(condition);
+    AppendUTF16toUTF8(condition, str);
+
+    str.AppendLiteral(" }\n");
+    fprintf_stderr(aOut, "%s", str.get());
+  }
+}
+#endif
+
+size_t
+nsDocumentRuleResultCacheKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = 0;
+  n += mMatchingRules.SizeOfExcludingThis(aMallocSizeOf);
+  return n;
+}
+
 void
 nsMediaQuery::AppendToString(nsAString& aString) const
 {
   if (mHadUnknownExpression) {
     aString.AppendLiteral("not all");
@@ -980,10 +1068,11 @@
     mOwnerRule(nullptr),
     mDocument(nullptr),
     mOwningNode(nullptr),
     mDisabled(false),
     mDirty(false),
+    mInRuleProcessorCache(false),
     mScopeElement(nullptr),
     mRuleProcessors(nullptr)
 {
   mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy);
 }
@@ -998,10 +1087,11 @@
     mOwnerRule(aOwnerRuleToUse),
     mDocument(aDocumentToUse),
     mOwningNode(aOwningNodeToUse),
     mDisabled(aCopy.mDisabled),
     mDirty(aCopy.mDirty),
+    mInRuleProcessorCache(false),
     mScopeElement(nullptr),
     mInner(aCopy.mInner),
     mRuleProcessors(nullptr)
 {
 
@@ -1040,10 +1130,13 @@
   // away.
   if (mRuleProcessors) {
     NS_ASSERTION(mRuleProcessors->Length() == 0, "destructing sheet with rule processor reference");
     delete mRuleProcessors; // weak refs, should be empty here anyway
   }
+  if (mInRuleProcessorCache) {
+    RuleProcessorCache::RemoveSheet(this);
+  }
 }
 
 void
 CSSStyleSheet::DropRuleCollection()
 {
@@ -1588,14 +1681,22 @@
 #endif
 
 void 
 CSSStyleSheet::ClearRuleCascades()
 {
+  bool removedSheetFromRuleProcessorCache = false;
   if (mRuleProcessors) {
     nsCSSRuleProcessor **iter = mRuleProcessors->Elements(),
                        **end = iter + mRuleProcessors->Length();
     for(; iter != end; ++iter) {
+      if (!removedSheetFromRuleProcessorCache && (*iter)->IsShared()) {
+        // Since the sheet has been modified, we need to remove all
+        // RuleProcessorCache entries that contain this sheet, as the
+        // list of @-moz-document rules might have changed.
+        RuleProcessorCache::RemoveSheet(this);
+        removedSheetFromRuleProcessorCache = true;
+      }
       (*iter)->ClearRuleCascades();
     }
   }
   if (mParent) {
     CSSStyleSheet* parent = (CSSStyleSheet*)mParent;
diff -rU5 a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h
--- a/layout/style/CSSStyleSheet.h	2018-03-04 21:20:28.730334100 +0800
+++ b/layout/style/CSSStyleSheet.h	2018-03-30 11:15:41.773809300 +0800
@@ -242,10 +242,12 @@
   bool UseForPresentation(nsPresContext* aPresContext,
                             nsMediaQueryResultCacheKey& aKey) const;
 
   nsresult ParseSheet(const nsAString& aInput);
 
+  void SetInRuleProcessorCache() { mInRuleProcessorCache = true; }
+
   // nsIDOMStyleSheet interface
   NS_DECL_NSIDOMSTYLESHEET
 
   // nsIDOMCSSStyleSheet interface
   NS_DECL_NSIDOMCSSSTYLESHEET
@@ -360,10 +362,11 @@
   nsRefPtr<CSSRuleListImpl> mRuleCollection;
   nsIDocument*          mDocument; // weak ref; parents maintain this for their children
   nsINode*              mOwningNode; // weak ref
   bool                  mDisabled;
   bool                  mDirty; // has been modified 
+  bool                  mInRuleProcessorCache;
   nsRefPtr<dom::Element> mScopeElement;
 
   CSSStyleSheetInner*   mInner;
 
   nsAutoTArray<nsCSSRuleProcessor*, 8>* mRuleProcessors;
diff -rU5 a/layout/style/moz.build b/layout/style/moz.build
--- a/layout/style/moz.build	2018-03-04 21:20:28.796842600 +0800
+++ b/layout/style/moz.build	2018-03-30 11:19:34.055305300 +0800
@@ -61,10 +61,11 @@
 EXPORTS.mozilla += [
     'CSSStyleSheet.h',
     'CSSVariableDeclarations.h',
     'CSSVariableResolver.h',
     'CSSVariableValues.h',
+    'RuleProcessorCache.h',
     'StyleAnimationValue.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'CSS.h',
@@ -133,10 +134,11 @@
     'nsStyleSet.cpp',
     'nsStyleStruct.cpp',
     'nsStyleTransformMatrix.cpp',
     'nsStyleUtil.cpp',
     'nsTransitionManager.cpp',
+    'RuleProcessorCache.cpp',
     'StyleAnimationValue.cpp',
     'StyleRule.cpp',
     'SVGAttrAnimationRuleProcessor.cpp',
 ]
 
diff -rU5 a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp
--- a/layout/style/nsCSSRuleProcessor.cpp	2018-03-04 21:20:28.810844400 +0800
+++ b/layout/style/nsCSSRuleProcessor.cpp	2018-03-30 11:23:45.409723200 +0800
@@ -52,10 +52,11 @@
 #include "mozilla/EventStates.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/Likely.h"
 #include "mozilla/TypedEnumBits.h"
+#include "RuleProcessorCache.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled"
@@ -1047,19 +1048,24 @@
 
 nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets,
                                        uint8_t aSheetType,
                                        Element* aScopeElement,
                                        nsCSSRuleProcessor*
-                                         aPreviousCSSRuleProcessor)
+                                         aPreviousCSSRuleProcessor,
+                                       bool aIsShared)
   : mSheets(aSheets)
   , mRuleCascades(nullptr)
   , mPreviousCacheKey(aPreviousCSSRuleProcessor
                        ? aPreviousCSSRuleProcessor->CloneMQCacheKey()
                        : UniquePtr<nsMediaQueryResultCacheKey>())
   , mLastPresContext(nullptr)
   , mScopeElement(aScopeElement)
+  , mStyleSetRefCnt(0)
   , mSheetType(aSheetType)
+  , mIsShared(aIsShared)
+  , mMustGatherDocumentRules(aIsShared)
+  , mInRuleProcessorCache(false)
 {
   NS_ASSERTION(!!mScopeElement == (aSheetType == nsStyleSet::eScopedDocSheet),
                "aScopeElement must be specified iff aSheetType is "
                "eScopedDocSheet");
   for (sheet_array_type::size_type i = mSheets.Length(); i-- != 0; ) {
@@ -1067,10 +1073,14 @@
   }
 }
 
 nsCSSRuleProcessor::~nsCSSRuleProcessor()
 {
+  if (mInRuleProcessorCache) {
+    RuleProcessorCache::RemoveRuleProcessor(this);
+  }
+  MOZ_ASSERT(!mExpirationState.IsTracked());
   ClearSheets();
   ClearRuleCascades();
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCSSRuleProcessor)
@@ -3030,10 +3040,17 @@
 {
   if (!mPreviousCacheKey) {
     mPreviousCacheKey = CloneMQCacheKey();
   }
 
+  // No need to remove the rule processor from the RuleProcessorCache here,
+  // since CSSStyleSheet::ClearRuleCascades will have called
+  // RuleProcessorCache::RemoveSheet() passing itself, which will catch
+  // this rule processor (and any others for different @-moz-document
+  // cache key results).
+  MOZ_ASSERT(!RuleProcessorCache::HasRuleProcessor(this));
+
   // 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;
@@ -3327,20 +3344,26 @@
                   nsTArray<nsFontFaceRuleContainer>& aFontFaceRules,
                   nsTArray<nsCSSKeyframesRule*>& aKeyframesRules,
                   nsTArray<nsCSSFontFeatureValuesRule*>& aFontFeatureValuesRules,
                   nsTArray<nsCSSPageRule*>& aPageRules,
                   nsTArray<nsCSSCounterStyleRule*>& aCounterStyleRules,
+                  nsTArray<css::DocumentRule*>& aDocumentRules,
                   nsMediaQueryResultCacheKey& aKey,
-                  uint8_t aSheetType)
+                  nsDocumentRuleResultCacheKey& aDocumentKey,
+                  uint8_t aSheetType,
+                  bool aMustGatherDocumentRules)
     : mPresContext(aPresContext),
       mFontFaceRules(aFontFaceRules),
       mKeyframesRules(aKeyframesRules),
       mFontFeatureValuesRules(aFontFeatureValuesRules),
       mPageRules(aPageRules),
       mCounterStyleRules(aCounterStyleRules),
+      mDocumentRules(aDocumentRules),
       mCacheKey(aKey),
-      mSheetType(aSheetType)
+      mDocumentCacheKey(aDocumentKey),
+      mSheetType(aSheetType),
+      mMustGatherDocumentRules(aMustGatherDocumentRules)
   {
     PL_DHashTableInit(&mRulesByWeight, &gRulesByWeightOps,
                       sizeof(RuleByWeightEntry), 32);
 
     // Initialize our arena
@@ -3359,18 +3382,50 @@
   nsTArray<nsFontFaceRuleContainer>& mFontFaceRules;
   nsTArray<nsCSSKeyframesRule*>& mKeyframesRules;
   nsTArray<nsCSSFontFeatureValuesRule*>& mFontFeatureValuesRules;
   nsTArray<nsCSSPageRule*>& mPageRules;
   nsTArray<nsCSSCounterStyleRule*>& mCounterStyleRules;
+  nsTArray<css::DocumentRule*>& mDocumentRules;
   nsMediaQueryResultCacheKey& mCacheKey;
+  nsDocumentRuleResultCacheKey& mDocumentCacheKey;
   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;
+  bool mMustGatherDocumentRules;
 };
 
+static bool
+GatherDocRuleEnumFunc(css::Rule* aRule, void* aData)
+{
+  CascadeEnumData* data = (CascadeEnumData*)aData;
+  int32_t type = aRule->GetType();
+
+  MOZ_ASSERT(data->mMustGatherDocumentRules,
+             "should only call GatherDocRuleEnumFunc if "
+             "mMustGatherDocumentRules is true");
+
+  if (css::Rule::MEDIA_RULE == type ||
+      css::Rule::SUPPORTS_RULE == type) {
+    css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
+    if (!groupRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData))
+      return false;
+  }
+  else if (css::Rule::DOCUMENT_RULE == type) {
+    css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+    if (!data->mDocumentRules.AppendElement(docRule))
+      return false;
+    if (docRule->UseForPresentation(data->mPresContext))
+      if (!data->mDocumentCacheKey.AddMatchingRule(docRule))
+        return false;
+    if (!docRule->EnumerateRulesForwards(GatherDocRuleEnumFunc, aData))
+      return false;
+  }
+  return true;
+}
+
 /*
  * 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
@@ -3409,16 +3464,38 @@
         entry->data.mTail = &newItem->mNext;
       }
     }
   }
   else if (css::Rule::MEDIA_RULE == type ||
-           css::Rule::DOCUMENT_RULE == type ||
            css::Rule::SUPPORTS_RULE == type) {
     css::GroupRule* groupRule = static_cast<css::GroupRule*>(aRule);
-    if (groupRule->UseForPresentation(data->mPresContext, data->mCacheKey))
-      if (!groupRule->EnumerateRulesForwards(CascadeRuleEnumFunc, aData))
+    const bool use =
+      groupRule->UseForPresentation(data->mPresContext, data->mCacheKey);
+    if (use || data->mMustGatherDocumentRules) {
+      if (!groupRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc :
+                                                   GatherDocRuleEnumFunc,
+                                             aData))
         return false;
+    }
+  }
+  else if (css::Rule::DOCUMENT_RULE == type) {
+    css::DocumentRule* docRule = static_cast<css::DocumentRule*>(aRule);
+    if (data->mMustGatherDocumentRules) {
+      if (!data->mDocumentRules.AppendElement(docRule))
+        return false;
+    }
+    const bool use = docRule->UseForPresentation(data->mPresContext);
+    if (use && data->mMustGatherDocumentRules) {
+      if (!data->mDocumentCacheKey.AddMatchingRule(docRule))
+        return false;
+    }
+    if (use || data->mMustGatherDocumentRules) {
+      if (!docRule->EnumerateRulesForwards(use ? CascadeRuleEnumFunc
+                                               : GatherDocRuleEnumFunc,
+                                           aData))
+        return false;
+    }
   }
   else if (css::Rule::FONT_FACE_RULE == type) {
     nsCSSFontFaceRule *fontFaceRule = static_cast<nsCSSFontFaceRule*>(aRule);
     nsFontFaceRuleContainer *ptr = data->mFontFaceRules.AppendElement();
     if (!ptr)
@@ -3561,12 +3638,16 @@
       CascadeEnumData data(aPresContext, newCascade->mFontFaceRules,
                            newCascade->mKeyframesRules,
                            newCascade->mFontFeatureValuesRules,
                            newCascade->mPageRules,
                            newCascade->mCounterStyleRules,
+                           mDocumentRules,
                            newCascade->mCacheKey,
-                           mSheetType);
+                           mDocumentCacheKey,
+                           mSheetType,
+                           mMustGatherDocumentRules);
+
       if (!data.mRulesByWeight.IsInitialized())
         return; /* out of memory */
 
       for (uint32_t i = 0; i < mSheets.Length(); ++i) {
         if (!CascadeSheet(mSheets.ElementAt(i), &data))
@@ -3606,10 +3687,16 @@
            iEnd = newCascade->mCounterStyleRules.Length(); i < iEnd; ++i) {
         nsCSSCounterStyleRule* rule = newCascade->mCounterStyleRules[i];
         newCascade->mCounterStyleRuleTable.Put(rule->GetName(), rule);
       }
 
+      if (mMustGatherDocumentRules) {
+        mDocumentRules.Sort();
+        mDocumentCacheKey.Finalize();
+        mMustGatherDocumentRules = false;
+      }
+
       // Ensure that the current one is always mRuleCascades.
       newCascade->mNext = mRuleCascades;
       mRuleCascades = newCascade.forget();
     }
   }
@@ -3643,10 +3730,43 @@
   }
 
   return false;
 }
 
+nsTArray<css::DocumentRule*>&&
+nsCSSRuleProcessor::TakeDocumentRules()
+{
+  MOZ_ASSERT(mIsShared);
+  return Move(mDocumentRules);
+}
+
+nsDocumentRuleResultCacheKey
+nsCSSRuleProcessor::GetDocumentCacheKey(nsPresContext* aPresContext)
+{
+  MOZ_ASSERT(mIsShared);
+  GetRuleCascade(aPresContext);
+  return mDocumentCacheKey;
+}
+
+void
+nsCSSRuleProcessor::AddStyleSetRef()
+{
+  MOZ_ASSERT(mIsShared);
+  if (++mStyleSetRefCnt == 1) {
+    RuleProcessorCache::StopTracking(this);
+  }
+}
+
+void
+nsCSSRuleProcessor::ReleaseStyleSetRef()
+{
+  MOZ_ASSERT(mIsShared);
+  if (--mStyleSetRefCnt == 0 && mInRuleProcessorCache) {
+    RuleProcessorCache::StartTracking(this);
+  }
+}
+
 // TreeMatchContext and AncestorFilter out of line methods
 void
 TreeMatchContext::InitAncestors(Element *aElement)
 {
   MOZ_ASSERT(!mAncestorFilter.mFilter);
diff -rU5 a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h
--- a/layout/style/nsCSSRuleProcessor.h	2018-03-04 21:20:28.810844400 +0800
+++ b/layout/style/nsCSSRuleProcessor.h	2018-03-30 11:23:45.411723500 +0800
@@ -14,12 +14,14 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsIStyleRuleProcessor.h"
+#include "nsIMediaList.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "nsExpirationTracker.h"
 #include "nsRuleWalker.h"
 #include "mozilla/UniquePtr.h"
 
 struct CascadeEnumData;
 struct nsCSSSelector;
@@ -31,10 +33,13 @@
 class nsCSSFontFeatureValuesRule;
 class nsCSSCounterStyleRule;
 
 namespace mozilla {
 class CSSStyleSheet;
+namespace css {
+class DocumentRule;
+} // namespace css
 } // namespace mozilla
 
 /**
  * The CSS style rule processor provides a mechanism for sibling style
  * sheets to combine their rule processing in order to allow proper
@@ -55,11 +60,12 @@
   // aPreviousCSSRuleProcessor is the rule processor (if any) that this
   // one is replacing.
   nsCSSRuleProcessor(const sheet_array_type& aSheets,
                      uint8_t aSheetType,
                      mozilla::dom::Element* aScopeElement,
-                     nsCSSRuleProcessor* aPreviousCSSRuleProcessor);
+                     nsCSSRuleProcessor* aPreviousCSSRuleProcessor,
+                     bool aIsShared = false);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor)
 
 public:
@@ -157,10 +163,25 @@
    * processor is for.  If this is not a rule processor for scoped style
    * sheets, it returns null.
    */
   mozilla::dom::Element* GetScopeElement() const { return mScopeElement; }
 
+  nsTArray<mozilla::css::DocumentRule*>&& TakeDocumentRules();
+  nsDocumentRuleResultCacheKey GetDocumentCacheKey(nsPresContext* aPresContext);
+
+  bool IsShared() const { return mIsShared; }
+
+  nsExpirationState* GetExpirationState() { return &mExpirationState; }
+  void AddStyleSetRef();
+  void ReleaseStyleSetRef();
+  void SetInRuleProcessorCache(bool aVal) {
+    MOZ_ASSERT(mIsShared);
+    printf("%p SetInRuleProcessorCache %d\n", this, aVal);
+    mInRuleProcessorCache = aVal;
+  }
+  bool IsInRuleProcessorCache() const { return mInRuleProcessorCache; }
+
 #ifdef DEBUG
   void AssertQuirksChangeOK() {
     NS_ASSERTION(!mRuleCascades, "can't toggle quirks style sheet without "
                                  "clearing rule cascades");
   }
@@ -217,13 +238,23 @@
 
   // The scope element for this rule processor's scoped style sheets.
   // Only used if mSheetType == nsStyleSet::eScopedDocSheet.
   nsRefPtr<mozilla::dom::Element> mScopeElement;
 
+  nsTArray<mozilla::css::DocumentRule*> mDocumentRules;
+  nsDocumentRuleResultCacheKey mDocumentCacheKey;
+
+  nsExpirationState mExpirationState;
+  int32_t mStyleSetRefCnt;
+
   // type of stylesheet using this processor
   uint8_t mSheetType;  // == nsStyleSet::sheetType
 
+  bool mIsShared;
+  bool mMustGatherDocumentRules;
+  bool mInRuleProcessorCache;
+
 #ifdef XP_WIN
   static uint8_t sWinThemeId;
 #endif
 };
 
diff -rU5 a/layout/style/nsCSSRules.cpp b/layout/style/nsCSSRules.cpp
--- a/layout/style/nsCSSRules.cpp	2018-03-04 21:20:28.811344400 +0800
+++ b/layout/style/nsCSSRules.cpp	2018-03-30 11:14:41.256124500 +0800
@@ -1146,10 +1146,16 @@
 // GroupRule interface
 /* virtual */ bool
 DocumentRule::UseForPresentation(nsPresContext* aPresContext,
                                  nsMediaQueryResultCacheKey& aKey)
 {
+  return UseForPresentation(aPresContext);
+}
+
+bool
+DocumentRule::UseForPresentation(nsPresContext* aPresContext)
+{
   nsIDocument *doc = aPresContext->Document();
   nsIURI *docURI = doc->GetDocumentURI();
   nsAutoCString docURISpec;
   if (docURI)
     docURI->GetSpec(docURISpec);
diff -rU5 a/layout/style/nsCSSRules.h b/layout/style/nsCSSRules.h
--- a/layout/style/nsCSSRules.h	2018-03-04 21:20:28.811844500 +0800
+++ b/layout/style/nsCSSRules.h	2018-03-30 11:14:41.257624700 +0800
@@ -145,10 +145,12 @@
 
   // rest of GroupRule
   virtual bool UseForPresentation(nsPresContext* aPresContext,
                                     nsMediaQueryResultCacheKey& aKey) override;
 
+  bool UseForPresentation(nsPresContext* aPresContext);
+
   enum Function {
     eURL,
     eURLPrefix,
     eDomain,
     eRegExp
diff -rU5 a/layout/style/nsIMediaList.h b/layout/style/nsIMediaList.h
--- a/layout/style/nsIMediaList.h	2018-03-04 21:20:28.823846000 +0800
+++ b/layout/style/nsIMediaList.h	2018-03-30 11:14:41.277627200 +0800
@@ -24,10 +24,13 @@
 class nsAString;
 struct nsMediaFeature;
 
 namespace mozilla {
 class CSSStyleSheet;
+namespace css {
+class DocumentRule;
+} // namespace css
 } // namespace mozilla
 
 struct nsMediaExpression {
   enum Range { eMin, eMax, eEqual };
 
@@ -127,10 +130,45 @@
   };
   nsCOMPtr<nsIAtom> mMedium;
   nsTArray<FeatureEntry> mFeatureCache;
 };
 
+class nsDocumentRuleResultCacheKey
+{
+public:
+#ifdef DEBUG
+  nsDocumentRuleResultCacheKey()
+    : mFinalized(false) {}
+#endif
+
+  bool AddMatchingRule(mozilla::css::DocumentRule* aRule);
+  bool Matches(nsPresContext* aPresContext,
+               const nsTArray<mozilla::css::DocumentRule*>& aRules) const;
+
+  bool operator==(const nsDocumentRuleResultCacheKey& aOther) const {
+    MOZ_ASSERT(mFinalized);
+    return mMatchingRules == aOther.mMatchingRules;
+  }
+  bool operator!=(const nsDocumentRuleResultCacheKey& aOther) const {
+    return !(*this == aOther);
+  }
+
+  void Finalize();
+
+#ifdef DEBUG
+  void List(FILE* aOut = stdout, int32_t aIndex = 0) const;
+#endif
+
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+  nsTArray<mozilla::css::DocumentRule*> mMatchingRules;
+#ifdef DEBUG
+  bool mFinalized;
+#endif
+};
+
 class nsMediaQuery {
 public:
   nsMediaQuery()
     : mNegated(false)
     , mHasOnly(false)
diff -rU5 a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp
--- a/layout/style/nsStyleSet.cpp	2018-03-04 21:20:28.834347400 +0800
+++ b/layout/style/nsStyleSet.cpp	2018-03-30 11:23:54.262347400 +0800
@@ -13,10 +13,11 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CSSStyleSheet.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/RuleProcessorCache.h"
 #include "nsIDocumentInlines.h"
 #include "nsRuleWalker.h"
 #include "nsStyleContext.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsCSSAnonBoxes.h"
@@ -167,11 +168,20 @@
 {
   size_t n = aMallocSizeOf(this);
 
   for (int i = 0; i < eSheetTypeCount; i++) {
     if (mRuleProcessors[i]) {
-      n += mRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf);
+      bool shared = false;
+      if (i == eAgentSheet || i == eUserSheet) {
+        // The only two origins we consider caching rule processors for.
+        nsCSSRuleProcessor* rp =
+          static_cast<nsCSSRuleProcessor*>(mRuleProcessors[i].get());
+        shared = rp->IsShared();
+      }
+      if (!shared) {
+        n += mRuleProcessors[i]->SizeOfIncludingThis(aMallocSizeOf);
+      }
     }
     // mSheets is a C-style array of nsCOMArrays.  We do not own the sheets in
     // the nsCOMArrays (either the nsLayoutStyleSheetCache singleton or our
     // document owns them) so we do not count the sheets here (we pass nullptr
     // as the aSizeOfElementIncludingThis argument).  All we're doing here is
@@ -349,16 +359,40 @@
   for (uint32_t i = 0; i < n; i++) {
     aSheets[i] = sheets[i].mSheet;
   }
 }
 
+nsStyleSet::~nsStyleSet()
+{
+  // drop reference to cached rule processors
+  nsCSSRuleProcessor* rp;
+  rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[eAgentSheet].get());
+  if (rp) {
+    MOZ_ASSERT(rp->IsShared());
+    rp->ReleaseStyleSetRef();
+  }
+  rp = static_cast<nsCSSRuleProcessor*>(mRuleProcessors[eUserSheet].get());
+  if (rp) {
+    MOZ_ASSERT(rp->IsShared());
+    rp->ReleaseStyleSetRef();
+  }
+}
+
 nsresult
 nsStyleSet::GatherRuleProcessors(sheetType aType)
 {
   nsCOMPtr<nsIStyleRuleProcessor> oldRuleProcessor(mRuleProcessors[aType]);
   nsTArray<nsCOMPtr<nsIStyleRuleProcessor>> oldScopedDocRuleProcessors;
-
+  if (aType == eAgentSheet || aType == eUserSheet) {
+    // drop reference to cached rule processor
+    nsCSSRuleProcessor* rp =
+      static_cast<nsCSSRuleProcessor*>(mRuleProcessors[aType].get());
+    if (rp) {
+      MOZ_ASSERT(rp->IsShared());
+      rp->ReleaseStyleSetRef();
+    }
+  }
   mRuleProcessors[aType] = nullptr;
   if (aType == eScopedDocSheet) {
     for (uint32_t i = 0; i < mScopedDocSheetRuleProcessors.Length(); i++) {
       nsIStyleRuleProcessor* processor = mScopedDocSheetRuleProcessors[i].get();
       Element* scope =
@@ -467,14 +501,43 @@
     return NS_OK;
   }
   if (mSheets[aType].Count()) {
     switch (aType) {
       case eAgentSheet:
-      case eUserSheet:
+      case eUserSheet: {
+        // levels containing non-scoped CSS style sheets whose rule processors
+        // we want to re-use
+        nsCOMArray<nsIStyleSheet>& sheets = mSheets[aType];
+        nsTArray<nsRefPtr<CSSStyleSheet>> cssSheets(sheets.Count());
+        for (int32_t i = 0, i_end = sheets.Count(); i < i_end; ++i) {
+          nsRefPtr<CSSStyleSheet> cssSheet = do_QueryObject(sheets[i]);
+          NS_ASSERTION(cssSheet, "not a CSS sheet");
+          cssSheets.AppendElement(cssSheet);
+        }
+        nsTArray<CSSStyleSheet*> cssSheetsRaw(cssSheets.Length());
+        for (int32_t i = 0, i_end = cssSheets.Length(); i < i_end; ++i) {
+          cssSheetsRaw.AppendElement(cssSheets[i]);
+        }
+        nsCSSRuleProcessor* rp =
+          RuleProcessorCache::GetRuleProcessor(cssSheetsRaw, PresContext());
+        if (!rp) {
+          rp = new nsCSSRuleProcessor(cssSheets, uint8_t(aType), nullptr,
+                                      static_cast<nsCSSRuleProcessor*>(
+                                       oldRuleProcessor.get()),
+                                      true /* aIsShared */);
+          RuleProcessorCache::PutRuleProcessor(
+              cssSheetsRaw, rp->TakeDocumentRules(),
+              rp->GetDocumentCacheKey(PresContext()), rp);
+        }
+        mRuleProcessors[aType] = rp;
+        rp->AddStyleSetRef();
+        break;
+      }
       case eDocSheet:
       case eOverrideSheet: {
-        // levels containing CSS stylesheets (apart from eScopedDocSheet)
+        // levels containing non-scoped CSS stylesheets whose rule processors
+        // we don't want to re-use
         nsCOMArray<nsIStyleSheet>& sheets = mSheets[aType];
         nsTArray<nsRefPtr<CSSStyleSheet>> cssSheets(sheets.Count());
         for (int32_t i = 0, i_end = sheets.Count(); i < i_end; ++i) {
           nsRefPtr<CSSStyleSheet> cssSheet = do_QueryObject(sheets[i]);
           NS_ASSERTION(cssSheet, "not a CSS sheet");
diff -rU5 a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h
--- a/layout/style/nsStyleSet.h	2018-03-04 21:20:28.834347400 +0800
+++ b/layout/style/nsStyleSet.h	2018-03-30 11:23:54.263847600 +0800
@@ -84,10 +84,11 @@
 
 class nsStyleSet
 {
  public:
   nsStyleSet();
+  ~nsStyleSet();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   void Init(nsPresContext *aPresContext);
 
--- /dev/null	2018-03-30 11:36:51.000000000 +0800
+++ b/layout/style/RuleProcessorCache.cpp	2018-03-30 11:15:41.775809500 +0800
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "RuleProcessorCache.h"
+
+#include <algorithm>
+#include "nsCSSRuleProcessor.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(RuleProcessorCache, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(RuleProcessorCacheMallocSizeOf)
+
+NS_IMETHODIMP
+RuleProcessorCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+                                   nsISupports* aData, bool aAnonymize)
+{
+  return MOZ_COLLECT_REPORT(
+    "explicit/layout/rule-processor-cache", KIND_HEAP, UNITS_BYTES,
+    SizeOfIncludingThis(RuleProcessorCacheMallocSizeOf),
+    "Memory used for cached rule processors.");
+}
+
+RuleProcessorCache::~RuleProcessorCache()
+{
+  UnregisterWeakMemoryReporter(this);
+
+  for (Entry& e : mEntries) {
+    for (DocumentEntry& de : e.mDocumentEntries) {
+      if (de.mRuleProcessor->GetExpirationState()->IsTracked()) {
+        mExpirationTracker.RemoveObject(de.mRuleProcessor);
+      }
+      de.mRuleProcessor->SetInRuleProcessorCache(false);
+    }
+  }
+}
+
+void
+RuleProcessorCache::InitMemoryReporter()
+{
+  RegisterWeakMemoryReporter(this);
+}
+
+/* static */ bool
+RuleProcessorCache::EnsureGlobal()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gShutdown) {
+    return false;
+  }
+
+  if (!gRuleProcessorCache) {
+    gRuleProcessorCache = new RuleProcessorCache;
+    gRuleProcessorCache->InitMemoryReporter();
+  }
+  return true;
+}
+
+/* static */ void
+RuleProcessorCache::RemoveSheet(CSSStyleSheet* aSheet)
+{
+  if (!EnsureGlobal()) {
+    return;
+  }
+  gRuleProcessorCache->DoRemoveSheet(aSheet);
+}
+
+#ifdef DEBUG
+/* static */ bool
+RuleProcessorCache::HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (!EnsureGlobal()) {
+    return false;
+  }
+  return gRuleProcessorCache->DoHasRuleProcessor(aRuleProcessor);
+}
+#endif
+
+/* static */ void
+RuleProcessorCache::RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (!EnsureGlobal()) {
+    return;
+  }
+  gRuleProcessorCache->DoRemoveRuleProcessor(aRuleProcessor);
+}
+
+/* static */ nsCSSRuleProcessor*
+RuleProcessorCache::GetRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+                                     nsPresContext* aPresContext)
+{
+  if (!EnsureGlobal()) {
+    return nullptr;
+  }
+  return gRuleProcessorCache->DoGetRuleProcessor(aSheets, aPresContext);
+}
+
+/* static */ void
+RuleProcessorCache::PutRuleProcessor(
+    const nsTArray<CSSStyleSheet*>& aSheets,
+    nsTArray<css::DocumentRule*>&& aDocumentRules,
+    const nsDocumentRuleResultCacheKey& aCacheKey,
+    nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (!EnsureGlobal()) {
+    return;
+  }
+  gRuleProcessorCache->DoPutRuleProcessor(aSheets, Move(aDocumentRules),
+                                          aCacheKey, aRuleProcessor);
+}
+
+/* static */ void
+RuleProcessorCache::StartTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (!EnsureGlobal()) {
+    return;
+  }
+  return gRuleProcessorCache->DoStartTracking(aRuleProcessor);
+}
+
+/* static */ void
+RuleProcessorCache::StopTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (!EnsureGlobal()) {
+    return;
+  }
+  return gRuleProcessorCache->DoStopTracking(aRuleProcessor);
+}
+
+void
+RuleProcessorCache::DoRemoveSheet(CSSStyleSheet* aSheet)
+{
+  auto last = std::remove_if(mEntries.begin(), mEntries.end(), HasSheet(aSheet));
+  for (auto it = last; it < mEntries.end(); it++) {
+    for (DocumentEntry& de : it->mDocumentEntries) {
+      de.mRuleProcessor->SetInRuleProcessorCache(false);
+      mExpirationTracker.RemoveFromTracker(de.mRuleProcessor);
+    }
+  }
+  mEntries.TruncateLength(last - mEntries.begin());
+}
+
+nsCSSRuleProcessor*
+RuleProcessorCache::DoGetRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+                                       nsPresContext* aPresContext)
+{
+  for (Entry& e : mEntries) {
+    if (e.mSheets == aSheets) {
+      for (DocumentEntry& de : e.mDocumentEntries) {
+        if (de.mCacheKey.Matches(aPresContext, e.mDocumentRules)) {
+          return de.mRuleProcessor;
+        }
+      }
+      // Entry::mSheets is unique; if we matched aSheets but didn't
+      // find a matching DocumentEntry, we won't find one later in
+      // mEntries.
+      return nullptr;
+    }
+  }
+  return nullptr;
+}
+
+void
+RuleProcessorCache::DoPutRuleProcessor(
+    const nsTArray<CSSStyleSheet*>& aSheets,
+    nsTArray<css::DocumentRule*>&& aDocumentRules,
+    const nsDocumentRuleResultCacheKey& aCacheKey,
+    nsCSSRuleProcessor* aRuleProcessor)
+{
+  Entry* entry = nullptr;
+  for (Entry& e : mEntries) {
+    if (e.mSheets == aSheets) {
+      entry = &e;
+      break;
+    }
+  }
+
+  if (!entry) {
+    entry = mEntries.AppendElement();
+    entry->mSheets = aSheets;
+    entry->mDocumentRules = aDocumentRules;
+    for (CSSStyleSheet* sheet : aSheets) {
+      sheet->SetInRuleProcessorCache();
+    }
+  } else {
+    MOZ_ASSERT(entry->mDocumentRules == aDocumentRules,
+               "DocumentRule array shouldn't have changed");
+  }
+
+#ifdef DEBUG
+  for (DocumentEntry& de : entry->mDocumentEntries) {
+    MOZ_ASSERT(de.mCacheKey != aCacheKey,
+               "should not have duplicate document cache keys");
+  }
+#endif
+
+  DocumentEntry* documentEntry = entry->mDocumentEntries.AppendElement();
+  documentEntry->mCacheKey = aCacheKey;
+  documentEntry->mRuleProcessor = aRuleProcessor;
+  aRuleProcessor->SetInRuleProcessorCache(true);
+}
+
+#ifdef DEBUG
+bool
+RuleProcessorCache::DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+  for (Entry& e : mEntries) {
+    for (DocumentEntry& de : e.mDocumentEntries) {
+      if (de.mRuleProcessor == aRuleProcessor) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+#endif
+
+void
+RuleProcessorCache::DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor)
+{
+  aRuleProcessor->SetInRuleProcessorCache(false);
+  mExpirationTracker.RemoveFromTracker(aRuleProcessor);
+  for (Entry& e : mEntries) {
+    for (size_t i = 0; i < e.mDocumentEntries.Length(); i++) {
+      if (e.mDocumentEntries[i].mRuleProcessor == aRuleProcessor) {
+        e.mDocumentEntries.RemoveElementAt(i);
+        return;
+      }
+    }
+  }
+}
+
+void
+RuleProcessorCache::DoStartTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+  mExpirationTracker.AddObject(aRuleProcessor);
+}
+
+void
+RuleProcessorCache::DoStopTracking(nsCSSRuleProcessor* aRuleProcessor)
+{
+  mExpirationTracker.RemoveFromTracker(aRuleProcessor);
+}
+
+size_t
+RuleProcessorCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+  size_t n = aMallocSizeOf(this);
+
+  int count = 0;
+  n += mEntries.SizeOfExcludingThis(aMallocSizeOf);
+  for (Entry& e : mEntries) {
+    n += e.mDocumentEntries.SizeOfExcludingThis(aMallocSizeOf);
+    for (DocumentEntry& de : e.mDocumentEntries) {
+      count++;
+      n += de.mRuleProcessor->SizeOfIncludingThis(aMallocSizeOf);
+    }
+  }
+
+  return n;
+}
+
+void
+RuleProcessorCache::ExpirationTracker::RemoveFromTracker(
+    nsCSSRuleProcessor* aRuleProcessor)
+{
+  if (aRuleProcessor->GetExpirationState()->IsTracked()) {
+    RemoveObject(aRuleProcessor);
+  }
+}
+
+bool RuleProcessorCache::gShutdown = false;
+mozilla::StaticRefPtr<RuleProcessorCache> RuleProcessorCache::gRuleProcessorCache;
--- /dev/null	2018-03-30 11:36:56.000000000 +0800
+++ b/layout/style/RuleProcessorCache.h	2018-03-30 11:15:41.791811600 +0800
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_RuleProcessorCache_h
+#define mozilla_RuleProcessorCache_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+#include "nsExpirationTracker.h"
+#include "nsIMediaList.h"
+#include "nsIMemoryReporter.h"
+#include "nsTArray.h"
+
+class nsCSSRuleProcessor;
+namespace mozilla {
+class CSSStyleSheet;
+namespace css {
+class DocumentRule;
+}
+}
+
+namespace mozilla {
+
+class RuleProcessorCache final : public nsIMemoryReporter
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMEMORYREPORTER
+
+public:
+  static nsCSSRuleProcessor* GetRuleProcessor(
+      const nsTArray<CSSStyleSheet*>& aSheets,
+      nsPresContext* aPresContext);
+  static void PutRuleProcessor(
+      const nsTArray<CSSStyleSheet*>& aSheets,
+      nsTArray<css::DocumentRule*>&& aDocumentRules,
+      const nsDocumentRuleResultCacheKey& aCacheKey,
+      nsCSSRuleProcessor* aRuleProcessor);
+  static void StartTracking(nsCSSRuleProcessor* aRuleProcessor);
+  static void StopTracking(nsCSSRuleProcessor* aRuleProcessor);
+
+#ifdef DEBUG
+  static bool HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+#endif
+  static void RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+  static void RemoveSheet(CSSStyleSheet* aSheet);
+
+  static void Shutdown() { gShutdown = true; gRuleProcessorCache = nullptr; }
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+  class ExpirationTracker : public nsExpirationTracker<nsCSSRuleProcessor,3>
+  {
+  public:
+    ExpirationTracker(RuleProcessorCache* aCache)
+      : nsExpirationTracker<nsCSSRuleProcessor,3>(10000)
+      , mCache(aCache) {}
+
+    void RemoveFromTracker(nsCSSRuleProcessor* aRuleProcessor);
+
+    virtual void NotifyExpired(nsCSSRuleProcessor* aRuleProcessor) override {
+      mCache->RemoveRuleProcessor(aRuleProcessor);
+    }
+
+  private:
+    RuleProcessorCache* mCache;
+  };
+
+  RuleProcessorCache() : mExpirationTracker(this) {}
+  ~RuleProcessorCache();
+
+  void InitMemoryReporter();
+
+  static bool EnsureGlobal();
+  static StaticRefPtr<RuleProcessorCache> gRuleProcessorCache;
+  static bool gShutdown;
+
+  void DoRemoveSheet(CSSStyleSheet* aSheet);
+  nsCSSRuleProcessor* DoGetRuleProcessor(
+      const nsTArray<CSSStyleSheet*>& aSheets,
+      nsPresContext* aPresContext);
+  void DoPutRuleProcessor(const nsTArray<CSSStyleSheet*>& aSheets,
+                          nsTArray<css::DocumentRule*>&& aDocumentRules,
+                          const nsDocumentRuleResultCacheKey& aCacheKey,
+                          nsCSSRuleProcessor* aRuleProcessor);
+#ifdef DEBUG
+  bool DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+#endif
+  void DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor);
+  void DoStartTracking(nsCSSRuleProcessor* aRuleProcessor);
+  void DoStopTracking(nsCSSRuleProcessor* aRuleProcessor);
+
+  size_t DoSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+  struct DocumentEntry {
+    nsDocumentRuleResultCacheKey mCacheKey;
+    nsRefPtr<nsCSSRuleProcessor> mRuleProcessor;
+  };
+
+  struct Entry {
+    nsTArray<CSSStyleSheet*>     mSheets;
+    nsTArray<css::DocumentRule*> mDocumentRules;
+    nsTArray<DocumentEntry>      mDocumentEntries;
+  };
+
+  // Function object to test whether an Entry object has a given sheet
+  // in its mSheets array.
+  struct HasSheet {
+    HasSheet(CSSStyleSheet* aSheet) : mSheet(aSheet) {}
+    CSSStyleSheet* mSheet;
+    bool operator()(const Entry& aEntry)
+    { return aEntry.mSheets.Contains(mSheet); }
+  };
+
+  ExpirationTracker mExpirationTracker;
+  nsTArray<Entry> mEntries;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RuleProcessorCache_h
