Added WindowFocusListener to catch Window level blur and focus events.  Also refactored the Window event system to sink events lazily instead of all at once, reducing compiled code size for most apps that only sink one Window level event.  Renamed WindowScrollListener.onScroll() to WindowScrollListener.onWindowScrolled() to be consistent with other Window events.

Patch by: jlabanca
Review by: ajr (desk)
Issue: 68, 2804



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3565 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 e9e1aeb..e825feb 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
@@ -16,9 +16,10 @@
 package com.google.gwt.museum.client.defaultmuseum;
 
 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.WindowCloseListener;
+import com.google.gwt.user.client.WindowFocusListener;
 import com.google.gwt.user.client.WindowResizeListener;
 import com.google.gwt.user.client.WindowScrollListener;
 import com.google.gwt.user.client.ui.Button;
@@ -27,6 +28,7 @@
 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
 import com.google.gwt.user.client.ui.HasVerticalAlignment;
 import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.Widget;
@@ -39,6 +41,12 @@
  * Verify that events fire in all browsers.
  */
 public class TestFireEvents extends AbstractIssue {
+  private static final int WINDOW_EVENT_SCROLL = -1;
+  private static final int WINDOW_EVENT_RESIZE = -2;
+  private static final int WINDOW_EVENT_FOCUS = -3;
+  private static final int WINDOW_EVENT_BLUR = -4;
+  private static final int WINDOW_EVENT_CLOSING = -5;
+
   /**
    * The main grid used for layout.
    */
@@ -56,7 +64,6 @@
     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") {
@@ -200,30 +207,46 @@
     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");
-        }
-      });
-    }
+    Label windowLabel = new Label("Window level events");
+    addTest(WINDOW_EVENT_SCROLL, "window.onscroll", windowLabel);
+    Window.addWindowScrollListener(new WindowScrollListener() {
+      public void onWindowScrolled(int scrollLeft, int scrollTop) {
+        passTest(WINDOW_EVENT_SCROLL);
+      }
+    });
 
     // 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");
-        }
-      });
-    }
+    addDependentTest(WINDOW_EVENT_RESIZE, "window.onresize");
+    Window.addWindowResizeListener(new WindowResizeListener() {
+      public void onWindowResized(int width, int height) {
+        passTest(WINDOW_EVENT_RESIZE);
+      }
+    });
+
+    // Window Focus/Blur Events
+    addDependentTest(WINDOW_EVENT_FOCUS, "window.onfocus");
+    addDependentTest(WINDOW_EVENT_BLUR, "window.onblur");
+    Window.addWindowFocusListener(new WindowFocusListener() {
+      public void onWindowFocused() {
+        passTest(WINDOW_EVENT_FOCUS);
+      }
+
+      public void onWindowLostFocus() {
+        passTest(WINDOW_EVENT_BLUR);
+      }
+    });
+
+    // Window Closing Event
+    addDependentTest(WINDOW_EVENT_CLOSING, "window.onbeforeunload");
+    Window.addWindowCloseListener(new WindowCloseListener() {
+      public void onWindowClosed() {
+      }
+
+      public String onWindowClosing() {
+        passTest(WINDOW_EVENT_CLOSING);
+        return "";
+      }
+    });
 
     // The following are not testable or not supported in all browsers
     // onlosecapture
