Adds WindowScrollListener support to the Window.  This patch also moves the initHandlers method out of the *Template.js files and into Window, reducing the size of the nocache files by 1.5k.  The Window class now injects the initWindowHandlers method onto the outer window as needed, then removes it immediately.

Patch by: jlabanca, ecc
Review by: jgw
Issue: 639



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3552 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
index 6fc0870..07d00a9 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/HostedModeTemplate.js
@@ -86,8 +86,6 @@
     if (scriptsDone && loadDone) {
       var iframe = $doc.getElementById('__MODULE_NAME__');
       var frameWnd = iframe.contentWindow;
-      // copy the init handlers function into the iframe
-      frameWnd.__gwt_initHandlers = __MODULE_FUNC__.__gwt_initHandlers;
       // inject hosted mode property evaluation function
       if (isHostedMode()) {
         frameWnd.__gwt_getProperty = function(name) {
@@ -460,60 +458,4 @@
   $doc.write('<script defer="defer">__MODULE_FUNC__.onInjectionDone(\'__MODULE_NAME__\')</script>');
 }
 
-// Called from compiled code to hook the window's resize & load events (the
-// code running in the script frame is not allowed to hook these directly).
-//
-// Notes:
-// 1) We declare it here in the global scope so that it won't closure the
-// internals of the module func.
-//
-// 2) We hang it off the module func to avoid polluting the global namespace.
-//
-// 3) This function will be copied directly into the script frame window!
-//
-__MODULE_FUNC__.__gwt_initHandlers = function(resize, beforeunload, unload) {
-  var $wnd = window
-  , oldOnResize = $wnd.onresize
-  , oldOnBeforeUnload = $wnd.onbeforeunload
-  , oldOnUnload = $wnd.onunload
-  ;
-
-  $wnd.onresize = function(evt) {
-    try {
-      resize();
-    } finally {
-      oldOnResize && oldOnResize(evt);
-    }
-  };
-
-  $wnd.onbeforeunload = function(evt) {
-    var ret, oldRet;
-    try {
-      ret = beforeunload();
-    } finally {
-      oldRet = oldOnBeforeUnload && oldOnBeforeUnload(evt);
-    }
-    // Avoid returning null as IE6 will coerce it into a string.
-    // Ensure that "" gets returned properly.
-    if (ret != null) {
-      return ret;
-    }
-    if (oldRet != null) {
-      return oldRet;
-    }
-    // returns undefined.
-  };
-
-  $wnd.onunload = function(evt) {
-    try {
-      unload();
-    } finally {
-      oldOnUnload && oldOnUnload(evt);
-      $wnd.onresize = null;
-      $wnd.onbeforeunload = null;
-      $wnd.onunload = null;
-    }
-  };
-};
-
 __MODULE_FUNC__();
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
index 0f1bdd4..1f35a26 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
@@ -82,8 +82,6 @@
     if (scriptsDone && loadDone) {
       var iframe = $doc.getElementById('__MODULE_NAME__');
       var frameWnd = iframe.contentWindow;
-      // copy the init handlers function into the iframe
-      frameWnd.__gwt_initHandlers = __MODULE_FUNC__.__gwt_initHandlers;
       // inject hosted mode property evaluation function
       if (isHostedMode()) {
         frameWnd.__gwt_getProperty = function(name) {
@@ -426,60 +424,4 @@
   $doc.write('<script defer="defer">__MODULE_FUNC__.onInjectionDone(\'__MODULE_NAME__\')</script>');
 }
 
-// Called from compiled code to hook the window's resize & load events (the
-// code running in the script frame is not allowed to hook these directly).
-//
-// Notes:
-// 1) We declare it here in the global scope so that it won't closure the
-// internals of the module func.
-//
-// 2) We hang it off the module func to avoid polluting the global namespace.
-//
-// 3) This function will be copied directly into the script frame window!
-//
-__MODULE_FUNC__.__gwt_initHandlers = function(resize, beforeunload, unload) {
-  var $wnd = window
-  , oldOnResize = $wnd.onresize
-  , oldOnBeforeUnload = $wnd.onbeforeunload
-  , oldOnUnload = $wnd.onunload
-  ;
-
-  $wnd.onresize = function(evt) {
-    try {
-      resize();
-    } finally {
-      oldOnResize && oldOnResize(evt);
-    }
-  };
-
-  $wnd.onbeforeunload = function(evt) {
-    var ret, oldRet;
-    try {
-      ret = beforeunload();
-    } finally {
-      oldRet = oldOnBeforeUnload && oldOnBeforeUnload(evt);
-    }
-    // Avoid returning null as IE6 will coerce it into a string.
-    // Ensure that "" gets returned properly.
-    if (ret != null) {
-      return ret;
-    }
-    if (oldRet != null) {
-      return oldRet;
-    }
-    // returns undefined.
-  };
-
-  $wnd.onunload = function(evt) {
-    try {
-      unload();
-    } finally {
-      oldOnUnload && oldOnUnload(evt);
-      $wnd.onresize = null;
-      $wnd.onbeforeunload = null;
-      $wnd.onunload = null;
-    }
-  };
-};
-
 __MODULE_FUNC__();
diff --git a/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js b/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
index ca584b9..d7c6c3d 100644
--- a/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
@@ -258,60 +258,4 @@
 // __MODULE_SCRIPTS_END__
 }
 
-// Called from compiled code to hook the window's resize & load events (the
-// code running in the script frame is not allowed to hook these directly).
-// 
-// Notes:
-// 1) We declare it here in the global scope so that it won't closure the
-// internals of the module func.
-//
-// 2) We hang it off the module func to avoid polluting the global namespace.
-//
-// 3) This function will be copied directly into the script namespace.
-//
-__MODULE_FUNC__.__gwt_initHandlers = function(resize, beforeunload, unload) {
-  var $wnd = window
-  , oldOnResize = $wnd.onresize
-  , oldOnBeforeUnload = $wnd.onbeforeunload
-  , oldOnUnload = $wnd.onunload
-  ;
-
-  $wnd.onresize = function(evt) {
-   try {
-     resize();
-   } finally {
-     oldOnResize && oldOnResize(evt);
-   }
-  };
-  
-  $wnd.onbeforeunload = function(evt) {
-    var ret, oldRet;
-    try {
-      ret = beforeunload();
-    } finally {
-      oldRet = oldOnBeforeUnload && oldOnBeforeUnload(evt);
-    }
-    // Avoid returning null as IE6 will coerce it into a string.
-    // Ensure that "" gets returned properly.
-    if (ret != null) {
-	  return ret;
-	}
-	if (oldRet != null) {
-	  return oldRet;
-	}
-   // returns undefined.
-  };
-  
-  $wnd.onunload = function(evt) {
-    try {
-      unload();
-    } finally {
-      oldOnUnload && oldOnUnload(evt);
-      $wnd.onresize = null;
-      $wnd.onbeforeunload = null;
-      $wnd.onunload = null;
-    }
-  };
-};
-
 __MODULE_FUNC__();
diff --git a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
index 6c7ea70..a88be2a 100644
--- a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
@@ -382,60 +382,4 @@
     + '\n--></script>');
 }
 
-// Called from compiled code to hook the window's resize & load events (the
-// code running in the script frame is not allowed to hook these directly).
-//
-// Notes:
-// 1) We declare it here in the global scope so that it won't closure the
-// internals of the module func.
-//
-// 2) We hang it off the module func to avoid polluting the global namespace.
-//
-// 3) This function will be copied directly into the script namespace.
-//
-__MODULE_FUNC__.__gwt_initHandlers = function(resize, beforeunload, unload) {
-  var $wnd = window
-  , oldOnResize = $wnd.onresize
-  , oldOnBeforeUnload = $wnd.onbeforeunload
-  , oldOnUnload = $wnd.onunload
-  ;
-
-  $wnd.onresize = function(evt) {
-    try {
-      resize();
-    } finally {
-      oldOnResize && oldOnResize(evt);
-    }
-  };
-
-  $wnd.onbeforeunload = function(evt) {
-    var ret, oldRet;
-    try {
-      ret = beforeunload();
-    } finally {
-      oldRet = oldOnBeforeUnload && oldOnBeforeUnload(evt);
-    }
-    // Avoid returning null as IE6 will coerce it into a string.
-    // Ensure that "" gets returned properly.
-    if (ret != null) {
-      return ret;
-    }
-    if (oldRet != null) {
-      return oldRet;
-    }
-    // returns undefined.
-  };
-
-  $wnd.onunload = function(evt) {
-    try {
-      unload();
-    } finally {
-      oldOnUnload && oldOnUnload(evt);
-      $wnd.onresize = null;
-      $wnd.onbeforeunload = null;
-      $wnd.onunload = null;
-    }
-  };
-};
-
 __MODULE_FUNC__();
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
index f67db1c..e9e1aeb 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/TestFireEvents.java
@@ -18,6 +18,9 @@
 import com.google.gwt.museum.client.common.AbstractIssue;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.WindowResizeListener;
+import com.google.gwt.user.client.WindowScrollListener;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTML;
@@ -53,6 +56,7 @@
     layout.setHTML(0, 0, "<b>Action to Perform</b>");
     layout.setHTML(0, 1, "<b>Event</b>");
     layout.setHTML(0, 2, "<b>Status</b>");
+    FlexCellFormatter formatter = layout.getFlexCellFormatter();
 
     // Mouse and click events
     Button button = new Button("Double-click me") {
@@ -195,6 +199,32 @@
     addDependentTest(Event.ONCONTEXTMENU, "contextMenu");
     loadable.setUrl("imageDoesNotExist.abc");
 
+    // Window Scroll Event
+    {
+      final int row = layout.getRowCount();
+      layout.setText(row, 0, "Window level events");
+      layout.setText(row, 1, "window scroll");
+      layout.setText(row, 2, "?");
+      Window.addWindowScrollListener(new WindowScrollListener() {
+        public void onScroll(int scrollLeft, int scrollTop) {
+          layout.setText(row, 2, "pass");
+        }
+      });
+    }
+
+    // Window Resize Event
+    {
+      final int row = layout.getRowCount();
+      formatter.setRowSpan(row - 1, 0, 2);
+      layout.setText(row, 0, "window resize");
+      layout.setText(row, 1, "?");
+      Window.addWindowResizeListener(new WindowResizeListener() {
+        public void onWindowResized(int width, int height) {
+          layout.setText(row, 1, "pass");
+        }
+      });
+    }
+
     // The following are not testable or not supported in all browsers
     // onlosecapture
 
diff --git a/user/src/com/google/gwt/user/client/Window.java b/user/src/com/google/gwt/user/client/Window.java
index 56b4ae9..ee1575c 100644
--- a/user/src/com/google/gwt/user/client/Window.java
+++ b/user/src/com/google/gwt/user/client/Window.java
@@ -17,8 +17,11 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.ScriptElement;
 import com.google.gwt.http.client.URL;
 import com.google.gwt.user.client.impl.WindowImpl;
+import com.google.gwt.user.client.ui.RootPanel;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -57,7 +60,6 @@
      * 
      * @return the string to the right of the URL's hash.
      */
-
     public static String getHash() {
       return impl.getHash();
     }
@@ -231,6 +233,7 @@
 
   private static ArrayList<WindowCloseListener> closingListeners;
   private static ArrayList<WindowResizeListener> resizeListeners;
+  private static ArrayList<WindowScrollListener> scrollListeners;
 
   /**
    * Adds a listener to receive window closing events.
@@ -259,6 +262,19 @@
   }
 
   /**
+   * Adds a listener to receive window scroll events.
+   * 
+   * @param listener the listener to be informed when the window is scrolled
+   */
+  public static void addWindowScrollListener(WindowScrollListener listener) {
+    maybeInitializeHandlers();
+    if (scrollListeners == null) {
+      scrollListeners = new ArrayList<WindowScrollListener>();
+    }
+    scrollListeners.add(listener);
+  }
+
+  /**
    * Displays a message in a modal dialog box.
    * 
    * @param msg the message to be displayed.
@@ -394,6 +410,27 @@
   }
 
   /**
+   * Removes a window scroll listener.
+   * 
+   * @param listener the listener to be removed
+   */
+  public static void removeWindowScrollListener(WindowScrollListener listener) {
+    if (scrollListeners != null) {
+      scrollListeners.remove(listener);
+    }
+  }
+
+  /**
+   * Scroll the window to the specified position.
+   * 
+   * @param left the left scroll position
+   * @param top the top scroll position
+   */
+  public static native void scrollTo(int left, int top) /*-{
+    $wnd.scrollTo(left, top);
+  }-*/;
+
+  /**
    * Sets the size of the margins used within the window's client area. It is
    * sometimes necessary to do this because some browsers, such as Internet
    * Explorer, add margins by default, which can confound attempts to resize
@@ -451,6 +488,15 @@
     }
   }
 
+  static void onScroll() {
+    UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
+    if (handler != null) {
+      fireScrollAndCatch(handler);
+    } else {
+      fireScrollImpl();
+    }
+  }
+
   private static void fireClosedAndCatch(UncaughtExceptionHandler handler) {
     try {
       fireClosedImpl();
@@ -508,13 +554,104 @@
     }
   }
 
+  private static void fireScrollAndCatch(UncaughtExceptionHandler handler) {
+    try {
+      fireScrollImpl();
+    } catch (Throwable e) {
+      handler.onUncaughtException(e);
+    }
+  }
+
+  private static void fireScrollImpl() {
+    if (scrollListeners != null) {
+      for (WindowScrollListener listener : scrollListeners) {
+        listener.onScroll(getScrollLeft(), getScrollTop());
+      }
+    }
+  }
+
+  /**
+   * This method defines a function that will in turn define the
+   * __gwt_initWindowHandlers method that will be used to initialize the event
+   * handlers used by the {@link Window}. However, this method returns the
+   * function as a String so the __gwt_initWindowHandlers method can be added to
+   * the outer window.
+   * 
+   * We need to declare __gwt_initWindowHandlers on the outer window because you
+   * cannot attach Window listeners from within an iframe on IE6.
+   * 
+   * Per ECMAScript 262 spec 15.3.4.2, Function.prototype.toString() returns a
+   * string representation of the function that has the syntax of the function.
+   */
+  private static native String getInitHandlerMethodString() /*-{
+    return function __gwt_initWindowHandlers(resize, scroll, beforeunload, unload) {
+      var wnd = window
+      , oldOnResize = wnd.onresize
+      , oldOnBeforeUnload = wnd.onbeforeunload
+      , oldOnUnload = wnd.onunload
+      , oldOnScroll = wnd.onscroll
+      ;
+
+      wnd.onresize = function(evt) {
+        try {
+          resize();
+        } finally {
+          oldOnResize && oldOnResize(evt);
+        }
+      };
+
+      wnd.onscroll = function(evt) {
+        try {
+          scroll();
+        } finally {
+          oldOnScroll && oldOnScroll(evt);
+        }
+      };
+
+      wnd.onbeforeunload = function(evt) {
+        var ret, oldRet;
+        try {
+          ret = beforeunload();
+        } finally {
+          oldRet = oldOnBeforeUnload && oldOnBeforeUnload(evt);
+        }
+        // Avoid returning null as IE6 will coerce it into a string.
+        // Ensure that "" gets returned properly.
+        if (ret != null) {
+          return ret;
+        }
+        if (oldRet != null) {
+          return oldRet;
+        }
+        // returns undefined.
+      };
+    
+      wnd.onunload = function(evt) {
+        try {
+          unload();
+        } finally {
+          oldOnUnload && oldOnUnload(evt);
+          wnd.onresize = null;
+          wnd.onscroll = null;
+          wnd.onbeforeunload = null;
+          wnd.onunload = null;
+        }
+      };
+      
+      // Remove the reference once we've initialize the handlers
+      wnd.__gwt_initWindowHandlers = undefined;
+    }.toString();
+  }-*/;
+
   private static native void init() /*-{
-    // Magic function defined by the selection script.
-    __gwt_initHandlers(
+    $wnd.__gwt_initWindowHandlers(
       function() {
         @com.google.gwt.user.client.Window::onResize()();
       },
       function() {
+        @com.google.gwt.user.client.Window::onScroll()();
+      },
+      function() {
         return @com.google.gwt.user.client.Window::onClosing()();
       },
       function() {
@@ -525,7 +662,16 @@
 
   private static void maybeInitializeHandlers() {
     if (GWT.isClient() && !handlersAreInitialized) {
+      // Embed the init script on the page
+      ScriptElement scriptElem = Document.get().createScriptElement();
+      scriptElem.setText(getInitHandlerMethodString());
+      Document.get().getBody().appendChild(scriptElem);
+      
+      // Initialize the handlers
       init();
+
+      // Remove the init script from the page
+      RootPanel.getBodyElement().removeChild(scriptElem);
       handlersAreInitialized = true;
     }
   }
diff --git a/user/src/com/google/gwt/user/client/WindowScrollListener.java b/user/src/com/google/gwt/user/client/WindowScrollListener.java
new file mode 100644
index 0000000..530d48f
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/WindowScrollListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+/**
+ * Implement this interface to receive scroll events from the browser window.
+ * 
+ * @see com.google.gwt.user.client.Window#addWindowScrollListener(WindowScrollListener)
+ */
+public interface WindowScrollListener extends java.util.EventListener {
+
+  /**
+   * Called when the browser window is scrolled.
+   * 
+   * @param scrollLeft the left scroll position
+   * @param scrollTop the top scroll position
+   */
+  void onScroll(int scrollLeft, int scrollTop);
+}
diff --git a/user/test/com/google/gwt/user/client/WindowTest.java b/user/test/com/google/gwt/user/client/WindowTest.java
index e8fa3da..81b86da 100644
--- a/user/test/com/google/gwt/user/client/WindowTest.java
+++ b/user/test/com/google/gwt/user/client/WindowTest.java
@@ -140,9 +140,34 @@
         int newClientWidth = Window.getClientWidth();
         assertTrue(newClientHeight < oldClientHeight);
         assertTrue(newClientWidth < oldClientWidth);
+        RootPanel.get().remove(largeDOM);
         finishTest();
       }
     });
     delayTestFinish(200);
   }
+
+  /**
+   * Tests the ability of scroll the Window and catch scroll events.
+   */
+  public void testScrolling() {
+    // Force scroll bars to appear
+    Window.enableScrolling(true);
+    int clientHeight = Window.getClientHeight();
+    int clientWidth = Window.getClientWidth();
+    final Label largeDOM = new Label();
+    largeDOM.setPixelSize(clientWidth + 500, clientHeight + 500);
+    RootPanel.get().add(largeDOM);
+
+    // Listener for scroll events
+    Window.scrollTo(100, 200);
+    assertEquals(100, Window.getScrollLeft());
+    assertEquals(200, Window.getScrollTop());
+    Window.scrollTo(0, 0);
+    assertEquals(0, Window.getScrollLeft());
+    assertEquals(0, Window.getScrollTop());
+
+    // Cleanup the window
+    RootPanel.get().remove(largeDOM);
+  }
 }