Bug 1287706 part 7 - Add support of AddEventListenerOptions.once. r?smaug
MozReview-Commit-ID: BzuhPtNW29u
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1179,16 +1179,17 @@ EventListenerManager::HandleEventInterna
Maybe<nsAutoPopupStatePusher> popupStatePusher;
if (mIsMainThreadELM) {
popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent));
}
bool hasListener = false;
bool hasListenerForCurrentGroup = false;
bool usingLegacyMessage = false;
+ bool hasRemovedListener = false;
EventMessage eventMessage = aEvent->mMessage;
while (true) {
nsAutoTObserverArray<Listener, 2>::EndLimitedIterator iter(mListeners);
Maybe<EventMessageAutoOverride> legacyAutoOverride;
while (iter.HasMore()) {
if (aEvent->mFlags.mImmediatePropagationStopped) {
break;
@@ -1243,16 +1244,25 @@ EventListenerManager::HandleEventInterna
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<EventTimelineMarker>(
typeStr, phase, MarkerTracingType::START)));
}
}
}
aEvent->mFlags.mInPassiveListener = listener->mFlags.mPassive;
+ Maybe<Listener> listenerHolder;
+ if (listener->mFlags.mOnce) {
+ // Move the listener to the stack before handling the event.
+ // The order is important, otherwise the listener could be
+ // called again inside the listener.
+ listenerHolder.emplace(Move(*listener));
+ listener = listenerHolder.ptr();
+ hasRemovedListener = true;
+ }
if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) {
aEvent->mFlags.mExceptionWasRaised = true;
}
aEvent->mFlags.mInPassiveListener = false;
if (needsEndEventMarker) {
timelines->AddMarkerForDocShell(
docShell, "DOMEvent", MarkerTracingType::END);
@@ -1279,16 +1289,46 @@ EventListenerManager::HandleEventInterna
// Recheck our listeners, using the legacy event message we just looked up:
eventMessage = legacyEventMessage;
usingLegacyMessage = true;
}
aEvent->mCurrentTarget = nullptr;
+ if (hasRemovedListener) {
+ // If there are any once listeners replaced with a placeholder in
+ // the loop above, we need to clean up them here. Note that, this
+ // could clear once listeners handled in some outer level as well,
+ // but that should not affect the result.
+ mListeners.RemoveElementsBy([](const Listener& aListener) {
+ return aListener.mListenerType == Listener::eNoListener;
+ });
+ NotifyEventListenerRemoved(aEvent->mSpecifiedEventType);
+ if (IsDeviceType(aEvent->mMessage)) {
+ // This is a device-type event, we need to check whether we can
+ // disable device after removing the once listeners.
+ bool hasAnyListener = false;
+ nsAutoTObserverArray<Listener, 2>::ForwardIterator iter(mListeners);
+ while (iter.HasMore()) {
+ Listener* listener = &iter.GetNext();
+ if (EVENT_TYPE_EQUALS(listener, aEvent->mMessage,
+ aEvent->mSpecifiedEventType,
+ aEvent->mSpecifiedEventTypeString,
+ /* all events */ false)) {
+ hasAnyListener = true;
+ break;
+ }
+ }
+ if (!hasAnyListener) {
+ DisableDevice(aEvent->mMessage);
+ }
+ }
+ }
+
if (mIsMainThreadELM && !hasListener) {
mNoListenerForEvent = aEvent->mMessage;
mNoListenerForEventAtom = aEvent->mSpecifiedEventType;
}
if (aEvent->DefaultPrevented()) {
*aEventStatus = nsEventStatus_eConsumeNoDefault;
}
@@ -1324,16 +1364,17 @@ EventListenerManager::AddEventListener(
EventListenerFlags flags;
if (aOptions.IsBoolean()) {
flags.mCapture = aOptions.GetAsBoolean();
} else {
const auto& options = aOptions.GetAsAddEventListenerOptions();
flags.mCapture = options.mCapture;
flags.mInSystemGroup = options.mMozSystemGroup;
flags.mPassive = options.mPassive;
+ flags.mOnce = options.mOnce;
}
flags.mAllowUntrustedEvents = aWantsUntrusted;
return AddEventListenerByType(Move(aListenerHolder), aType, flags);
}
void
EventListenerManager::RemoveEventListener(
const nsAString& aType,
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -56,39 +56,42 @@ public:
// system group.
bool mInSystemGroup : 1;
// If mAllowUntrustedEvents is true, the listener is listening to the
// untrusted events too.
bool mAllowUntrustedEvents : 1;
// If mPassive is true, the listener will not be calling preventDefault on the
// event. (If it does call preventDefault, we should ignore it).
bool mPassive : 1;
+ // If mOnce is true, the listener will be removed from the manager before it
+ // is invoked, so that it would only be invoked once.
+ bool mOnce : 1;
EventListenerFlags() :
mListenerIsJSListener(false),
mCapture(false), mInSystemGroup(false), mAllowUntrustedEvents(false),
- mPassive(false)
+ mPassive(false), mOnce(false)
{
}
bool EqualsForAddition(const EventListenerFlags& aOther) const
{
return (mCapture == aOther.mCapture &&
mInSystemGroup == aOther.mInSystemGroup &&
mListenerIsJSListener == aOther.mListenerIsJSListener &&
mAllowUntrustedEvents == aOther.mAllowUntrustedEvents);
- // Don't compare mPassive
+ // Don't compare mPassive or mOnce
}
bool EqualsForRemoval(const EventListenerFlags& aOther) const
{
return (mCapture == aOther.mCapture &&
mInSystemGroup == aOther.mInSystemGroup &&
mListenerIsJSListener == aOther.mListenerIsJSListener);
- // Don't compare mAllowUntrustedEvents or mPassive
+ // Don't compare mAllowUntrustedEvents, mPassive, or mOnce
}
};
inline EventListenerFlags TrustedEventsAtBubble()
{
EventListenerFlags flags;
return flags;
}
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -15,17 +15,17 @@ dictionary EventListenerOptions {
boolean capture = false;
/* Setting to true make the listener be added to the system group. */
[Func="ThreadSafeIsChromeOrXBL"]
boolean mozSystemGroup = false;
};
dictionary AddEventListenerOptions : EventListenerOptions {
boolean passive = false;
- // boolean once = false; Bug 1287706
+ boolean once = false;
};
[Exposed=(Window,Worker,WorkerDebugger,System)]
interface EventTarget {
/* Passing null for wantsUntrusted means "default behavior", which
differs in content and chrome. In content that default boolean
value is true, while in chrome the default boolean value is
false. */