330 lines
9.6 KiB
C++
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();
|
|
}
|