Bug 912874 - New API to enumerate mutation observers, r=peterv
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 11 Sep 2013 22:43:01 +0300
changeset 159611 050debae43a65922cee5d9748d4df343de39edf8
parent 159610 38bcba92aa14603204ced5378995c122e7e6a517
child 159612 0c5226d187b474ad97ad6164aad2ac9878519240
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs912874
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 912874 - New API to enumerate mutation observers, r=peterv
content/base/public/nsINode.h
content/base/src/nsDOMMutationObserver.cpp
content/base/src/nsDOMMutationObserver.h
content/base/src/nsINode.cpp
content/base/test/test_mutationobservers.html
dom/webidl/MutationObserver.webidl
dom/webidl/Node.webidl
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -41,16 +41,17 @@ class nsIFrame;
 class nsIMutationObserver;
 class nsINodeList;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIURI;
 class nsNodeSupportsWeakRefTearoff;
 class nsNodeWeakReference;
 class nsXPCClassInfo;
+class nsDOMMutationObserver;
 
 namespace mozilla {
 namespace dom {
 /**
  * @return true if aChar is what the DOM spec defines as 'space character'.
  * http://dom.spec.whatwg.org/#space-character
  */
 inline bool IsSpaceCharacter(PRUnichar aChar) {
@@ -1469,16 +1470,18 @@ protected:
 
 public:
   // Makes nsINode object to keep aObject alive.
   void BindObject(nsISupports* aObject);
   // After calling UnbindObject nsINode object doesn't keep
   // aObject alive anymore.
   void UnbindObject(nsISupports* aObject);
 
+  void GetBoundMutationObservers(nsTArray<nsRefPtr<nsDOMMutationObserver> >& aResult);
+
   /**
    * Returns the length of this node, as specified at
    * <http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length>
    */
   uint32_t Length() const;
 
   void GetNodeName(nsAString& aNodeName) const
   {
--- a/content/base/src/nsDOMMutationObserver.cpp
+++ b/content/base/src/nsDOMMutationObserver.cpp
@@ -61,17 +61,16 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_6(
 // Observer
 
 NS_IMPL_ADDREF(nsMutationReceiver)
 NS_IMPL_RELEASE(nsMutationReceiver)
 
 NS_INTERFACE_MAP_BEGIN(nsMutationReceiver)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
-  NS_INTERFACE_MAP_ENTRY(nsMutationReceiver)
 NS_INTERFACE_MAP_END
 
 nsMutationReceiver::nsMutationReceiver(nsINode* aTarget,
                                        nsDOMMutationObserver* aObserver)
 : nsMutationReceiverBase(aTarget, aObserver)
 {
   mTarget->BindObject(aObserver);
 }
@@ -316,16 +315,17 @@ void nsMutationReceiver::NodeWillBeDestr
   Disconnect(true);
 }
 
 // Observer
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationObserver)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsDOMMutationObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationObserver)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMutationObserver)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver)
@@ -520,16 +520,42 @@ nsDOMMutationObserver::Disconnect()
 void
 nsDOMMutationObserver::TakeRecords(
                          nsTArray<nsRefPtr<nsDOMMutationRecord> >& aRetVal)
 {
   aRetVal.Clear();
   mPendingMutations.SwapElements(aRetVal);
 }
 
+void
+nsDOMMutationObserver::GetObservingInfo(nsTArray<Nullable<MutationObservingInfoInitializer> >& aResult)
+{
+  aResult.SetCapacity(mReceivers.Count());
+  for (int32_t i = 0; i < mReceivers.Count(); ++i) {
+    MutationObservingInfoInitializer& info = aResult.AppendElement()->SetValue();
+    nsMutationReceiver* mr = mReceivers[i];
+    info.mChildList = mr->ChildList();
+    info.mAttributes = mr->Attributes();
+    info.mCharacterData = mr->CharacterData();
+    info.mSubtree = mr->Subtree();
+    info.mAttributeOldValue = mr->AttributeOldValue();
+    info.mCharacterDataOldValue = mr->CharacterDataOldValue();
+    nsCOMArray<nsIAtom>& filters = mr->AttributeFilter();
+    if (filters.Count()) {
+      info.mAttributeFilter.Construct();
+      mozilla::dom::Sequence<nsString>& filtersAsStrings =
+        info.mAttributeFilter.Value();
+      for (int32_t j = 0; j < filters.Count(); ++j) {
+        filtersAsStrings.AppendElement(nsDependentAtomString(filters[j]));
+      }
+    }
+    info.mObservedNode = mr->Target();
+  }
+}
+
 // static
 already_AddRefed<nsDOMMutationObserver>
 nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
                                    mozilla::dom::MutationCallback& aCb,
                                    mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
