Bug 1266066 - Implement DOM support for the 'passive' event listener flag. r=smaug

MozReview-Commit-ID: EvSCDxYC7g6
This commit is contained in:
Kartikaya Gupta 2016-05-12 14:50:22 -04:00
parent 6c277c9ba0
commit a412581b20
6 changed files with 144 additions and 13 deletions

View File

@ -520,6 +520,9 @@ Event::PreventDefaultInternal(bool aCalledByDefaultHandler)
if (!mEvent->mFlags.mCancelable) {
return;
}
if (mEvent->mFlags.mInPassiveListener) {
return;
}
mEvent->PreventDefault(aCalledByDefaultHandler);

View File

@ -264,7 +264,7 @@ EventListenerManager::AddEventListenerInternal(
listener = &mListeners.ElementAt(i);
// mListener == aListenerHolder is the last one, since it can be a bit slow.
if (listener->mListenerIsHandler == aHandler &&
listener->mFlags == aFlags &&
listener->mFlags.EqualsForAddition(aFlags) &&
EVENT_TYPE_EQUALS(listener, aEventMessage, aTypeAtom, aTypeString,
aAllEvents) &&
listener->mListener == aListenerHolder) {
@ -617,7 +617,7 @@ EventListenerManager::RemoveEventListenerInternal(
aAllEvents)) {
++typeCount;
if (listener->mListener == aListenerHolder &&
listener->mFlags.EqualsIgnoringTrustness(aFlags)) {
listener->mFlags.EqualsForRemoval(aFlags)) {
RefPtr<EventListenerManager> kungFuDeathGrip(this);
mListeners.RemoveElementAt(i);
--count;
@ -1279,9 +1279,11 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
}
}
aEvent->mFlags.mInPassiveListener = listener->mFlags.mPassive;
if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) {
aEvent->mFlags.mExceptionWasRaised = true;
}
aEvent->mFlags.mInPassiveListener = false;
if (needsEndEventMarker) {
timelines->AddMarkerForDocShell(
@ -1352,9 +1354,12 @@ EventListenerManager::AddEventListener(
bool aWantsUntrusted)
{
EventListenerFlags flags;
flags.mCapture =
aOptions.IsBoolean() ? aOptions.GetAsBoolean()
: aOptions.GetAsAddEventListenerOptions().mCapture;
if (aOptions.IsBoolean()) {
flags.mCapture = aOptions.GetAsBoolean();
} else {
flags.mCapture = aOptions.GetAsAddEventListenerOptions().mCapture;
flags.mPassive = aOptions.GetAsAddEventListenerOptions().mPassive;
}
flags.mAllowUntrustedEvents = aWantsUntrusted;
return AddEventListenerByType(aListenerHolder, aType, flags);
}

View File

@ -58,31 +58,32 @@ public:
// 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;
EventListenerFlags() :
mListenerIsJSListener(false),
mCapture(false), mInSystemGroup(false), mAllowUntrustedEvents(false)
mCapture(false), mInSystemGroup(false), mAllowUntrustedEvents(false),
mPassive(false)
{
}
bool Equals(const EventListenerFlags& aOther) const
bool EqualsForAddition(const EventListenerFlags& aOther) const
{
return (mCapture == aOther.mCapture &&
mInSystemGroup == aOther.mInSystemGroup &&
mListenerIsJSListener == aOther.mListenerIsJSListener &&
mAllowUntrustedEvents == aOther.mAllowUntrustedEvents);
// Don't compare mPassive
}
bool EqualsIgnoringTrustness(const EventListenerFlags& aOther) const
bool EqualsForRemoval(const EventListenerFlags& aOther) const
{
return (mCapture == aOther.mCapture &&
mInSystemGroup == aOther.mInSystemGroup &&
mListenerIsJSListener == aOther.mListenerIsJSListener);
}
bool operator==(const EventListenerFlags& aOther) const
{
return Equals(aOther);
// Don't compare mAllowUntrustedEvents or mPassive
}
};

View File

@ -199,3 +199,4 @@ skip-if = buildapp == 'b2g' # no wheel events on b2g
[test_dom_activate_event.html]
[test_bug1264380.html]
run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode.
[test_passive_listeners.html]

View File

@ -0,0 +1,118 @@
<html>
<head>
<title>Tests for passive event listeners</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<body>
<p id="display"></p>
<div id="dummy">
</div>
<script>
var listenerHitCount;
var doPreventDefault;
function listener(e)
{
listenerHitCount++;
if (doPreventDefault) {
// When this function is registered as a passive listener, this
// call should be a no-op and might report a console warning.
e.preventDefault();
}
}
function listener2(e)
{
if (doPreventDefault) {
e.preventDefault();
}
}
var elem = document.getElementById('dummy');
function doTest(description, passiveArg)
{
listenerHitCount = 0;
elem.addEventListener('test', listener, { passive: passiveArg });
// Test with a cancelable event
var e1 = new Event('test', { cancelable: true });
elem.dispatchEvent(e1);
is(listenerHitCount, 1, description + ' | hit count');
var expectedDefaultPrevented = (doPreventDefault && !passiveArg);
is(e1.defaultPrevented, expectedDefaultPrevented, description + ' | default prevented');
// Test with a non-cancelable event
var e2 = new Event('test', { cancelable: false });
elem.dispatchEvent(e2);
is(listenerHitCount, 2, description + ' | hit count after non-cancelable event');
is(e2.defaultPrevented, false, description + ' | default prevented on non-cancelable event');
// Test combining passive-enabled and "traditional" listeners
elem.addEventListener('test', listener2, false);
var e3 = new Event('test', { cancelable: true });
elem.dispatchEvent(e3);
is(listenerHitCount, 3, description + ' | hit count with second listener');
is(e3.defaultPrevented, doPreventDefault, description + ' | default prevented with second listener');
elem.removeEventListener('test', listener2, false);
elem.removeEventListener('test', listener, false);
}
function testAddListenerKey(passiveListenerFirst)
{
listenerHitCount = 0;
doPreventDefault = true;
elem.addEventListener('test', listener, { capture: false, passive: passiveListenerFirst });
// This second listener should not be registered, because the "key" of
// { type, callback, capture } is the same, even though the 'passive' flag
// is different.
elem.addEventListener('test', listener, { capture: false, passive: !passiveListenerFirst });
var e1 = new Event('test', { cancelable: true });
elem.dispatchEvent(e1);
is(listenerHitCount, 1, 'Duplicate addEventListener was correctly ignored');
is(e1.defaultPrevented, !passiveListenerFirst, 'Prevent-default result based on first registered listener');
// Even though passive is the opposite of the first addEventListener call, it
// should remove the listener registered above.
elem.removeEventListener('test', listener, { capture: false, passive: !passiveListenerFirst });
var e2 = new Event('test', { cancelable: true });
elem.dispatchEvent(e2);
is(listenerHitCount, 1, 'non-passive listener was correctly unregistered');
is(e2.defaultPrevented, false, 'no listener was registered to preventDefault this event');
}
function test()
{
doPreventDefault = false;
doTest('base case', undefined);
doTest('non-passive listener', false);
doTest('passive listener', true);
doPreventDefault = true;
doTest('base case', undefined);
doTest('non-passive listener', false);
doTest('passive listener', true);
testAddListenerKey(false);
testAddListenerKey(true);
}
test();
</script>
</body>
</html>

View File

@ -123,6 +123,9 @@ public:
// perform its associated action. This is currently only relevant for
// wheel and touch events.
bool mHandledByAPZ : 1;
// True if the event is currently being handled by an event listener that
// was registered as a passive listener.
bool mInPassiveListener: 1;
// If the event is being handled in target phase, returns true.
inline bool InTargetPhase() const