Bug 69787. Implement MouseEvent.offsetX/Y. r=mats,smaug
authorRobert O'Callahan <robert@ocallahan.org>
Sat, 14 Mar 2015 00:50:10 +1300
changeset 233530 43b9c8451378e2b26c2c9088d6db8906438894bd
parent 233529 2b9f5019abf1749c5c58240e7722d56467ca4e6b
child 233531 4100738d662d18654f30b8f1333d6f366b1781ec
push id28417
push userryanvm@gmail.com
push dateFri, 13 Mar 2015 19:52:44 +0000
treeherdermozilla-central@977add19414a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats, smaug
bugs69787
milestone39.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 69787. Implement MouseEvent.offsetX/Y. r=mats,smaug
dom/events/Event.cpp
dom/events/Event.h
dom/events/MouseEvent.cpp
dom/events/MouseEvent.h
dom/events/test/mochitest.ini
dom/events/test/test_offsetxy.html
dom/webidl/MouseEvent.webidl
layout/base/nsLayoutUtils.cpp
widget/tests/test_assign_event_data.html
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -954,27 +954,64 @@ Event::GetClientCoords(nsPresContext* aP
       !aEvent->AsGUIEvent()->widget) {
     return aDefaultPoint;
   }
 
   nsIPresShell* shell = aPresContext->GetPresShell();
   if (!shell) {
     return CSSIntPoint(0, 0);
   }
-
   nsIFrame* rootFrame = shell->GetRootFrame();
   if (!rootFrame) {
     return CSSIntPoint(0, 0);
   }
   nsPoint pt =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, aPoint, rootFrame);
 
   return CSSIntPoint::FromAppUnitsRounded(pt);
 }
 
+// static
+CSSIntPoint
+Event::GetOffsetCoords(nsPresContext* aPresContext,
+                       WidgetEvent* aEvent,
+                       LayoutDeviceIntPoint aPoint,
+                       CSSIntPoint aDefaultPoint)
+{
+  if (!aEvent->mFlags.mIsBeingDispatched) {
+    return GetPageCoords(aPresContext, aEvent, aPoint, aDefaultPoint);
+  }
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->target);
+  if (!content) {
+    return CSSIntPoint(0, 0);
+  }
+  nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
+  if (!shell) {
+    return CSSIntPoint(0, 0);
+  }
+  shell->FlushPendingNotifications(Flush_Layout);
+  nsIFrame* frame = content->GetPrimaryFrame();
+  if (!frame) {
+    return CSSIntPoint(0, 0);
+  }
+  nsIFrame* rootFrame = shell->GetRootFrame();
+  if (!rootFrame) {
+    return CSSIntPoint(0, 0);
+  }
+  CSSIntPoint clientCoords =
+    GetClientCoords(aPresContext, aEvent, aPoint, aDefaultPoint);
+  nsPoint pt = CSSPixel::ToAppUnits(clientCoords);
+  if (nsLayoutUtils::TransformPoint(rootFrame, frame, pt) ==
+      nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+    pt -= frame->GetPaddingRectRelativeToSelf().TopLeft();
+    return CSSPixel::FromAppUnitsRounded(pt);
+  }
+  return CSSIntPoint(0, 0);
+}
+
 // To be called ONLY by Event::GetType (which has the additional
 // logic for handling user-defined events).
 // static
 const char*
 Event::GetEventName(uint32_t aEventType)
 {
   switch(aEventType) {
 #define ID_TO_EVENT(name_, _id, _type, _struct) \
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -136,16 +136,20 @@ public:
                                      CSSIntPoint aDefaultPoint);
   static CSSIntPoint GetPageCoords(nsPresContext* aPresContext,
                                    WidgetEvent* aEvent,
                                    LayoutDeviceIntPoint aPoint,
                                    CSSIntPoint aDefaultPoint);
   static LayoutDeviceIntPoint GetScreenCoords(nsPresContext* aPresContext,
                                               WidgetEvent* aEvent,
                                               LayoutDeviceIntPoint aPoint);
+  static CSSIntPoint GetOffsetCoords(nsPresContext* aPresContext,
+                                     WidgetEvent* aEvent,
+                                     LayoutDeviceIntPoint aPoint,
+                                     CSSIntPoint aDefaultPoint);
 
   static already_AddRefed<Event> Constructor(const GlobalObject& aGlobal,
                                              const nsAString& aType,
                                              const EventInit& aParam,
                                              ErrorResult& aRv);
 
   // Implemented as xpidl method
   // void GetType(nsString& aRetval) {}
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -381,16 +381,30 @@ MouseEvent::GetClientY(int32_t* aClientY
 
 int32_t
 MouseEvent::ClientY()
 {
   return Event::GetClientCoords(mPresContext, mEvent, mEvent->refPoint,
                                 mClientPoint).y;
 }
 
+int32_t
+MouseEvent::OffsetX()
+{
+  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->refPoint,
+                                mClientPoint).x;
+}
+
+int32_t
+MouseEvent::OffsetY()
+{
+  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->refPoint,
+                                mClientPoint).y;
+}
+
 bool
 MouseEvent::AltKey()
 {
   return mEvent->AsInputEvent()->IsAlt();
 }
 
 NS_IMETHODIMP
 MouseEvent::GetAltKey(bool* aIsDown)
