Skip to content

Commit

Permalink
Bug 1679204 - Consider to add signal to addEventListener, r=edgar
Browse files Browse the repository at this point in the history
This passes the tests which are in web-platform-tests/wpt#26472

Because of complications in #include handling, AbortFollower needs to be in a different
header file than AbortSignal, yet AbortSignalImpl needs to be available when AbortFollower is used.
Another option would have been to make DOMEventTargetHelper.h a bit different and uninline some hot methods
there or move them to another file, but that would have been equally bad and Abort* is used way less often.
AbortFollower and AbortSignalImpl are thus just moved to a new header.

Memory management is such that Listener in EventListenerManager owns the possible ListenerSignalFollower
instance which follows the relevant signal. In order to be able remove event listener,
ListenerSignalFollower has many similar fields as Listener.
ListenerSignalFollower can't easily have just a pointer to Listener* since Listener isn't stored as a pointer
in EventListenerManager.
ListenerSignalFollower is cycle collectable so that Listener->ListenerSignalFollower can be traversed/unlinked
and also strong pointers in ListenerSignalFollower itself can be traversed/unlinked.

There is an XXX in the .webidl, since nullability of signal is unclear in the spec pr.
Whether or not it ends up being nullable shouldn't change the actual C++ implementation.

Differential Revision: https://phabricator.services.mozilla.com/D97938
  • Loading branch information
Olli Pettay authored and Olli Pettay committed Dec 14, 2020
1 parent 246f677 commit ff1b3ed
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 107 deletions.
83 changes: 83 additions & 0 deletions dom/abort/AbortFollower.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#ifndef mozilla_dom_AbortFollower_h
#define mozilla_dom_AbortFollower_h

#include "nsISupportsImpl.h"
#include "nsTObserverArray.h"

