Revision: 8208
Author: jlaba...@google.com
Date: Tue May 25 07:34:51 2010
Log: Reapplying r5742 and r5747 to fix RichTextArea issues 3133, 3176, 3503. The original version of this patch was reverted because it failed in FF. There is a small delay between the time an iframe is attached to the DOM and when it is actually available for focus. Programatically calling setFocus() immediately after attach will fail to focus on the iframe. This patch defers focus until design mode has completely initialized.

Review at http://gwt-code-reviews.appspot.com/555801

Review by: j...@google.com
http://code.google.com/p/google-web-toolkit/source/detail?r=8208

Added:
/branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOldMozilla.java
Modified:
 /branches/2.1/user/src/com/google/gwt/user/RichText.gwt.xml
/branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
 /branches/2.1/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java

=======================================
--- /dev/null
+++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOldMozilla.java Tue May 25 07:34:51 2010
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 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.ui.impl;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+
+/**
+ * Old Mozilla-specific implementation of rich-text editing.
+ */
+public class RichTextAreaImplOldMozilla extends RichTextAreaImplMozilla {
+  /**
+ * The content window cannot be focused immediately after the content window + * has been loaded, so we need to wait for an additional deferred command.
+   */
+  @Override
+  protected void onElementInitialized() {
+    DeferredCommand.addCommand(new Command() {
+      public void execute() {
+        RichTextAreaImplOldMozilla.super.onElementInitialized();
+      }
+    });
+  }
+
+  @Override
+  protected void setFirstFocusImpl() {
+    setFocusImpl(true);
+  }
+
+  @Override
+  protected void setFocusImpl(boolean focused) {
+ // Old Mozilla does not support blur on the content window of an iframe.
+    if (focused) {
+      super.setFocusImpl(focused);
+    }
+  }
+}
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/RichText.gwt.xml Thu Jul 16 17:54:03 2009 +++ /branches/2.1/user/src/com/google/gwt/user/RichText.gwt.xml Tue May 25 07:34:51 2010
@@ -34,10 +34,15 @@
     class="com.google.gwt.user.client.ui.impl.RichTextAreaImplMozilla">
     <when-type-is
       class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
-    <any>
-      <when-property-is name="user.agent" value="gecko1_8" />
-      <when-property-is name="user.agent" value="gecko" />
-    </any>
+    <when-property-is name="user.agent" value="gecko1_8" />
+  </replace-with>
+
+  <!-- Old Mozilla-specific implementation -->
+  <replace-with
+    class="com.google.gwt.user.client.ui.impl.RichTextAreaImplOldMozilla">
+    <when-type-is
+      class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
+    <when-property-is name="user.agent" value="gecko" />
   </replace-with>

   <!-- Safari-specific implementation -->
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java Wed Feb 10 06:13:42 2010 +++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java Tue May 25 07:34:51 2010
@@ -33,7 +33,7 @@
   @Override
   public native void initElement() /*-{
     var _this = this;
- _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::initializing = true; + _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::onElementInitializing()();

     setTimeout($entry(function() {
if (_th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::initializing == false) {
@@ -97,7 +97,7 @@
// Weird: this code has the context of the script frame, but we need the
           // event from the edit iframe's window.
           var evt = elem.contentWindow.event;
- elem.__listen...@com.google.gwt.user.client.eventlistener::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt); + @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, elem.__listener);
         }
       }
     });
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java Fri Oct 16 14:48:33 2009 +++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java Tue May 25 07:34:51 2010
@@ -20,22 +20,26 @@
  */
 public class RichTextAreaImplMozilla extends RichTextAreaImplStandard {

+  /**
+   * Indicates that the RichTextArea has never received focus after
+   * initialization.
+   */
+  boolean isFirstFocus;
+
   @Override
   public native void initElement() /*-{
// Mozilla doesn't allow designMode to be set reliably until the iframe is
     // fully loaded.
     var _this = this;
var iframe = _th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem; - _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::initializing = true; + _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::onElementInitializing()(); + _th...@com.google.gwt.user.client.ui.impl.richtextareaimplmozilla::isFirstFocus = true;

     iframe.onload = $entry(function() {
// Some Mozillae have the nasty habit of calling onload again when you set
       // designMode, so let's avoid doing it more than once.
       iframe.onload = null;

-      // Send notification that the iframe has finished loading.
- _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::onElementInitialized()();
-
// Don't set designMode until the RTA is targeted by an event. This is
       // necessary because editing won't work on Mozilla if the iframe is
// *hidden, but attached*. Waiting for an event gets around this issue.
@@ -47,17 +51,16 @@
         iframe.contentWindow.onmouseover = null;
         iframe.contentWindow.document.designMode = 'On';
       };
-
+
// Issue 1441: we also need to catch the onmouseover event because focus // occurs after mouse down, so the cursor will not appear until the user // clicks twice, making the RichTextArea look uneditable. Catching the // mouseover event allows us to set design mode earlier. The focus event
       // is still needed to handle tab selection.
-      iframe.contentWindow.onmouseover = function() {
-        iframe.contentWindow.onfocus = null;
-        iframe.contentWindow.onmouseover = null;
-        iframe.contentWindow.document.designMode = 'On';
-      };
+      iframe.contentWindow.onmouseover = iframe.contentWindow.onfocus;
+
+      // Send notification that the iframe has finished loading.
+ _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::onElementInitialized()();
     });
   }-*/;

@@ -67,4 +70,42 @@
     // does what we actually want.
     execCommand("HiliteColor", color);
   }