--- a/content/base/src/nsDOMMutationObserver.h
+++ b/content/base/src/nsDOMMutationObserver.h
@@ -20,16 +20,17 @@
 #include "mozilla/dom/Element.h"
 #include "nsClassHashtable.h"
 #include "nsNodeUtils.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/MutationObserverBinding.h"
 
 class nsDOMMutationObserver;
+using mozilla::dom::MutationObservingInfoInitializer;
 
 class nsDOMMutationRecord : public nsISupports,
                             public nsWrapperCache
 {
 public:
   nsDOMMutationRecord(const nsAString& aType, nsISupports* aOwner)
   : mType(aType), mOwner(aOwner)
   {
@@ -262,20 +263,16 @@ private:
   bool                               mCharacterDataOldValue;
   bool                               mAttributes;
   bool                               mAllAttributes;
   bool                               mAttributeOldValue;
   nsCOMArray<nsIAtom>                mAttributeFilter;
 };
 
 
-#define NS_MUTATION_OBSERVER_IID \
-{ 0xe628f313, 0x8129, 0x4f90, \
-  { 0x8e, 0xc3, 0x85, 0xe8, 0x28, 0x22, 0xe7, 0xab } }
-
 class nsMutationReceiver : public nsMutationReceiverBase
 {
 public:
   nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver);
 
   nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
   : nsMutationReceiverBase(aRegisterTarget, aParent)
   {
@@ -310,17 +307,16 @@ public:
 
     mParent = nullptr;
     NS_ASSERTION(!mTarget, "Should not have mTarget");
     NS_ASSERTION(!mObserver, "Should not have mObserver");
   }
 
   void Disconnect(bool aRemoveFromObserver);
 
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMUTATION_OBSERVER_IID)
   NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
@@ -332,31 +328,34 @@ public:
                                           nsIAtom* aAttribute) MOZ_OVERRIDE
   {
     // We can reuse AttributeWillChange implementation.
     AttributeWillChange(aDocument, aElement, aNameSpaceID, aAttribute,
                         nsIDOMMutationEvent::MODIFICATION);
   }
 };
 
-NS_DEFINE_STATIC_IID_ACCESSOR(nsMutationReceiver, NS_MUTATION_OBSERVER_IID)
+#define NS_DOM_MUTATION_OBSERVER_IID \
+{ 0x0c3b91f8, 0xcc3b, 0x4b08, \
+  { 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 } }
 
 class nsDOMMutationObserver : public nsISupports,
                               public nsWrapperCache
 {
 public:
   nsDOMMutationObserver(already_AddRefed<nsPIDOMWindow> aOwner,
                         mozilla::dom::MutationCallback& aCb)
   : mOwner(aOwner), mCallback(&aCb), mWaitingForRun(false), mId(++sCount)
   {
     SetIsDOMBinding();
   }
   virtual ~nsDOMMutationObserver();
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID)
 
   static already_AddRefed<nsDOMMutationObserver>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
               mozilla::dom::MutationCallback& aCb,
               mozilla::ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
@@ -374,16 +373,20 @@ public:
                mozilla::ErrorResult& aRv);
 
   void Disconnect();
 
   void TakeRecords(nsTArray<nsRefPtr<nsDOMMutationRecord> >& aRetVal);
 
   void HandleMutation();
 
+  void GetObservingInfo(nsTArray<Nullable<MutationObservingInfoInitializer> >& aResult);
+
+  mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
+
   // static methods
   static void HandleMutations()
   {
     if (sScheduledMutationObservers) {
       HandleMutationsInternal();
     }
   }
 
@@ -440,16 +443,18 @@ protected:
   static nsTArray<nsRefPtr<nsDOMMutationObserver> >* sScheduledMutationObservers;
   static nsDOMMutationObserver*                      sCurrentObserver;
 
   static uint32_t                                    sMutationLevel;
   static nsAutoTArray<nsTArray<nsRefPtr<nsDOMMutationObserver> >, 4>*
                                                      sCurrentlyHandlingObservers;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver, NS_DOM_MUTATION_OBSERVER_IID)
