Bug 967796 - Implement Pointer Enter/Leave events support. Tests. r=smaug

--HG--
rename : dom/events/test/test_bug432698.html => dom/events/test/test_bug967796.html
This commit is contained in:
Oleg Romashin 2014-02-10 22:35:17 -08:00
parent 1e1bc78268
commit 3ef61ffbe1
5 changed files with 393 additions and 1 deletions

View File

@ -634,6 +634,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(const nsAString& aType,
aOptionalArgCount >= 4 ? aIsSynthesized : true);
}
static LayoutDeviceIntPoint
ToWidgetPoint(const CSSPoint& aPoint, const nsPoint& aOffset,
nsPresContext* aPresContext)
@ -752,6 +753,88 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
return rv;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendPointerEvent(const nsAString& aType,
float aX,
float aY,
int32_t aButton,
int32_t aClickCount,
int32_t aModifiers,
bool aIgnoreRootScrollFrame,
float aPressure,
unsigned short aInputSourceArg,
int32_t aPointerId,
int32_t aWidth,
int32_t aHeight,
int32_t tiltX,
int32_t tiltY,
bool aIsPrimary,
bool aIsSynthesized,
uint8_t aOptionalArgCount,
bool* aPreventDefault)
{
if (!nsContentUtils::IsCallerChrome()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsPoint offset;
nsCOMPtr<nsIWidget> widget = GetWidget(&offset);
if (!widget) {
return NS_ERROR_FAILURE;
}
int32_t msg;
if (aType.EqualsLiteral("pointerdown")) {
msg = NS_POINTER_DOWN;
} else if (aType.EqualsLiteral("pointerup")) {
msg = NS_POINTER_UP;
} else if (aType.EqualsLiteral("pointermove")) {
msg = NS_POINTER_MOVE;
} else if (aType.EqualsLiteral("pointerover")) {
msg = NS_POINTER_OVER;
} else if (aType.EqualsLiteral("pointerout")) {
msg = NS_POINTER_OUT;
} else {
return NS_ERROR_FAILURE;
}
if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
}
WidgetPointerEvent event(true, msg, widget);
event.modifiers = GetWidgetModifiers(aModifiers);
event.button = aButton;
event.buttons = GetButtonsFlagForButton(aButton);
event.widget = widget;
event.pressure = aPressure;
event.inputSource = aInputSourceArg;
event.pointerId = aPointerId;
event.width = aWidth;
event.height = aHeight;
event.tiltX = tiltX;
event.tiltY = tiltY;
event.isPrimary = aIsPrimary;
event.clickCount = aClickCount;
event.time = PR_IntervalNow();
event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
nsPresContext* presContext = GetPresContext();
if (!presContext) {
return NS_ERROR_FAILURE;
}
event.refPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&event, status);
*aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
return rv;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendWheelEvent(float aX,
float aY,

View File

@ -86,6 +86,7 @@ skip-if = true # Disabled due to timeouts.
[test_bug864040.html]
[test_bug930374-content.html]
[test_bug944847.html]
[test_bug967796.html]
skip-if = toolkit == "gonk"
[test_bug944011.html]
[test_bug946632.html]

View File

@ -0,0 +1,207 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=967796
-->
<head>
<title>Test for Bug 967796</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=967796">Mozilla Bug 967796</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 967796 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests);
var outer;
var middle;
var inner;
var outside;
var container;
var file;
var iframe;
var checkRelatedTarget = false;
var expectedRelatedEnter = null;
var expectedRelatedLeave = null;
var pointerentercount = 0;
var pointerleavecount = 0;
var pointerovercount = 0;
var pointeroutcount = 0;
function sendPointerEvent(t, elem) {
var r = elem.getBoundingClientRect();
synthesizePointer(elem, r.width / 2, r.height / 2, {type: t});
}
var expectedPointerEnterTargets = [];
var expectedPointerLeaveTargets = [];
function runTests() {
SpecialPowers.setBoolPref("dom.w3c_pointer_events.enabled", true); // Enable Pointer Events
outer = document.getElementById("outertest");
middle = document.getElementById("middletest");
inner = document.getElementById("innertest");
outside = document.getElementById("outside");
container = document.getElementById("container");
file = document.getElementById("file");
iframe = document.getElementById("iframe");
// Make sure ESM thinks pointer is outside the test elements.
sendPointerEvent("pointermove", outside);
pointerentercount = 0;
pointerleavecount = 0;
pointerovercount = 0;
pointeroutcount = 0;
checkRelatedTarget = true;
expectedRelatedEnter = outside;
expectedRelatedLeave = inner;
expectedPointerEnterTargets = ["outertest", "middletest", "innertest"];
sendPointerEvent("pointermove", inner);
is(pointerentercount, 3, "Unexpected pointerenter event count!");
is(pointerovercount, 1, "Unexpected pointerover event count!");
is(pointeroutcount, 0, "Unexpected pointerout event count!");
is(pointerleavecount, 0, "Unexpected pointerleave event count!");
expectedRelatedEnter = inner;
expectedRelatedLeave = outside;
expectedPointerLeaveTargets = ["innertest", "middletest", "outertest"];
sendPointerEvent("pointermove", outside);
is(pointerentercount, 3, "Unexpected pointerenter event count!");
is(pointerovercount, 1, "Unexpected pointerover event count!");
is(pointeroutcount, 1, "Unexpected pointerout event count!");
is(pointerleavecount, 3, "Unexpected pointerleave event count!");
// Event handling over native anonymous content.
var r = file.getBoundingClientRect();
expectedRelatedEnter = outside;
expectedRelatedLeave = file;
synthesizePointer(file, r.width / 6, r.height / 2, {type: "pointermove"});
is(pointerentercount, 4, "Unexpected pointerenter event count!");
is(pointerovercount, 2, "Unexpected pointerover event count!");
is(pointeroutcount, 1, "Unexpected pointerout event count!");
is(pointerleavecount, 3, "Unexpected pointerleave event count!");
// Moving pointer over type="file" shouldn't cause pointerover/out/enter/leave events
synthesizePointer(file, r.width - (r.width / 6), r.height / 2, {type: "pointermove"});
is(pointerentercount, 4, "Unexpected pointerenter event count!");
is(pointerovercount, 2, "Unexpected pointerover event count!");
is(pointeroutcount, 1, "Unexpected pointerout event count!");
is(pointerleavecount, 3, "Unexpected pointerleave event count!");
expectedRelatedEnter = file;
expectedRelatedLeave = outside;
sendPointerEvent("pointermove", outside);
is(pointerentercount, 4, "Unexpected pointerenter event count!");
is(pointerovercount, 2, "Unexpected pointerover event count!");
is(pointeroutcount, 2, "Unexpected pointerout event count!");
is(pointerleavecount, 4, "Unexpected pointerleave event count!");
// Initialize iframe
iframe.contentDocument.documentElement.style.overflow = "hidden";
iframe.contentDocument.body.style.margin = "0px";
iframe.contentDocument.body.style.width = "100%";
iframe.contentDocument.body.style.height = "100%";
iframe.contentDocument.body.innerHTML =
"<div style='width: 100%; height: 50%; border: 1px solid black;'></div>" +
"<div style='width: 100%; height: 50%; border: 1px solid black;'></div>";
iframe.contentDocument.body.offsetLeft; // flush
iframe.contentDocument.body.firstChild.onpointerenter = penter;
iframe.contentDocument.body.firstChild.onpointerleave = pleave;
iframe.contentDocument.body.lastChild.onpointerenter = penter;
iframe.contentDocument.body.lastChild.onpointerleave = pleave;
r = iframe.getBoundingClientRect();
expectedRelatedEnter = outside;
expectedRelatedLeave = iframe;
// Move pointer inside the iframe.
synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "pointermove"},
iframe.contentWindow);
synthesizePointer(iframe.contentDocument.body, r.width / 2, r.height - (r.height / 4), {type: "pointermove"},
iframe.contentWindow);
is(pointerentercount, 7, "Unexpected pointerenter event count!");
expectedRelatedEnter = iframe;
expectedRelatedLeave = outside;
sendPointerEvent("pointermove", outside);
is(pointerleavecount, 7, "Unexpected pointerleave event count!");
SpecialPowers.clearUserPref("dom.w3c_pointer_events.enabled"); // Disable Pointer Events
SimpleTest.finish();
}
function penter(evt) {
++pointerentercount;
evt.stopPropagation();
if (expectedPointerEnterTargets.length) {
var t = expectedPointerEnterTargets.shift();
is(evt.target.id, t, "Wrong event target!");
}
is(evt.bubbles, false, evt.type + " should not bubble!");
is(evt.cancelable, true, evt.type + " is cancelable!");
is(evt.target, evt.currentTarget, "Wrong event target!");
ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument,
"Leaking nodes to another document?");
if (checkRelatedTarget && evt.target.ownerDocument == document) {
is(evt.relatedTarget, expectedRelatedEnter, "Wrong related target (pointerenter)");
}
}
function pleave(evt) {
++pointerleavecount;
evt.stopPropagation();
if (expectedPointerLeaveTargets.length) {
var t = expectedPointerLeaveTargets.shift();
is(evt.target.id, t, "Wrong event target!");
}
is(evt.bubbles, false, evt.type + " should not bubble!");
is(evt.cancelable, true, evt.type + " is cancelable!");
is(evt.target, evt.currentTarget, "Wrong event target!");
ok(!evt.relatedTarget || evt.target.ownerDocument == evt.relatedTarget.ownerDocument,
"Leaking nodes to another document?");
if (checkRelatedTarget && evt.target.ownerDocument == document) {
is(evt.relatedTarget, expectedRelatedLeave, "Wrong related target (pointerleave)");
}
}
function pover(evt) {
++pointerovercount;
evt.stopPropagation();
}
function pout(evt) {
++pointeroutcount;
evt.stopPropagation();
}
</script>
</pre>
<div id="container" onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)">
<div id="outside" onpointerout="event.stopPropagation()" onpointerover="event.stopPropagation()">foo</div>
<div id="outertest" onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)">
<div id="middletest" onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)">
<div id="innertest" onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)">foo</div>
</div>
</div>
<input type="file" id="file"
onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)">
<br>
<iframe id="iframe" width="50px" height="50px"
onpointerenter="penter(event)" onpointerleave="pleave(event)"
onpointerout="pout(event)" onpointerover="pover(event)"></iframe>
</div>
</body>
</html>

