Bug 839103 - Part 11: Add StyleRule{Added,Removed,Changed} events. r=bz
authorCameron McCormack <cam@mcc.id.au>
Thu, 16 May 2013 17:13:36 +1000
changeset 132421 4b2d807f7fb42a47d969fe3e93181260e1f67f57
parent 132420 cf13d8dac8d950a48dc82b5262746941afe40ff2
child 132422 789367132def277605e2cc3a5cc04a23a446140b
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbz
bugs839103
milestone24.0a1
Bug 839103 - Part 11: Add StyleRule{Added,Removed,Changed} events. r=bz
browser/base/content/test/browser_bug839103.js
browser/base/content/test/test_bug839103.html
content/base/src/nsDocument.cpp
dom/interfaces/events/moz.build
dom/interfaces/events/nsIDOMStyleRuleChangeEvent.idl
dom/tests/mochitest/general/test_interfaces.html
js/xpconnect/src/event_impl_gen.conf.in
layout/style/StyleRule.cpp
layout/style/nsCSSStyleSheet.cpp
--- a/browser/base/content/test/browser_bug839103.js
+++ b/browser/base/content/test/browser_bug839103.js
@@ -63,46 +63,89 @@ function dynamicStylesheetAdded(evt) {
   is(evt.type, "StyleSheetAdded", "evt.type has expected value");
   is(evt.target, gBrowser.contentDocument, "event targets correct document");
   ok(evt.stylesheet, "evt.stylesheet is defined");
   ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
   ok(evt.documentSheet, "style sheet is a document sheet");
 }
 
 function dynamicStylesheetApplicableStateChanged(evt) {
+  gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true);
   ok(true, "received dynamic style sheet applicable state change event");
   is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
   is(evt.target, gBrowser.contentDocument, "event targets correct document");
   is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value");
   is(evt.applicable, true, "evt.applicable has the right value");
-  gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true);
+
   gBrowser.addEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true);
   gLinkElement.disabled = true;
 }
 
 function dynamicStylesheetApplicableStateChangedToFalse(evt) {
   gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true);
   is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
   ok(true, "received dynamic style sheet applicable state change event after media=\"\" changed");
   is(evt.target, gBrowser.contentDocument, "event targets correct document");
   is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value");
   is(evt.applicable, false, "evt.applicable has the right value");
+
   gBrowser.addEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true);
   gBrowser.contentDocument.body.removeChild(gLinkElement);
 }
 
 function dynamicStylesheetRemoved(evt) {
   gBrowser.removeEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true);
   ok(true, "received dynamic style sheet removal");
   is(evt.type, "StyleSheetRemoved", "evt.type has expected value");
   is(evt.target, gBrowser.contentDocument, "event targets correct document");
   ok(evt.stylesheet, "evt.stylesheet is defined");
   ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
   ok(evt.stylesheet.href.contains(gStyleSheet), "evt.stylesheet is the removed stylesheet");