+
 class nsAutoMutationBatch
 {
 public:
   nsAutoMutationBatch()
   : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false),
     mFromFirstToLast(false), mAllowNestedBatches(false)    
   {
   }
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -98,16 +98,17 @@
 #include "nsCSSParser.h"
 #include "HTMLLegendElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "WrapperFactory.h"
 #include "DocumentType.h"
 #include <algorithm>
 #include "nsDOMEvent.h"
 #include "nsGlobalWindow.h"
+#include "nsDOMMutationObserver.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsINode::nsSlots::~nsSlots()
 {
   if (mChildNodes) {
     mChildNodes->DropReference();
@@ -2139,16 +2140,32 @@ nsINode::UnbindObject(nsISupports* aObje
 {
   nsCOMArray<nsISupports>* objects =
     static_cast<nsCOMArray<nsISupports>*>(GetProperty(nsGkAtoms::keepobjectsalive));
   if (objects) {
     objects->RemoveObject(aObject);
   }
 }
 
+void
+nsINode::GetBoundMutationObservers(nsTArray<nsRefPtr<nsDOMMutationObserver> >& aResult)
+{
+  nsCOMArray<nsISupports>* objects =
+    static_cast<nsCOMArray<nsISupports>*>(GetProperty(nsGkAtoms::keepobjectsalive));
+  if (objects) {
+    for (int32_t i = 0; i < objects->Count(); ++i) {
+      nsCOMPtr<nsDOMMutationObserver> mo = do_QueryInterface(objects->ObjectAt(i));
+      if (mo) {
+        MOZ_ASSERT(!aResult.Contains(mo));
+        aResult.AppendElement(mo.forget());
+      }
+    }
+  }
+}
+
 size_t
 nsINode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
   nsEventListenerManager* elm =
     const_cast<nsINode*>(this)->GetListenerManager(false);
   if (elm) {
     n += elm->SizeOfIncludingThis(aMallocSizeOf);
--- a/content/base/test/test_mutationobservers.html
+++ b/content/base/test/test_mutationobservers.html
@@ -148,17 +148,20 @@ function runTest() {
       is(records.length, 1, "Should have one record.");
       is(records[0].type, "attributes", "Should have got attributes record");
       is(records[0].target, div, "Should have got div as target");
       is(records[0].attributeName, "foo", "Should have got record about foo attribute");
       observer.disconnect();
       then(testThisBind);
       m = null;
     });
-  m.observe(div, { attributes: true});
+  m.observe(div, { attributes: true, attributeFilter: ["foo"] });
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true);
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter.length, 1)
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter[0], "foo")
   div.setAttribute("foo", "bar");
 }
 
 // 'this' handling when fn.bind() is used.
 function testThisBind() {
   var child = div.appendChild(document.createElement("div"));
   var gchild = child.appendChild(document.createElement("div"));
   m = new M((function(records, observer) {
@@ -177,16 +180,19 @@ function testThisBind() {
       is(records[2].target, gchild, "Should have got div as target");
       is(records[2].attributeName, "foo", "Should have got record about foo attribute");
       is(records[2].oldValue, null, "oldValue should be bar2");
       observer.disconnect();
       then(testCharacterData);
       m = null;
     }).bind(window));
   m.observe(div, { attributes: true, attributeOldValue: true, subtree: true });
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true)
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeOldValue, true)
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
   div.setAttribute("foo", "bar2");
   div.removeAttribute("foo");
   div.removeChild(child);
   child.removeChild(gchild);
   div.appendChild(gchild);
   div.removeChild(gchild);
   gchild.setAttribute("foo", "bar");
 }
@@ -220,16 +226,21 @@ function testCharacterData() {
 
   div.appendChild(document.createTextNode("foo"));
   m.observe(div, { characterData: true, subtree: true });
   m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true});
   // If observing the same node twice, only the latter option should apply.
   m3.observe(div, { characterData: true, subtree: true });
   m3.observe(div, { characterData: true, subtree: false });
   m4.observe(div.firstChild, { characterData: true, subtree: false });
