Bug 1420762: Add some notification methods to StyleSheet to enable per-StyleSet notifications. r=heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 28 Nov 2017 01:50:45 +0100
changeset 704230 9b06fb69f41a9bb97ec84d7b9a29be8fd3166256
parent 703941 f5f03ee9e6abf77964f8dc1b9d69c6ccd3f655fd
child 742038 69a7c6179f14a22d06168972c097e6c22ba19a92
push id91117
push userbmo:emilio@crisal.io
push dateTue, 28 Nov 2017 11:38:29 +0000
reviewersheycam
bugs1420762
milestone59.0a1
Bug 1420762: Add some notification methods to StyleSheet to enable per-StyleSet notifications. r=heycam This is also the first step for the rule map to not process all the stylesheets for the document, which would be a mess with shadow DOM. Far from the final, ideal state, but hey, progress. MozReview-Commit-ID: 7TrifME9VZ
dom/base/nsDocument.cpp
dom/base/nsIDocumentObserver.h
layout/base/PresShell.cpp
layout/base/PresShell.h
layout/inspector/ServoStyleRuleMap.cpp
layout/inspector/ServoStyleRuleMap.h
layout/style/CSSStyleSheet.cpp
layout/style/CSSStyleSheet.h
layout/style/GroupRule.cpp
layout/style/MediaList.cpp
layout/style/ServoKeyframeRule.cpp
layout/style/ServoKeyframesRule.cpp
layout/style/ServoStyleRule.cpp
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/ServoStyleSheet.cpp
layout/style/StyleRule.cpp
layout/style/StyleSetHandle.h
layout/style/StyleSetHandleInlines.h
layout/style/StyleSheet.cpp
layout/style/StyleSheet.h
layout/style/nsCSSCounterStyleRule.cpp
layout/style/nsCSSRules.cpp
layout/style/nsStyleSet.cpp
layout/style/nsStyleSet.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4729,16 +4729,18 @@ nsDocument::InsertStyleSheetAt(StyleShee
 
 void
 nsDocument::SetStyleSheetApplicableState(StyleSheet* aSheet,
                                          bool aApplicable)
 {
   NS_PRECONDITION(aSheet, "null arg");
 
   // If we're actually in the document style sheet list
+  //
+  // FIXME(emilio): Shadow DOM.
   MOZ_DIAGNOSTIC_ASSERT(aSheet->IsServo() == IsStyledByServo());
   if (mStyleSheets.IndexOf(aSheet) != mStyleSheets.NoIndex) {
     if (aApplicable) {
       AddStyleSheetToStyleSets(aSheet);
     } else {
       RemoveStyleSheetFromStyleSets(aSheet);
     }
   }
@@ -5751,55 +5753,52 @@ nsDocument::DocumentStatesChanged(EventS
   // Invalidate our cached state.
   mGotDocumentState &= ~aStateMask;
   mDocumentState &= ~aStateMask;
 
   NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask));
 }
 
 void
-nsDocument::StyleRuleChanged(StyleSheet* aSheet,
-                             css::Rule* aStyleRule)
-{
-  NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged, (aSheet, aStyleRule));
-
-  if (StyleSheetChangeEventsEnabled()) {
-    DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
-                               "StyleRuleChanged",
-                               mRule,
-                               aStyleRule);
-  }
-}
-
-void
-nsDocument::StyleRuleAdded(StyleSheet* aSheet,
-                           css::Rule* aStyleRule)
-{
-  NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded, (aSheet, aStyleRule));
-
-  if (StyleSheetChangeEventsEnabled()) {
-    DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
-                               "StyleRuleAdded",
-                               mRule,
-                               aStyleRule);
-  }
-}
-
-void
-nsDocument::StyleRuleRemoved(StyleSheet* aSheet,
-                             css::Rule* aStyleRule)
-{
-  NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved, (aSheet, aStyleRule));
-
-  if (StyleSheetChangeEventsEnabled()) {
-    DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
-                               "StyleRuleRemoved",
-                               mRule,
-                               aStyleRule);
-  }
+nsDocument::StyleRuleChanged(StyleSheet* aSheet, css::Rule* aStyleRule)
+{
+  if (!StyleSheetChangeEventsEnabled()) {
+    return;
+  }
+
+  DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+                             "StyleRuleChanged",
+                             mRule,
+                             aStyleRule);
+}
+
+void
+nsDocument::StyleRuleAdded(StyleSheet* aSheet, css::Rule* aStyleRule)
+{
+  if (!StyleSheetChangeEventsEnabled()) {
+    return;
+  }
+
+  DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+                             "StyleRuleAdded",
+                             mRule,
+                             aStyleRule);
+}
+
+void
+nsDocument::StyleRuleRemoved(StyleSheet* aSheet, css::Rule* aStyleRule)
+{
+  if (!StyleSheetChangeEventsEnabled()) {
+    return;
+  }
+
+  DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+                             "StyleRuleRemoved",
+                             mRule,
+                             aStyleRule);
 }
 
 #undef DO_STYLESHEET_NOTIFICATION
 
 already_AddRefed<AnonymousContent>
 nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv)
 {
   nsIPresShell* shell = GetShell();
--- a/dom/base/nsIDocumentObserver.h
+++ b/dom/base/nsIDocumentObserver.h
@@ -9,22 +9,16 @@
 #include "mozilla/EventStates.h"
 #include "mozilla/StyleSheet.h"
 #include "nsISupports.h"
 #include "nsIMutationObserver.h"
 
 class nsIContent;
 class nsIDocument;
 
-namespace mozilla {
-namespace css {
-class Rule;
-} // namespace css
-} // namespace mozilla
-
 #define NS_IDOCUMENT_OBSERVER_IID \
 { 0x71041fa3, 0x6dd7, 0x4cde, \
   { 0xbb, 0x76, 0xae, 0xcc, 0x69, 0xe1, 0x75, 0x78 } }
 
 typedef uint32_t nsUpdateType;
 
 #define UPDATE_CONTENT_MODEL 0x00000001
 #define UPDATE_STYLE         0x00000002
@@ -121,55 +115,16 @@ public:
    * This method is called automatically when the applicable state
    * of a StyleSheet gets changed. The style sheet passes this
    * notification to the document. The notification is passed on
    * to all of the document observers.
    *
    * @param aStyleSheet the StyleSheet that has changed state
    */
   virtual void StyleSheetApplicableStateChanged(mozilla::StyleSheet* aStyleSheet) = 0;
-
-  /**
-   * A StyleRule has just been modified within a style sheet.
-   * This method is called automatically when the rule gets
-   * modified. The style sheet passes this notification to
-   * the document. The notification is passed on to all of
-   * the document observers.
-   *
-   * @param aStyleSheet the StyleSheet that contians the rule
-   * @param aStyleRule the changed rule
-   */
-  virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,
-                                mozilla::css::Rule* aStyleRule) = 0;
-
-  /**
-   * A StyleRule has just been added to a style sheet.
-   * This method is called automatically when the rule gets
-   * added to the sheet. The style sheet passes this
-   * notification to the document. The notification is passed on
-   * to all of the document observers.
-   *
-   * @param aStyleSheet the StyleSheet that has been modified
-   * @param aStyleRule the added rule
-   */
-  virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,
-                              mozilla::css::Rule* aStyleRule) = 0;
-
-  /**
-   * A StyleRule has just been removed from a style sheet.
-   * This method is called automatically when the rule gets
-   * removed from the sheet. The style sheet passes this
-   * notification to the document. The notification is passed on
-   * to all of the document observers.
-   *
-   * @param aStyleSheet the StyleSheet that has been modified
-   * @param aStyleRule the removed rule
-   */
-  virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,
-                                mozilla::css::Rule* aStyleRule) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentObserver, NS_IDOCUMENT_OBSERVER_IID)
 
 #define NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE                              \
     virtual void BeginUpdate(nsIDocument* aDocument,                         \
                              nsUpdateType aUpdateType) override;
 
