Bug 288392, DOMSubtreeModified event, r=peterv, sr=jst
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -85,21 +85,22 @@ class nsHTMLStyleSheet;
class nsIHTMLCSSStyleSheet;
class nsILayoutHistoryState;
class nsIVariant;
class nsIDOMUserDataHandler;
template<class E> class nsCOMArray;
class nsIDocumentObserver;
class nsBindingManager;
class nsIDOMNodeList;
+class mozAutoSubtreeModified;
// IID for the nsIDocument interface
#define NS_IDOCUMENT_IID \
-{ 0x0b8acf09, 0x7877, 0x4928, \
- { 0x8b, 0xfb, 0x6d, 0x7c, 0x6d, 0x53, 0xff, 0x32 } }
+{ 0x1b8ed19c, 0xb87d, 0x4058, \
+ { 0x92, 0x2a, 0xff, 0xbc, 0x36, 0x29, 0x3b, 0xd7 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
//----------------------------------------------------------------------
// Document interface. This is implemented by all document objects in
// Gecko.
@@ -841,26 +842,40 @@ public:
virtual nsresult GetContentListFor(nsIContent* aContent,
nsIDOMNodeList** aResult) = 0;
/**
* See FlushSkinBindings on nsBindingManager
*/
virtual void FlushSkinBindings() = 0;
+ /**
+ * Returns PR_TRUE if one or more mutation events are being dispatched.
+ */
+ virtual PRBool MutationEventBeingDispatched() = 0;
+
protected:
~nsIDocument()
{
// XXX The cleanup of mNodeInfoManager (calling DropDocumentReference and
// releasing it) happens in the nsDocument destructor. We'd prefer to
// do it here but nsNodeInfoManager is a concrete class that we don't
// want to expose to users of the nsIDocument API outside of Gecko.
// XXX Same thing applies to mBindingManager
}
+ /**
+ * These methods should be called before and after dispatching
+ * a mutation event.
+ * To make this easy and painless, use the mozAutoSubtreeModified helper class.
+ */
+ virtual void WillDispatchMutationEvent(nsINode* aTarget) = 0;
+ virtual void MutationEventDispatched(nsINode* aTarget) = 0;
+ friend class mozAutoSubtreeModified;
+
nsString mDocumentTitle;
nsCOMPtr<nsIURI> mDocumentURI;
nsCOMPtr<nsIURI> mDocumentBaseURI;
nsWeakPtr mDocumentLoadGroup;
nsWeakPtr mDocumentContainer;
@@ -947,16 +962,58 @@ private:
#define MOZ_AUTO_DOC_UPDATE_PASTE2(tok,line) tok##line
#define MOZ_AUTO_DOC_UPDATE_PASTE(tok,line) \
MOZ_AUTO_DOC_UPDATE_PASTE2(tok,line)
#define MOZ_AUTO_DOC_UPDATE(doc,type,notify) \
mozAutoDocUpdate MOZ_AUTO_DOC_UPDATE_PASTE(_autoDocUpdater_, __LINE__) \
(doc,type,notify)
+/**
+ * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
+ * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
+ * object is deleted.
+ */
+class mozAutoSubtreeModified
+{
+public:
+ /**
+ * @param aSubTreeOwner The document in which a subtree will be modified.
+ * @param aTarget The target of the possible DOMSubtreeModified event.
+ * Can be nsnull, in which case mozAutoSubtreeModified
+ * is just used to batch DOM mutations.
+ */
+ mozAutoSubtreeModified(nsIDocument* aSubtreeOwner, nsINode* aTarget)
+ {
+ UpdateTarget(aSubtreeOwner, aTarget);
+ }
+
+ ~mozAutoSubtreeModified()
+ {
+ UpdateTarget(nsnull, nsnull);
+ }
+
+ void UpdateTarget(nsIDocument* aSubtreeOwner, nsINode* aTarget)
+ {
+ if (mSubtreeOwner) {
+ mSubtreeOwner->MutationEventDispatched(mTarget);
+ }
+
+ mTarget = aTarget;
+ mSubtreeOwner = aSubtreeOwner;
+ if (mSubtreeOwner) {
+ mSubtreeOwner->WillDispatchMutationEvent(mTarget);
+ }
+ }
+
+private:
+ nsCOMPtr<nsINode> mTarget;
+ nsCOMPtr<nsIDocument> mSubtreeOwner;
+};
+
// XXX These belong somewhere else
nsresult
NS_NewHTMLDocument(nsIDocument** aInstancePtrResult);
nsresult
NS_NewXMLDocument(nsIDocument** aInstancePtrResult);
#ifdef MOZ_SVG
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -2815,16 +2815,22 @@ PRBool
nsContentUtils::HasMutationListeners(nsINode* aNode,
PRUint32 aType)
{
nsIDocument* doc = aNode->GetOwnerDoc();
if (!doc) {
return PR_FALSE;
}
+ // To batch DOMSubtreeModified properly, all mutation events should be
+ // processed if one is being processed already.
+ if (doc->MutationEventBeingDispatched()) {
+ return PR_TRUE;
+ }
+
// global object will be null for documents that don't have windows.
nsCOMPtr<nsPIDOMWindow> window;
window = do_QueryInterface(doc->GetScriptGlobalObject());
if (window && !window->HasMutationListeners(aType)) {
return PR_FALSE;
}
// If we have a window, we can check it for mutation listeners now.
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -140,16 +140,17 @@ static NS_DEFINE_CID(kDOMEventGroupCID,
#include "nsICharsetAlias.h"
#include "nsIParser.h"
#include "nsIContentSink.h"
#include "nsDateTimeFormatCID.h"
#include "nsIDateTimeFormat.h"
#include "nsEventDispatcher.h"
+#include "nsMutationEvent.h"
#include "nsIDOMXPathEvaluator.h"
#include "nsDOMCID.h"
#include "nsLayoutStatics.h"
#include "nsIJSContextStack.h"
#include "nsIXPConnect.h"
#include "nsCycleCollector.h"
@@ -5699,16 +5700,82 @@ nsDocument::OnPageHide(PRBool aPersisted
// Now send out a PageHide event.
nsPageTransitionEvent event(PR_TRUE, NS_PAGE_HIDE, aPersisted);
DispatchEventToWindow(&event);
mVisible = PR_FALSE;
}
+void
+nsDocument::WillDispatchMutationEvent(nsINode* aTarget)
+{
+ NS_ASSERTION(mSubtreeModifiedDepth != 0 ||
+ mSubtreeModifiedTargets.Count() == 0,
+ "mSubtreeModifiedTargets not cleared after dispatching?");
+ ++mSubtreeModifiedDepth;
+ if (aTarget) {
+ mSubtreeModifiedTargets.AppendObject(aTarget);
+ }
+}
+
+void
+nsDocument::MutationEventDispatched(nsINode* aTarget)
+{
+ --mSubtreeModifiedDepth;
+ if (mSubtreeModifiedDepth == 0) {
+ PRInt32 count = mSubtreeModifiedTargets.Count();
+ if (!count) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindow> window;
+ window = do_QueryInterface(GetScriptGlobalObject());
+ if (window &&
+ !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
+ mSubtreeModifiedTargets.Clear();
+ return;
+ }
+
+ nsCOMArray<nsINode> realTargets;
+ for (PRInt32 i = 0; i < count; ++i) {
+ nsINode* possibleTarget = mSubtreeModifiedTargets[i];
+ nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
+ if (content && content->IsAnonymousForEvents()) {
+ if (realTargets.IndexOf(possibleTarget) == -1) {
+ realTargets.AppendObject(possibleTarget);
+ }
+ continue;
+ }
+
+ nsINode* commonAncestor = nsnull;
+ PRInt32 realTargetCount = realTargets.Count();
+ for (PRInt32 j = 0; j < realTargetCount; ++j) {
+ commonAncestor =
+ nsContentUtils::GetCommonAncestor(possibleTarget, realTargets[j]);
+ if (commonAncestor) {
+ realTargets.ReplaceObjectAt(commonAncestor, j);
+ break;
+ }
+ }
+ if (!commonAncestor) {
+ realTargets.AppendObject(possibleTarget);
+ }
+ }
+
+ mSubtreeModifiedTargets.Clear();
+
+ PRInt32 realTargetCount = realTargets.Count();
+ for (PRInt32 k = 0; k < realTargetCount; ++k) {
+ nsMutationEvent mutation(PR_TRUE, NS_MUTATION_SUBTREEMODIFIED);
+ nsEventDispatcher::Dispatch(realTargets[k], nsnull, &mutation);
+ }
+ }
+}
+
static PRUint32 GetURIHash(nsIURI* aURI)
{
nsCAutoString str;
aURI->GetSpec(str);
return HashString(str);
}
void
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -504,16 +504,23 @@ public:
const PRInt32 aStandalone);
virtual void GetXMLDeclaration(nsAString& aVersion,
nsAString& aEncoding,
nsAString& Standalone);
virtual PRBool IsScriptEnabled();
virtual void OnPageShow(PRBool aPersisted);
virtual void OnPageHide(PRBool aPersisted);
+
+ virtual void WillDispatchMutationEvent(nsINode* aTarget);
+ virtual void MutationEventDispatched(nsINode* aTarget);
+ virtual PRBool MutationEventBeingDispatched()
+ {
+ return (mSubtreeModifiedDepth > 0);
+ }
// nsINode
virtual PRBool IsNodeOfType(PRUint32 aFlags) const;
virtual nsIContent *GetChildAt(PRUint32 aIndex) const;
virtual PRInt32 IndexOf(nsINode* aPossibleChild) const;
virtual PRUint32 GetChildCount() const;
virtual nsresult InsertChildAt(nsIContent* aKid, PRUint32 aIndex,
PRBool aNotify);
@@ -822,14 +829,17 @@ private:
// A map from unvisited URI hashes to content elements
nsTHashtable<nsUint32ToContentHashEntry> mLinkMap;
// URIs whose visitedness has changed while we were hidden
nsCOMArray<nsIURI> mVisitednessChangedURIs;
// Member to store out last-selected stylesheet set.
nsString mLastStyleSheetSet;
+ nsCOMArray<nsINode> mSubtreeModifiedTargets;
+ PRUint32 mSubtreeModifiedDepth;
+
// Our update nesting level
PRUint32 mUpdateNestLevel;
};
#endif /* nsDocument_h___ */
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -465,16 +465,17 @@ nsGenericDOMDataNode::SetTextInternal(PR
mutation.mPrevAttrValue = oldValue;
if (aLength > 0) {
nsAutoString val;
mText.AppendTo(val);
mutation.mNewAttrValue = do_GetAtom(val);
}
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), this);
nsEventDispatcher::Dispatch(this, nsnull, &mutation);
}
CharacterDataChangeInfo info = {
aOffset == textLength,
aOffset,
endOffset,
aLength
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -341,16 +341,19 @@ nsNode3Tearoff::GetTextContent(nsAString
nsContentUtils::GetNodeTextContent(mContent, PR_TRUE, aTextContent);
return NS_OK;
}
NS_IMETHODIMP
nsNode3Tearoff::SetTextContent(const nsAString &aTextContent)
{
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(mContent->GetOwnerDoc(), nsnull);
+
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
NS_ASSERTION(node, "We have an nsIContent which doesn't support nsIDOMNode");
PRUint16 nodeType;
node->GetNodeType(&nodeType);
if (nodeType == nsIDOMNode::DOCUMENT_TYPE_NODE ||
nodeType == nsIDOMNode::NOTATION_NODE) {
return NS_OK;
@@ -1662,16 +1665,19 @@ nsGenericElement::JoinTextNodes(nsIConte
}
return rv;
}
nsresult
nsGenericElement::Normalize()
{
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), nsnull);
+
nsresult result = NS_OK;
PRUint32 index, count = GetChildCount();
for (index = 0; (index < count) && (NS_OK == result); index++) {
nsIContent *child = GetChildAt(index);
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(child);
if (node) {
@@ -2338,16 +2344,17 @@ nsGenericElement::doInsertChildAt(nsICon
} else {
nsNodeUtils::ContentInserted(container, aKid, aIndex);
}
if (nsContentUtils::HasMutationListeners(aKid,
NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
nsMutationEvent mutation(PR_TRUE, NS_MUTATION_NODEINSERTED);
mutation.mRelatedNode = do_QueryInterface(container);
+ mozAutoSubtreeModified subtree(container->GetOwnerDoc(), container);
nsEventDispatcher::Dispatch(aKid, nsnull, &mutation);
}
}
return NS_OK;
}
nsresult
@@ -2382,21 +2389,23 @@ nsGenericElement::doRemoveChildAt(PRUint
NS_PRECONDITION(aKid && aKid->GetParent() == aParent &&
aKid == container->GetChildAt(aIndex) &&
container->IndexOf(aKid) == (PRInt32)aIndex, "Bogus aKid");
mozAutoDocUpdate updateBatch(aDocument, UPDATE_CONTENT_MODEL, aNotify);
nsMutationGuard guard;
+ mozAutoSubtreeModified subtree(nsnull, nsnull);
if (aNotify &&
nsContentUtils::HasMutationListeners(aKid,
NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
nsMutationEvent mutation(PR_TRUE, NS_MUTATION_NODEREMOVED);
mutation.mRelatedNode = do_QueryInterface(container);
+ subtree.UpdateTarget(container->GetOwnerDoc(), container);
nsEventDispatcher::Dispatch(aKid, nsnull, &mutation);
}
// Someone may have removed the kid or any of its siblings while that event
// was processing.
if (guard.Mutated(0)) {
aIndex = container->IndexOf(aKid);
if (NS_STATIC_CAST(PRInt32, aIndex) < 0) {
@@ -3321,16 +3330,17 @@ nsGenericElement::SetAttrAndNotify(PRInt
GetAttr(aNamespaceID, aName, newValue);
if (!newValue.IsEmpty()) {
mutation.mNewAttrValue = do_GetAtom(newValue);
}
if (!aOldValue.IsEmpty()) {
mutation.mPrevAttrValue = do_GetAtom(aOldValue);
}
mutation.mAttrChange = modType;
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), this);
nsEventDispatcher::Dispatch(this, nsnull, &mutation);
}
if (aNotify) {
nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, modType);
}
if (aNamespaceID == kNameSpaceID_XMLEvents &&
@@ -3548,16 +3558,17 @@ nsGenericElement::UnsetAttr(PRInt32 aNam
mutation.mAttrName = aName;
nsAutoString value;
oldValue.ToString(value);
if (!value.IsEmpty())
mutation.mPrevAttrValue = do_GetAtom(value);
mutation.mAttrChange = nsIDOMMutationEvent::REMOVAL;
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), this);
nsEventDispatcher::Dispatch(this, nsnull, &mutation);
}
return NS_OK;
}
const nsAttrName*
nsGenericElement::GetAttrNameAt(PRUint32 aIndex) const
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -1082,16 +1082,19 @@ CollapseRangeAfterDelete(nsIDOMRange *aR
return aRange->Collapse(PR_FALSE);
}
nsresult nsRange::DeleteContents()
{
if(IsDetached())
return NS_ERROR_DOM_INVALID_STATE_ERR;
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(mRoot ? mRoot->GetOwnerDoc(): nsnull, nsnull);
+
// Save the range end points locally to avoid interference
// of Range gravity during our edits!
nsCOMPtr<nsIDOMNode> startContainer = do_QueryInterface(mStartParent);
PRInt32 startOffset = mStartOffset;
nsCOMPtr<nsIDOMNode> endContainer = do_QueryInterface(mEndParent);
PRInt32 endOffset = mEndOffset;
@@ -1271,16 +1274,19 @@ nsRange::CompareBoundaryPoints(PRUint16
return NS_OK;
}
nsresult nsRange::ExtractContents(nsIDOMDocumentFragment** aReturn)
{
if(mIsDetached)
return NS_ERROR_DOM_INVALID_STATE_ERR;
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(mRoot ? mRoot->GetOwnerDoc(): nsnull, nsnull);
+
// XXX_kin: The spec says that nodes that are completely in the
// XXX_kin: range should be moved into the document fragment, not
// XXX_kin: copied. This method will have to be rewritten using
// XXX_kin: DeleteContents() as a template, with the charData cloning
// XXX_kin: code from CloneContents() merged in.
nsresult res = CloneContents(aReturn);
if (NS_FAILED(res))
--- a/content/events/public/nsIEventListenerManager.h
+++ b/content/events/public/nsIEventListenerManager.h
@@ -174,17 +174,18 @@ public:
* listeners registered.
*/
virtual PRBool HasUnloadListeners() = 0;
/**
* Returns the mutation bits depending on which mutation listeners are
* registered to this listener manager.
* @note If a listener is an nsIDOMMutationListener, all possible mutation
- * event bits are returned.
+ * event bits are returned. All bits are also returned if one of the
+ * event listeners is registered to handle DOMSubtreeModified events.
*/
virtual PRUint32 MutationListenerBits() = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIEventListenerManager,
NS_IEVENTLISTENERMANAGER_IID)
nsresult
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -522,17 +522,21 @@ nsEventListenerManager::AddEventListener
}
if (!window) {
window = do_QueryInterface(mTarget);
}
if (window) {
NS_ASSERTION(window->IsInnerWindow(),
"Setting mutation listener bits on outer window?");
- window->SetMutationListeners(MutationBitForEventType(aType));
+ // If aType is NS_MUTATION_SUBTREEMODIFIED, we need to listen all
+ // mutations.
+ window->SetMutationListeners((aType == NS_MUTATION_SUBTREEMODIFIED) ?
+ kAllMutationBits :
+ MutationBitForEventType(aType));
}
}
return NS_OK;
}
nsresult
nsEventListenerManager::RemoveEventListener(nsIDOMEventListener *aListener,
@@ -1863,16 +1867,19 @@ nsEventListenerManager::MutationListener
if (mMayHaveMutationListeners) {
PRInt32 i, count = mListeners.Count();
for (i = 0; i < count; ++i) {
nsListenerStruct* ls = NS_STATIC_CAST(nsListenerStruct*,
mListeners.FastElementAt(i));
if (ls &&
(ls->mEventType >= NS_MUTATION_START &&
ls->mEventType <= NS_MUTATION_END)) {
+ if (ls->mEventType == NS_MUTATION_SUBTREEMODIFIED) {
+ return kAllMutationBits;
+ }
bits |= MutationBitForEventType(ls->mEventType);
}
}
}
return bits;
}
PRBool
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -757,16 +757,19 @@ nsGenericHTMLElement::GetInnerHTML(nsASt
nsresult
nsGenericHTMLElement::SetInnerHTML(const nsAString& aInnerHTML)
{
// This BeginUpdate/EndUpdate pair is important to make us reenable the
// scriptloader before the last EndUpdate call.
mozAutoDocUpdate updateBatch(GetCurrentDoc(), UPDATE_CONTENT_MODEL, PR_TRUE);
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), nsnull);
+
// Remove childnodes
nsContentUtils::SetNodeTextContent(this, EmptyString(), PR_FALSE);
nsCOMPtr<nsIDOMDocumentFragment> df;
nsCOMPtr<nsIDocument> doc = GetOwnerDoc();
// Strong ref since appendChild can fire events
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -1493,16 +1493,17 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpa
mutation.mRelatedNode = attrNode;
mutation.mAttrName = aName;
if (!oldValue.IsEmpty())
mutation.mPrevAttrValue = do_GetAtom(oldValue);
mutation.mAttrChange = nsIDOMMutationEvent::REMOVAL;
+ mozAutoSubtreeModified subtree(GetOwnerDoc(), this);
nsEventDispatcher::Dispatch(NS_STATIC_CAST(nsIContent*, this),
nsnull, &mutation);
}
if (doc) {
nsXBLBinding *binding = doc->BindingManager()->GetBinding(this);
if (binding)
binding->AttributeChanged(aName, aNameSpaceID, PR_TRUE, aNotify);