diff --git a/mozilla/content/base/public/nsIDocument.h b/mozilla/content/base/public/nsIDocument.h index 271be432519..869852abcb1 100644 --- a/mozilla/content/base/public/nsIDocument.h +++ b/mozilla/content/base/public/nsIDocument.h @@ -90,11 +90,12 @@ template 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) @@ -846,6 +847,11 @@ public: */ virtual void FlushSkinBindings() = 0; + /** + * Returns PR_TRUE if one or more mutation events are being dispatched. + */ + virtual PRBool MutationEventBeingDispatched() = 0; + protected: ~nsIDocument() { @@ -856,6 +862,15 @@ protected: // 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 mDocumentURI; nsCOMPtr mDocumentBaseURI; @@ -952,6 +967,48 @@ private: 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 mTarget; + nsCOMPtr mSubtreeOwner; +}; + // XXX These belong somewhere else nsresult NS_NewHTMLDocument(nsIDocument** aInstancePtrResult); diff --git a/mozilla/content/base/src/nsContentUtils.cpp b/mozilla/content/base/src/nsContentUtils.cpp index fee7dd0915a..00bccf85f8c 100644 --- a/mozilla/content/base/src/nsContentUtils.cpp +++ b/mozilla/content/base/src/nsContentUtils.cpp @@ -2820,6 +2820,12 @@ nsContentUtils::HasMutationListeners(nsINode* aNode, 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 window; window = do_QueryInterface(doc->GetScriptGlobalObject()); diff --git a/mozilla/content/base/src/nsDocument.cpp b/mozilla/content/base/src/nsDocument.cpp index c707231dcb8..e07bdcdfb82 100644 --- a/mozilla/content/base/src/nsDocument.cpp +++ b/mozilla/content/base/src/nsDocument.cpp @@ -145,6 +145,7 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID); #include "nsDateTimeFormatCID.h" #include "nsIDateTimeFormat.h" #include "nsEventDispatcher.h" +#include "nsMutationEvent.h" #include "nsIDOMXPathEvaluator.h" #include "nsDOMCID.h" @@ -5704,6 +5705,72 @@ nsDocument::OnPageHide(PRBool aPersisted) 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 window; + window = do_QueryInterface(GetScriptGlobalObject()); + if (window && + !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) { + mSubtreeModifiedTargets.Clear(); + return; + } + + nsCOMArray realTargets; + for (PRInt32 i = 0; i < count; ++i) { + nsINode* possibleTarget = mSubtreeModifiedTargets[i]; + nsCOMPtr 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; diff --git a/mozilla/content/base/src/nsDocument.h b/mozilla/content/base/src/nsDocument.h index ba50ae3d6aa..efc35e52f29 100644 --- a/mozilla/content/base/src/nsDocument.h +++ b/mozilla/content/base/src/nsDocument.h @@ -509,6 +509,13 @@ public: 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; @@ -827,6 +834,9 @@ private: // Member to store out last-selected stylesheet set. nsString mLastStyleSheetSet; + nsCOMArray mSubtreeModifiedTargets; + PRUint32 mSubtreeModifiedDepth; + // Our update nesting level PRUint32 mUpdateNestLevel; }; diff --git a/mozilla/content/base/src/nsGenericDOMDataNode.cpp b/mozilla/content/base/src/nsGenericDOMDataNode.cpp index a800883da55..ed7e78dbce3 100644 --- a/mozilla/content/base/src/nsGenericDOMDataNode.cpp +++ b/mozilla/content/base/src/nsGenericDOMDataNode.cpp @@ -470,6 +470,7 @@ nsGenericDOMDataNode::SetTextInternal(PRUint32 aOffset, PRUint32 aCount, mutation.mNewAttrValue = do_GetAtom(val); } + mozAutoSubtreeModified subtree(GetOwnerDoc(), this); nsEventDispatcher::Dispatch(this, nsnull, &mutation); } diff --git a/mozilla/content/base/src/nsGenericElement.cpp b/mozilla/content/base/src/nsGenericElement.cpp index aca72bee06b..57467e18077 100644 --- a/mozilla/content/base/src/nsGenericElement.cpp +++ b/mozilla/content/base/src/nsGenericElement.cpp @@ -346,6 +346,9 @@ nsNode3Tearoff::GetTextContent(nsAString &aTextContent) NS_IMETHODIMP nsNode3Tearoff::SetTextContent(const nsAString &aTextContent) { + // Batch possible DOMSubtreeModified events. + mozAutoSubtreeModified subtree(mContent->GetOwnerDoc(), nsnull); + nsCOMPtr node(do_QueryInterface(mContent)); NS_ASSERTION(node, "We have an nsIContent which doesn't support nsIDOMNode"); @@ -1667,6 +1670,9 @@ nsGenericElement::JoinTextNodes(nsIContent* aFirst, nsresult nsGenericElement::Normalize() { + // Batch possible DOMSubtreeModified events. + mozAutoSubtreeModified subtree(GetOwnerDoc(), nsnull); + nsresult result = NS_OK; PRUint32 index, count = GetChildCount(); @@ -2343,6 +2349,7 @@ nsGenericElement::doInsertChildAt(nsIContent* aKid, PRUint32 aIndex, 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); } } @@ -2387,11 +2394,13 @@ nsGenericElement::doRemoveChildAt(PRUint32 aIndex, PRBool 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); } @@ -3326,6 +3335,7 @@ nsGenericElement::SetAttrAndNotify(PRInt32 aNamespaceID, mutation.mPrevAttrValue = do_GetAtom(aOldValue); } mutation.mAttrChange = modType; + mozAutoSubtreeModified subtree(GetOwnerDoc(), this); nsEventDispatcher::Dispatch(this, nsnull, &mutation); } @@ -3553,6 +3563,7 @@ nsGenericElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, mutation.mPrevAttrValue = do_GetAtom(value); mutation.mAttrChange = nsIDOMMutationEvent::REMOVAL; + mozAutoSubtreeModified subtree(GetOwnerDoc(), this); nsEventDispatcher::Dispatch(this, nsnull, &mutation); } diff --git a/mozilla/content/base/src/nsRange.cpp b/mozilla/content/base/src/nsRange.cpp index d1d88641324..edc2d9d8b4e 100644 --- a/mozilla/content/base/src/nsRange.cpp +++ b/mozilla/content/base/src/nsRange.cpp @@ -1087,6 +1087,9 @@ 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! @@ -1276,6 +1279,9 @@ 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 diff --git a/mozilla/content/events/public/nsIEventListenerManager.h b/mozilla/content/events/public/nsIEventListenerManager.h index 6f73d954cfd..2f3b34ce066 100644 --- a/mozilla/content/events/public/nsIEventListenerManager.h +++ b/mozilla/content/events/public/nsIEventListenerManager.h @@ -179,7 +179,8 @@ public: * 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; }; diff --git a/mozilla/content/events/src/nsEventListenerManager.cpp b/mozilla/content/events/src/nsEventListenerManager.cpp index f7e384961e2..9c51dc18755 100644 --- a/mozilla/content/events/src/nsEventListenerManager.cpp +++ b/mozilla/content/events/src/nsEventListenerManager.cpp @@ -527,7 +527,11 @@ nsEventListenerManager::AddEventListener(nsIDOMEventListener *aListener, 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)); } } @@ -1868,6 +1872,9 @@ nsEventListenerManager::MutationListenerBits() if (ls && (ls->mEventType >= NS_MUTATION_START && ls->mEventType <= NS_MUTATION_END)) { + if (ls->mEventType == NS_MUTATION_SUBTREEMODIFIED) { + return kAllMutationBits; + } bits |= MutationBitForEventType(ls->mEventType); } } diff --git a/mozilla/content/html/content/src/nsGenericHTMLElement.cpp b/mozilla/content/html/content/src/nsGenericHTMLElement.cpp index 95a64537f6e..cb5cb43c562 100644 --- a/mozilla/content/html/content/src/nsGenericHTMLElement.cpp +++ b/mozilla/content/html/content/src/nsGenericHTMLElement.cpp @@ -762,6 +762,9 @@ nsGenericHTMLElement::SetInnerHTML(const nsAString& aInnerHTML) // 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); diff --git a/mozilla/content/xul/content/src/nsXULElement.cpp b/mozilla/content/xul/content/src/nsXULElement.cpp index 24187206e6f..0ecb676044f 100644 --- a/mozilla/content/xul/content/src/nsXULElement.cpp +++ b/mozilla/content/xul/content/src/nsXULElement.cpp @@ -1498,6 +1498,7 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify) mutation.mPrevAttrValue = do_GetAtom(oldValue); mutation.mAttrChange = nsIDOMMutationEvent::REMOVAL; + mozAutoSubtreeModified subtree(GetOwnerDoc(), this); nsEventDispatcher::Dispatch(NS_STATIC_CAST(nsIContent*, this), nsnull, &mutation); }