-}
+
+  /**
+   * Firefox will not display the caret the first time a RichTextArea is
+   * programmatically focused, so we need to focus, blur, and refocus the
+   * RichTextArea. This only needs to be done the first time after the
+   * RichTextArea is initialized. See issue 3503.
+   */
+  protected native void setFirstFocusImpl() /*-{
+ var elem = th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem;
+    var wnd = elem.contentWindow;
+
+ // Remove event listeners so we don't generate extra focus and blur events.
+    wnd.removeEventListener('focus', elem.__gwt_focusHandler, true);
+    wnd.removeEventListener('blur', elem.__gwt_blurHandler, true);
+    wnd.focus();
+    wnd.blur();
+    wnd.focus();
+
+    // Add the event listeners now that we have focus and a caret.
+    wnd.addEventListener('focus', elem.__gwt_focusHandler, true);
+    wnd.addEventListener('blur', elem.__gwt_blurHandler, true);
+
+ // Fire a synthetic focus event. We can't move the last call to wnd.focus()
+    // here because firefox will not fire the focus event reliably.
+    var evt = document.createEvent('HTMLEvents');
+    evt.initEvent('focus', false, false);
+    wnd.dispatchEvent(evt);
+  }-*/;
+
+  @Override
+  protected void setFocusImpl(boolean focused) {
+    if (isFirstFocus) {
+      isFirstFocus = false;
+      setFirstFocusImpl();
+    } else {
+      super.setFocusImpl(focused);
+    }
+  }
+}
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java Thu Jul 16 17:54:03 2009 +++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java Tue May 25 07:34:51 2010
@@ -28,7 +28,7 @@
   }

   @Override
-  public native void setFocus(boolean focused) /*-{
+  protected native void setFocusImpl(boolean focused) /*-{
     // Opera needs the *iframe* focused, not its window.
     if (focused) {
th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.focus();
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java Wed Feb 10 06:13:42 2010 +++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java Tue May 25 07:34:51 2010
@@ -40,7 +40,7 @@
     elem.__gwt_handler = function(evt) {
       if (elem.__listener) {
if (@com.google.gwt.user.client.impl.DOMImpl::isMyListener(Ljava/lang/Object;)(elem.__listener)) { - elem.__listen...@com.google.gwt.user.client.eventlistener::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt); + @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, elem.__listener);
         }
       }
     };
@@ -59,15 +59,15 @@
// doesn't work on the iframe element (at least not for focus/blur). Don't // dispatch through the normal handler method, as some of the querying we do
     // there interferes with focus.
-    elem.onfocus = function(evt) {
+    wnd.onfocus = function(evt) {
       if (elem.__listener) {
- elem.__listen...@com.google.gwt.user.client.ui.widget::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt); + @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, elem.__listener);
       }
     };

-    elem.onblur = function(evt) {
+    wnd.onblur = function(evt) {
       if (elem.__listener) {
- elem.__listen...@com.google.gwt.user.client.ui.widget::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt); + @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, elem.__listener);
       }
     };
   }-*/;
=======================================
--- /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java Wed Feb 10 06:13:42 2010 +++ /branches/2.1/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java Tue May 25 07:34:51 2010
@@ -52,6 +52,11 @@
    */
   protected boolean initializing;

+  /**
+ * Indicates that the text area should be focused as soon as it is loaded.
+   */
+  private boolean isPendingFocus;
+
   /**
    * True when the element has been attached.
    */
@@ -90,7 +95,7 @@
     // the iframe becomes attached to the DOM. Any non-zero timeout will do
     // just fine.
     var _this = this;
- _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::initializing = true; + _th...@com.google.gwt.user.client.ui.impl.richtextareaimplstandard::onElementInitializing()();
     setTimeout($entry(function() {
       // Turn on design mode.
_th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.document.designMode = 'On';
@@ -188,13 +193,15 @@
   }

   @Override
-  public native void setFocus(boolean focused) /*-{
-    if (focused) {
- th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.focus();
+  public void setFocus(boolean focused) {
+    if (initializing) {
+ // Issue 3503: if we focus before the iframe is in design mode, the text
+      // caret will not appear.
+      isPendingFocus = focused;
     } else {
- th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.blur();
-    }
-  }-*/;
+      setFocusImpl(focused);
+    }
+  }

   public void setFontName(String name) {
     execCommand("FontName", name);
@@ -307,7 +314,7 @@
     elem.__gwt_handler = $entry(function(evt) {
       if (elem.__listener) {
if (@com.google.gwt.user.client.impl.DOMImpl::isMyListener(Ljava/lang/Object;)(elem.__listener)) { - elem.__listen...@com.google.gwt.user.client.eventlistener::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt); + @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, elem.__listener);
         }
       }
     });