@@ -297,7 +320,15 @@
    * @param event the event that was triggered
    */
   private void passTest(Event event) {
-    int eventType = DOM.eventGetType(event);
+    passTest(event.getTypeInt());
+  }
+
+  /**
+   * Mark the event as passed.
+   * 
+   * @param eventType the event type that was triggered
+   */
+  private void passTest(int eventType) {
     int rowIndex = eventMap.get(new Integer(eventType));
     if (layout.getCellCount(rowIndex) == 3) {
       layout.setHTML(rowIndex, 2, "pass");
diff --git a/user/src/com/google/gwt/user/client/Window.java b/user/src/com/google/gwt/user/client/Window.java
index 367e39a..7607e0e 100644
--- a/user/src/com/google/gwt/user/client/Window.java
+++ b/user/src/com/google/gwt/user/client/Window.java
@@ -232,6 +232,7 @@
   private static final WindowImpl impl = GWT.create(WindowImpl.class);
 
   private static ArrayList<WindowCloseListener> closingListeners;
+  private static ArrayList<WindowFocusListener> focusListeners;
   private static ArrayList<WindowResizeListener> resizeListeners;
   private static ArrayList<WindowScrollListener> scrollListeners;
 
@@ -241,7 +242,7 @@
    * @param listener the listener to be informed when the window is closing
    */
   public static void addWindowCloseListener(WindowCloseListener listener) {
-    maybeInitializeHandlers();
+    maybeInitializeCloseHandlers();
     if (closingListeners == null) {
       closingListeners = new ArrayList<WindowCloseListener>();
     }
@@ -249,14 +250,37 @@
   }
 
   /**
+   * Adds a listener to receive window focus events.
+   * 
+   * @param listener the listener to be informed when the window is focused
+   */
+  public static void addWindowFocusListener(WindowFocusListener listener) {
+    if (focusListeners == null) {
+      focusListeners = new ArrayList<WindowFocusListener>();
+      initHandler(getWindowFocusHandlerMethodString(),
+          "__gwt_initWindowFocusHandler", new Command() {
+            public void execute() {
+              initWindowFocusHandler();
+            }
+          });
+    }
+    focusListeners.add(listener);
+  }
+
+  /**
    * Adds a listener to receive window resize events.
    * 
    * @param listener the listener to be informed when the window is resized
    */
   public static void addWindowResizeListener(WindowResizeListener listener) {
-    maybeInitializeHandlers();
     if (resizeListeners == null) {
       resizeListeners = new ArrayList<WindowResizeListener>();
+      initHandler(getWindowResizeHandlerMethodString(),
+          "__gwt_initWindowResizeHandler", new Command() {
+            public void execute() {
+              initWindowResizeHandler();
+            }
+          });
     }
     resizeListeners.add(listener);
   }
@@ -267,9 +291,14 @@
    * @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>();
+      initHandler(getWindowScrollHandlerMethodString(),
+          "__gwt_initWindowScrollHandler", new Command() {
+            public void execute() {
+              initWindowScrollHandler();
+            }
+          });
     }
     scrollListeners.add(listener);
   }
@@ -399,6 +428,17 @@
   }
 
   /**
+   * Removes a window focus listener.
+   * 
+   * @param listener the listener to be removed
+   */
+  public static void removeWindowFocusListener(WindowFocusListener listener) {
+    if (focusListeners != null) {
+      focusListeners.remove(listener);
+    }
+  }
+
+  /**
    * Removes a window resize listener.
    * 
    * @param listener the listener to be removed
@@ -461,6 +501,15 @@
     $doc.title = title;
   }-*/;
 
+  static void onBlur() {
+    UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
+    if (handler != null) {
+      fireBlurAndCatch(handler);
+    } else {
+      fireBlurImpl();
+    }
+  }
+
   static void onClosed() {
     UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
     if (handler != null) {
@@ -479,6 +528,15 @@
     }
   }
 
+  static void onFocus() {
+    UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
+    if (handler != null) {
+      fireFocusAndCatch(handler);
+    } else {
+      fireFocusImpl();
+    }
+  }
+
   static void onResize() {
     UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
     if (handler != null) {
@@ -497,6 +555,22 @@
     }
   }
 
