/* 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 "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/Element.h" #include "nsContentUtils.h" #include "nsIAtom.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsMenuContainer.h" #include "nsNativeMenuDocListener.h" using namespace mozilla; uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 0; nsNativeMenuDocListenerTArray* gPendingListeners; /* * Small helper which caches a single listener, so that consecutive * events which go to the same node avoid multiple hash table lookups */ class MOZ_STACK_CLASS DispatchHelper { public: DispatchHelper(nsNativeMenuDocListener* aListener, nsIContent* aContent MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mObserver(nullptr) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (aContent == aListener->mLastSource) { mObserver = aListener->mLastTarget; } else { mObserver = aListener->mContentToObserverTable.Get(aContent); if (mObserver) { aListener->mLastSource = aContent; aListener->mLastTarget = mObserver; } } } ~DispatchHelper() { }; nsNativeMenuChangeObserver* Observer() const { return mObserver; } bool HasObserver() const { return !!mObserver; } private: nsNativeMenuChangeObserver* mObserver; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver) nsNativeMenuDocListener::~nsNativeMenuDocListener() { MOZ_ASSERT(mContentToObserverTable.Count() == 0, "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)"); MOZ_COUNT_DTOR(nsNativeMenuDocListener); } void nsNativeMenuDocListener::AttributeChanged(nsIDocument* aDocument, mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { if (sUpdateBlockersCount == 0) { DoAttributeChanged(aElement, aAttribute); return; } MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); m->mType = MutationRecord::eAttributeChanged; m->mTarget = aElement; m->mAttribute = aAttribute; ScheduleFlush(this); } void nsNativeMenuDocListener::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer, nsIContent* aFirstNewContent, int32_t aNewIndexInContainer) { for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) { ContentInserted(aDocument, aContainer, c, 0); } } void nsNativeMenuDocListener::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer, nsIContent* aChild, int32_t aIndexInContainer) { nsIContent* prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild); if (sUpdateBlockersCount == 0) { DoContentInserted(aContainer, aChild, prevSibling); return; } MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); m->mType = MutationRecord::eContentInserted; m->mTarget = aContainer; m->mChild = aChild; m->mPrevSibling = prevSibling; ScheduleFlush(this); } void nsNativeMenuDocListener::ContentRemoved(nsIDocument* aDocument, nsIContent* aContainer, nsIContent* aChild, int32_t aIndexInContainer, nsIContent* aPreviousSibling) { if (sUpdateBlockersCount == 0) { DoContentRemoved(aContainer, aChild); return; } MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord); m->mType = MutationRecord::eContentRemoved; m->mTarget = aContainer; m->mChild = aChild; ScheduleFlush(this); } void nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode* aNode) { mDocument = nullptr; } void nsNativeMenuDocListener::DoAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) { DispatchHelper h(this, aContent); if (h.HasObserver()) { h.Observer()->OnAttributeChanged(aContent, aAttribute); } } void nsNativeMenuDocListener::DoContentInserted(nsIContent* aContainer, nsIContent* aChild, nsIContent* aPrevSibling) { DispatchHelper h(this, aContainer); if (h.HasObserver()) { h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling); } } void nsNativeMenuDocListener::DoContentRemoved(nsIContent* aContainer, nsIContent* aChild) { DispatchHelper h(this, aContainer); if (h.HasObserver()) { h.Observer()->OnContentRemoved(aContainer, aChild); } } void nsNativeMenuDocListener::DoBeginUpdates(nsIContent* aTarget) { DispatchHelper h(this, aTarget); if (h.HasObserver()) { h.Observer()->OnBeginUpdates(aTarget); } } void nsNativeMenuDocListener::DoEndUpdates(nsIContent* aTarget) { DispatchHelper h(this, aTarget); if (h.HasObserver()) { h.Observer()->OnEndUpdates(); } } void nsNativeMenuDocListener::FlushPendingMutations() { nsIContent* currentTarget = nullptr; bool inUpdateSequence = false; while (mPendingMutations.Length() > 0) { MutationRecord* m = mPendingMutations[0]; if (m->mTarget != currentTarget) { if (inUpdateSequence) { DoEndUpdates(currentTarget); inUpdateSequence = false; } currentTarget = m->mTarget; if (mPendingMutations.Length() > 1 && mPendingMutations[1]->mTarget == currentTarget) { DoBeginUpdates(currentTarget); inUpdateSequence = true; } } switch (m->mType) { case MutationRecord::eAttributeChanged: DoAttributeChanged(m->mTarget, m->mAttribute); break; case MutationRecord::eContentInserted: DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling); break; case MutationRecord::eContentRemoved: DoContentRemoved(m->mTarget, m->mChild); break; default: NS_NOTREACHED("Invalid type"); } mPendingMutations.RemoveElementAt(0); } if (inUpdateSequence) { DoEndUpdates(currentTarget); } } /* static */ void nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener* aListener) { MOZ_ASSERT(sUpdateBlockersCount > 0, "Shouldn't be doing this now"); if (!gPendingListeners) { gPendingListeners = new nsNativeMenuDocListenerTArray; } if (gPendingListeners->IndexOf(aListener) == nsNativeMenuDocListenerTArray::NoIndex) { gPendingListeners->AppendElement(aListener); } } /* static */ void nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener* aListener) { if (!gPendingListeners) { return; } gPendingListeners->RemoveElement(aListener); } /* static */ void nsNativeMenuDocListener::RemoveUpdateBlocker() { if (sUpdateBlockersCount == 1 && gPendingListeners) { while (gPendingListeners->Length() > 0) { (*gPendingListeners)[0]->FlushPendingMutations(); gPendingListeners->RemoveElementAt(0); } } MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!"); sUpdateBlockersCount--; } nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent* aRootNode) : mRootNode(aRootNode), mDocument(nullptr), mLastSource(nullptr), mLastTarget(nullptr) { MOZ_COUNT_CTOR(nsNativeMenuDocListener); } void nsNativeMenuDocListener::RegisterForContentChanges(nsIContent* aContent, nsNativeMenuChangeObserver* aObserver) { MOZ_ASSERT(aContent, "Need content parameter"); MOZ_ASSERT(aObserver, "Need observer parameter"); if (!aContent || !aObserver) { return; } DebugOnly old; MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver, "Multiple observers for the same content node are not supported"); mContentToObserverTable.Put(aContent, aObserver); } void nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent* aContent) { MOZ_ASSERT(aContent, "Need content parameter"); if (!aContent) { return; } mContentToObserverTable.Remove(aContent); if (aContent == mLastSource) { mLastSource = nullptr; mLastTarget = nullptr; } } void nsNativeMenuDocListener::Start() { if (mDocument) { return; } mDocument = mRootNode->OwnerDoc(); if (!mDocument) { return; } mDocument->AddMutationObserver(this); } void nsNativeMenuDocListener::Stop() { if (mDocument) { mDocument->RemoveMutationObserver(this); mDocument = nullptr; } CancelFlush(this); mPendingMutations.Clear(); }