@@ -365,8 +372,19 @@
       setEnabledImpl(isEnabled());
       beforeInitPlaceholder = null;
     }
-
+
     super.onElementInitialized();
+
+    // Focus on the element now that it is initialized
+    if (isPendingFocus) {
+      isPendingFocus = false;
+      setFocus(true);
+    }
+  }
+
+  protected void onElementInitializing() {
+    initializing = true;
+    isPendingFocus = false;
   }

   protected native void setEnabledImpl(boolean enabled) /*-{
@@ -374,6 +392,14 @@
     elem.contentWindow.document.designMode = enabled ? 'On' : 'Off';
   }-*/;

+  protected native void setFocusImpl(boolean focused) /*-{
+   if (focused) {
+ th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.focus();
+    } else {
+ th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.blur();
+    }
+  }-*/;
+
   protected native void setHTMLImpl(String html) /*-{
th...@com.google.gwt.user.client.ui.impl.richtextareaimpl::elem.contentWindow.document.body.innerHTML = html;
   }-*/;
=======================================
--- /branches/2.1/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java Thu Mar 25 10:46:07 2010 +++ /branches/2.1/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java Tue May 25 07:34:51 2010
@@ -16,14 +16,25 @@
 package com.google.gwt.user.client.ui;

 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
 import com.google.gwt.event.logical.shared.InitializeEvent;
 import com.google.gwt.event.logical.shared.InitializeHandler;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.RichTextArea.BasicFormatter;

+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Tests the {...@link RichTextArea} widget.
  */
@@ -58,6 +69,47 @@
     RootPanel.get().add(area);
     area.setHTML("foo");
   }
+
+  public void testBlurAfterAttach() {
+    final RichTextArea rta = new RichTextArea();
+    final List<String> actual = new ArrayList<String>();
+    rta.addFocusHandler(new FocusHandler() {
+      public void onFocus(FocusEvent event) {
+        actual.add("test");
+      }
+    });
+    RootPanel.get().add(rta);
+    rta.setFocus(true);
+    rta.setFocus(false);
+
+    // This has to be done on a timer because the rta can take some time to
+    // finish initializing (on some browsers).
+    this.delayTestFinish(3000);
+    new Timer() {
+      @Override
+      public void run() {
+        assertEquals(0, actual.size());
+        RootPanel.get().remove(rta);
+        finishTest();
+      }
+    }.schedule(2000);
+  }
+
+  public void testFocusAfterAttach() {
+    final RichTextArea rta = new RichTextArea();
+    final List<String> actual = new ArrayList<String>();
+    rta.addFocusHandler(new FocusHandler() {
+      public void onFocus(FocusEvent event) {
+        actual.add("test");
+      }
+    });
+    RootPanel.get().add(rta);
+    rta.setFocus(true);
+
+ // Webkit based browsers will not fire a focus event if the browser window + // is behind another window, so we can only test that this doesn't cause
+    // an error.
+  }

   /**
    * Test that adding and removing an RTA before initialization completes
@@ -198,6 +250,38 @@
     RootPanel.get().add(richTextArea);
     assertEquals(false, richTextArea.isEnabled());
   }
+
+  /**
+   * Test that events are dispatched correctly to handlers.
+   */
+  @DoNotRunWith(Platform.HtmlUnitUnknown)
+  public void testEventDispatch() {
+    final RichTextArea rta = new RichTextArea();
+    RootPanel.get().add(rta);
+
+    final List<String> actual = new ArrayList<String>();
+    rta.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        assertNotNull(Event.getCurrentEvent());
+        actual.add("test");
+      }
+    });
+
+    // Fire a click event after the iframe is available
+    delayTestFinish(1000);
+    new Timer() {
+      @Override
+      public void run() {
+        assertEquals(0, actual.size());
+ NativeEvent event = getDocument(rta).createClickEvent(0, 0, 0, 0, 0,
+            false, false, false, false);
+        getBodyElement(rta).dispatchEvent(event);
+        assertEquals(1, actual.size());
+        RootPanel.get().remove(rta);
+        finishTest();
+      }
+    }.schedule(500);
+  }

   /**
* Test that a delayed set of HTML is reflected. Some platforms have timing
@@ -277,4 +361,29 @@
     RootPanel.get().add(richTextArea);
     assertEquals("foo", richTextArea.getText());
   }
-}
+
+  /**
+   * Get the body element from a RichTextArea.
+   *
+   * @param rta the {...@link RichTextArea}
+   * @return the body element
+   */
+  private Element getBodyElement(RichTextArea rta) {
+    return getDocument(rta).getBody().cast();
+  }
+
+  /**
+ * Get the iframe's Document. This is useful for creating events, which must
+   * be created in the iframe's document to work correctly.
+   *
+   * @param rta the {...@link RichTextArea}
+   * @return the document element
+   */
+  private Document getDocument(RichTextArea rta) {
+    return getDocumentImpl(rta.getElement());
+  }
+
+  private native Document getDocumentImpl(Element iframe) /*-{
+    return iframe.contentWindow.document;
+  }-*/;
+}

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to