Bug 967796 - Implement Pointer Enter/Leave events support. Tests. r=smaug
authorOleg Romashin <oleg.romashin@microsoft.com>
Tue, 11 Feb 2014 06:16:56 -0800
changeset 185253 af35bd7bd9a83d3ca0451fe2e0be293a4b4d8e0a
parent 185252 3e252a679f72adfdd5fc2a8d527add97b4e312d1
child 185254 cc975b34307953d8db137e087f9a7a02bd7b0efe
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs967796
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 967796 - Implement Pointer Enter/Leave events support. Tests. r=smaug
dom/base/nsDOMWindowUtils.cpp
dom/events/test/mochitest.ini
dom/events/test/test_bug967796.html
dom/interfaces/base/nsIDOMWindowUtils.idl
testing/mochitest/tests/SimpleTest/EventUtils.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -748,16 +748,98 @@ nsDOMWindowUtils::SendMouseEventCommon(c
   }
   nsresult rv = widget->DispatchEvent(&event, status);
   *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
 
   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,
                                  double aDeltaX,
                                  double aDeltaY,
                                  double aDeltaZ,
                                  uint32_t aDeltaMode,
                                  int32_t aModifiers,
                                  int32_t aLineOrPageDeltaX,
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -81,16 +81,17 @@ skip-if = true # Disabled due to timeout
 [test_bug741666.html]
 [test_bug742376.html]
 [test_bug812744.html]
 [test_bug847597.html]
 [test_bug855741.html]
 [test_bug864040.html]
 [test_bug930374-content.html]
 [test_bug944847.html]
+[test_bug967796.html]
 skip-if = toolkit == "gonk"
 [test_bug944011.html]
 [test_bug946632.html]
 [test_clickevent_on_input.html]
 [test_continuous_wheel_events.html]
 [test_dblclick_explicit_original_target.html]
 [test_dom_keyboard_event.html]
 [test_dom_mouse_event.html]
copy from dom/events/test/test_bug432698.html
copy to dom/events/test/test_bug967796.html
--- a/dom/events/test/test_bug432698.html
+++ b/dom/events/test/test_bug967796.html
@@ -1,203 +1,208 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=432698
+https://bugzilla.mozilla.org/show_bug.cgi?id=967796
 -->
 <head>
-  <title>Test for Bug 432698</title>
+  <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=432698">Mozilla Bug 432698</a>
+<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 432698 **/
+/** Test for Bug 967796 **/
+
+SpecialPowers.setBoolPref("dom.w3c_pointer_events.enabled", true);      // Enable Pointer Events
+
 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 mouseentercount = 0;
-var mouseleavecount = 0;
-var mouseovercount = 0;
-var mouseoutcount = 0;
+var pointerentercount = 0;
+var pointerleavecount = 0;
+var pointerovercount = 0;
+var pointeroutcount = 0;
 