-  is(evt.rule, null, "evt.rule is null");
+
+  gBrowser.addEventListener("StyleRuleAdded", styleRuleAdded, true);
+  gBrowser.contentDocument.querySelector("style").sheet.insertRule("*{color:black}", 0);
+}
+
+function styleRuleAdded(evt) {
+  gBrowser.removeEventListener("StyleRuleAdded", styleRuleAdded, true);
+  ok(true, "received style rule added event");
+  is(evt.type, "StyleRuleAdded", "evt.type has expected value");
+  is(evt.target, gBrowser.contentDocument, "event targets correct document");
+  ok(evt.stylesheet, "evt.stylesheet is defined");
+  ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+  ok(evt.rule, "evt.rule is defined");
+  is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value");
+
+  gBrowser.addEventListener("StyleRuleChanged", styleRuleChanged, true);
+  evt.rule.style.cssText = "color:green";
+}
+
+function styleRuleChanged(evt) {
+  gBrowser.removeEventListener("StyleRuleChanged", styleRuleChanged, true);
+  ok(true, "received style rule changed event");
+  is(evt.type, "StyleRuleChanged", "evt.type has expected value");
+  is(evt.target, gBrowser.contentDocument, "event targets correct document");
+  ok(evt.stylesheet, "evt.stylesheet is defined");
+  ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+  ok(evt.rule, "evt.rule is defined");
+  is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value");
+
+  gBrowser.addEventListener("StyleRuleRemoved", styleRuleRemoved, true);
+  evt.stylesheet.deleteRule(0);
+}
+
+function styleRuleRemoved(evt) {
+  gBrowser.removeEventListener("StyleRuleRemoved", styleRuleRemoved, true);
+  ok(true, "received style rule removed event");
+  is(evt.type, "StyleRuleRemoved", "evt.type has expected value");
+  is(evt.target, gBrowser.contentDocument, "event targets correct document");
+  ok(evt.stylesheet, "evt.stylesheet is defined");
+  ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+  ok(evt.rule, "evt.rule is defined");
+
   executeSoon(concludeTest);
 }
 
 function concludeTest() {
   let doc = gBrowser.contentDocument;
   doc.removeEventListener("StyleSheetAdded", unexpectedContentEvent, false);
   doc.removeEventListener("StyleSheetRemoved", unexpectedContentEvent, false);
   doc.removeEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false);
--- a/browser/base/content/test/test_bug839103.html
+++ b/browser/base/content/test/test_bug839103.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>Document for Bug 839103</title>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style></style>
 </head>
 <body>
 </body>
 </html>
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -132,16 +132,17 @@
 #include "nsIDocumentLoaderFactory.h"
 #include "nsIDocumentLoader.h"
 #include "nsIContentViewer.h"
 #include "nsIXMLContentSink.h"
 #include "nsIXULDocument.h"
 #include "nsIPrompt.h"
 #include "nsIPropertyBag2.h"
 #include "nsIDOMPageTransitionEvent.h"
+#include "nsIDOMStyleRuleChangeEvent.h"
 #include "nsIDOMStyleSheetChangeEvent.h"
 #include "nsIDOMStyleSheetApplicableStateChangeEvent.h"
 #include "nsJSUtils.h"
 #include "nsFrameLoader.h"
 #include "nsEscape.h"
 #include "nsObjectLoadingContent.h"
 #include "nsHtml5TreeOpExecutor.h"
 #include "nsIDOMElementReplaceEvent.h"
@@ -197,16 +198,18 @@
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsViewportInfo.h"
 #include "nsDOMEvent.h"
 #include "nsIContentPermissionPrompt.h"
 #include "mozilla/StaticPtr.h"
 #include "nsITextControlElement.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIEditor.h"
+#include "nsIDOMCSSStyleRule.h"
+#include "mozilla/css/Rule.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gDocumentLeakPRLog;
@@ -3899,18 +3902,16 @@ nsDocument::SetStyleSheetApplicableState
     DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleSheetApplicableStateChangeEvent,
                                nsIDOMStyleSheetApplicableStateChangeEvent,
                                InitStyleSheetApplicableStateChangeEvent,
                                "StyleSheetApplicableStateChanged",
                                aApplicable);
   }
 }
 
-#undef DO_STYLESHEET_NOTIFICATION
-
 // These three functions are a lot like the implementation of the
 // corresponding API for regular stylesheets.
 
 int32_t
 nsDocument::GetNumberOfCatalogStyleSheets() const
 {
   return mCatalogSheets.Count();
 }
@@ -4628,40 +4629,69 @@ nsDocument::DocumentStatesChanged(nsEven
   // Invalidate our cached state.
   mGotDocumentState &= ~aStateMask;
   mDocumentState &= ~aStateMask;
 
   NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask));
 }
 
 void