--- a/dom/events/MouseEvent.h
+++ b/dom/events/MouseEvent.h
@@ -40,16 +40,18 @@ public:
   {
     return Button() + 1;
   }
 
   int32_t ScreenX();
   int32_t ScreenY();
   int32_t ClientX();
   int32_t ClientY();
+  int32_t OffsetX();
+  int32_t OffsetY();
   bool CtrlKey();
   bool ShiftKey();
   bool AltKey();
   bool MetaKey();
   int16_t Button();
   uint16_t Buttons();
   already_AddRefed<EventTarget> GetRelatedTarget();
   void GetRegion(nsAString& aRegion);
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -177,8 +177,9 @@ support-files =
 [test_dom_before_after_keyboard_event_remote.html]
 support-files =
   bug989198_embedded.html
   bug989198_helper.js
 skip-if = buildapp == 'b2g' || e10s
 [test_bug1096146.html]
 support-files =
   bug1096146_embedded.html
+[test_offsetxy.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_offsetxy.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for DOM MouseEvent offsetX/Y</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="d" style="position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div>
+<div id="d2" style="position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px; transform:translateX(100px)"></div>
+<div id="d3" style="display:none; position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div>
+<div id="d4" style="transform:scale(0); position:absolute; top:100px; left:100px; width:100px; border:5px dotted black; height:100px"></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+var offsetX = -1, offsetY = -1;
+var ev = new MouseEvent("click", {clientX:110, clientY:110});
+d.addEventListener("click", function (event) {
+  is(ev, event, "Event objects must match");
+  offsetX = event.offsetX;
+  offsetY = event.offsetY;
+});
+d.dispatchEvent(ev);
+is(offsetX, 5);
+is(offsetY, 5);
+is(ev.offsetX, ev.pageX);
+is(ev.offsetY, ev.pageY);
+
+var ev2 = new MouseEvent("click", {clientX:220, clientY:130});
+d2.addEventListener("click", function (event) {
+  is(ev2, event, "Event objects must match");
+  offsetX = event.offsetX;
+  offsetY = event.offsetY;
+});
+d2.dispatchEvent(ev2);
+is(offsetX, 15);
+is(offsetY, 25);
+is(ev2.offsetX, ev2.pageX);
+is(ev2.offsetY, ev2.pageY);
+
+var ev3 = new MouseEvent("click", {clientX:110, clientY:110});
+d3.addEventListener("click", function (event) {
+  is(ev3, event, "Event objects must match");
+  offsetX = event.offsetX;
+  offsetY = event.offsetY;
+});
+d3.dispatchEvent(ev3);
+is(offsetX, 0);
+is(offsetY, 0);
+is(ev3.offsetX, ev3.pageX);
+is(ev3.offsetY, ev3.pageY);
+
+var ev4 = new MouseEvent("click", {clientX:110, clientY:110});
+d4.addEventListener("click", function (event) {
+  is(ev4, event, "Event objects must match");
+  offsetX = event.offsetX;
+  offsetY = event.offsetY;
+});
+d4.dispatchEvent(ev4);
+is(offsetX, 0);
+is(offsetY, 0);
+is(ev4.offsetX, ev4.pageX);
+is(ev4.offsetY, ev4.pageY);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/MouseEvent.webidl
+++ b/dom/webidl/MouseEvent.webidl
@@ -10,16 +10,18 @@
  * liability, trademark and document use rules apply.
  */
 
 interface MouseEvent : UIEvent {
   readonly attribute long           screenX;
   readonly attribute long           screenY;
   readonly attribute long           clientX;
   readonly attribute long           clientY;
+  readonly attribute long           offsetX;
+  readonly attribute long           offsetY;
   readonly attribute boolean        ctrlKey;
   readonly attribute boolean        shiftKey;
   readonly attribute boolean        altKey;
   readonly attribute boolean        metaKey;
   readonly attribute short          button;
   readonly attribute unsigned short buttons;
   readonly attribute EventTarget?   relatedTarget;
   readonly attribute DOMString?     region;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2398,18 +2398,18 @@ nsLayoutUtils::TransformPoint(nsIFrame* 
   Point4D toDevPixels = downToDest.ProjectPoint(
       upToAncestor * Point(aPoint.x * devPixelsPerAppUnitFromFrame,
                            aPoint.y * devPixelsPerAppUnitFromFrame));
   if (!toDevPixels.HasPositiveWCoord()) {
     // Not strictly true, but we failed to get a valid point in this
     // coordinate space.
     return NONINVERTIBLE_TRANSFORM;
   }
-  aPoint.x = toDevPixels.x / devPixelsPerAppUnitToFrame;
-  aPoint.y = toDevPixels.y / devPixelsPerAppUnitToFrame;
+  aPoint.x = NSToCoordRound(toDevPixels.x / devPixelsPerAppUnitToFrame);
+  aPoint.y = NSToCoordRound(toDevPixels.y / devPixelsPerAppUnitToFrame);
   return TRANSFORM_SUCCEEDED;
 }
 
 nsLayoutUtils::TransformResult
 nsLayoutUtils::TransformRect(nsIFrame* aFromFrame, nsIFrame* aToFrame,
                              nsRect& aRect)
 {
   nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
--- a/widget/tests/test_assign_event_data.html
+++ b/widget/tests/test_assign_event_data.html
@@ -588,16 +588,19 @@ function doTest(aTest)
       if (name == "rangeOffset") {
         todo(false, description + ": " + name + " attribute value is never reset (" + gEvent[name] + ")");
       } else if (name == "eventPhase") {
         is(gEvent[name], 0, description + ": mismatch with fixed value (" + name + ")");
       } else if (name == "rangeParent" || name == "currentTarget") {
         is(gEvent[name], null, description + ": mismatch with fixed value (" + name + ")");
       } else if (aTest.todoMismatch.indexOf(name) >= 0) {
         todo_is(gEvent[name], gCopiedEvent[i].value, description + ": mismatch (" + name + ")");
+      } else if (name == "offsetX" || name == "offsetY") {
+        // do nothing; these are defined to return different values during event dispatch
+        // vs not during event dispatch
       } else {
         is(gEvent[name], gCopiedEvent[i].value, description + ": mismatch (" + name + ")");
       }
     }
     runNextTest();
   };
   aTest.dispatchEvent();
 }