-function sendMouseEvent(t, elem) {
+function sendPointerEvent(t, elem) {
   var r = elem.getBoundingClientRect();
-  synthesizeMouse(elem, r.width / 2, r.height / 2, {type: t});
+  synthesizePointer(elem, r.width / 2, r.height / 2, {type: t});
 }
 
-var expectedMouseEnterTargets = [];
-var expectedMouseLeaveTargets = [];
+var expectedPointerEnterTargets = [];
+var expectedPointerLeaveTargets = [];
 
 function runTests() {
   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 mouse is outside the test elements.
-  sendMouseEvent("mousemove", outside);
+  // Make sure ESM thinks pointer is outside the test elements.
+  sendPointerEvent("pointermove", outside);
 
-  mouseentercount = 0;
-  mouseleavecount = 0;
-  mouseovercount = 0;
-  mouseoutcount = 0;
+  pointerentercount = 0;
+  pointerleavecount = 0;
+  pointerovercount = 0;
+  pointeroutcount = 0;
   checkRelatedTarget = true;
   expectedRelatedEnter = outside;
   expectedRelatedLeave = inner;
-  expectedMouseEnterTargets = ["outertest", "middletest", "innertest"];
-  sendMouseEvent("mousemove", inner);
-  is(mouseentercount, 3, "Unexpected mouseenter event count!");
-  is(mouseovercount, 1, "Unexpected mouseover event count!");
-  is(mouseoutcount, 0, "Unexpected mouseout event count!");
-  is(mouseleavecount, 0, "Unexpected mouseleave event count!");
+  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;
-  expectedMouseLeaveTargets = ["innertest", "middletest", "outertest"];
-  sendMouseEvent("mousemove", outside);
-  is(mouseentercount, 3, "Unexpected mouseenter event count!");
-  is(mouseovercount, 1, "Unexpected mouseover event count!");
-  is(mouseoutcount, 1, "Unexpected mouseout event count!");
-  is(mouseleavecount, 3, "Unexpected mouseleave event count!");
+  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;
-  synthesizeMouse(file, r.width / 6, r.height / 2, {type: "mousemove"});
-  is(mouseentercount, 4, "Unexpected mouseenter event count!");
-  is(mouseovercount, 2, "Unexpected mouseover event count!");
-  is(mouseoutcount, 1, "Unexpected mouseout event count!");
-  is(mouseleavecount, 3, "Unexpected mouseleave event count!");
+  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 mouse over type="file" shouldn't cause mouseover/out/enter/leave events
-  synthesizeMouse(file, r.width - (r.width / 6), r.height / 2, {type: "mousemove"});
-  is(mouseentercount, 4, "Unexpected mouseenter event count!");
-  is(mouseovercount, 2, "Unexpected mouseover event count!");
-  is(mouseoutcount, 1, "Unexpected mouseout event count!");
-  is(mouseleavecount, 3, "Unexpected mouseleave 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;
-  sendMouseEvent("mousemove", outside);
-  is(mouseentercount, 4, "Unexpected mouseenter event count!");
-  is(mouseovercount, 2, "Unexpected mouseover event count!");
-  is(mouseoutcount, 2, "Unexpected mouseout event count!");
-  is(mouseleavecount, 4, "Unexpected mouseleave event count!");
-  
+  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.onmouseenter = menter;
-  iframe.contentDocument.body.firstChild.onmouseleave = mleave;
-  iframe.contentDocument.body.lastChild.onmouseenter = menter;
-  iframe.contentDocument.body.lastChild.onmouseleave = mleave;
+  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 mouse inside the iframe.
-  synthesizeMouse(iframe.contentDocument.body, r.width / 2, r.height / 4, {type: "mousemove"},
-                  iframe.contentWindow);
-  synthesizeMouse(iframe.contentDocument.body, r.width / 2, r.height - (r.height / 4), {type: "mousemove"},
-                  iframe.contentWindow);
-  is(mouseentercount, 7, "Unexpected mouseenter event count!");
+  // 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;
-  sendMouseEvent("mousemove", outside);
-  is(mouseleavecount, 7, "Unexpected mouseleave event count!");
+  sendPointerEvent("pointermove", outside);
+  is(pointerleavecount, 7, "Unexpected pointerleave event count!");
+
+  SpecialPowers.clearUserPref("dom.w3c_pointer_events.enabled");      // Disable Pointer Events
 
   SimpleTest.finish();
 }
 
-function menter(evt) {
-  ++mouseentercount;
+function penter(evt) {
+  ++pointerentercount;
   evt.stopPropagation();
-  if (expectedMouseEnterTargets.length) {
-    var t = expectedMouseEnterTargets.shift();
+  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, false, evt.type + " is not cancelable!");
+  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 (mouseenter)");
+    is(evt.relatedTarget, expectedRelatedEnter, "Wrong related target (pointerenter)");
   }
 }
 
-function mleave(evt) {
-  ++mouseleavecount;
+function pleave(evt) {
+  ++pointerleavecount;
   evt.stopPropagation();
-  if (expectedMouseLeaveTargets.length) {
-    var t = expectedMouseLeaveTargets.shift();
+  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, false, evt.type + " is not cancelable!");
+  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 (mouseleave)");
+    is(evt.relatedTarget, expectedRelatedLeave, "Wrong related target (pointerleave)");
   }
 }
 
-function mover(evt) {
-  ++mouseovercount;
+function pover(evt) {
+  ++pointerovercount;
   evt.stopPropagation();
 }
 
-function mout(evt) {
-  ++mouseoutcount;
+function pout(evt) {
+  ++pointeroutcount;
   evt.stopPropagation();
 }
 
 </script>
 </pre>
-<div id="container" onmouseenter="menter(event)" onmouseleave="mleave(event)"
-                    onmouseout="mout(event)" onmouseover="mover(event)">
-  <div id="outside" onmouseout="event.stopPropagation()" onmouseover="event.stopPropagation()">foo</div>
-  <div id="outertest" onmouseenter="menter(event)" onmouseleave="mleave(event)"
-                      onmouseout="mout(event)" onmouseover="mover(event)">
-    <div id="middletest" onmouseenter="menter(event)" onmouseleave="mleave(event)"
-                         onmouseout="mout(event)" onmouseover="mover(event)">
-      <div id="innertest" onmouseenter="menter(event)" onmouseleave="mleave(event)"
-                          onmouseout="mout(event)" onmouseover="mover(event)">foo</div>
+<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"
-         onmouseenter="menter(event)" onmouseleave="mleave(event)"
-         onmouseout="mout(event)" onmouseover="mover(event)">
+         onpointerenter="penter(event)" onpointerleave="pleave(event)"
+         onpointerout="pout(event)" onpointerover="pover(event)">
   <br>
   <iframe id="iframe" width="50px" height="50px"
-          onmouseenter="menter(event)" onmouseleave="mleave(event)"
-          onmouseout="mout(event)" onmouseover="mover(event)"></iframe>
+          onpointerenter="penter(event)" onpointerleave="pleave(event)"
+          onpointerout="pout(event)" onpointerover="pover(event)"></iframe>
 </div>
 </body>
 </html>
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -38,17 +38,17 @@ interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 
-[scriptable, uuid(fa0fe174-7c07-11e3-a5ba-000c290c393e)]
+[scriptable, uuid(ce671a4a-92bb-11e3-b2d0-2c27d728e7f9)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -262,16 +262,82 @@ interface nsIDOMWindowUtils : nsISupport
                          in long aButton,
                          in long aClickCount,
                          in long aModifiers,
                          [optional] in boolean aIgnoreRootScrollFrame,
                          [optional] in float aPressure,
                          [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
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -2,16 +2,17 @@
  * EventUtils provides some utility methods for creating and sending DOM events.
  * Current methods:
  *  sendMouseEvent
  *  sendChar
  *  sendString
  *  sendKey
  *  synthesizeMouse
  *  synthesizeMouseAtCenter
+ *  synthesizePointer
  *  synthesizeWheel
  *  synthesizeKey
  *  synthesizeNativeKey
  *  synthesizeMouseExpectEvent
  *  synthesizeKeyExpectEvent
  *
  *  When adding methods to this file, please add a performance test for it.
  */
@@ -221,16 +222,22 @@ function synthesizeMouse(aTarget, aOffse
        aEvent, aWindow);
 }
 function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
   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.
  *
  * aEvent is an object which may contain the properties:
  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
  *
  * If the type is specified, an mouse event of that type is fired. Otherwise,
@@ -281,16 +288,44 @@ function synthesizeTouchAtPoint(left, to
       utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
     }
     else {
       utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
       utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
     }
   }
 }
+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)
 {
   var rect = aTarget.getBoundingClientRect();
   synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
                   aWindow);
 }
 function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)