-nsDocument::StyleRuleChanged(nsIStyleSheet* aStyleSheet,
+nsDocument::StyleRuleChanged(nsIStyleSheet* aSheet,
                              nsIStyleRule* aOldStyleRule,
                              nsIStyleRule* aNewStyleRule)
 {
   NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged,
-                               (this, aStyleSheet,
+                               (this, aSheet,
                                 aOldStyleRule, aNewStyleRule));
-}
-
-void
-nsDocument::StyleRuleAdded(nsIStyleSheet* aStyleSheet,
+
+  if (StyleSheetChangeEventsEnabled()) {
+    nsCOMPtr<css::Rule> rule = do_QueryInterface(aNewStyleRule);
+    DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent,
+                               nsIDOMStyleRuleChangeEvent,
+                               InitStyleRuleChangeEvent,
+                               "StyleRuleChanged",
+                               rule ? rule->GetDOMRule() : nullptr);
+  }
+}
+
+void
+nsDocument::StyleRuleAdded(nsIStyleSheet* aSheet,
                            nsIStyleRule* aStyleRule)
 {
   NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded,
-                               (this, aStyleSheet, aStyleRule));
-}
-
-void
-nsDocument::StyleRuleRemoved(nsIStyleSheet* aStyleSheet,
+                               (this, aSheet, aStyleRule));
+
+  if (StyleSheetChangeEventsEnabled()) {
+    nsCOMPtr<css::Rule> rule = do_QueryInterface(aStyleRule);
+    DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent,
+                               nsIDOMStyleRuleChangeEvent,
+                               InitStyleRuleChangeEvent,
+                               "StyleRuleAdded",
+                               rule ? rule->GetDOMRule() : nullptr);
+  }
+}
+
+void
+nsDocument::StyleRuleRemoved(nsIStyleSheet* aSheet,
                              nsIStyleRule* aStyleRule)
 {
   NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved,
-                               (this, aStyleSheet, aStyleRule));
-}
+                               (this, aSheet, aStyleRule));
+
+  if (StyleSheetChangeEventsEnabled()) {
+    nsCOMPtr<css::Rule> rule = do_QueryInterface(aStyleRule);
+    DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent,
+                               nsIDOMStyleRuleChangeEvent,
+                               InitStyleRuleChangeEvent,
+                               "StyleRuleRemoved",
+                               rule ? rule->GetDOMRule() : nullptr);
+  }
+}
+
+#undef DO_STYLESHEET_NOTIFICATION
 
 
 //
 // nsIDOMDocument interface
 //
 DocumentType*
 nsIDocument::GetDoctype() const
 {
--- a/dom/interfaces/events/moz.build
+++ b/dom/interfaces/events/moz.build
@@ -41,16 +41,17 @@ XPIDL_SOURCES += [
     'nsIDOMPaintRequest.idl',
     'nsIDOMPaintRequestList.idl',
     'nsIDOMPopStateEvent.idl',
     'nsIDOMPopupBlockedEvent.idl',
     'nsIDOMProgressEvent.idl',
     'nsIDOMScrollAreaEvent.idl',
     'nsIDOMSimpleGestureEvent.idl',
     'nsIDOMSmartCardEvent.idl',
+    'nsIDOMStyleRuleChangeEvent.idl',
     'nsIDOMStyleSheetChangeEvent.idl',
     'nsIDOMStyleSheetApplicableStateChangeEvent.idl',
     'nsIDOMTouchEvent.idl',
     'nsIDOMTransitionEvent.idl',
     'nsIDOMUIEvent.idl',
     'nsIDOMUserProximityEvent.idl',
     'nsIDOMWheelEvent.idl',
 ]
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMStyleRuleChangeEvent.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDOMEvent.idl"
+
+interface nsIDOMCSSRule;
+interface nsIDOMCSSStyleSheet;
+
+[scriptable, builtinclass, uuid(36098d39-b471-47e9-976e-33fee3d81467)]
+interface nsIDOMStyleRuleChangeEvent : nsIDOMEvent
+{
+  readonly attribute nsIDOMCSSStyleSheet stylesheet;
+  readonly attribute nsIDOMCSSRule rule;
+  [noscript] void initStyleRuleChangeEvent(in DOMString aTypeArg,
+                                           in boolean aCanBubbleArg,
+                                           in boolean aCancelableArg,
+                                           in nsIDOMCSSStyleSheet aStyleSheet,
+                                           in nsIDOMCSSRule aRule);
+};
+
+dictionary StyleRuleChangeEventInit : EventInit
+{
+  nsIDOMCSSStyleSheet stylesheet;
+  nsIDOMCSSRule rule;
+};
+
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -541,16 +541,17 @@ var interfaceNamesInGlobalScope =
     "Gamepad",
     "GamepadEvent",
     "GamepadButtonEvent",
     "GamepadAxisMoveEvent",
     "SpeechRecognitionEvent",
     "SpeechRecognitionError",
     "SpeechSynthesisEvent",
     "PushManager",
+    "StyleRuleChangeEvent",
     "StyleSheetChangeEvent",
     "StyleSheetApplicableStateChangeEvent",
     "MozMobileMessageThread",
     "PaymentRequestInfo",
   ]
 // IMPORTANT: Do not change this list without review from a DOM peer!
 
 // If your interface is named nsIDOMSomeInterface and you don't mean to expose