@@ -198,41 +153,26 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumen
 #define NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED                        \
     virtual void StyleSheetRemoved(mozilla::StyleSheet* aStyleSheet,         \
                                    bool aDocumentSheet) override;
 
 #define NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED         \
     virtual void StyleSheetApplicableStateChanged(                           \
         mozilla::StyleSheet* aStyleSheet) override;
 
-#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED                         \
-    virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,          \
-                                  mozilla::css::Rule* aStyleRule) override;
-
-#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED                           \
-    virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,            \
-                                mozilla::css::Rule* aStyleRule) override;
-
-#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED                         \
-    virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,          \
-                                  mozilla::css::Rule* aStyleRule) override;
-
 #define NS_DECL_NSIDOCUMENTOBSERVER                                          \
     NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE                                  \
     NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE                                    \
     NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD                                    \
     NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD                                      \
     NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED                          \
     NS_DECL_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED                        \
     NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED                              \
     NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED                            \
     NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED             \
-    NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED                             \
-    NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED                               \
-    NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED                             \
     NS_DECL_NSIMUTATIONOBSERVER
 
 
 #define NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(_class)                     \
 void                                                                      \
 _class::BeginUpdate(nsIDocument* aDocument, nsUpdateType aUpdateType)     \
 {                                                                         \
 }                                                                         \
@@ -279,25 +219,10 @@ void                                    
 _class::StyleSheetRemoved(mozilla::StyleSheet* aStyleSheet,               \
                           bool aDocumentSheet)                            \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::StyleSheetApplicableStateChanged(mozilla::StyleSheet* aStyleSheet)\
 {                                                                         \
 }                                                                         \
-void                                                                      \
-_class::StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,                \
-                         mozilla::css::Rule* aStyleRule)                  \
-{                                                                         \
-}                                                                         \
-void                                                                      \
-_class::StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,                  \
-                       mozilla::css::Rule* aStyleRule)                    \
-{                                                                         \
-}                                                                         \
-void                                                                      \
-_class::StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,                \
-                         mozilla::css::Rule* aStyleRule)                  \
-{                                                                         \
-}
 
 #endif /* nsIDocumentObserver_h___ */
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4648,34 +4648,16 @@ void
 PresShell::StyleSheetApplicableStateChanged(StyleSheet* aStyleSheet)
 {
   if (aStyleSheet->HasRules()) {
     RecordStyleSheetChange(
         aStyleSheet, StyleSheet::ChangeType::ApplicableStateChanged);
   }
 }
 
-void
-PresShell::StyleRuleChanged(StyleSheet* aStyleSheet, css::Rule* aStyleRule)
-{
-  RecordStyleSheetChange(aStyleSheet, StyleSheet::ChangeType::RuleChanged);
-}
-
-void
-PresShell::StyleRuleAdded(StyleSheet* aStyleSheet, css::Rule* aStyleRule)
-{
-  RecordStyleSheetChange(aStyleSheet, StyleSheet::ChangeType::RuleAdded);
-}
-
-void
-PresShell::StyleRuleRemoved(StyleSheet* aStyleSheet, css::Rule* aStyleRule)
-{
-  RecordStyleSheetChange(aStyleSheet, StyleSheet::ChangeType::RuleRemoved);
-}
-
 nsresult
 PresShell::RenderDocument(const nsRect& aRect, uint32_t aFlags,
                           nscolor aBackgroundColor,
                           gfxContext* aThebesContext)
 {
   NS_ENSURE_TRUE(!(aFlags & RENDER_IS_UNTRUSTED), NS_ERROR_NOT_IMPLEMENTED);
 
   nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -294,19 +294,16 @@ public:
   NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
   NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD
   NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD
   NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED
   NS_DECL_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED
   NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED
   NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED
   NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED
-  NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED
-  NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED
-  NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
--- a/layout/inspector/ServoStyleRuleMap.cpp
+++ b/layout/inspector/ServoStyleRuleMap.cpp
@@ -71,36 +71,34 @@ ServoStyleRuleMap::StyleSheetApplicableS
   // We don't care if the stylesheet is disabled. Not removing no longer
   // applicable stylesheets wouldn't make anything wrong.
   if (!IsEmpty() && aStyleSheet->IsApplicable()) {
     FillTableFromStyleSheet(aStyleSheet->AsServo());
   }
 }
 
 void
-ServoStyleRuleMap::StyleRuleAdded(StyleSheet* aStyleSheet,
-                                  css::Rule* aStyleRule)
+ServoStyleRuleMap::RuleAdded(StyleSheet& aStyleSheet, css::Rule& aStyleRule)
 {
   if (!IsEmpty()) {
-    FillTableFromRule(aStyleRule);
+    FillTableFromRule(&aStyleRule);
   }
 }
 
 void