+  private static void fireBlurAndCatch(UncaughtExceptionHandler handler) {
+    try {
+      fireBlurImpl();
+    } catch (Throwable e) {
+      handler.onUncaughtException(e);
+    }
+  }
+
+  private static void fireBlurImpl() {
+    if (focusListeners != null) {
+      for (WindowFocusListener listener : focusListeners) {
+        listener.onWindowLostFocus();
+      }
+    }
+  }
+
   private static void fireClosedAndCatch(UncaughtExceptionHandler handler) {
     try {
       fireClosedImpl();
@@ -538,6 +612,22 @@
     return ret;
   }
 
+  private static void fireFocusAndCatch(UncaughtExceptionHandler handler) {
+    try {
+      fireFocusImpl();
+    } catch (Throwable e) {
+      handler.onUncaughtException(e);
+    }
+  }
+
+  private static void fireFocusImpl() {
+    if (focusListeners != null) {
+      for (WindowFocusListener listener : focusListeners) {
+        listener.onWindowFocused();
+      }
+    }
+  }
+
   private static void fireResizedAndCatch(UncaughtExceptionHandler handler) {
     try {
       fireResizedImpl();
@@ -565,49 +655,28 @@
   private static void fireScrollImpl() {
     if (scrollListeners != null) {
       for (WindowScrollListener listener : scrollListeners) {
-        listener.onScroll(getScrollLeft(), getScrollTop());
+        listener.onWindowScrolled(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.
+   * This method defines a function that sinks an event on the Window.  However,
+   * this method returns the function as a String so it 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.
+   * We need to declare this method 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(resize, scroll, beforeunload, unload) {
+  private static native String getWindowCloseHandlerMethodString() /*-{
+    return function(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);
-        }
-      };
-
+      , oldOnUnload = wnd.onunload;
+      
       wnd.onbeforeunload = function(evt) {
         var ret, oldRet;
         try {
@@ -625,7 +694,7 @@
         }
         // returns undefined.
       };
-    
+      
       wnd.onunload = function(evt) {
         try {
           unload();
@@ -633,24 +702,115 @@
           oldOnUnload && oldOnUnload(evt);
           wnd.onresize = null;
           wnd.onscroll = null;
+          wnd.onfocus = null;
+          wnd.onblur = null;
           wnd.onbeforeunload = null;
           wnd.onunload = null;
         }
       };
       
-      // Remove the reference once we've initialize the handlers
-      wnd.__gwt_initWindowHandlers = undefined;
+      // Remove the reference once we've initialize the handler
+      wnd.__gwt_initWindowCloseHandler = undefined;
     }.toString();
   }-*/;
 
-  private static native void init() /*-{
-    $wnd.__gwt_initWindowHandlers(
-      function() {
-        @com.google.gwt.user.client.Window::onResize()();
-      },
-      function() {
-        @com.google.gwt.user.client.Window::onScroll()();
-      },
+  /**
+   * @see #getWindowCloseHandlerMethodString()
+   */
+  private static native String getWindowFocusHandlerMethodString() /*-{
+    return function(focus, blur) {
+      var wnd = window
+      , oldOnFocus = wnd.onfocus
+      , oldOnBlur = wnd.onblur;
+      
+      wnd.onfocus = function(evt) {
+        try {
+          focus();
+        } finally {
+          oldOnFocus && oldOnFocus(evt);
+        }
+      };
+      
+      wnd.onblur = function(evt) {
+        try {
+          blur();
+        } finally {
+          oldOnBlur && oldOnBlur(evt);
+        }
+      };
+      
+      // Remove the reference once we've initialize the handler
+      wnd.__gwt_initWindowFocusHandler = undefined;
+    }.toString();
+  }-*/;
+  
+  /**
+   * @see #getWindowCloseHandlerMethodString()
+   */
+  private static native String getWindowResizeHandlerMethodString() /*-{
+    return function(resize) {
+      var wnd = window, oldOnResize = wnd.onresize;
+      
+      wnd.onresize = function(evt) {
+        try {
+          resize();
+        } finally {
+          oldOnResize && oldOnResize(evt);
+        }
+      };
+      
+      // Remove the reference once we've initialize the handler
+      wnd.__gwt_initWindowResizeHandler = undefined;
+    }.toString();
+  }-*/;
+
+  /**
+   * @see #getWindowCloseHandlerMethodString()
+   */
+  private static native String getWindowScrollHandlerMethodString() /*-{
+    return function(scroll) {
+      var wnd = window, oldOnScroll = wnd.onscroll;
+      
+      wnd.onscroll = function(evt) {
+        try {
+          scroll();
+        } finally {
+          oldOnScroll && oldOnScroll(evt);
+        }
+      };
+      
+      // Remove the reference once we've initialize the handler
+      wnd.__gwt_initWindowScrollHandler = undefined;
+    }.toString();
+  }-*/;
+
+  /**
+   * Embed a script on the outer window and use it to initialize an event.
+   * 
+   * @param initFunc the string representation of the init function
+   * @param funcName the name to assign to the init function
+   * @param cmd the command to execute the init function
+   */
+  private static void initHandler(String initFunc, String funcName, Command cmd) {
+    if (GWT.isClient()) {
+      // Always intialize the close handlers first
+      maybeInitializeCloseHandlers();
+
+      // Embed the init script on the page
+      initFunc = initFunc.replaceFirst("function", "function " + funcName);
+      ScriptElement scriptElem = Document.get().createScriptElement(initFunc);
+      Document.get().getBody().appendChild(scriptElem);
+
+      // Initialize the handler
+      cmd.execute();
+
+      // Remove the init script from the page
+      RootPanel.getBodyElement().removeChild(scriptElem);
+    }
+  }
+
+  private static native void initWindowCloseHandler() /*-{
+    $wnd.__gwt_initWindowCloseHandler(
       function() {
         return @com.google.gwt.user.client.Window::onClosing()();
       },
@@ -660,21 +820,42 @@
     );
   }-*/;
 
-  private static void maybeInitializeHandlers() {
-    if (GWT.isClient() && !handlersAreInitialized) {
-      handlersAreInitialized = true;
-      
-      // Embed the init script on the page
-      String initFunc = getInitHandlerMethodString().replaceFirst("function",
-          "function __gwt_initWindowHandlers");
-      ScriptElement scriptElem = Document.get().createScriptElement(initFunc);
-      Document.get().getBody().appendChild(scriptElem);
-      
-      // Initialize the handlers
-      init();
+  private static native void initWindowFocusHandler() /*-{
+    $wnd.__gwt_initWindowFocusHandler(
+      function() {
+        @com.google.gwt.user.client.Window::onFocus()();
+      },
+      function() {
+        @com.google.gwt.user.client.Window::onBlur()();
+      }
+    );
+  }-*/;
 
-      // Remove the init script from the page
-      RootPanel.getBodyElement().removeChild(scriptElem);
+  private static native void initWindowResizeHandler() /*-{
+    $wnd.__gwt_initWindowResizeHandler(
+      function() {
+        @com.google.gwt.user.client.Window::onResize()();
+      }
+    );
+  }-*/;
+
+  private static native void initWindowScrollHandler() /*-{
+    $wnd.__gwt_initWindowScrollHandler(
+      function() {
+        @com.google.gwt.user.client.Window::onScroll()();
+      }
+    );
+  }-*/;
+
+  private static void maybeInitializeCloseHandlers() {
+    if (GWT.isClient() && !handlersAreInitialized) {
+      handlersAreInitialized = true;      
+      initHandler(getWindowCloseHandlerMethodString(),
+          "__gwt_initWindowCloseHandler", new Command() {
+            public void execute() {
+              initWindowCloseHandler();
+            }
+          });
     }
   }
 
diff --git a/user/src/com/google/gwt/user/client/WindowFocusListener.java b/user/src/com/google/gwt/user/client/WindowFocusListener.java
new file mode 100644
index 0000000..f1d6cc3
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/WindowFocusListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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 focus events from the browser window.
+ * 
+ * @see com.google.gwt.user.client.Window#addWindowFocusListener(WindowFocusListener)
+ */
+public interface WindowFocusListener extends java.util.EventListener {
+
+  /**
+   * Fired when the browser window receives focus.
+   */
+  void onWindowFocused();
+
+  /**
+   * Fired when the browser window loses focus.
+   */
+  void onWindowLostFocus();
+}
diff --git a/user/src/com/google/gwt/user/client/WindowScrollListener.java b/user/src/com/google/gwt/user/client/WindowScrollListener.java
index 530d48f..ce4ded4 100644
--- a/user/src/com/google/gwt/user/client/WindowScrollListener.java
+++ b/user/src/com/google/gwt/user/client/WindowScrollListener.java
@@ -28,5 +28,5 @@
    * @param scrollLeft the left scroll position
    * @param scrollTop the top scroll position
    */
-  void onScroll(int scrollLeft, int scrollTop);
+  void onWindowScrolled(int scrollLeft, int scrollTop);
 }