Fixes the firing of focus by FocusImplSafari. Calls to element.focus and
element.blur are unreliable in an event handler, so the impl now makes
the calls from within a setTimeout. This also has the side-effect of
fixing mouse-initiated keyboard navigation in CustomButtons.

Patch by: bobv, scottb
Review by: knorton



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1176 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/impl/FocusImplOld.java b/user/src/com/google/gwt/user/client/ui/impl/FocusImplOld.java
index 09c952b..e0d7ab6 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/FocusImplOld.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/FocusImplOld.java
@@ -24,37 +24,13 @@
  */
 public class FocusImplOld extends FocusImpl {
 
-  static JavaScriptObject blurHandler = createBlurHandler();
-  static JavaScriptObject focusHandler = createFocusHandler();
-  static JavaScriptObject mouseHandler = createMouseHandler();
-
-  private static native JavaScriptObject createBlurHandler() /*-{
-    return function(evt) {
-      // This function is called directly as an event handler, so 'this' is
-      // set up by the browser to be the input on which the event is fired.
-      if (this.parentNode.onblur) {
-        this.parentNode.onblur(evt);
-      } 
-    };
-  }-*/;
-
-  private static native JavaScriptObject createFocusHandler() /*-{
-    return function(evt) {
-      // This function is called directly as an event handler, so 'this' is
-      // set up by the browser to be the input on which the event is fired.
-      if (this.parentNode.onfocus) {
-        this.parentNode.onfocus(evt);
-      } 
-    };
-  }-*/;
-
-  private static native JavaScriptObject createMouseHandler() /*-{
-    return function() {
-      // This function is called directly as an event handler, so 'this' is
-      // set up by the browser to be the div on which the event is fired.
-      this.firstChild.focus();
-    };
-  }-*/;
+  /*
+   * Use isolated method calls to create all of the handlers to avoid creating
+   * memory leaks via handler-closures-element.
+   */
+  JavaScriptObject blurHandler = createBlurHandler();
+  JavaScriptObject focusHandler = createFocusHandler();
+  JavaScriptObject mouseHandler = createMouseHandler();
 
   public native void blur(Element elem) /*-{
     elem.firstChild.blur();
@@ -77,17 +53,17 @@
 
     input.addEventListener(
       'blur',
-      @com.google.gwt.user.client.ui.impl.FocusImplOld::blurHandler,
+      this.@com.google.gwt.user.client.ui.impl.FocusImplOld::blurHandler,
       false);
 
     input.addEventListener(
       'focus',
-      @com.google.gwt.user.client.ui.impl.FocusImplOld::focusHandler,
+      this.@com.google.gwt.user.client.ui.impl.FocusImplOld::focusHandler,
       false);
 
     div.addEventListener(
       'mousedown',
-      @com.google.gwt.user.client.ui.impl.FocusImplOld::mouseHandler,
+      this.@com.google.gwt.user.client.ui.impl.FocusImplOld::mouseHandler,
       false);
 
     div.appendChild(input);
@@ -110,6 +86,26 @@
     elem.firstChild.tabIndex = index;
   }-*/;
 
+  protected native JavaScriptObject createBlurHandler() /*-{
+    return function(evt) {
+      // This function is called directly as an event handler, so 'this' is
+      // set up by the browser to be the input on which the event is fired.
+      if (this.parentNode.onblur) {
+        this.parentNode.onblur(evt);
+      } 
+    };
+  }-*/;
+
+  protected native JavaScriptObject createFocusHandler() /*-{
+    return function(evt) {
+      // This function is called directly as an event handler, so 'this' is
+      // set up by the browser to be the input on which the event is fired.
+      if (this.parentNode.onfocus) {
+        this.parentNode.onfocus(evt);
+      } 
+    };
+  }-*/;
+
   protected native Element createHiddenInput() /*-{
     var input = $doc.createElement('input');
     input.type = 'text';
@@ -118,4 +114,12 @@
     input.style.position = 'absolute';
     return input;
   }-*/;
+
+  protected native JavaScriptObject createMouseHandler() /*-{
+    return function() {
+      // This function is called directly as an event handler, so 'this' is
+      // set up by the browser to be the div on which the event is fired.
+      this.firstChild.focus();
+    };
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/user/client/ui/impl/FocusImplSafari.java b/user/src/com/google/gwt/user/client/ui/impl/FocusImplSafari.java
index 82fa984..7f39aad 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/FocusImplSafari.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/FocusImplSafari.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.client.ui.impl;
 
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.Element;
 
 /**
@@ -23,6 +24,23 @@
  * element that has zero width and height.
  */
 public class FocusImplSafari extends FocusImplOld {
+  
+  public native void blur(Element elem) /*-{
+    // Attempts to blur elements from within an event callback will generally
+    // be unsuccessful, so we invoke blur() from outside of the callback.
+    $wnd.setTimeout(function() {
+      elem.firstChild.blur();
+    }, 0);
+  }-*/;
+
+  public native void focus(Element elem) /*-{
+    // Attempts to focus elements from within an event callback will generally
+    // be unsuccessful, so we invoke focus() from outside of the callback.
+    $wnd.setTimeout(function() {
+      elem.firstChild.focus();
+    }, 0);
+  }-*/;
+
   protected native Element createHiddenInput() /*-{
     var input = $doc.createElement('input');
     input.type = 'text';
@@ -31,4 +49,19 @@
     input.style.position = 'absolute';
     return input;
   }-*/;
+
+  protected native JavaScriptObject createMouseHandler() /*-{
+    return function() {
+      // This function is called directly as an event handler, so 'this' is
+      // set up by the browser to be the div on which the event is fired.
+      var firstChild = this.firstChild;
+
+      // Attempts to focus elements from within an event callback will generally
+      // be unsuccessful, so we invoke focus() from outside of the callback.
+      $wnd.setTimeout(function() {
+        firstChild.focus();
+      }, 0);
+    }
+  }-*/;
+
 }