Bug 1304044 - implement auxclick r=smaug
After click events with button 2 or 3 are fired, fire auxclick, a new
event intended to represent a non-primary mouse click. Because this
event, based on the design examples and blink's implementation, is
intended to be used with content listeners, always dispatch on content
listeners--not just those that force all events to be dispatched (i.e.
document/window). This diverges from the behavior of our click events
from non-primary buttons.
Eventually, we hope this will replace click events for non-primary
buttons. For now, leave those events for compatibility reasons.
Additionally, add handling of this new event, where necessary.
MozReview-Commit-ID: 8osozM4h6Ya
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -698,16 +698,17 @@ GK_ATOM(onanimationiteration, "onanimati
GK_ATOM(onanimationstart, "onanimationstart")
GK_ATOM(onantennaavailablechange, "onantennaavailablechange")
GK_ATOM(onAppCommand, "onAppCommand")
GK_ATOM(onappinstalled, "onappinstalled")
GK_ATOM(onattributechanged, "onattributechanged")
GK_ATOM(onattributereadreq, "onattributereadreq")
GK_ATOM(onattributewritereq, "onattributewritereq")
GK_ATOM(onaudioprocess, "onaudioprocess")
+GK_ATOM(onauxclick, "onauxclick")
GK_ATOM(onbeforecopy, "onbeforecopy")
GK_ATOM(onbeforecut, "onbeforecut")
GK_ATOM(onbeforepaste, "onbeforepaste")
GK_ATOM(onbeforeevicted, "onbeforeevicted")
GK_ATOM(onbeforeprint, "onbeforeprint")
GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
GK_ATOM(onbeforeunload, "onbeforeunload")
GK_ATOM(onblocked, "onblocked")
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -159,16 +159,20 @@ EVENT(canplay,
EVENT(canplaythrough,
eCanPlayThrough,
EventNameType_HTML,
eBasicEventClass)
EVENT(change,
eFormChange,
EventNameType_HTMLXUL,
eBasicEventClass)
+EVENT(auxclick,
+ eMouseAuxClick,
+ EventNameType_All,
+ eMouseEventClass)
EVENT(click,
eMouseClick,
EventNameType_All,
eMouseEventClass)
EVENT(contextmenu,
eContextMenu,
EventNameType_HTMLXUL,
eMouseEventClass)
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -484,16 +484,17 @@ EventStateManager::TryToFlushPendingNoti
}
static bool
IsMessageMouseUserActivity(EventMessage aMessage)
{
return aMessage == eMouseMove ||
aMessage == eMouseUp ||
aMessage == eMouseDown ||
+ aMessage == eMouseAuxClick ||
aMessage == eMouseDoubleClick ||
aMessage == eMouseClick ||
aMessage == eMouseActivate ||
aMessage == eMouseLongTap;
}
static bool
IsMessageGamepadUserActivity(EventMessage aMessage)
@@ -4613,16 +4614,42 @@ EventStateManager::SetClickCount(WidgetM
}
break;
}
return NS_OK;
}
nsresult
+EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aEvent,
+ nsEventStatus* aStatus,
+ EventMessage aMessage,
+ nsIPresShell* aPresShell,
+ nsIContent* aMouseTarget,
+ nsWeakFrame aCurrentTarget,
+ bool aNoContentDispatch)
+{
+ WidgetMouseEvent event(aEvent->IsTrusted(), aMessage,
+ aEvent->mWidget, WidgetMouseEvent::eReal);
+
+ event.mRefPoint = aEvent->mRefPoint;
+ event.mClickCount = aEvent->mClickCount;
+ event.mModifiers = aEvent->mModifiers;
+ event.buttons = aEvent->buttons;
+ event.mTime = aEvent->mTime;
+ event.mTimeStamp = aEvent->mTimeStamp;
+ event.mFlags.mNoContentDispatch = aNoContentDispatch;
+ event.button = aEvent->button;
+ event.inputSource = aEvent->inputSource;
+
+ return aPresShell->HandleEventWithTarget(&event, aCurrentTarget,
+ aMouseTarget, aStatus);
+}
+
+nsresult
EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
nsEventStatus* aStatus)
{
nsresult ret = NS_OK;
//If mouse is still over same element, clickcount will be > 1.
//If it has moved it will be zero, so no click.
if (aEvent->mClickCount) {
@@ -4631,27 +4658,17 @@ EventStateManager::CheckForAndDispatchCl
if (aEvent->mWidget && !aEvent->mWidget->IsEnabled()) {
return ret;
}
//fire click
bool notDispatchToContents =
(aEvent->button == WidgetMouseEvent::eMiddleButton ||
aEvent->button == WidgetMouseEvent::eRightButton);
- WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick,
- aEvent->mWidget, WidgetMouseEvent::eReal);
- event.mRefPoint = aEvent->mRefPoint;
- event.mClickCount = aEvent->mClickCount;
- event.mModifiers = aEvent->mModifiers;
- event.buttons = aEvent->buttons;
- event.mTime = aEvent->mTime;
- event.mTimeStamp = aEvent->mTimeStamp;
- event.mFlags.mNoContentDispatch = notDispatchToContents;
- event.button = aEvent->button;
- event.inputSource = aEvent->inputSource;
+ bool fireAuxClick = notDispatchToContents;
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
if (presShell) {
nsCOMPtr<nsIContent> mouseContent = GetEventTargetContent(aEvent);
// Click events apply to *elements* not nodes. At this point the target
// content may have been reset to some non-element content, and so we need
// to walk up the closest ancestor element, just like we do in
// nsPresShell::HandlePositionedEvent.
@@ -4660,35 +4677,32 @@ EventStateManager::CheckForAndDispatchCl
}
if (!mouseContent && !mCurrentTarget) {
return NS_OK;
}
// HandleEvent clears out mCurrentTarget which we might need again
nsWeakFrame currentTarget = mCurrentTarget;
- ret = presShell->HandleEventWithTarget(&event, currentTarget,
- mouseContent, aStatus);
+ ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseClick,
+ presShell, mouseContent, currentTarget,
+ notDispatchToContents);
+
if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 &&
mouseContent && mouseContent->IsInComposedDoc()) {
//fire double click
- WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick,
- aEvent->mWidget, WidgetMouseEvent::eReal);
- event2.mRefPoint = aEvent->mRefPoint;
- event2.mClickCount = aEvent->mClickCount;
- event2.mModifiers = aEvent->mModifiers;
- event2.buttons = aEvent->buttons;
- event2.mTime = aEvent->mTime;
- event2.mTimeStamp = aEvent->mTimeStamp;
- event2.mFlags.mNoContentDispatch = notDispatchToContents;
- event2.button = aEvent->button;
- event2.inputSource = aEvent->inputSource;
-
- ret = presShell->HandleEventWithTarget(&event2, currentTarget,
- mouseContent, aStatus);
+ ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseDoubleClick,
+ presShell, mouseContent, currentTarget,
+ notDispatchToContents);
+ }
+ if (NS_SUCCEEDED(ret) && mouseContent && fireAuxClick &&
+ mouseContent->IsInComposedDoc()) {
+ ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseAuxClick,
+ presShell, mouseContent, currentTarget,
+ false);
}
}
}
return ret;
}
nsIFrame*
EventStateManager::GetEventTarget()
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -410,16 +410,23 @@ protected:
nsIContent* aTargetContent,
nsWeakFrame& aTargetFrame);
/**
* Update the initial drag session data transfer with any changes that occur
* on cloned data transfer objects used for events.
*/
void UpdateDragDataTransfer(WidgetDragEvent* dragEvent);
+ static nsresult InitAndDispatchClickEvent(WidgetMouseEvent* aEvent,
+ nsEventStatus* aStatus,
+ EventMessage aMessage,
+ nsIPresShell* aPresShell,
+ nsIContent* aMouseTarget,
+ nsWeakFrame aCurrentTarget,
+ bool aNoContentDispatch);
nsresult SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus);
nsresult CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
nsEventStatus* aStatus);
void EnsureDocument(nsPresContext* aPresContext);
void FlushPendingEvents(nsPresContext* aPresContext);
/**
* The phases of HandleAccessKey processing. See below.
@@ -1039,11 +1046,12 @@ private:
} // namespace mozilla
// Click and double-click events need to be handled even for content that
// has no frame. This is required for Web compatibility.
#define NS_EVENT_NEEDS_FRAME(event) \
(!(event)->HasPluginActivationEventMessage() && \
(event)->mMessage != eMouseClick && \
- (event)->mMessage != eMouseDoubleClick)
+ (event)->mMessage != eMouseDoubleClick && \
+ (event)->mMessage != eMouseAuxClick)
#endif // mozilla_EventStateManager_h_
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -252,16 +252,17 @@ WheelTransaction::OnEvent(WidgetEvent* a
return;
}
case eKeyPress:
case eKeyUp:
case eKeyDown:
case eMouseUp:
case eMouseDown:
case eMouseDoubleClick:
+ case eMouseAuxClick:
case eMouseClick:
case eContextMenu:
case eDrop:
EndTransaction();
return;
default:
break;
}
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -179,8 +179,9 @@ skip-if = toolkit == 'android' #CRASH_DU
[test_offsetxy.html]
[test_onerror_handler_args.html]
[test_passive_listeners.html]
[test_paste_image.html]
[test_wheel_default_action.html]
[test_bug687787.html]
[test_bug1305458.html]
[test_bug1298970.html]
+[test_bug1304044.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug1304044.html
@@ -0,0 +1,133 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1304044
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1304044</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var eventsFired = [];
+ var target;
+ var eventsExpected;
+
+ function GetNodeString(node) {
+ if (node == window)
+ return "window";
+ if (node == document)
+ return "document";
+ if (node.id)
+ return node.id;
+ if (node.nodeName)
+ return node.nodeName;
+ return node;
+ }
+
+ function TargetAndListener(listener, target) {
+ this.listener = listener;
+ this.target = target;
+ }
+
+ TargetAndListener.prototype.toString = function() {
+ var targetName = GetNodeString(this.target);
+ var listenerName = GetNodeString(this.listener);
+ return "(listener: " + listenerName + ", target: " + targetName + ")";
+ }
+
+ var tests = [
+ TestAuxClickBubblesForEventListener,
+ TestAuxClickBubblesForOnAuxClick,
+ ];
+
+ function CompareEvents(evt, expected) {
+ return evt && expected && evt.listener == expected.listener &&
+ evt.target == expected.target;
+ }
+
+ function ResetEventsFired() {
+ eventsFired = [];
+ }
+
+ function ClearEventListeners() {
+ for (i in arguments) {
+ arguments[i].removeEventListener("auxclick", log_event);
+ }
+ }
+
+ function ClickTarget(tgt) {
+ synthesizeMouseAtCenter(tgt, {type : "mousedown", button: 2}, window);
+ synthesizeMouseAtCenter(tgt, {type : "mouseup", button: 2}, window);
+ }
+
+ function log_event(e) {
+ eventsFired[eventsFired.length] = new TargetAndListener(this, e.target);
+ }
+
+ function CompareEventsToExpected(expected, actual) {
+ for (var i = 0; i < expected.length || i < actual.length; i++) {
+ ok(CompareEvents(actual[i], expected[i]),
+ "Auxclick receiver's don't match: TargetAndListener " +
+ i + ": Expected: " + expected[i] + ", Actual: " + actual[i]);
+ }
+ }
+
+ function TestAuxClickBubblesForEventListener() {
+ target.addEventListener("auxclick", log_event);
+ document.addEventListener("auxclick", log_event);
+ window.addEventListener("auxclick", log_event);
+
+ ClickTarget(target)
+ CompareEventsToExpected(eventsExpected, eventsFired);
+ ResetEventsFired();
+ ClearEventListeners(target, document, window);
+ }
+
+ function TestAuxClickBubblesForOnAuxClick() {
+ target.onauxclick = log_event;
+ document.onauxclick = log_event;
+ window.onauxclick = log_event;
+
+ ClickTarget(target);
+ CompareEventsToExpected(eventsExpected, eventsFired);
+ ResetEventsFired();
+ }
+
+ function RunTests(){
+ for (var i = 0; i < tests.length; i++) {
+ tests[i]();
+ }
+ }
+
+ function Begin() {
+ target = document.getElementById("target");
+ eventsExpected = [
+ new TargetAndListener(target, target),
+ new TargetAndListener(document, target),
+ new TargetAndListener(window, target),
+ ];
+ RunTests();
+ target.remove();
+ SimpleTest.finish();
+ }
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.executeSoon(Begin);
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1304044">Mozilla Bug 1304044</a>
+<p id="display">
+ <div id="target">Target</div>
+</p>
+<div id="content" style:"display:none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2527,16 +2527,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
if (pPluginEvent) {
// Make event coordinates relative to our enclosing widget,
// not the widget they were received on.
// See use of NPEvent in widget/windows/nsWindow.cpp
// for why this assert should be safe
NS_ASSERTION(anEvent.mMessage == eMouseDown ||
anEvent.mMessage == eMouseUp ||
anEvent.mMessage == eMouseDoubleClick ||
+ anEvent.mMessage == eMouseAuxClick ||
anEvent.mMessage == eMouseOver ||
anEvent.mMessage == eMouseOut ||
anEvent.mMessage == eMouseMove ||
anEvent.mMessage == eWheel,
"Incorrect event type for coordinate translation");
nsPoint pt =
nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) -
mPluginFrame->GetContentRectRelativeToSelf().TopLeft();
@@ -2589,16 +2590,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
pluginEvent.type = 0;
switch(anEvent.mClass) {
case eMouseEventClass:
{
switch (anEvent.mMessage) {
case eMouseClick:
case eMouseDoubleClick:
+ case eMouseAuxClick:
// Button up/down events sent instead.
return rv;
default:
break;
}
// Get reference point relative to plugin origin.
const nsPresContext* presContext = mPluginFrame->PresContext();
@@ -2792,16 +2794,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
}
}
switch(anEvent.mClass) {
case eMouseEventClass:
{
switch (anEvent.mMessage) {
case eMouseClick:
case eMouseDoubleClick:
+ case eMouseAuxClick:
// Button up/down events sent instead.
return rv;
default:
break;
}
// Get reference point relative to plugin origin.
const nsPresContext* presContext = mPluginFrame->PresContext();
--- a/dom/webidl/EventHandler.webidl
+++ b/dom/webidl/EventHandler.webidl
@@ -28,16 +28,17 @@ typedef OnErrorEventHandlerNonNull? OnEr
interface GlobalEventHandlers {
attribute EventHandler onabort;
attribute EventHandler onblur;
// We think the spec is wrong here. See OnErrorEventHandlerForNodes/Window
// below.
// attribute OnErrorEventHandler onerror;
attribute EventHandler onfocus;
//(Not implemented)attribute EventHandler oncancel;
+ attribute EventHandler onauxclick;
attribute EventHandler oncanplay;
attribute EventHandler oncanplaythrough;
attribute EventHandler onchange;
attribute EventHandler onclick;
//(Not implemented)attribute EventHandler onclose;
attribute EventHandler oncontextmenu;
//(Not implemented)attribute EventHandler oncuechange;
attribute EventHandler ondblclick;
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1272,17 +1272,17 @@ nsXULElement::List(FILE* out, int32_t aI
bool
nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage)
{
return (IsRootOfNativeAnonymousSubtree() &&
IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) &&
(aMessage == eMouseClick || aMessage == eMouseDoubleClick ||
aMessage == eXULCommand || aMessage == eContextMenu ||
- aMessage == eDragStart));
+ aMessage == eDragStart || aMessage == eMouseAuxClick));
}
nsresult
nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor,
nsAutoString& aCommand)
{
// XXX sXBL/XBL2 issue! Owner or current document?
nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetUncomposedDoc()));
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1140,16 +1140,17 @@ APZCTreeManager::UpdateWheelTransaction(
return;
}
case eKeyPress:
case eKeyUp:
case eKeyDown:
case eMouseUp:
case eMouseDown:
case eMouseDoubleClick:
+ case eMouseAuxClick:
case eMouseClick:
case eContextMenu:
case eDrop:
txn->EndTransaction();
return;
default:
break;
}
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -590,16 +590,17 @@ public:
break;
case eKeyboardEventClass:
mFlags.mComposed = mMessage == eKeyDown || mMessage == eKeyUp ||
mMessage == eKeyPress;
break;
case eMouseEventClass:
mFlags.mComposed = mMessage == eMouseClick ||
mMessage == eMouseDoubleClick ||
+ mMessage == eMouseAuxClick ||
mMessage == eMouseDown || mMessage == eMouseUp ||
mMessage == eMouseEnter || mMessage == eMouseLeave ||
mMessage == eMouseOver || mMessage == eMouseOut ||
mMessage == eMouseMove || mMessage == eContextMenu;
break;
case ePointerEventClass:
// All pointer events are composed
mFlags.mComposed = mMessage == ePointerDown ||
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -79,16 +79,17 @@ NS_EVENT_MESSAGE(eLanguageChange)
NS_EVENT_MESSAGE(eMouseMove)
NS_EVENT_MESSAGE(eMouseUp)
NS_EVENT_MESSAGE(eMouseDown)
NS_EVENT_MESSAGE(eMouseEnterIntoWidget)
NS_EVENT_MESSAGE(eMouseExitFromWidget)
NS_EVENT_MESSAGE(eMouseDoubleClick)
NS_EVENT_MESSAGE(eMouseClick)
+NS_EVENT_MESSAGE(eMouseAuxClick)
// eMouseActivate is fired when the widget is activated by a click.
NS_EVENT_MESSAGE(eMouseActivate)
NS_EVENT_MESSAGE(eMouseOver)
NS_EVENT_MESSAGE(eMouseOut)
NS_EVENT_MESSAGE(eMouseHitTest)
NS_EVENT_MESSAGE(eMouseEnter)
NS_EVENT_MESSAGE(eMouseLeave)
NS_EVENT_MESSAGE(eMouseLongTap)
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -231,16 +231,17 @@ WidgetEvent::IsNativeEventDelivererForPl
bool
WidgetEvent::HasMouseEventMessage() const
{
switch (mMessage) {
case eMouseDown:
case eMouseUp:
case eMouseClick:
case eMouseDoubleClick:
+ case eMouseAuxClick:
case eMouseEnterIntoWidget:
case eMouseExitFromWidget:
case eMouseActivate:
case eMouseOver:
case eMouseOut:
case eMouseHitTest:
case eMouseMove:
return true;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -3081,16 +3081,17 @@ case _value: eventName.AssignLiteral(_na
_ASSIGN_eventName(eKeyDown,"eKeyDown");
_ASSIGN_eventName(eKeyPress,"eKeyPress");
_ASSIGN_eventName(eKeyUp,"eKeyUp");
_ASSIGN_eventName(eMouseEnterIntoWidget,"eMouseEnterIntoWidget");
_ASSIGN_eventName(eMouseExitFromWidget,"eMouseExitFromWidget");
_ASSIGN_eventName(eMouseDown,"eMouseDown");
_ASSIGN_eventName(eMouseUp,"eMouseUp");
_ASSIGN_eventName(eMouseClick,"eMouseClick");
+ _ASSIGN_eventName(eMouseAuxClick,"eMouseAuxClick");
_ASSIGN_eventName(eMouseDoubleClick,"eMouseDoubleClick");
_ASSIGN_eventName(eMouseMove,"eMouseMove");
_ASSIGN_eventName(eLoad,"eLoad");
_ASSIGN_eventName(ePopState,"ePopState");
_ASSIGN_eventName(eBeforeScriptExecute,"eBeforeScriptExecute");
_ASSIGN_eventName(eAfterScriptExecute,"eAfterScriptExecute");
_ASSIGN_eventName(eUnload,"eUnload");
_ASSIGN_eventName(eHashChange,"eHashChange");
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -1138,17 +1138,18 @@ WinUtils::GetMousePointerID()
/* static */
bool
WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage)
{
const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE;
const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK;
return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown ||
- aEventMessage == eMouseUp || aEventMessage == eMouseDoubleClick) &&
+ aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick ||
+ aEventMessage == eMouseDoubleClick) &&
(GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH);
}
/* static */
MSG
WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd)
{
MSG msg;