--- a/js/xpconnect/src/event_impl_gen.conf.in
+++ b/js/xpconnect/src/event_impl_gen.conf.in
@@ -19,16 +19,17 @@ simple_events = [
     'PopStateEvent',
     'HashChangeEvent',
     'CloseEvent',
     'MozContactChangeEvent',
     'DeviceOrientationEvent',
     'DeviceLightEvent',
     'MozApplicationEvent',
     'SmartCardEvent',
+    'StyleRuleChangeEvent',
     'StyleSheetChangeEvent',
     'StyleSheetApplicableStateChangeEvent',
 #ifdef MOZ_B2G_BT
     'BluetoothDeviceEvent',
 #endif
 #ifdef MOZ_B2G_RIL
     'CallEvent',
     'CFStateChangeEvent',
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -1381,22 +1381,23 @@ StyleRule::Clone() const
 {
   nsRefPtr<Rule> clone = new StyleRule(*this);
   return clone.forget();
 }
 
 /* virtual */ nsIDOMCSSRule*
 StyleRule::GetDOMRule()
 {
-  if (!GetStyleSheet()) {
-    // inline style rules aren't supposed to have a DOM rule object, only
-    // a declaration.
-    return nullptr;
-  }
   if (!mDOMRule) {
+    if (!GetStyleSheet()) {
+      // Inline style rules aren't supposed to have a DOM rule object, only
+      // a declaration.  But if we do have one already, from a style sheet
+      // rule that used to be in a document, we still want to return it.
+      return nullptr;
+    }
     mDOMRule = new DOMCSSStyleRule(this);
     NS_ADDREF(mDOMRule);
   }
   return mDOMRule;
 }
 
 /* virtual */ nsIDOMCSSRule*
 StyleRule::GetExistingDOMRule()
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -2030,16 +2030,21 @@ nsCSSStyleSheet::DeleteRule(uint32_t aIn
 
     NS_ASSERTION(uint32_t(mInner->mOrderedRules.Count()) <= INT32_MAX,
                  "Too many style rules!");
 
     // Hold a strong ref to the rule so it doesn't die when we RemoveObjectAt
     nsRefPtr<css::Rule> rule = mInner->mOrderedRules.ObjectAt(aIndex);
     if (rule) {
       mInner->mOrderedRules.RemoveObjectAt(aIndex);
+      if (mDocument && mDocument->StyleSheetChangeEventsEnabled()) {
+        // Force creation of the DOM rule, so that it can be put on the
+        // StyleRuleRemoved event object.
+        rule->GetDOMRule();
+      }
       rule->SetStyleSheet(nullptr);
       DidDirty();
 
       if (mDocument) {
         mDocument->StyleRuleRemoved(this, rule);
       }
     }
   }