+  
+  is(SpecialPowers.wrap(div).getBoundMutationObservers().length, 3)
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].characterData, true)
+  is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].subtree, false)
+
   div.firstChild.data = "bar";
 }
 
 function testChildList() {
   var fc = div.firstChild;
   m = new M(function(records, observer) {
       is(records[0].type, "childList", "Should have got childList");
       is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
@@ -273,35 +284,42 @@ function testChildList3() {
 function testChildList4() {
   div.textContent = null;
   var df = document.createDocumentFragment();
   var t1 = df.appendChild(document.createTextNode("Hello "));
   var t2 = df.appendChild(document.createTextNode("world!"));
   var s1 = div.appendChild(document.createElement("span"));
   s1.textContent = "foo";
   var s2 = div.appendChild(document.createElement("span"));
-  m = new M(function(records, observer) {
+  function callback(records, observer) {
       is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div");
       is(records[0].removedNodes.length, 2, "Should have got removedNodes");
       is(records[0].removedNodes[0], t1, "Should be the 1st textnode");
       is(records[0].removedNodes[1], t2, "Should be the 2nd textnode");
       is(records[1].addedNodes.length, 2, "Should have got addedNodes");
       is(records[1].addedNodes[0], t1, "Should be the 1st textnode");
       is(records[1].addedNodes[1], t2, "Should be the 2nd textnode");
       is(records[1].previousSibling, s1, "Should have previousSibling");
       is(records[1].nextSibling, s2, "Should have nextSibling");
       is(records[2].type, "characterData", "3rd record should be characterData");
       is(records[2].target, t1, "target should be the textnode");
       is(records[2].oldValue, "Hello ", "oldValue was 'Hello '");
       observer.disconnect();
       then(testChildList5);
       m = null;
-    });
+    };
+  m = new M(callback);
   m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true });
+  is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].childList, true)
+  is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterData, true)
+  is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterDataOldValue, true)
+  is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
+  ok(SpecialPowers.compare(SpecialPowers.wrap(df).getBoundMutationObservers()[0].mutationCallback, callback))
   m.observe(div, { childList: true });
+  is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo().length, 2)
   
   // Make sure transient observers aren't leaked.
   var leakTest = new M(function(){});
   leakTest.observe(div, { characterData: true, subtree: true });
   
   div.insertBefore(df, s2);
   s1.firstChild.data = "bar"; // This should *not* create a record.
   t1.data = "Hello the whole "; // This should create a record.
@@ -549,16 +567,18 @@ function testTakeRecords() {
       is(records[1].attributeName, "foo", "");
       is(records[1].oldValue, "bar", "");
       observer.disconnect();
       div.removeEventListener("DOMAttrModified", mutationListener);
       then(testExpandos);
       m = null;
     });
   m.observe(div, { attributes: true, attributeOldValue: true });
+  // Note, [0] points to a mutation observer which is there for a leak test!
+  ok(SpecialPowers.compare(SpecialPowers.wrap(div).getBoundMutationObservers()[1], m));
   var mutationEventCount = 0;
   div.addEventListener("DOMAttrModified", mutationListener);
   div.setAttribute("foo", "bar");
   div.setAttribute("foo", "bar");
   is(mutationEventCount, 1, "Should have got only one mutation event!");
 }
 
 function testExpandos() {
--- a/dom/webidl/MutationObserver.webidl
+++ b/dom/webidl/MutationObserver.webidl
@@ -22,21 +22,31 @@ interface MutationRecord {
 };
 
 [Constructor(MutationCallback mutationCallback)]
 interface MutationObserver {
   [Throws]
   void observe(Node target, optional MutationObserverInit options);
   void disconnect();
   sequence<MutationRecord> takeRecords();
+
+  [ChromeOnly]
+  sequence<MutationObservingInfo?> getObservingInfo();
+  [ChromeOnly]
+  readonly attribute MutationCallback mutationCallback;
 };
 
 callback MutationCallback = void (sequence<MutationRecord> mutations, MutationObserver observer);
 
 dictionary MutationObserverInit {
   boolean childList = false;
   boolean attributes = false;
   boolean characterData = false;
   boolean subtree = false;
   boolean attributeOldValue = false;
   boolean characterDataOldValue = false;
   sequence<DOMString> attributeFilter;
 };
+
+dictionary MutationObservingInfo : MutationObserverInit
+{
+  Node? observedNode = null;
+};
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -99,9 +99,11 @@ interface Node : EventTarget {
   [Throws, Func="IsChromeOrXBL"]
   any setUserData(DOMString key, any data, UserDataHandler? handler);
   [Throws, Func="IsChromeOrXBL"]
   any getUserData(DOMString key);
   [ChromeOnly]
   readonly attribute Principal nodePrincipal;
   [ChromeOnly]
   readonly attribute URI? baseURIObject;
+  [ChromeOnly]
+  sequence<MutationObserver> getBoundMutationObservers();
 };