зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
1fdbe9da54
Коммит
1c600e44b6
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче