From d18faf81938c947f1d02feab2c394b8135f88d8f Mon Sep 17 00:00:00 2001 From: Lootyhoof Date: Sat, 6 Jun 2020 00:37:29 +0100 Subject: Issue MoonchildProductions/UXP#1578 - Add global menubar support for GTK --- widget/gtk/nsNativeMenuDocListener.cpp | 329 +++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 widget/gtk/nsNativeMenuDocListener.cpp (limited to 'widget/gtk/nsNativeMenuDocListener.cpp') diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp new file mode 100644 index 000000000..46a9c3aa9 --- /dev/null +++ b/widget/gtk/nsNativeMenuDocListener.cpp @@ -0,0 +1,329 @@ +/* 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(); +} -- cgit v1.2.3