Adds some assertions to RichTextArea to prevent users from using the formatters before the RichTextArea is initialized.
Patch by: jlabanca
Review by: jgw
Issue: 2749
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5637 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/RichTextArea.java b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
index 3bd17a9..b31780c 100644
--- a/user/src/com/google/gwt/user/client/ui/RichTextArea.java
+++ b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
@@ -43,10 +43,19 @@
SourcesMouseEvents, HasAllMouseHandlers {
/**
+ * <p>
* This interface is used to access basic formatting options, when available.
* If the implementation supports basic formatting, then
* {@link RichTextArea#getBasicFormatter()} will return an instance of this
* class.
+ * </p>
+ * <p>
+ * The formatter will format the user selected text in the
+ * {@link RichTextArea}. As a result, it will only work reliably if the
+ * {@link RichTextArea} is attached, visible to on the page, and has been
+ * focused at least once. If you just want to initialize the content of
+ * the {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
+ * </p>
*/
public interface BasicFormatter {
@@ -166,10 +175,19 @@
}
/**
+ * <p>
* This interface is used to access full formatting options, when available.
* If the implementation supports full formatting, then
* {@link RichTextArea#getExtendedFormatter()} will return an instance of this
* class.
+ * </p>
+ * <p>
+ * The formatter will format the user selected text in the
+ * {@link RichTextArea}. As a result, it will only work reliably if the
+ * {@link RichTextArea} is attached, visible to on the page, and has been
+ * focused at least once. If you just want to initialize the content of
+ * the {@link RichTextArea}, use {@link RichTextArea#setHTML(String)} instead.
+ * </p>
*/
public interface ExtendedFormatter extends BasicFormatter {
@@ -363,7 +381,9 @@
}
/**
- * Gets the basic rich text formatting interface.
+ * Gets the basic rich text formatting interface. Note that formatting can
+ * only be done when the {@link RichTextArea} is attached, visible on the
+ * page, and has been focused by the user.
*
* @return <code>null</code> if basic formatting is not supported
*/
@@ -375,7 +395,9 @@
}
/**
- * Gets the full rich text formatting interface.
+ * Gets the full rich text formatting interface. Note that formatting can
+ * only be done when the {@link RichTextArea} is attached, visible on the
+ * page, and has been focused by the user.
*
* @return <code>null</code> if full formatting is not supported
*/
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 fe9e8cf..338359a 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
@@ -142,9 +142,4 @@
elem.contentWindow.onblur = null;
}
}-*/;
-
- @Override
- boolean isRichEditingActive(Element elem) {
- return true;
- }
}
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 4d2389b..1dd081a 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
@@ -15,6 +15,7 @@
*/
package com.google.gwt.user.client.ui.impl;
+import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.RichTextArea;
@@ -28,6 +29,13 @@
RichTextArea.BasicFormatter, RichTextArea.ExtendedFormatter {
/**
+ * The message displayed when the formatter is used before the RichTextArea
+ * is initialized.
+ */
+ private static final String INACTIVE_MESSAGE = "RichTextArea formatters " +
+ "cannot be used until the RichTextArea is attached and focused.";
+
+ /**
* Holds a cached copy of any user setHTML/setText actions until the real
* text area is fully initialized. Becomes <code>null</code> after init.
*/
@@ -41,6 +49,11 @@
*/
protected boolean initializing;
+ /**
+ * True when the element has been attached.
+ */
+ private boolean isReady;
+
@Override
public native Element createElement() /*-{
return $doc.createElement('iframe');
@@ -247,6 +260,8 @@
@Override
public void uninitElement() {
+ isReady = false;
+
// Issue 1897: initElement uses a timeout, so its possible to call this
// method after calling initElement, but before the event system is in
// place.
@@ -325,6 +340,7 @@
return;
}
initializing = false;
+ isReady = true;
super.onElementInitialized();
@@ -366,11 +382,17 @@
}-*/;
void execCommand(String cmd, String param) {
- if (isRichEditingActive(elem)) {
+ assert isReady : INACTIVE_MESSAGE;
+ if (isReady) {
// When executing a command, focus the iframe first, since some commands
// don't take properly when it's not focused.
setFocus(true);
- execCommandAssumingFocus(cmd, param);
+ try {
+ execCommandAssumingFocus(cmd, param);
+ } catch (JavaScriptException e) {
+ // In mozilla, editing throws a JS exception if the iframe is
+ // *hidden, but attached*.
+ }
}
}
@@ -378,19 +400,18 @@
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.execCommand(cmd, false, param);
}-*/;
- native boolean isRichEditingActive(Element e) /*-{
- return ((e.contentWindow.document.designMode).toUpperCase()) == 'ON';
- }-*/;
-
boolean queryCommandState(String cmd) {
- if (isRichEditingActive(elem)) {
+ if (isReady) {
// When executing a command, focus the iframe first, since some commands
// don't take properly when it's not focused.
setFocus(true);
- return queryCommandStateAssumingFocus(cmd);
- } else {
- return false;
+ try {
+ return queryCommandStateAssumingFocus(cmd);
+ } catch (JavaScriptException e) {
+ return false;
+ }
}
+ return false;
}
native boolean queryCommandStateAssumingFocus(String cmd) /*-{
@@ -398,10 +419,17 @@
}-*/;
String queryCommandValue(String cmd) {
- // When executing a command, focus the iframe first, since some commands
- // don't take properly when it's not focused.
- setFocus(true);
- return queryCommandValueAssumingFocus(cmd);
+ if (isReady) {
+ // When executing a command, focus the iframe first, since some commands
+ // don't take properly when it's not focused.
+ setFocus(true);
+ try {
+ return queryCommandValueAssumingFocus(cmd);
+ } catch (JavaScriptException e) {
+ return "";
+ }
+ }
+ return "";
}
native String queryCommandValueAssumingFocus(String cmd) /*-{
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 9a55823..fc4ecd0 100644
--- a/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
+++ b/user/test/com/google/gwt/user/client/ui/RichTextAreaTest.java
@@ -15,8 +15,10 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.RichTextArea.BasicFormatter;
/**
* Tests the {@link RichTextArea} widget.
@@ -65,6 +67,87 @@
RootPanel.get().remove(richTextArea);
}
+ public void testFormatAfterAttach() {
+ final RichTextArea area = new RichTextArea();
+ BasicFormatter formatter = area.getBasicFormatter();
+ RootPanel.get().add(area);
+ if (formatter != null) {
+ try {
+ formatter.toggleBold();
+ if (!GWT.isScript()) {
+ fail("Expected AssertionError");
+ }
+ } catch (AssertionError e) {
+ // Expected because the iframe is not initialized
+ return;
+ }
+ if (!GWT.isScript()) {
+ fail("Expected AssertionError");
+ }
+ }
+ }
+
+ public void testFormatAfterInitialize() {
+ final RichTextArea area = new RichTextArea();
+ RootPanel.get().add(area);
+
+ // 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() {
+ BasicFormatter formatter = area.getBasicFormatter();
+ if (formatter != null) {
+ formatter.toggleBold();
+ }
+ RootPanel.get().remove(area);
+ finishTest();
+ }
+ }.schedule(500);
+ }
+
+ public void testFormatBeforeAttach() {
+ final RichTextArea area = new RichTextArea();
+ BasicFormatter formatter = area.getBasicFormatter();
+ if (formatter != null) {
+ try {
+ formatter.toggleBold();
+ if (!GWT.isScript()) {
+ fail("Expected AssertionError");
+ }
+ } catch (AssertionError e) {
+ // expected
+ return;
+ }
+ if (!GWT.isScript()) {
+ fail("Expected AssertionError");
+ }
+ }
+ }
+
+ public void testFormatWhenHidden() {
+ final RichTextArea area = new RichTextArea();
+ RootPanel.get().add(area);
+
+ // 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() {
+ area.setVisible(false);
+ BasicFormatter formatter = area.getBasicFormatter();
+ if (formatter != null) {
+ // This won't work on some browsers, but it should return quietly.
+ formatter.toggleBold();
+ }
+ RootPanel.get().remove(area);
+ finishTest();
+ }
+ }.schedule(500);
+ }
+
/**
* Test that a delayed set of HTML is reflected. Some platforms have timing
* subtleties that need to be tested.