View File

@ -43,7 +43,7 @@ interface nsIDOMEventTarget;
interface nsIRunnable;
interface nsICompositionStringSynthesizer;
[scriptable, uuid(fa0fe174-7c07-11e3-a5ba-000c290c393e)]
[scriptable, uuid(ce671a4a-92bb-11e3-b2d0-2c27d728e7f9)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -267,6 +267,72 @@ interface nsIDOMWindowUtils : nsISupports {
[optional] in unsigned short aInputSourceArg,
[optional] in boolean aIsSynthesized);
/** Synthesize a pointer event. The event types supported are:
* pointerdown, pointerup, pointermove, pointerover, pointerout
*
* Events are sent in coordinates offset by aX and aY from the window.
*
* Note that additional events may be fired as a result of this call. For
* instance, typically a click event will be fired as a result of a
* mousedown and mouseup in sequence.
*
* Normally at this level of events, the pointerover and pointerout events are
* only fired when the window is entered or exited. For inter-element
* pointerover and pointerout events, a movemove event fired on the new element
* should be sufficient to generate the correct over and out events as well.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without chrome privileges.
*
* The event is dispatched via the toplevel window, so it could go to any
* window under the toplevel window, in some cases it could never reach this
* window at all.
*
* @param aType event type
* @param aX x offset in CSS pixels
* @param aY y offset in CSS pixels
* @param aButton button to synthesize
* @param aClickCount number of clicks that have been performed
* @param aModifiers modifiers pressed, using constants defined as MODIFIER_*
* @param aIgnoreRootScrollFrame whether the event should ignore viewport bounds
* during dispatch
* @param aPressure touch input pressure: 0.0 -> 1.0
* @param aInputSourceArg input source, see nsIDOMMouseEvent for values,
* defaults to mouse input.
* @param aPointerId A unique identifier for the pointer causing the event. default is 0
* @param aWidth The width (magnitude on the X axis), default is 0
* @param aHeight The height (magnitude on the Y axis), default is 0
* @param aTilt The plane angle between the Y-Z plane
* and the plane containing both the transducer (e.g. pen stylus) axis and the Y axis. default is 0
* @param aTiltX The plane angle between the X-Z plane
* and the plane containing both the transducer (e.g. pen stylus) axis and the X axis. default is 0
* @param aIsPrimary Indicates if the pointer represents the primary pointer of this pointer type.
* @param aIsSynthesized controls nsIDOMEvent.isSynthesized value
* that helps identifying test related events,
* defaults to true
*
* returns true if the page called prevent default on this event
*/
[optional_argc]
boolean sendPointerEvent(in AString aType,
in float aX,
in float aY,
in long aButton,
in long aClickCount,
in long aModifiers,
[optional] in boolean aIgnoreRootScrollFrame,
[optional] in float aPressure,
[optional] in unsigned short aInputSourceArg,
[optional] in long aPointerId,
[optional] in long aWidth,
[optional] in long aHeight,
[optional] in long tiltX,
[optional] in long tiltY,
[optional] in boolean aIsPrimary,
[optional] in boolean aIsSynthesized);
/** Synthesize a touch event. The event types supported are:
* touchstart, touchend, touchmove, and touchcancel
*

View File

@ -7,6 +7,7 @@
* sendKey
* synthesizeMouse
* synthesizeMouseAtCenter
* synthesizePointer
* synthesizeWheel
* synthesizeKey
* synthesizeNativeKey
@ -226,6 +227,12 @@ function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
aEvent, aWindow);
}
function synthesizePointer(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
var rect = aTarget.getBoundingClientRect();
return synthesizePointerAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
aEvent, aWindow);
}
/*
* Synthesize a mouse event at a particular point in aWindow.
@ -286,6 +293,34 @@ function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
}
}
}
function synthesizePointerAtPoint(left, top, aEvent, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
var defaultPrevented = false;
if (utils) {
var button = aEvent.button || 0;
var clickCount = aEvent.clickCount || 1;
var modifiers = _parseModifiers(aEvent);
var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
var synthesized = ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true;
if (("type" in aEvent) && aEvent.type) {
defaultPrevented = utils.sendPointerEvent(aEvent.type, left, top, button,
clickCount, modifiers, false,
pressure, inputSource,
synthesized);
}
else {
utils.sendPointerEvent("pointerdown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
utils.sendPointerEvent("pointerup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
}
}
return defaultPrevented;
}
// Call synthesizeMouse with coordinates at the center of aTarget.
function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
{