Bug 1720574 - Add a way to disable event listeners, r=edgar,Honza

The old test for the service is reused here. Added some basic tests there.

Differential Revision: https://phabricator.services.mozilla.com/D119921
This commit is contained in:
Olli Pettay 2021-07-16 12:04:37 +00:00
Родитель 1fdbe9da54
Коммит 1c600e44b6
6 изменённых файлов: 209 добавлений и 12 удалений

Просмотреть файл

@ -665,6 +665,12 @@ bool EventListenerManager::ListenerCanHandle(const Listener* aListener,
if (aListener->mListenerType == Listener::eNoListener) {
return false;
}
// The listener has been disabled, for example by devtools.
if (!aListener->mEnabled) {
return false;
}
// This is slightly different from EVENT_TYPE_EQUALS in that it returns
// true even when aEvent->mMessage == eUnidentifiedEvent and
// aListener=>mEventMessage != eUnidentifiedEvent as long as the atoms are
@ -1589,13 +1595,81 @@ nsresult EventListenerManager::GetListenerInfo(
}
RefPtr<EventListenerInfo> info = new EventListenerInfo(
eventType, callback, callbackGlobal, listener.mFlags.mCapture,
listener.mFlags.mAllowUntrustedEvents, listener.mFlags.mInSystemGroup);
this, eventType, callback, callbackGlobal, listener.mFlags.mCapture,
listener.mFlags.mAllowUntrustedEvents, listener.mFlags.mInSystemGroup,
listener.mListenerIsHandler);
aList.AppendElement(info.forget());
}
return NS_OK;
}
EventListenerManager::Listener* EventListenerManager::GetListenerFor(
nsAString& aType, JSObject* aListener, bool aCapturing,
bool aAllowsUntrusted, bool aInSystemEventGroup, bool aIsHandler) {
NS_ENSURE_TRUE(aListener, nullptr);
for (Listener& listener : mListeners.ForwardRange()) {
if ((aType.IsVoid() && !listener.mAllEvents) ||
!Substring(nsDependentAtomString(listener.mTypeAtom), 2)
.Equals(aType) ||
listener.mListenerType == Listener::eNoListener) {
continue;
}
if (listener.mFlags.mCapture != aCapturing ||
listener.mFlags.mAllowUntrustedEvents != aAllowsUntrusted ||
listener.mFlags.mInSystemGroup != aInSystemEventGroup) {
continue;
}
if (aIsHandler) {
if (JSEventHandler* handler = listener.GetJSEventHandler()) {
if (handler->GetTypedEventHandler().HasEventHandler()) {
if (handler->GetTypedEventHandler().Ptr()->CallableOrNull() ==
aListener) {
return &listener;
}
}
}
} else if (listener.mListenerType == Listener::eWebIDLListener &&
listener.mListener.GetWebIDLCallback()->CallbackOrNull() ==
aListener) {
return &listener;
}
}
return nullptr;
}
nsresult EventListenerManager::IsListenerEnabled(
nsAString& aType, JSObject* aListener, bool aCapturing,
bool aAllowsUntrusted, bool aInSystemEventGroup, bool aIsHandler,
bool* aEnabled) {
Listener* listener =
GetListenerFor(aType, aListener, aCapturing, aAllowsUntrusted,
aInSystemEventGroup, aIsHandler);
NS_ENSURE_TRUE(listener, NS_ERROR_NOT_AVAILABLE);
*aEnabled = listener->mEnabled;
return NS_OK;
}
nsresult EventListenerManager::SetListenerEnabled(
nsAString& aType, JSObject* aListener, bool aCapturing,
bool aAllowsUntrusted, bool aInSystemEventGroup, bool aIsHandler,
bool aEnabled) {
Listener* listener =
GetListenerFor(aType, aListener, aCapturing, aAllowsUntrusted,
aInSystemEventGroup, aIsHandler);
NS_ENSURE_TRUE(listener, NS_ERROR_NOT_AVAILABLE);
listener->mEnabled = aEnabled;
if (aEnabled) {
// We may have enabled some listener, clear the cache for which events
// we don't have listeners.
mNoListenerForEvent = eVoidEvent;
mNoListenerForEventAtom = nullptr;
}
return NS_OK;
}
bool EventListenerManager::HasUnloadListeners() {
uint32_t count = mListeners.Length();
for (uint32_t i = 0; i < count; ++i) {

Просмотреть файл

@ -221,6 +221,7 @@ class EventListenerManager final : public EventListenerManagerBase {
bool mHandlerIsString : 1;
bool mAllEvents : 1;
bool mIsChrome : 1;
bool mEnabled : 1;
EventListenerFlags mFlags;
@ -236,7 +237,8 @@ class EventListenerManager final : public EventListenerManagerBase {
mListenerIsHandler(false),
mHandlerIsString(false),
mAllEvents(false),
mIsChrome(false) {}
mIsChrome(false),
mEnabled(true) {}
Listener(Listener&& aOther)
: mSignalFollower(std::move(aOther.mSignalFollower)),
@ -247,13 +249,15 @@ class EventListenerManager final : public EventListenerManagerBase {
mListenerIsHandler(aOther.mListenerIsHandler),
mHandlerIsString(aOther.mHandlerIsString),
mAllEvents(aOther.mAllEvents),
mIsChrome(aOther.mIsChrome) {
mIsChrome(aOther.mIsChrome),
mEnabled(aOther.mEnabled) {
aOther.mEventMessage = eVoidEvent;
aOther.mListenerType = eNoListener;
aOther.mListenerIsHandler = false;
aOther.mHandlerIsString = false;
aOther.mAllEvents = false;
aOther.mIsChrome = false;
aOther.mEnabled = true;
}
~Listener() {
@ -444,6 +448,16 @@ class EventListenerManager final : public EventListenerManagerBase {
*/
nsresult GetListenerInfo(nsTArray<RefPtr<nsIEventListenerInfo>>& aList);
nsresult IsListenerEnabled(nsAString& aType, JSObject* aListener,
bool aCapturing, bool aAllowsUntrusted,
bool aInSystemEventGroup, bool aIsHandler,
bool* aEnabled);
nsresult SetListenerEnabled(nsAString& aType, JSObject* aListener,
bool aCapturing, bool aAllowsUntrusted,
bool aInSystemEventGroup, bool aIsHandler,
bool aEnabled);
uint32_t GetIdentifierForEvent(nsAtom* aEvent);
/**
@ -574,6 +588,10 @@ class EventListenerManager final : public EventListenerManagerBase {
bool HasListenersForInternal(nsAtom* aEventNameWithOn,
bool aIgnoreSystemGroup) const;
Listener* GetListenerFor(nsAString& aType, JSObject* aListener,
bool aCapturing, bool aAllowsUntrusted,
bool aInSystemEventGroup, bool aIsHandler);
public:
/**
* Set the "inline" event listener for aEventName to aHandler. If

Просмотреть файл

@ -75,15 +75,18 @@ EventListenerChange::GetCountOfEventListenerChangesAffectingAccessibility(
******************************************************************************/
EventListenerInfo::EventListenerInfo(
const nsAString& aType, JS::Handle<JSObject*> aScriptedListener,
EventListenerManager* aListenerManager, const nsAString& aType,
JS::Handle<JSObject*> aScriptedListener,
JS::Handle<JSObject*> aScriptedListenerGlobal, bool aCapturing,
bool aAllowsUntrusted, bool aInSystemEventGroup)
: mType(aType),
bool aAllowsUntrusted, bool aInSystemEventGroup, bool aIsHandler)
: mListenerManager(aListenerManager),
mType(aType),
mScriptedListener(aScriptedListener),
mScriptedListenerGlobal(aScriptedListenerGlobal),
mCapturing(aCapturing),
mAllowsUntrusted(aAllowsUntrusted),
mInSystemEventGroup(aInSystemEventGroup) {
mInSystemEventGroup(aInSystemEventGroup),
mIsHandler(aIsHandler) {
if (aScriptedListener) {
MOZ_ASSERT(JS_IsGlobalObject(aScriptedListenerGlobal));
js::AssertSameCompartment(aScriptedListener, aScriptedListenerGlobal);
@ -97,9 +100,11 @@ EventListenerInfo::~EventListenerInfo() { DropJSObjects(this); }
NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerInfo)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
tmp->mScriptedListener = nullptr;
tmp->mScriptedListenerGlobal = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -141,6 +146,22 @@ EventListenerInfo::GetInSystemEventGroup(bool* aInSystemEventGroup) {
return NS_OK;
}
NS_IMETHODIMP
EventListenerInfo::GetEnabled(bool* aEnabled) {
NS_ENSURE_STATE(mListenerManager);
return mListenerManager->IsListenerEnabled(
mType, mScriptedListener, mCapturing, mAllowsUntrusted,
mInSystemEventGroup, mIsHandler, aEnabled);
}
NS_IMETHODIMP
EventListenerInfo::SetEnabled(bool aEnabled) {
NS_ENSURE_STATE(mListenerManager);
return mListenerManager->SetListenerEnabled(
mType, mScriptedListener, mCapturing, mAllowsUntrusted,
mInSystemEventGroup, mIsHandler, aEnabled);
}
NS_IMETHODIMP
EventListenerInfo::GetListenerObject(JSContext* aCx,
JS::MutableHandle<JS::Value> aObject) {

Просмотреть файл

@ -10,6 +10,7 @@
#include "jsapi.h"
#include "mozilla/Attributes.h"
#include "nsCycleCollectionParticipant.h"
#include "EventListenerManager.h"
#include "nsIEventListenerService.h"
#include "nsString.h"
#include "nsTObserverArray.h"
@ -43,11 +44,12 @@ class EventListenerChange final : public nsIEventListenerChange {
class EventListenerInfo final : public nsIEventListenerInfo {
public:
EventListenerInfo(const nsAString& aType,
EventListenerInfo(EventListenerManager* aListenerManager,
const nsAString& aType,
JS::Handle<JSObject*> aScriptedListener,
JS::Handle<JSObject*> aScriptedListenerGlobal,
bool aCapturing, bool aAllowsUntrusted,
bool aInSystemEventGroup);
bool aInSystemEventGroup, bool aIsHandler);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(EventListenerInfo)
@ -59,6 +61,7 @@ class EventListenerInfo final : public nsIEventListenerInfo {
bool GetJSVal(JSContext* aCx, Maybe<JSAutoRealm>& aAr,
JS::MutableHandle<JS::Value> aJSVal);
RefPtr<EventListenerManager> mListenerManager;
nsString mType;
JS::Heap<JSObject*> mScriptedListener; // May be null.
// mScriptedListener may be a cross-compartment wrapper so we cannot use it
@ -69,6 +72,7 @@ class EventListenerInfo final : public nsIEventListenerInfo {
bool mCapturing;
bool mAllowsUntrusted;
bool mInSystemEventGroup;
bool mIsHandler;
};
class EventListenerService final : public nsIEventListenerService {

Просмотреть файл

@ -44,6 +44,12 @@ interface nsIEventListenerInfo : nsISupports
readonly attribute boolean allowsUntrusted;
readonly attribute boolean inSystemEventGroup;
/**
* Changing the enabled state works only with listeners implemented in
* JS. An error is thrown for native listeners.
*/
attribute boolean enabled;
/**
* The underlying JS object of the event listener, if this listener
* has one. Null otherwise.

Просмотреть файл

@ -21,6 +21,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=448602
var els, root, l2, l3;
var handlerCalled = false;
var capturingListenerCalled = false;
var bubblingListenerCalled = false;
function clearListenerStates() {
handlerCalled = false;
capturingListenerCalled = false;
bubblingListenerCalled = false;
}
function runTests() {
els = SpecialPowers.Cc["@mozilla.org/eventlistenerservice;1"]
.getService(SpecialPowers.Ci.nsIEventListenerService);
@ -30,7 +40,7 @@ function runTests() {
var infos = els.getListenerInfoFor(root);
is(infos.length, 0, "Element shouldn't have listeners (1)");
var listenerSource = 'alert(event);';
var listenerSource = 'handlerCalled = true;';
root.setAttribute("onclick", listenerSource);
infos = els.getListenerInfoFor(root);
is(infos.length, 1, "Element should have listeners (1)");
@ -42,7 +52,72 @@ function runTests() {
is(SpecialPowers.unwrap(infos[0].listenerObject), root.onclick,
"Should have the right listener object (1)");
// Test disabling and enabling the listener.
ok(!handlerCalled);
root.click();
ok(handlerCalled);
clearListenerStates()
infos[0].enabled = false;
root.click();
ok(!handlerCalled);
clearListenerStates()
infos[0].enabled = true;
root.click();
ok(handlerCalled);
clearListenerStates();
function capturingListener() {
capturingListenerCalled = true;
}
function bubblingListener() {
bubblingListenerCalled = true;
}
root.addEventListener("click", capturingListener, true);
root.addEventListener("click", bubblingListener);
root.addEventListener("fooevent", capturingListener, true);
root.addEventListener("fooevent", bubblingListener);
infos = els.getListenerInfoFor(root);
// Use a child node to dispatch events so that both capturing and bubbling
// listeners get called.
l2 = document.getElementById("testlevel2");
l2.click();
ok(handlerCalled);
ok(capturingListenerCalled);
ok(bubblingListenerCalled);
clearListenerStates();
infos[0].enabled = false;
l2.click();
ok(!handlerCalled);
ok(capturingListenerCalled);
ok(bubblingListenerCalled);
clearListenerStates();
infos[0].enabled = true;
infos[1].enabled = false;
l2.click();
ok(handlerCalled);
ok(!capturingListenerCalled);
ok(bubblingListenerCalled);
clearListenerStates();
infos[1].enabled = true;
infos[2].enabled = false;
l2.click();
ok(handlerCalled);
ok(capturingListenerCalled);
ok(!bubblingListenerCalled);
clearListenerStates();
infos[2].enabled = true;
root.removeEventListener("click", capturingListener, true);
root.removeEventListener("click", bubblingListener);
root.removeEventListener("fooevent", capturingListener, true);
root.removeEventListener("fooevent", bubblingListener);
root.removeAttribute("onclick");
root.setAttribute("onclick", "...invalid script...");
SimpleTest.expectUncaughtException(true);
infos = els.getListenerInfoFor(root);
@ -91,7 +166,6 @@ function runTests() {
"Should have the right listener object (4)");
// Event target chain tests
l2 = document.getElementById("testlevel2");
l3 = document.getElementById("testlevel3");
var textnode = l3.firstChild;
var chain = els.getEventTargetChainFor(textnode, true);