-ServoStyleRuleMap::StyleRuleRemoved(StyleSheet* aStyleSheet,
-                                    css::Rule* aStyleRule)
+ServoStyleRuleMap::RuleRemoved(StyleSheet& aStyleSheet, css::Rule& aStyleRule)
 {
   if (IsEmpty()) {
     return;
   }
 
-  switch (aStyleRule->Type()) {
+  switch (aStyleRule.Type()) {
     case nsIDOMCSSRule::STYLE_RULE: {
-      auto rule = static_cast<ServoStyleRule*>(aStyleRule);
-      mTable.Remove(rule->Raw());
+      auto& rule = static_cast<ServoStyleRule&>(aStyleRule);
+      mTable.Remove(rule.Raw());
       break;
     }
     case nsIDOMCSSRule::IMPORT_RULE:
     case nsIDOMCSSRule::MEDIA_RULE:
     case nsIDOMCSSRule::SUPPORTS_RULE:
     case nsIDOMCSSRule::DOCUMENT_RULE: {
       // See the comment in StyleSheetRemoved.
       mTable.Clear();
--- a/layout/inspector/ServoStyleRuleMap.h
+++ b/layout/inspector/ServoStyleRuleMap.h
@@ -34,18 +34,19 @@ public:
   ServoStyleRule* Lookup(const RawServoStyleRule* aRawRule) const {
     return mTable.Get(aRawRule);
   }
 
   // nsIDocumentObserver methods
   void StyleSheetAdded(StyleSheet* aStyleSheet, bool aDocumentSheet) final;
   void StyleSheetRemoved(StyleSheet* aStyleSheet, bool aDocumentSheet) final;
   void StyleSheetApplicableStateChanged(StyleSheet* aStyleSheet) final;
-  void StyleRuleAdded(StyleSheet* aStyleSheet, css::Rule* aStyleRule) final;
-  void StyleRuleRemoved(StyleSheet* aStyleSheet, css::Rule* aStyleRule) final;
+
+  void RuleAdded(StyleSheet& aStyleSheet, css::Rule&);
+  void RuleRemoved(StyleSheet& aStyleSheet, css::Rule& aStyleRule);
 
   // nsICSSLoaderObserver
   NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet,
                               bool aWasAlternate, nsresult aStatus) final;
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
 private:
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -761,19 +761,18 @@ CSSStyleSheet::InsertRuleInternal(const 
     aRv = RegisterNamespaceRule(rule);
     if (NS_WARN_IF(aRv.Failed())) {
       return 0;
     }
   }
 
   // We don't notify immediately for @import rules, but rather when
   // the sheet the rule is importing is loaded (see StyleSheetLoaded)
-  if ((type != css::Rule::IMPORT_RULE || !RuleHasPendingChildSheet(rule)) &&
-      mDocument) {
-    mDocument->StyleRuleAdded(this, rule);
+  if (type != css::Rule::IMPORT_RULE || !RuleHasPendingChildSheet(rule)) {
+    RuleAdded(*rule);
   }
 
   return aIndex;
 }
 
 void
 CSSStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
 {
@@ -790,21 +789,17 @@ CSSStyleSheet::DeleteRuleInternal(uint32
   NS_ASSERTION(uint32_t(Inner()->mOrderedRules.Count()) <= INT32_MAX,
                "Too many style rules!");
 
   // Hold a strong ref to the rule so it doesn't die when we RemoveObjectAt
   RefPtr<css::Rule> rule = Inner()->mOrderedRules.ObjectAt(aIndex);
   if (rule) {
     Inner()->mOrderedRules.RemoveObjectAt(aIndex);
     rule->SetStyleSheet(nullptr);
-    DidDirty();
-
-    if (mDocument) {
-      mDocument->StyleRuleRemoved(this, rule);
-    }
+    RuleRemoved(*rule);
   }
 }
 
 nsresult
 CSSStyleSheet::InsertRuleIntoGroupInternal(const nsAString& aRule,
                                            css::GroupRule* aGroup,
                                            uint32_t aIndex)
 {
@@ -860,22 +855,19 @@ CSSStyleSheet::StyleSheetLoaded(StyleShe
   CSSStyleSheet* sheet = aSheet->AsGecko();
 
   if (sheet->GetParentSheet() == nullptr) {
     return NS_OK; // ignore if sheet has been detached already (see parseSheet)
   }
   NS_ASSERTION(this == sheet->GetParentSheet(),
                "We are being notified of a sheet load for a sheet that is not our child!");
 
-  if (mDocument && NS_SUCCEEDED(aStatus)) {
+  if (NS_SUCCEEDED(aStatus)) {
     mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
-
-    // XXXldb @import rules shouldn't even implement nsIStyleRule (but
-    // they do)!
-    mDocument->StyleRuleAdded(this, sheet->GetOwnerRule());
+    RuleAdded(*sheet->GetOwnerRule());
   }
 
   return NS_OK;
 }
 
 nsresult
 CSSStyleSheet::ReparseSheet(const nsAString& aInput)
 {
@@ -912,19 +904,17 @@ CSSStyleSheet::ReparseSheet(const nsAStr
       nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
       importRule->GetStyleSheet(getter_AddRefs(childSheet));
 
       RefPtr<CSSStyleSheet> cssSheet = do_QueryObject(childSheet);
       if (cssSheet && cssSheet->GetOriginalURI()) {
         reusableSheets.AddReusableSheet(cssSheet);
       }
     }
-    if (mDocument) {
-      mDocument->StyleRuleRemoved(this, rule);
-    }
+    RuleRemoved(*rule);
   }
 
   // nuke child sheets list and current namespace map
   for (StyleSheet* child = GetFirstChild(); child; ) {
     NS_ASSERTION(child->mParent == this, "Child sheet is not parented to this!");
     StyleSheet* next = child->mNext;
     child->mParent = nullptr;
     child->SetAssociatedDocument(nullptr, NotOwnedByDocument);
@@ -944,22 +934,20 @@ CSSStyleSheet::ReparseSheet(const nsAStr
 
   nsCSSParser parser(loader, this);
   nsresult rv = parser.ParseSheet(aInput, mInner->mSheetURI, mInner->mBaseURI,
                                   mInner->mPrincipal, lineNumber, &reusableSheets);
   DidDirty(); // we are always 'dirty' here since we always remove rules first
   NS_ENSURE_SUCCESS(rv, rv);
 
   // notify document of all new rules
-  if (mDocument) {
-    for (int32_t index = 0; index < Inner()->mOrderedRules.Count(); ++index) {
-      RefPtr<css::Rule> rule = Inner()->mOrderedRules.ObjectAt(index);
-      if (rule->GetType() == css::Rule::IMPORT_RULE &&
-          RuleHasPendingChildSheet(rule)) {
-        continue; // notify when loaded (see StyleSheetLoaded)
-      }
-      mDocument->StyleRuleAdded(this, rule);
+  for (int32_t index = 0; index < Inner()->mOrderedRules.Count(); ++index) {
+    RefPtr<css::Rule> rule = Inner()->mOrderedRules.ObjectAt(index);
+    if (rule->GetType() == css::Rule::IMPORT_RULE &&
+        RuleHasPendingChildSheet(rule)) {
+      continue; // notify when loaded (see StyleSheetLoaded)
     }
+    RuleAdded(*rule);
   }
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -115,22 +115,16 @@ public:
     return Inner()->mNameSpaceMap;
   }
 
   already_AddRefed<StyleSheet> Clone(StyleSheet* aCloneParent,
     dom::CSSImportRule* aCloneOwnerRule,
     nsIDocument* aCloneDocument,
     nsINode* aCloneOwningNode) const final;
 
-  void SetModifiedByChildRule() {
-    NS_ASSERTION(mDirty,
-                 "sheet must be marked dirty before handing out child rules");
-    DidDirty();
-  }
-
   nsresult AddRuleProcessor(nsCSSRuleProcessor* aProcessor);
   nsresult DropRuleProcessor(nsCSSRuleProcessor* aProcessor);
 
   // nsICSSLoaderObserver interface
   NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasAlternate,
                               nsresult aStatus) override;
 
   bool UseForPresentation(nsPresContext* aPresContext,
--- a/layout/style/GroupRule.cpp
+++ b/layout/style/GroupRule.cpp
@@ -292,17 +292,17 @@ GroupRule::SetStyleSheet(StyleSheet* aSh
 void
 GroupRule::AppendStyleRule(Rule* aRule)
 {
   GeckoRules().AppendObject(aRule);
   StyleSheet* sheet = GetStyleSheet();
   aRule->SetStyleSheet(sheet);
   aRule->SetParentRule(this);
   if (sheet) {
-    sheet->AsGecko()->SetModifiedByChildRule();
+    sheet->RuleChanged(this);
   }
 }
 
 bool
 GroupRule::EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const
 {
   for (const Rule* rule : GeckoRules()) {
     if (!aFunc(const_cast<Rule*>(rule), aData)) {
--- a/layout/style/MediaList.cpp
+++ b/layout/style/MediaList.cpp
@@ -57,22 +57,22 @@ MediaList::DoMediaChange(Func aCallback)
   }
 
   nsresult rv = aCallback();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (mStyleSheet) {
-    mStyleSheet->DidDirty();
+    // FIXME(emilio): We should discern between "owned by a rule" (as in @media)
+    // and "owned by a sheet" (as in <style media>), and then pass something
+    // meaningful here.
+    mStyleSheet->RuleChanged(nullptr);
   }
-  /* XXXldb Pass something meaningful? */
-  if (doc) {
-    doc->StyleRuleChanged(mStyleSheet, nullptr);
-  }
+
   return rv;
 }
 
 /* static */ already_AddRefed<MediaList>
 MediaList::Create(
     StyleBackendType aBackendType,
     const nsAString& aMedia,
     CallerType aCallerType)
--- a/layout/style/ServoKeyframeRule.cpp
+++ b/layout/style/ServoKeyframeRule.cpp
@@ -161,20 +161,17 @@ void
 ServoKeyframeRule::UpdateRule(Func aCallback)
 {
   nsIDocument* doc = GetDocument();
   MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
   aCallback();
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    // FIXME sheet->AsGecko()->SetModifiedByChildRule();
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 }
 
 NS_IMETHODIMP
 ServoKeyframeRule::GetKeyText(nsAString& aKeyText)
 {
   Servo_Keyframe_GetKeyText(mRaw, &aKeyText);
   return NS_OK;
--- a/layout/style/ServoKeyframesRule.cpp
+++ b/layout/style/ServoKeyframesRule.cpp
@@ -238,20 +238,17 @@ void
 ServoKeyframesRule::UpdateRule(Func aCallback)
 {
   nsIDocument* doc = GetDocument();
   MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
   aCallback();
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    // FIXME sheet->AsGecko()->SetModifiedByChildRule();
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 }
 
 NS_IMETHODIMP
 ServoKeyframesRule::GetName(nsAString& aName)
 {
   nsAtom* name = Servo_KeyframesRule_GetName(mRawRule);
   aName = nsDependentAtomString(name);
--- a/layout/style/ServoStyleRule.cpp
+++ b/layout/style/ServoStyleRule.cpp
@@ -75,19 +75,17 @@ ServoStyleRuleDeclaration::SetCSSDeclara
     mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true);
     if (aDecl != mDecls) {
       mDecls->SetOwningRule(nullptr);
       RefPtr<ServoDeclarationBlock> decls = aDecl->AsServo();
       Servo_StyleRule_SetStyle(rule->Raw(), decls->Raw());
       mDecls = decls.forget();
       mDecls->SetOwningRule(rule);
     }
-    if (doc) {
-      doc->StyleRuleChanged(sheet, rule);
-    }
+    sheet->RuleChanged(rule);
   }
   return NS_OK;
 }
 
 nsIDocument*
 ServoStyleRuleDeclaration::DocToUpdate()
 {
   return nullptr;
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1023,24 +1023,41 @@ ServoStyleSet::MarkOriginsDirty(OriginFl
   Servo_StyleSet_NoteStyleSheetsChanged(mRawSet.get(),
                                         mAuthorStyleDisabled,
                                         aChangedOrigins);
 }
 
 void
 ServoStyleSet::RecordStyleSheetChange(
     ServoStyleSheet* aSheet,
-    StyleSheet::ChangeType aChangeType)
+    StyleSheet::ChangeType aChangeType,
+    css::Rule* aChangedRule)
 {
+  if (MOZ_UNLIKELY(IsForXBL())) {
+    // FIXME(emilio): This can be reached from Shadow DOM, and ignoring it is
+    // wrong. It only works because Shadow DOM does the update synchronously
+    // right now, so we don't need to do anything here. But we need to fix that!
+    return;
+  }
+
   switch (aChangeType) {
+    // FIXME(emilio): We can presumably do better in a bunch of these.
     case StyleSheet::ChangeType::RuleAdded:
+      MOZ_ASSERT(aChangedRule);
+      if (mStyleRuleMap) {
+        mStyleRuleMap->RuleAdded(*aSheet, *aChangedRule);
+      }
+      return MarkOriginsDirty(aSheet->GetOrigin());
     case StyleSheet::ChangeType::RuleRemoved:
+      MOZ_ASSERT(aChangedRule);
+      if (mStyleRuleMap) {
+        mStyleRuleMap->RuleRemoved(*aSheet, *aChangedRule);
+      }
+      return MarkOriginsDirty(aSheet->GetOrigin());
     case StyleSheet::ChangeType::RuleChanged:
-    case StyleSheet::ChangeType::ReparsedFromInspector:
-      // FIXME(emilio): We can presumably do better in a bunch of these.
       return MarkOriginsDirty(aSheet->GetOrigin());
     case StyleSheet::ChangeType::ApplicableStateChanged:
     case StyleSheet::ChangeType::Added:
     case StyleSheet::ChangeType::Removed:
       // Do nothing, we've already recorded the change in the
       // Append/Remove/Replace methods, etc, and will act consequently.
       return;
   }
@@ -1428,19 +1445,19 @@ ServoStyleSet::RunPostTraversalTasks()
 }
 
 ServoStyleRuleMap*
 ServoStyleSet::StyleRuleMap()
 {
   if (!mStyleRuleMap) {
     mStyleRuleMap = new ServoStyleRuleMap(this);
     if (mPresContext) {
-      nsIDocument* doc = mPresContext->Document();
-      doc->AddObserver(mStyleRuleMap);
-      doc->CSSLoader()->AddObserver(mStyleRuleMap);
+      // TODO(emilio): Just forward the methods from the StyleSet instead of
+      // observing the CSS loader, and make the rule map a UniquePtr?
+      mPresContext->Document()->CSSLoader()->AddObserver(mStyleRuleMap);
     }
   }
   return mStyleRuleMap;
 }
 
 bool
 ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
                                             nsAtom* aAttribute) const
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -131,19 +131,41 @@ public:
   static UniquePtr<ServoStyleSet>
   CreateXBLServoStyleSet(nsPresContext* aPresContext,
                          const nsTArray<RefPtr<ServoStyleSheet>>& aNewSheets);
 
   void Init(nsPresContext* aPresContext, nsBindingManager* aBindingManager);
   void BeginShutdown();
   void Shutdown();
 
-  void RecordStyleSheetChange(mozilla::ServoStyleSheet*, StyleSheet::ChangeType);
+  void RuleAdded(ServoStyleSheet& aSheet, css::Rule& aRule)
+  {
+    // TODO(emilio): Ditch RecordStyleSheetChange in favor of this, which has
+    // more information for invalidation and such.
+    RecordStyleSheetChange(&aSheet, StyleSheet::ChangeType::RuleAdded, &aRule);
+  }
+
+  void RuleRemoved(ServoStyleSheet& aSheet, css::Rule& aRule)
+  {
+    // TODO(emilio): Ditch RecordStyleSheetChange in favor of this.
+    RecordStyleSheetChange(&aSheet, StyleSheet::ChangeType::RuleRemoved, &aRule);
+  }
 
-  void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) {
+  void RuleChanged(ServoStyleSheet& aSheet, css::Rule* aRule)
+  {
+    // TODO(emilio): Ditch RecordStyleSheetChange in favor of this.
+    RecordStyleSheetChange(&aSheet, StyleSheet::ChangeType::RuleChanged, aRule);
+  }
+
+  void RecordStyleSheetChange(
+      ServoStyleSheet*,
+      StyleSheet::ChangeType,
+      css::Rule* aChangedRule = nullptr);
+
+  void RecordShadowStyleChange(dom::ShadowRoot* aShadowRoot) {
     // FIXME(emilio): When we properly support shadow dom we'll need to do
     // better.
     MarkOriginsDirty(OriginFlags::All);
   }
 
   bool StyleSheetsHaveChanged() const
   {
     return StylistNeedsUpdate();
@@ -281,17 +303,17 @@ public:
   }
 
   nsresult RemoveDocStyleSheet(ServoStyleSheet* aSheet);
   nsresult AddDocStyleSheet(ServoStyleSheet* aSheet, nsIDocument* aDocument);
 
   // check whether there is ::before/::after style for an element
   already_AddRefed<ServoStyleContext>
   ProbePseudoElementStyle(dom::Element* aOriginatingElement,
-                          mozilla::CSSPseudoElementType aType,
+                          CSSPseudoElementType aType,
                           ServoStyleContext* aParentContext);
 
   /**
    * Performs a Servo traversal to compute style for all dirty nodes in the
    * document.
    *
    * This will traverse all of the document's style roots (that is, its document
    * element, and the roots of the document-level native anonymous content).
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -276,113 +276,86 @@ ServoStyleSheet::ReparseSheet(const nsAS
   uint32_t lineNumber = 1;
   if (mOwningNode) {
     nsCOMPtr<nsIStyleSheetLinkingElement> link = do_QueryInterface(mOwningNode);
     if (link) {
       lineNumber = link->GetLineNumber();
     }
   }
 
-  // Notify mDocument that all our rules are removed.
-  if (mDocument) {
-    // Get the rule list.
+  // Get the rule list.
+  {
     ServoCSSRuleList* ruleList = GetCssRulesInternal();
     MOZ_ASSERT(ruleList);
 
     uint32_t ruleCount = ruleList->Length();
     for (uint32_t i = 0; i < ruleCount; ++i) {
       css::Rule* rule = ruleList->GetRule(i);
       MOZ_ASSERT(rule);
       if (rule->GetType() == css::Rule::IMPORT_RULE &&
           RuleHasPendingChildSheet(rule)) {
         continue; // notify when loaded (see StyleSheetLoaded)
       }
-      mDocument->StyleRuleRemoved(this, rule);
-
-      // Document observers could possibly detach document from this sheet.
-      if (!mDocument) {
-        // If detached, don't process any more rules.
-        break;
-      }
+      RuleRemoved(*rule);
     }
   }
 
   DropRuleList();
 
   nsresult rv = ParseSheet(loader,
                            NS_ConvertUTF16toUTF8(aInput),
                            mInner->mSheetURI,
                            mInner->mBaseURI,
                            mInner->mPrincipal,
                            lineNumber,
                            eCompatibility_FullStandards,
                            &reusableSheets);
   DidDirty();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Notify mDocument that all our new rules are added.
-  if (mDocument) {
+  {
     // Get the rule list (which will need to be regenerated after ParseSheet).
     ServoCSSRuleList* ruleList = GetCssRulesInternal();
     MOZ_ASSERT(ruleList);
 
     uint32_t ruleCount = ruleList->Length();
     for (uint32_t i = 0; i < ruleCount; ++i) {
       css::Rule* rule = ruleList->GetRule(i);
       MOZ_ASSERT(rule);
       if (rule->GetType() == css::Rule::IMPORT_RULE &&
           RuleHasPendingChildSheet(rule)) {
         continue; // notify when loaded (see StyleSheetLoaded)
       }
 
-      mDocument->StyleRuleAdded(this, rule);
-
-      // Document observers could possibly detach document from this sheet.
-      if (!mDocument) {
-        // If detached, don't process any more rules.
-        break;
-      }
+      RuleAdded(*rule);
     }
   }
 
-  // FIXME(emilio): This is kind-of a hack for bug 1420713. As you may notice,
-  // there's nothing that triggers a style flush or anything similar (neither
-  // here or in the relevant Gecko path inside DidDirty).
-  //
-  // The tl;dr is: if we want to make sure scripted changes to sheets not
-  // associated with any document get properly reflected, we need to rejigger a
-  // fair amount of stuff. I'm probably doing that work as part of the shadow
-  // DOM stuff.
-  for (StyleSetHandle handle : mStyleSets) {
-    handle->AsServo()->RecordStyleSheetChange(
-      this, StyleSheet::ChangeType::ReparsedFromInspector);
-  }
-
   return NS_OK;
 }
 
 // nsICSSLoaderObserver implementation
 NS_IMETHODIMP
 ServoStyleSheet::StyleSheetLoaded(StyleSheet* aSheet,
                                   bool aWasAlternate,
                                   nsresult aStatus)
 {
   MOZ_ASSERT(aSheet->IsServo(),
              "why we were called back with a CSSStyleSheet?");
 
   ServoStyleSheet* sheet = aSheet->AsServo();
-  if (sheet->GetParentSheet() == nullptr) {
+  if (!sheet->GetParentSheet()) {
     return NS_OK; // ignore if sheet has been detached already
   }
   NS_ASSERTION(this == sheet->GetParentSheet(),
                "We are being notified of a sheet load for a sheet that is not our child!");
 
-  if (mDocument && NS_SUCCEEDED(aStatus)) {
+  if (NS_SUCCEEDED(aStatus)) {
     mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
-    mDocument->StyleRuleAdded(this, sheet->GetOwnerRule());
+    RuleAdded(*sheet->GetOwnerRule());
   }
 
   return NS_OK;
 }
 
 void
 ServoStyleSheet::DropRuleList()
 {
@@ -427,24 +400,25 @@ ServoStyleSheet::InsertRuleInternal(cons
   // Ensure mRuleList is constructed.
   GetCssRulesInternal();
 
   mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
   aRv = mRuleList->InsertRule(aRule, aIndex);
   if (aRv.Failed()) {
     return 0;
   }
-  if (mDocument) {
-    if (mRuleList->GetDOMCSSRuleType(aIndex) != nsIDOMCSSRule::IMPORT_RULE ||
-        !RuleHasPendingChildSheet(mRuleList->GetRule(aIndex))) {
-      // XXX We may not want to get the rule when stylesheet change event
-      // is not enabled.
-      mDocument->StyleRuleAdded(this, mRuleList->GetRule(aIndex));
-    }
+
+  // XXX We may not want to get the rule when stylesheet change event
+  // is not enabled.
+  css::Rule* rule = mRuleList->GetRule(aIndex);
+  if (rule->GetType() != css::Rule::IMPORT_RULE ||
+      !RuleHasPendingChildSheet(rule)) {
+    RuleAdded(*rule);
   }
+
   return aIndex;
 }
 
 void
 ServoStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
 {
   // Ensure mRuleList is constructed.
   GetCssRulesInternal();
@@ -456,18 +430,18 @@ ServoStyleSheet::DeleteRuleInternal(uint
   mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
   // Hold a strong ref to the rule so it doesn't die when we remove it
   // from the list. XXX We may not want to hold it if stylesheet change
   // event is not enabled.
   RefPtr<css::Rule> rule = mRuleList->GetRule(aIndex);
   aRv = mRuleList->DeleteRule(aIndex);
   MOZ_ASSERT(!aRv.ErrorCodeIs(NS_ERROR_DOM_INDEX_SIZE_ERR),
              "IndexSizeError should have been handled earlier");
-  if (!aRv.Failed() && mDocument) {
-    mDocument->StyleRuleRemoved(this, rule);
+  if (!aRv.Failed()) {
+    RuleRemoved(*rule);
   }
 }
 
 nsresult
 ServoStyleSheet::InsertRuleIntoGroupInternal(const nsAString& aRule,
                                              css::GroupRule* aGroup,
                                              uint32_t aIndex)
 {
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -1157,21 +1157,17 @@ DOMCSSDeclarationImpl::SetCSSDeclaration
     doc = sheet->GetAssociatedDocument();
   }
 
   mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true);
 
   mRule->SetDeclaration(aDecl->AsGecko());
 
   if (sheet) {
-    sheet->DidDirty();
-  }
-
-  if (doc) {
-    doc->StyleRuleChanged(sheet, mRule);
+    sheet->RuleChanged(mRule);
   }
   return NS_OK;
 }
 
 nsIDocument*
 DOMCSSDeclarationImpl::DocToUpdate()
 {
   return nullptr;
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -13,39 +13,41 @@
 #include "mozilla/ServoTypes.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/StyleBackendType.h"
 #include "mozilla/StyleSheet.h"
 #include "nsChangeHint.h"
 #include "nsCSSPseudoElements.h"
 #include "nsTArray.h"
 
-namespace mozilla {
-class CSSStyleSheet;
-class ServoStyleSet;
-namespace dom {
-class Element;
-class ShadowRoot;
-} // namespace dom
-} // namespace mozilla
 class nsBindingManager;
 class nsCSSCounterStyleRule;
 struct nsFontFaceRuleContainer;
 class nsAtom;
 class nsICSSAnonBoxPseudo;
 class nsIContent;
 class nsIDocument;
 class nsStyleContext;
 class nsStyleSet;
 class nsPresContext;
 class gfxFontFeatureValueSet;
 struct TreeMatchContext;
 
 namespace mozilla {
 
+class CSSStyleSheet;
+class ServoStyleSet;
+namespace dom {
+class Element;
+class ShadowRoot;
+} // namespace dom
+namespace css {
+class Rule;
+} // namespace css
+
 #define SERVO_BIT 0x1
 
 /**
  * Smart pointer class that can hold a pointer to either an nsStyleSet
  * or a ServoStyleSet.
  */
 class StyleSetHandle
 {
@@ -161,16 +163,22 @@ public:
     inline nsresult InsertStyleSheetBefore(SheetType aType,
                                     StyleSheet* aNewSheet,
                                     StyleSheet* aReferenceSheet);
     inline int32_t SheetCount(SheetType aType) const;
     inline StyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;
     inline void AppendAllXBLStyleSheets(nsTArray<StyleSheet*>& aArray) const;
     inline nsresult RemoveDocStyleSheet(StyleSheet* aSheet);
     inline nsresult AddDocStyleSheet(StyleSheet* aSheet, nsIDocument* aDocument);
+
+    inline void RuleRemoved(StyleSheet&, css::Rule&);
+    inline void RuleAdded(StyleSheet&, css::Rule&);
+    inline void RuleChanged(StyleSheet&, css::Rule*);
+
+    // TODO(emilio): Remove in favor of Rule* methods.
     inline void RecordStyleSheetChange(StyleSheet* aSheet, StyleSheet::ChangeType);
     inline void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot);
     inline bool StyleSheetsHaveChanged() const;
     inline void InvalidateStyleForCSSRuleChanges();
     inline nsRestyleHint MediumFeaturesChanged(bool aViewportChanged);
     inline already_AddRefed<nsStyleContext>
     ProbePseudoElementStyle(dom::Element* aParentElement,
                             mozilla::CSSPseudoElementType aType,
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -245,16 +245,37 @@ nsresult
 StyleSetHandle::Ptr::AddDocStyleSheet(StyleSheet* aSheet,
                                       nsIDocument* aDocument)
 {
   FORWARD_CONCRETE(AddDocStyleSheet, (aSheet->AsGecko(), aDocument),
                                      (aSheet->AsServo(), aDocument));
 }
 
 void
+StyleSetHandle::Ptr::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule)
+{
+  FORWARD_CONCRETE(RuleRemoved, (*aSheet.AsGecko(), aRule),
+                                (*aSheet.AsServo(), aRule));
+}
+
+void
+StyleSetHandle::Ptr::RuleAdded(StyleSheet& aSheet, css::Rule& aRule)
+{
+  FORWARD_CONCRETE(RuleAdded, (*aSheet.AsGecko(), aRule),
+                              (*aSheet.AsServo(), aRule));
+}
+
+void
+StyleSetHandle::Ptr::RuleChanged(StyleSheet& aSheet, css::Rule* aRule)
+{
+  FORWARD_CONCRETE(RuleChanged, (*aSheet.AsGecko(), aRule),
+                                (*aSheet.AsServo(), aRule));
+}
+
+void
 StyleSetHandle::Ptr::RecordStyleSheetChange(StyleSheet* aSheet,
                                             StyleSheet::ChangeType aChangeType)
 {
   FORWARD_CONCRETE(RecordStyleSheetChange, (aSheet->AsGecko(), aChangeType),
                                            (aSheet->AsServo(), aChangeType));
 }
 
 void
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -598,24 +598,61 @@ StyleSheet::DeleteRuleFromGroup(css::Gro
   mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
 
   WillDirty();
 
   nsresult result = aGroup->DeleteStyleRuleAt(aIndex);
   NS_ENSURE_SUCCESS(result, result);
 
   rule->SetStyleSheet(nullptr);
+  RuleRemoved(*rule);
+  return NS_OK;
+}
 
+#define NOTIFY_STYLE_SETS(function_, args_) do {          \
+  StyleSheet* current = this;                             \
+  do {                                                    \
+    for (StyleSetHandle handle : current->mStyleSets) {   \
+      handle->function_ args_;                            \
+    }                                                     \
+    current = current->mParent;                           \
+  } while (current);                                      \
+} while (0)
+
+void
+StyleSheet::RuleAdded(css::Rule& aRule)
+{
   DidDirty();
+  NOTIFY_STYLE_SETS(RuleAdded, (*this, aRule));
 
   if (mDocument) {
-    mDocument->StyleRuleRemoved(this, rule);
+    mDocument->StyleRuleAdded(this, &aRule);
   }
+}
 
-  return NS_OK;
+void
+StyleSheet::RuleRemoved(css::Rule& aRule)
+{
+  DidDirty();
+  NOTIFY_STYLE_SETS(RuleRemoved, (*this, aRule));
+
+  if (mDocument) {
+    mDocument->StyleRuleRemoved(this, &aRule);
+  }
+}
+
+void
+StyleSheet::RuleChanged(css::Rule* aRule)
+{
+  DidDirty();
+  NOTIFY_STYLE_SETS(RuleChanged, (*this, aRule));
+
+  if (mDocument) {
+    mDocument->StyleRuleChanged(this, aRule);
+  }
 }
 
 nsresult
 StyleSheet::InsertRuleIntoGroup(const nsAString& aRule,
                                 css::GroupRule* aGroup,
                                 uint32_t aIndex)
 {
   NS_ASSERTION(IsComplete(), "No inserting into an incomplete sheet!");
@@ -631,22 +668,17 @@ StyleSheet::InsertRuleIntoGroup(const ns
 
   nsresult result;
   if (IsGecko()) {
     result = AsGecko()->InsertRuleIntoGroupInternal(aRule, aGroup, aIndex);
   } else {
     result = AsServo()->InsertRuleIntoGroupInternal(aRule, aGroup, aIndex);
   }
   NS_ENSURE_SUCCESS(result, result);
-
-  DidDirty();
-
-  if (mDocument) {
-    mDocument->StyleRuleAdded(this, aGroup->GetStyleRuleAt(aIndex));
-  }
+  RuleAdded(*aGroup->GetStyleRuleAt(aIndex));
 
   return NS_OK;
 }
 
 uint64_t
 StyleSheet::FindOwningWindowInnerID() const
 {
   uint64_t windowID = 0;
--- a/layout/style/StyleSheet.h
+++ b/layout/style/StyleSheet.h
@@ -72,17 +72,16 @@ public:
    */
   enum class ChangeType {
     Added,
     Removed,
     ApplicableStateChanged,
     RuleAdded,
     RuleRemoved,
     RuleChanged,
-    ReparsedFromInspector,
   };
 
   void SetOwningNode(nsINode* aOwningNode)
   {
     mOwningNode = aOwningNode;
   }
 
   css::SheetParsingMode ParsingMode() const { return mParsingMode; }
@@ -257,16 +256,26 @@ public:
   NS_IMETHOD DeleteRule(uint32_t aIndex) final;
 
   // Changes to sheets should be inside of a WillDirty-DidDirty pair.
   // However, the calls do not need to be matched; it's ok to call
   // WillDirty and then make no change and skip the DidDirty call.
   void WillDirty();
   virtual void DidDirty() {}
 
+  void RuleAdded(css::Rule&);
+  void RuleRemoved(css::Rule&);
+
+  // Called from SetEnabled when the enabled state changed.
+  void EnabledStateChanged();
+
+  // FIXME(emilio): This shouldn't allow null, but MediaList doesn't know about
+  // it's owning media rule, plus it's used for the stylesheet media itself.
+  void RuleChanged(css::Rule*);
+
   void AddStyleSet(const StyleSetHandle& aStyleSet);
   void DropStyleSet(const StyleSetHandle& aStyleSet);
 
   nsresult DeleteRuleFromGroup(css::GroupRule* aGroup, uint32_t aIndex);
   nsresult InsertRuleIntoGroup(const nsAString& aRule,
                                css::GroupRule* aGroup, uint32_t aIndex);
 
   // Find the ID of the owner inner window.
@@ -312,19 +321,16 @@ protected:
   // UniversalXPConnect or if access is allowed by CORS.  In the latter case,
   // it will set the principal of the inner to the subject principal.
   void SubjectSubsumesInnerPrincipal(nsIPrincipal& aSubjectPrincipal,
                                      ErrorResult& aRv);
 
   // Drop our reference to mMedia
   void DropMedia();
 
-  // Called from SetEnabled when the enabled state changed.
-  void EnabledStateChanged();
-
   // Unlink our inner, if needed, for cycle collection
   virtual void UnlinkInner();
   // Traverse our inner, if needed, for cycle collection
   virtual void TraverseInner(nsCycleCollectionTraversalCallback &);
 
   // Return whether the given @import rule has pending child sheet.
   static bool RuleHasPendingChildSheet(css::Rule* aRule);
 
--- a/layout/style/nsCSSCounterStyleRule.cpp
+++ b/layout/style/nsCSSCounterStyleRule.cpp
@@ -135,22 +135,17 @@ nsCSSCounterStyleRule::SetName(const nsA
   nsCSSParser parser;
   if (RefPtr<nsAtom> name = parser.ParseCounterStyleName(aName, nullptr)) {
     nsIDocument* doc = GetDocument();
     MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
     mName = name;
 
     if (StyleSheet* sheet = GetStyleSheet()) {
-      if (sheet->IsGecko()) {
-        sheet->AsGecko()->SetModifiedByChildRule();
-      }
-      if (doc) {
-        doc->StyleRuleChanged(sheet, this);
-      }
+      sheet->RuleChanged(this);
     }
   }
   return NS_OK;
 }
 
 int32_t
 nsCSSCounterStyleRule::GetSystem() const
 {
@@ -182,22 +177,17 @@ nsCSSCounterStyleRule::SetDesc(nsCSSCoun
 
   nsIDocument* doc = GetDocument();
   MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
   mValues[aDescID] = aValue;
   mGeneration++;
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    if (sheet->IsGecko()) {
-      sheet->AsGecko()->SetModifiedByChildRule();
-    }
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 }
 
 NS_IMETHODIMP
 nsCSSCounterStyleRule::GetSystem(nsAString& aSystem)
 {
   const nsCSSValue& value = GetDesc(eCSSCounterDesc_System);
   if (value.GetUnit() == eCSSUnit_Null) {
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -1123,20 +1123,17 @@ nsCSSKeyframeRule::SetKeyText(const nsAS
   }
 
   nsIDocument* doc = GetDocument();
   MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
   newSelectors.SwapElements(mKeys);
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    sheet->AsGecko()->SetModifiedByChildRule();
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 
   return NS_OK;
 }
 
 nsICSSDeclaration*
 nsCSSKeyframeRule::Style()
 {
@@ -1157,20 +1154,17 @@ nsCSSKeyframeRule::ChangeDeclaration(css
 
   if (aDeclaration != mDeclaration) {
     mDeclaration->SetOwningRule(nullptr);
     mDeclaration = aDeclaration;
     mDeclaration->SetOwningRule(this);
   }
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    sheet->AsGecko()->SetModifiedByChildRule();
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 }
 
 /* virtual */ size_t
 nsCSSKeyframeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 
@@ -1260,20 +1254,17 @@ nsCSSKeyframesRule::SetName(const nsAStr
   }
 
   nsIDocument* doc = GetDocument();
   MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
   mName = NS_Atomize(aName);
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    sheet->AsGecko()->SetModifiedByChildRule();
-    if (doc) {
-      doc->StyleRuleChanged(sheet, this);
-    }
+    sheet->RuleChanged(this);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCSSKeyframesRule::AppendRule(const nsAString& aRule)
 {
@@ -1287,20 +1278,17 @@ nsCSSKeyframesRule::AppendRule(const nsA
     parser.ParseKeyframeRule(aRule, nullptr, 0);
   if (rule) {
     nsIDocument* doc = GetDocument();
     MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
     AppendStyleRule(rule);
 
     if (StyleSheet* sheet = GetStyleSheet()) {
-      sheet->AsGecko()->SetModifiedByChildRule();
-      if (doc) {
-        doc->StyleRuleChanged(sheet, this);
-      }
+      sheet->RuleChanged(this);
     }
   }
 
   return NS_OK;
 }
 
 static const uint32_t RULE_NOT_FOUND = uint32_t(-1);
 
@@ -1334,21 +1322,17 @@ nsCSSKeyframesRule::DeleteRule(const nsA
   uint32_t index = FindRuleIndexForKey(aKey);
   if (index != RULE_NOT_FOUND) {
     nsIDocument* doc = GetDocument();
     MOZ_AUTO_DOC_UPDATE(doc, UPDATE_STYLE, true);
 
     DeleteStyleRuleAt(index);
 
     if (StyleSheet* sheet = GetStyleSheet()) {
-      sheet->AsGecko()->SetModifiedByChildRule();
-
-      if (doc) {
-        doc->StyleRuleChanged(sheet, this);
-      }
+      sheet->RuleChanged(this);
     }
   }
   return NS_OK;
 }
 
 nsCSSKeyframeRule*
 nsCSSKeyframesRule::FindRule(const nsAString& aKey)
 {
@@ -1545,17 +1529,17 @@ nsCSSPageRule::ChangeDeclaration(css::De
 {
   if (aDeclaration != mDeclaration) {
     mDeclaration->SetOwningRule(nullptr);
     mDeclaration = aDeclaration;
     mDeclaration->SetOwningRule(this);
   }
 
   if (StyleSheet* sheet = GetStyleSheet()) {
-    sheet->AsGecko()->SetModifiedByChildRule();
+    sheet->RuleChanged(this);
   }
 }
 
 /* virtual */ size_t
 nsCSSPageRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 }
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -2349,26 +2349,25 @@ nsStyleSet::Shutdown()
   ClearNonInheritingStyleContexts();
   mRuleTree = nullptr;
   GCRuleTrees();
   MOZ_ASSERT(mUnusedRuleNodeList.isEmpty());
   MOZ_ASSERT(mUnusedRuleNodeCount == 0);
 }
 
 void
-nsStyleSet::RecordStyleSheetChange(CSSStyleSheet* aStyleSheet,
-                                   StyleSheet::ChangeType)
+nsStyleSet::SheetChanged(CSSStyleSheet& aStyleSheet)
 {
   MOZ_ASSERT(mBatching != 0, "Should be in an update");
 
   if (mStylesHaveChanged) {
     return;
   }
 
-  if (Element* scopeElement = aStyleSheet->GetScopeElement()) {
+  if (Element* scopeElement = aStyleSheet.GetScopeElement()) {
     mChangedScopeStyleRoots.AppendElement(scopeElement);
     return;
   }
 
   mStylesHaveChanged = true;
   // If we need to restyle everything, no need to restyle individual
   // scoped style roots.
   mChangedScopeStyleRoots.Clear();
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -336,19 +336,39 @@ class nsStyleSet final
 
   // Begin ignoring style context destruction, to avoid lots of unnecessary
   // work on document teardown.
   void BeginShutdown();
 
   // Free all of the data associated with this style set.
   void Shutdown();
 
+  void RuleAdded(mozilla::CSSStyleSheet& aSheet, mozilla::css::Rule&)
+  {
+    SheetChanged(aSheet);
+  }
+
+  void RuleRemoved(mozilla::CSSStyleSheet& aSheet, mozilla::css::Rule&)
+  {
+    SheetChanged(aSheet);
+  }
+
+  void RuleChanged(mozilla::CSSStyleSheet& aSheet, mozilla::css::Rule*)
+  {
+    SheetChanged(aSheet);
+  }
+
   // Notes that a style sheet has changed.
-  void RecordStyleSheetChange(mozilla::CSSStyleSheet* aStyleSheet,
-                              mozilla::StyleSheet::ChangeType);
+  void RecordStyleSheetChange(mozilla::CSSStyleSheet* aSheet,
+                              mozilla::StyleSheet::ChangeType)
+  {
+    SheetChanged(*aSheet);
+  }
+
+  void SheetChanged(mozilla::CSSStyleSheet&);
 
   // Notes that style sheets have changed in a shadow root.
   void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot);
 
   bool StyleSheetsHaveChanged() const
   {
     return mStylesHaveChanged || !mChangedScopeStyleRoots.IsEmpty();
   }