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