Fixes Issue #286.
Mouse event coordinates have been unreliable under various scrolling
scenarios. This patch addresses the various issues that were
responsible. First, it fixes a bug with getAbsoluteTop/Left for
DOMImplStandard and DOMImplSafari where scrolling was not full
accounted for due to a walk of the offsetParent not the parentNode.
Secondly, it adds the appropriate scroll offset for body and the
current element for MouseListenerCollection and MouseWheelCollection.
Thirdly, it changes DOM.eventGetClientX/Y to rely on pageX/Y for
DOMImplSafari since Safari2 returns the wrong value for clientX/Y
(This also makes the solution work on hosted mode's WebKit and WebKit
tip where the clientX/Y bug has been fixed).

Patch by: knorton, fredsa
Review by: jgw, ecc



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1005 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImpl.java b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
index 06036ef..4069ab9 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -137,20 +137,32 @@
 
   public native int getAbsoluteLeft(Element elem) /*-{
     var left = 0;
+    var curr = elem;
+    // This intentionally excludes body which has a null offsetParent.    
+    while (curr.offsetParent) {
+      left -= curr.scrollLeft;
+      curr = curr.parentNode;
+    }
     while (elem) {
-      left += elem.offsetLeft - elem.scrollLeft;
+      left += elem.offsetLeft;
       elem = elem.offsetParent;
     }
-    return left + $doc.body.scrollLeft;
+    return left;
   }-*/;
 
   public native int getAbsoluteTop(Element elem) /*-{
     var top = 0;
+    var curr = elem;
+    // This intentionally excludes body which has a null offsetParent.    
+    while (curr.offsetParent) {
+      top -= curr.scrollTop;
+      curr = curr.parentNode;
+    }
     while (elem) {
-      top += elem.offsetTop - elem.scrollTop;
+      top += elem.offsetTop;
       elem = elem.offsetParent;
     }
-    return top + $doc.body.scrollTop;
+    return top;
   }-*/;
 
   public abstract Element getChild(Element elem, int index);
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java b/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
index 0266024..dd452a5 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplSafari.java
@@ -22,6 +22,15 @@
  * Safari implementation of {@link com.google.gwt.user.client.impl.DOMImpl}.
  */
 class DOMImplSafari extends DOMImplStandard {
+  public native int eventGetClientX(Event evt) /*-{
+    // In Safari2: clientX is wrong and pageX is returned instead.
+    return evt.pageX - $doc.body.scrollLeft;
+  }-*/;
+
+  public native int eventGetClientY(Event evt) /*-{
+    // In Safari2: clientY is wrong and pageY is returned instead.
+    return evt.pageY - $doc.body.scrollTop;
+  }-*/;
 
   public native int eventGetMouseWheelVelocityY(Event evt) /*-{
     return -evt.wheelDelta / 40;
@@ -29,37 +38,49 @@
 
   public native int getAbsoluteLeft(Element elem) /*-{
     var left = 0;
+    var curr = elem;
+    // This intentionally excludes body which has a null offsetParent.
+    while (curr.offsetParent) {
+      left -= curr.scrollLeft;
+      curr = curr.parentNode;
+    }
     while (elem) {
-      left += elem.offsetLeft - elem.scrollLeft;
+      left += elem.offsetLeft;
 
       // Safari bug: a top-level absolutely positioned element includes the
       // body's offset position already.
       var parent = elem.offsetParent;
       if (parent && (parent.tagName == 'BODY') &&
           (elem.style.position == 'absolute')) {
-        return left;
+        break;
       }
 
       elem = parent;
     }
-    return left + $doc.body.scrollLeft;
+    return left;
   }-*/;
 
   public native int getAbsoluteTop(Element elem) /*-{
     var top = 0;
+    var curr = elem;
+    // This intentionally excludes body which has a null offsetParent.
+    while (curr.offsetParent) {
+      top -= curr.scrollTop;
+      curr = curr.parentNode;
+    }
     while (elem) {
-      top += elem.offsetTop - elem.scrollTop;
+      top += elem.offsetTop;
 
       // Safari bug: a top-level absolutely positioned element includes the
       // body's offset position already.
       var parent = elem.offsetParent;
       if (parent && (parent.tagName == 'BODY') &&
           (elem.style.position == 'absolute')) {
-        return top;
+        break;
       }
 
       elem = parent;
     }
-    return top + $doc.body.scrollTop;
+    return top;
   }-*/;
 }
diff --git a/user/src/com/google/gwt/user/client/ui/MouseListenerCollection.java b/user/src/com/google/gwt/user/client/ui/MouseListenerCollection.java
index 4d57c7f..044ace3 100644
--- a/user/src/com/google/gwt/user/client/ui/MouseListenerCollection.java
+++ b/user/src/com/google/gwt/user/client/ui/MouseListenerCollection.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -18,6 +18,7 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
 
 import java.util.Iterator;
 import java.util.Vector;
@@ -62,10 +63,15 @@
    * @param event the {@link Event} received by the widget
    */
   public void fireMouseEvent(Widget sender, Event event) {
+    final Element senderElem = sender.getElement();
     int x = DOM.eventGetClientX(event)
-        - DOM.getAbsoluteLeft(sender.getElement());
+        - DOM.getAbsoluteLeft(sender.getElement())
+        + DOM.getElementPropertyInt(senderElem, "scrollLeft")
+        + Window.getScrollLeft();
     int y = DOM.eventGetClientY(event)
-        - DOM.getAbsoluteTop(sender.getElement());
+        - DOM.getAbsoluteTop(sender.getElement())
+        + DOM.getElementPropertyInt(senderElem, "scrollTop")
+        + Window.getScrollTop();
 
     switch (DOM.eventGetType(event)) {
       case Event.ONMOUSEDOWN:
diff --git a/user/src/com/google/gwt/user/client/ui/MouseWheelListenerCollection.java b/user/src/com/google/gwt/user/client/ui/MouseWheelListenerCollection.java
index 1cb2906..0973196 100644
--- a/user/src/com/google/gwt/user/client/ui/MouseWheelListenerCollection.java
+++ b/user/src/com/google/gwt/user/client/ui/MouseWheelListenerCollection.java
@@ -16,7 +16,9 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
 
 import java.util.Iterator;
 import java.util.Vector;
@@ -53,10 +55,15 @@
    */
   public void fireMouseWheelEvent(Widget sender, Event event) {
     if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
+      final Element senderElem = sender.getElement();
       int x = DOM.eventGetClientX(event)
-          - DOM.getAbsoluteLeft(sender.getElement());
+          - DOM.getAbsoluteLeft(sender.getElement())
+          + DOM.getElementPropertyInt(senderElem, "scrollLeft")
+          + Window.getScrollLeft();          
       int y = DOM.eventGetClientY(event)
-          - DOM.getAbsoluteTop(sender.getElement());
+          - DOM.getAbsoluteTop(sender.getElement())
+          + DOM.getElementPropertyInt(senderElem, "scrollTop")
+          + Window.getScrollTop();          
       MouseWheelVelocity velocity = new MouseWheelVelocity(event);
 
       fireMouseWheel(sender, x, y, velocity);