namespace mozilla {
namespace dom {

class AbortSignal;
class AbortSignalImpl;

// This class must be implemented by objects who want to follow an
// AbortSignalImpl.
class AbortFollower : public nsISupports {
public:
virtual void RunAbortAlgorithm() = 0;

void Follow(AbortSignalImpl* aSignal);

void Unfollow();

bool IsFollowing() const;

AbortSignalImpl* Signal() const { return mFollowingSignal; }

protected:
// Subclasses of this class must call these Traverse and Unlink functions
// during corresponding cycle collection operations.
static void Traverse(AbortFollower* aFollower,
nsCycleCollectionTraversalCallback& cb);

static void Unlink(AbortFollower* aFollower) { aFollower->Unfollow(); }

virtual ~AbortFollower();

friend class AbortSignalImpl;

RefPtr<AbortSignalImpl> mFollowingSignal;
};

class AbortSignalImpl : public nsISupports {
public:
explicit AbortSignalImpl(bool aAborted);

bool Aborted() const;

virtual void SignalAbort();

protected:
// Subclasses of this class must call these Traverse and Unlink functions
// during corresponding cycle collection operations.
static void Traverse(AbortSignalImpl* aSignal,
nsCycleCollectionTraversalCallback& cb);

static void Unlink(AbortSignalImpl* aSignal) {
// To be filled in shortly.
}

virtual ~AbortSignalImpl() = default;

private:
friend class AbortFollower;

// Raw pointers. |AbortFollower::Follow| adds to this array, and
// |AbortFollower::Unfollow| (also callbed by the destructor) will remove
// from this array. Finally, calling |SignalAbort()| will (after running all
// abort algorithms) empty this and make all contained followers |Unfollow()|.
nsTObserverArray<AbortFollower*> mFollowers;

bool mAborted;
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_AbortFollower_h
67 changes: 1 addition & 66 deletions dom/abort/AbortSignal.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,77 +7,12 @@
#ifndef mozilla_dom_AbortSignal_h
#define mozilla_dom_AbortSignal_h

#include "mozilla/dom/AbortFollower.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "nsISupportsImpl.h"
#include "nsTObserverArray.h"

namespace mozilla {
namespace dom {

class AbortSignal;
class AbortSignalImpl;

// This class must be implemented by objects who want to follow an
// AbortSignalImpl.
class AbortFollower : public nsISupports {
public:
virtual void RunAbortAlgorithm() = 0;

void Follow(AbortSignalImpl* aSignal);

void Unfollow();

bool IsFollowing() const;

AbortSignalImpl* Signal() const { return mFollowingSignal; }

protected:
// Subclasses of this class must call these Traverse and Unlink functions
// during corresponding cycle collection operations.
static void Traverse(AbortFollower* aFollower,
nsCycleCollectionTraversalCallback& cb);

static void Unlink(AbortFollower* aFollower) { aFollower->Unfollow(); }

virtual ~AbortFollower();

friend class AbortSignalImpl;

RefPtr<AbortSignalImpl> mFollowingSignal;
};

class AbortSignalImpl : public nsISupports {
public:
explicit AbortSignalImpl(bool aAborted);

bool Aborted() const;

virtual void SignalAbort();

protected:
// Subclasses of this class must call these Traverse and Unlink functions
// during corresponding cycle collection operations.
static void Traverse(AbortSignalImpl* aSignal,
nsCycleCollectionTraversalCallback& cb);

static void Unlink(AbortSignalImpl* aSignal) {
// To be filled in shortly.
}

virtual ~AbortSignalImpl() = default;

private:
friend class AbortFollower;

// Raw pointers. |AbortFollower::Follow| adds to this array, and
// |AbortFollower::Unfollow| (also callbed by the destructor) will remove
// from this array. Finally, calling |SignalAbort()| will (after running all
// abort algorithms) empty this and make all contained followers |Unfollow()|.
nsTObserverArray<AbortFollower*> mFollowers;

bool mAborted;
};

// AbortSignal the spec concept includes the concept of a child signal
// "following" a parent signal -- internally, adding abort steps to the parent
// signal that will then signal abort on the child signal -- to propagate
Expand Down
1 change: 1 addition & 0 deletions dom/abort/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ TEST_DIRS += ["tests"]

EXPORTS.mozilla.dom += [
"AbortController.h",
"AbortFollower.h",
"AbortSignal.h",
]

Expand Down
1 change: 1 addition & 0 deletions dom/events/DOMEventTargetHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/Likely.h"
#include "MainThreadUtils.h"

Expand Down
72 changes: 68 additions & 4 deletions dom/events/EventListenerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/AbortSignal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/EventCallbackDebuggerNotification.h"
#include "mozilla/dom/Element.h"
Expand Down Expand Up @@ -172,6 +173,9 @@ inline void ImplCycleCollectionTraverse(
CycleCollectionNoteChild(aCallback, aField.mListener.GetISupports(), aName,
aFlags);
}

CycleCollectionNoteChild(aCallback, aField.mSignalFollower.get(),
"mSignalFollower", aFlags);
}

NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerManager)
Expand Down Expand Up @@ -205,14 +209,18 @@ EventListenerManager::GetTargetAsInnerWindow() const {
void EventListenerManager::AddEventListenerInternal(
EventListenerHolder aListenerHolder, EventMessage aEventMessage,
nsAtom* aTypeAtom, const EventListenerFlags& aFlags, bool aHandler,
bool aAllEvents) {
bool aAllEvents, AbortSignal* aSignal) {
MOZ_ASSERT((aEventMessage && aTypeAtom) || aAllEvents, // all-events listener
"Missing type");

if (!aListenerHolder || mClearingListeners) {
return;
}

if (aSignal && aSignal->Aborted()) {
return;
}

// Since there is no public API to call us with an EventListenerHolder, we
// know that there's an EventListenerHolder on the stack holding a strong ref
// to the listener.
Expand Down Expand Up @@ -255,6 +263,11 @@ void EventListenerManager::AddEventListenerInternal(
}
listener->mListener = std::move(aListenerHolder);

if (aSignal) {
listener->mSignalFollower = new ListenerSignalFollower(this, listener);
listener->mSignalFollower->Follow(aSignal);
}

if (aFlags.mInSystemGroup) {
mMayHaveSystemGroupListeners = true;
}
Expand Down Expand Up @@ -687,7 +700,8 @@ void EventListenerManager::MaybeMarkPassive(EventMessage aMessage,

void EventListenerManager::AddEventListenerByType(
EventListenerHolder aListenerHolder, const nsAString& aType,
const EventListenerFlags& aFlags, const Optional<bool>& aPassive) {
const EventListenerFlags& aFlags, const Optional<bool>& aPassive,
AbortSignal* aSignal) {
RefPtr<nsAtom> atom;
EventMessage message =
GetEventMessageAndAtomForListener(aType, getter_AddRefs(atom));
Expand All @@ -699,7 +713,8 @@ void EventListenerManager::AddEventListenerByType(
MaybeMarkPassive(message, flags);
}

AddEventListenerInternal(std::move(aListenerHolder), message, atom, flags);
AddEventListenerInternal(std::move(aListenerHolder), message, atom, flags,
false, false, aSignal);
}

void EventListenerManager::RemoveEventListenerByType(
Expand Down Expand Up @@ -1370,6 +1385,7 @@ void EventListenerManager::AddEventListener(
bool aWantsUntrusted) {
EventListenerFlags flags;
Optional<bool> passive;
AbortSignal* signal = nullptr;
if (aOptions.IsBoolean()) {
flags.mCapture = aOptions.GetAsBoolean();
} else {
Expand All @@ -1380,10 +1396,15 @@ void EventListenerManager::AddEventListener(
if (options.mPassive.WasPassed()) {
passive.Construct(options.mPassive.Value());
}

if (options.mSignal.WasPassed()) {
signal = options.mSignal.Value();
}
}

flags.mAllowUntrustedEvents = aWantsUntrusted;
return AddEventListenerByType(std::move(aListenerHolder), aType, flags,
passive);
passive, signal);
}

void EventListenerManager::RemoveEventListener(
Expand Down Expand Up @@ -1789,4 +1810,47 @@ EventListenerManager::GetScriptGlobalAndDocument(Document** aDoc) {
return global.forget();
}

EventListenerManager::ListenerSignalFollower::ListenerSignalFollower(
EventListenerManager* aListenerManager,
EventListenerManager::Listener* aListener)
: dom::AbortFollower(),
mListenerManager(aListenerManager),
mListener(aListener->mListener.Clone()),
mTypeAtom(aListener->mTypeAtom),
mEventMessage(aListener->mEventMessage),
mAllEvents(aListener->mAllEvents),
mFlags(aListener->mFlags){};

NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerManager::ListenerSignalFollower)

NS_IMPL_CYCLE_COLLECTING_ADDREF(EventListenerManager::ListenerSignalFollower)
NS_IMPL_CYCLE_COLLECTING_RELEASE(EventListenerManager::ListenerSignalFollower)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
EventListenerManager::ListenerSignalFollower)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
EventListenerManager::ListenerSignalFollower)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener)
AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
tmp->mListenerManager = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
EventListenerManager::ListenerSignalFollower)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

void EventListenerManager::ListenerSignalFollower::RunAbortAlgorithm() {
if (mListenerManager) {
RefPtr<EventListenerManager> elm = mListenerManager;
mListenerManager = nullptr;
elm->RemoveEventListenerInternal(std::move(mListener), mEventMessage,
mTypeAtom, mFlags, mAllEvents);
}
}

} // namespace mozilla
Loading

0 comments on commit ff1b3ed

Please sign in to comment.