Events that occur in a RichTextArea now go through DOM#dispatchEvent(), so all of the DOM event methods are available. I fixed a bunch of bugs on multiple browsers related to setting focus immediately after attaching the RichTextArea.
Patch by: jlabanca
Review by: jgw
Issue: 3133, 3176, 3503
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5742 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/RichText.gwt.xml b/user/src/com/google/gwt/user/RichText.gwt.xml
index 76dba69..a2edcd1 100644
--- a/user/src/com/google/gwt/user/RichText.gwt.xml
+++ b/user/src/com/google/gwt/user/RichText.gwt.xml
@@ -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 -->
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java
index 338359a..4c171f8 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java
@@ -33,7 +33,7 @@
@Override
public native void initElement() /*-{
var _this = this;
- _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::initializing = true;
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()();
setTimeout(function() {
if (_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::initializing == false) {
@@ -96,7 +96,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.__listener.@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);
}
};
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java
index a43219e..d5f8c23 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java
@@ -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 = _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
- _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::initializing = true;
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()();
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplMozilla::isFirstFocus = true;
iframe.onload = 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.
- _this.@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.
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitialized()();
};
}-*/;
@@ -67,4 +70,32 @@
// 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 = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
+ var wnd = elem.contentWindow;
+ wnd.removeEventListener('focus', elem.__gwt_focusHandler, true);
+ wnd.removeEventListener('blur', elem.__gwt_blurHandler, true);
+ wnd.focus();
+ wnd.blur();
+ wnd.addEventListener('focus', elem.__gwt_focusHandler, true);
+ wnd.addEventListener('blur', elem.__gwt_blurHandler, true);
+ wnd.focus();
+ }-*/;
+
+ @Override
+ protected void setFocusImpl(boolean focused) {
+ if (isFirstFocus) {
+ isFirstFocus = false;
+ setFirstFocusImpl();
+ } else {
+ super.setFocusImpl(focused);
+ }
+ }
}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOldMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOldMozilla.java
new file mode 100644
index 0000000..a5def16
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOldMozilla.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2009 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();
+ }
+ });
+ }
+
+ /**
+ * Old Mozilla does not support blur on the content window of an iframe.
+ */
+ @Override
+ protected void setFirstFocusImpl() {
+ setFocusImpl(true);
+ }
+
+ @Override
+ protected void setFocusImpl(boolean focused) {
+ if (focused) {
+ super.setFocusImpl(focused);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java
index 2b4d045..3aec890 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java
@@ -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) {
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.focus();
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java
index 7d94ad6..c2e091b 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java
@@ -39,7 +39,7 @@
elem.__gwt_handler = function(evt) {
if (elem.__listener) {
- elem.__listener.@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);
}
};
@@ -57,15 +57,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.__listener.@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.__listener.@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);
}
};
}-*/;
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
index d4e99d1..5001f68 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
@@ -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.
*/
private boolean isReady;
@@ -89,7 +94,7 @@
// the iframe becomes attached to the DOM. Any non-zero timeout will do
// just fine.
var _this = this;
- _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::initializing = true;
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()();
setTimeout(function() {
// Turn on design mode.
_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.designMode = 'On';
@@ -172,13 +177,15 @@
}
@Override
- public native void setFocus(boolean focused) /*-{
- if (focused) {
- this.@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 {
- this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.blur();
- }
- }-*/;
+ setFocusImpl(focused);
+ }
+ }
public void setFontName(String name) {
execCommand("FontName", name);
@@ -288,7 +295,7 @@
elem.__gwt_handler = function(evt) {
if (elem.__listener) {
- elem.__listener.@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);
}
};
@@ -341,8 +348,27 @@
setHTMLImpl(DOM.getInnerHTML(beforeInitPlaceholder));
beforeInitPlaceholder = null;
}
+
+ // 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 setFocusImpl(boolean focused) /*-{
+ if (focused) {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.focus();
+ } else {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.blur();
+ }
+ }-*/;
+
protected native void setHTMLImpl(String html) /*-{
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML = html;
}-*/;
diff --git a/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java b/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
index fc4ecd0..a57fb70 100644
--- a/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
+++ b/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
@@ -16,10 +16,21 @@
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.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.
*/
@@ -57,6 +68,57 @@
}.schedule(500);
}
+ 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(1000);
+ new Timer() {
+ @Override
+ public void run() {
+ assertEquals(0, actual.size());
+ RootPanel.get().remove(rta);
+ finishTest();
+ }
+ }.schedule(500);
+ }
+
+ 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);
+
+ // This has to be done on a timer because the rta can take some time to
+ // finish initializing (on some browsers).
+ this.delayTestFinish(1000);
+ new Timer() {
+ @Override
+ public void run() {
+ // IE focuses automatically, resulting in an extra event, so all we can
+ // test is that we got at least one focus.
+ assertTrue(actual.size() > 0);
+ RootPanel.get().remove(rta);
+ finishTest();
+ }
+ }.schedule(500);
+ }
+
/**
* Test that adding and removing an RTA before initialization completes
* doesn't throw an exception.
@@ -149,6 +211,37 @@
}
/**
+ * Test that events are dispatched correctly to handlers.
+ */
+ 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
* subtleties that need to be tested.
*/
@@ -221,4 +314,29 @@
}.schedule(200);
delayTestFinish(1000);
}
+
+ /**
+ * 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;
+ }-*/;
}