Mypal/widget/gtk/nsNativeMenuDocListener.cpp

330 lines
9.6 KiB
C++

/* 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<nsNativeMenuChangeObserver* > 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();
}