Introducing DirectionalTextHelper, a new helper class for text and direction manipulations in widgets, and using it to enhance Anchor and Hyperlink with bidi support.

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9187 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Anchor.java b/user/src/com/google/gwt/user/client/ui/Anchor.java
index bda6fce..35de8ef 100644
--- a/user/src/com/google/gwt/user/client/ui/Anchor.java
+++ b/user/src/com/google/gwt/user/client/ui/Anchor.java
@@ -20,7 +20,8 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.safehtml.client.HasSafeHtml;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
 import com.google.gwt.safehtml.shared.SafeHtml;
 
 /**
@@ -30,6 +31,14 @@
  * If you want use this anchor only for changing history states, use
  * {@link Hyperlink} instead.
  * </p>
+ *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
  * 
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
@@ -39,7 +48,11 @@
  * @see Hyperlink
  */
 public class Anchor extends FocusWidget implements HasHorizontalAlignment,
-    HasName, HasHTML, HasWordWrap, HasDirection, HasSafeHtml {
+    HasName, HasHTML, HasWordWrap, HasDirection,
+    HasDirectionEstimator, HasDirectionalSafeHtml {
+
+  public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR =
+      DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR;
 
   /**
    * Creates an Anchor widget that wraps an existing &lt;a&gt; element.
@@ -63,6 +76,8 @@
     return anchor;
   }
 
+  private final DirectionalTextHelper directionalTextHelper;
+
   private HorizontalAlignmentConstant horzAlign;
 
   /**
@@ -71,6 +86,45 @@
   public Anchor() {
     setElement(Document.get().createAnchorElement());
     setStyleName("gwt-Anchor");
+    directionalTextHelper = new DirectionalTextHelper(getAnchorElement(),
+        /* is inline */ true);
+  }
+
+  /**
+   * Creates an anchor for scripting.
+   *
+   * @param html the anchor's html
+   */
+  public Anchor(SafeHtml html) {
+    this(html.asString(), true);
+  }
+
+  /**
+   * Creates an anchor for scripting.
+   *
+   * The anchor's href is set to <code>javascript : ;</code>, based on the
+   * expectation that listeners will be added to the anchor.
+   *
+   * @param html the anchor's html
+   * @param dir the html's direction
+   */
+  public Anchor(SafeHtml html, Direction dir) {
+    this(html.asString(), true, dir, "javascript:;");
+  }
+  
+  /**
+   * Creates an anchor for scripting.
+   *
+   * The anchor's href is set to <code>javascript : ;</code>, based on the
+   * expectation that listeners will be added to the anchor.
+   *
+   * @param html the anchor's html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public Anchor(SafeHtml html, DirectionEstimator directionEstimator) {
+    this(html.asString(), true, directionEstimator, "javascript:;");
   }
 
   /**
@@ -88,10 +142,14 @@
   /**
    * Creates an anchor for scripting.
    *
-   * @param html the anchor's text
+   * The anchor's href is set to <code>javascript : ;</code>, based on the
+   * expectation that listeners will be added to the anchor.
+   *
+   * @param text the anchor's text
+   * @param dir the text's direction
    */
-  public Anchor(SafeHtml html) {
-    this(html.asString(), true);
+  public Anchor(String text, Direction dir) {
+    this(text, dir, "javascript:;");
   }
 
   /**
@@ -101,6 +159,21 @@
    * expectation that listeners will be added to the anchor.
    *
    * @param text the anchor's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public Anchor(String text, DirectionEstimator directionEstimator) {
+    this(text, directionEstimator, "javascript:;");
+  }
+
+  /**
+   * Creates an anchor for scripting.
+   * 
+   * The anchor's href is set to <code>javascript:;</code>, based on the
+   * expectation that listeners will be added to the anchor.
+   * 
+   * @param text the anchor's text
    * @param asHtml <code>true</code> to treat the specified text as html
    */
   public Anchor(String text, boolean asHtml) {
@@ -108,7 +181,7 @@
   }
 
   /**
-   * Creates an anchor with its text and href (target URL) specified.
+   * Creates an anchor with its html and href (target URL) specified.
    *
    * @param html the anchor's html
    * @param href the url to which it will link
@@ -118,6 +191,66 @@
   }
 
   /**
+   *  Creates an anchor with its html and href (target URL) specified.
+   *
+   * @param html the anchor's html
+   * @param dir the html's direction
+   * @param href the url to which it will link
+   */
+  public Anchor(SafeHtml html, Direction dir, String href) {
+    this(html.asString(), true, dir, href);
+  }
+  
+  /**
+   *  Creates an anchor with its html and href (target URL) specified.
+   *
+   * @param html the anchor's html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param href the url to which it will link
+   */
+  public Anchor(SafeHtml html, DirectionEstimator directionEstimator,
+      String href) {
+    this(html.asString(), true, directionEstimator, href);
+  }
+
+  /**
+   * Creates an anchor with its text and href (target URL) specified.
+   * 
+   * @param text the anchor's text
+   * @param href the url to which it will link
+   */
+  public Anchor(String text, String href) {
+    this(text, false, href);
+  }
+
+  /**
+   * Creates an anchor with its text and href (target URL) specified.
+   * 
+   * @param text the anchor's text
+   * @param dir the text's direction
+   * @param href the url to which it will link
+   */
+  public Anchor(String text, Direction dir, String href) {
+    this(text, false, dir, href);
+  }
+
+  /**
+   * Creates an anchor with its text and href (target URL) specified.
+   * 
+   * @param text the anchor's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param href the url to which it will link
+   */
+  public Anchor(String text, DirectionEstimator directionEstimator,
+      String href) {
+    this(text, false, directionEstimator, href);
+  }
+
+  /**
    * Creates an anchor with its text and href (target URL) specified.
    *
    * @param text the anchor's text
@@ -126,11 +259,7 @@
    */
   public Anchor(String text, boolean asHTML, String href) {
     this();
-    if (asHTML) {
-      setHTML(text);
-    } else {
-      setText(text);
-    }
+    directionalTextHelper.setTextOrHtml(text, asHTML);
     setHref(href);
   }
 
@@ -149,6 +278,18 @@
   }
 
   /**
+   * Creates a source anchor with a frame target.
+   * 
+   * @param text the anchor's text
+   * @param href the url to which it will link
+   * @param target the target frame (e.g. "_blank" to open the link in a new
+   *          window)
+   */
+  public Anchor(String text, String href, String target) {
+    this(text, false, href, target);
+  }
+
+  /**
    * Creates a source anchor (link to URI).
    *
    * That is, an anchor with an href attribute specifying the destination URI.
@@ -165,30 +306,6 @@
   }
 
   /**
-   * Creates an anchor with its text and href (target URL) specified.
-   * 
-   * @param text the anchor's text
-   * @param href the url to which it will link
-   */
-  public Anchor(String text, String href) {
-    this();
-    setText(text);
-    setHref(href);
-  }
-
-  /**
-   * Creates a source anchor with a frame target.
-   * 
-   * @param text the anchor's text
-   * @param href the url to which it will link
-   * @param target the target frame (e.g. "_blank" to open the link in a new
-   *          window)
-   */
-  public Anchor(String text, String href, String target) {
-    this(text, false, href, target);
-  }
-
-  /**
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be an &lt;a&gt; element.
    * 
@@ -197,12 +314,50 @@
   protected Anchor(Element element) {
     AnchorElement.as(element);
     setElement(element);
+    directionalTextHelper = new DirectionalTextHelper(getAnchorElement(),
+        /* is inline */ true);
+  }
+
+  /**
+   * Creates an anchor with its text, direction and href (target URL) specified.
+   * 
+   * @param text the anchor's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param dir the text's direction
+   * @param href the url to which it will link
+   */
+  private Anchor(String text, boolean asHTML, Direction dir, String href) {
+    this();
+    directionalTextHelper.setTextOrHtml(text, dir, asHTML);
+    setHref(href);
+  }
+  
+  /**
+   * Creates an anchor with its text, direction and href (target URL) specified.
+   * 
+   * @param text the anchor's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param href the url to which it will link
+   */
+  private Anchor(String text, boolean asHTML,
+      DirectionEstimator directionEstimator, String href) {
+    this();
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
+    directionalTextHelper.setTextOrHtml(text, asHTML);
+    setHref(href);
   }
 
   public Direction getDirection() {
     return BidiUtils.getDirectionOnElement(getElement());
   }
 
+  public DirectionEstimator getDirectionEstimator() {
+    return directionalTextHelper.getDirectionEstimator();
+  }
+
   public HorizontalAlignmentConstant getHorizontalAlignment() {
     return horzAlign;
   }
@@ -240,7 +395,11 @@
   }
 
   public String getText() {
-    return getElement().getInnerText();
+    return directionalTextHelper.getTextOrHtml(false);
+  }
+
+  public Direction getTextDirection() {
+    return directionalTextHelper.getTextDirection();
   }
 
   public boolean getWordWrap() {
@@ -252,8 +411,35 @@
     getAnchorElement().setAccessKey(Character.toString(key));
   }
 
+  /**
+   * @deprecated Use {@link #setDirectionEstimator} and / or pass explicit
+   * direction to {@link #setText}, {@link #setHTML} instead
+   */
+  @Deprecated
   public void setDirection(Direction direction) {
-    BidiUtils.setDirectionOnElement(getElement(), direction);
+    directionalTextHelper.setDirection(direction);
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * See note at {@link #setDirectionEstimator(DirectionEstimator)}.
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    directionalTextHelper.setDirectionEstimator(enabled);
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * Note: DirectionEstimator should be set before the widget has any content;
+   * it's highly recommended to set it using a constructor. Reason: if the
+   * widget already has non-empty content, this will update its direction
+   * according to the new estimator's result. This may cause flicker, and thus
+   * should be avoided.
+   */
+  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
   }
 
   @Override
@@ -284,7 +470,11 @@
   }
 
   public void setHTML(String html) {
-    getElement().setInnerHTML(html);
+    directionalTextHelper.setTextOrHtml(html, true);
+  }
+
+  public void setHTML(SafeHtml html, Direction dir) {
+    directionalTextHelper.setTextOrHtml(html.asString(), dir, true);
   }
 
   public void setName(String name) {
@@ -307,7 +497,11 @@
   }
 
   public void setText(String text) {
-    getElement().setInnerText(text);
+    directionalTextHelper.setTextOrHtml(text, false);
+  }
+
+  public void setText(String text, Direction dir) {
+    directionalTextHelper.setTextOrHtml(text, dir, false);
   }
 
   public void setWordWrap(boolean wrap) {
diff --git a/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java b/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java
new file mode 100644
index 0000000..68d3fbd
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java
@@ -0,0 +1,245 @@
+/*
+ * 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;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.BidiFormatter;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
+import com.google.gwt.i18n.shared.WordCountDirectionEstimator;
+
+/**
+ * A helper class for displaying bidi (i.e. potentially opposite-direction) text 
+ * or HTML in an element.
+ * Note: this class assumes that callers perform all their text/html and
+ * direction manipulations through it alone.
+ */
+public class DirectionalTextHelper implements HasDirectionEstimator {
+
+  /**
+   * A default direction estimator instance.
+   */
+  public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR =
+      WordCountDirectionEstimator.get();
+
+  /**
+   * The DirectionEstimator object.
+   */
+  private DirectionEstimator directionEstimator;
+
+  /**
+   * The target element.
+   */
+  private final Element element;
+
+  /**
+   * The initial direction of the element.
+   */
+  private Direction initialElementDir;
+
+  /**
+   * Whether direction was explicitly set on the last {@code setTextOrHtml}
+   * call. If so, {@link #setDirectionEstimator} will refrain from modifying the
+   * direction until {@link #setTextOrHtml} is called without specifying an
+   * explicit direction.
+   */
+  private boolean isDirectionExplicitlySet;
+
+  /**
+   * Whether the element is inline (e.g. a &lt;span&gt; element, but not a block
+   * element like &lt;div&gt;).
+   * This is needed because direction is handled differently for inline elements
+   * and for non-inline elements.
+   */
+  private final boolean isElementInline;
+
+  /**
+   * Whether the element contains a nested &lt;span&gt; element used to
+   * indicate the content's direction.
+   * <p>
+   * The element itself is used for this purpose when it is a block element
+   * (i.e. !isElementInline), but doing so on an inline element often results in
+   * garbling what follows it. Thus, when the element is inline, a nested
+   * &lt;span&gt; must be used to carry the content's direction, with an LRM or
+   * RLM character afterwards to prevent the garbling.
+   */
+  private boolean isSpanWrapped;
+
+  /**
+   * The direction of the element's content.
+   * Note: this may not match the direction attribute of the element itself.
+   * See
+   * {@link #setTextOrHtml(String, com.google.gwt.i18n.client.HasDirection.Direction, boolean) setTextOrHtml(String, Direction, boolean)}
+   * for details.
+   */
+  private Direction textDir;
+
+  /**
+   * @param element The widget's element holding text.
+   * @param isElementInline Whether the element is an inline element.
+   */
+  public DirectionalTextHelper(Element element, boolean isElementInline) {
+    this.element = element;
+    this.isElementInline = isElementInline;
+    isSpanWrapped = false;
+    this.initialElementDir = BidiUtils.getDirectionOnElement(element);
+    textDir = initialElementDir;
+    // setDirectionEstimator shouldn't refresh appearance of initial empty text.
+    isDirectionExplicitlySet = true;
+  }
+
+  public DirectionEstimator getDirectionEstimator() {
+    return directionEstimator;
+  }
+
+  public Direction getTextDirection() {
+    return textDir;
+  }
+
+  /**
+   * Get the inner text or html of the element, taking the inner span wrap into
+   * consideration, if needed.
+   * 
+   * @param isHtml true to get the inner html, false to get the inner text
+   * @return the text or html
+   */
+  public String getTextOrHtml(boolean isHtml) {
+    Element elem = isSpanWrapped ? element.getFirstChildElement() : element;
+    return isHtml ? elem.getInnerHTML() : elem.getInnerText();
+  }
+
+  /**
+   * Provides implementation for HasDirection's method setDirection (normally
+   * deprecated), dealing with backwards compatibility issues.
+   * @deprecated
+   */
+  @Deprecated
+  public void setDirection(Direction direction) {
+    BidiUtils.setDirectionOnElement(element, direction);
+    initialElementDir = direction;
+
+    /* 
+     * For backwards compatibility, assure there's no span wrap, and update the
+     * content direction.
+     */
+    setInnerTextOrHtml(getTextOrHtml(true), true);
+    isSpanWrapped = false;
+    textDir = initialElementDir;
+    isDirectionExplicitlySet = true;
+  }
+
+  /**
+   * See note at
+   * {@link #setDirectionEstimator(com.google.gwt.i18n.shared.DirectionEstimator)}.
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    setDirectionEstimator(enabled ? DEFAULT_DIRECTION_ESTIMATOR : null);
+  }
+
+  /**
+   * Note: if the element already has non-empty content, this will update
+   * its direction according to the new estimator's result. This may cause
+   * flicker, and thus should be avoided; DirectionEstimator should be set
+   * before the element has any content.
+   */
+  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    this.directionEstimator = directionEstimator;
+    /* 
+     * Refresh appearance unless direction was explicitly set on last
+     * setTextOrHtml call.
+     */
+    if (!isDirectionExplicitlySet) {
+      setTextOrHtml(getTextOrHtml(true), true);
+    }
+  }
+
+  /**
+   * Sets the element's content to the given value (either plain text or HTML).
+   * If direction estimation is off, the direction is verified to match the
+   * element's initial direction. Otherwise, the direction is affected as
+   * described at
+   * {@link #setTextOrHtml(String, com.google.gwt.i18n.client.HasDirection.Direction, boolean) setTextOrHtml(String, Direction, boolean)}.
+   *
+   * @param content the element's new content
+   * @param isHtml whether the content is HTML
+   */
+  public void setTextOrHtml(String content, boolean isHtml) {
+    if (directionEstimator == null) {
+      isSpanWrapped = false;
+      setInnerTextOrHtml(content, isHtml);
+
+      /*
+       * Preserves the initial direction of the element. This is different from
+       * passing the direction parameter explicitly as DEFAULT, which forces the
+       * element to inherit the direction from its parent.
+       */
+      if (textDir != initialElementDir) {
+        textDir = initialElementDir;
+        BidiUtils.setDirectionOnElement(element, initialElementDir);
+      }
+    } else {
+      setTextOrHtml(content, directionEstimator.estimateDirection(content,
+          isHtml), isHtml);
+    }
+    isDirectionExplicitlySet = false;
+  }
+
+  /**
+   * Sets the element's content to the given value (either plain text or HTML),
+   * applying the given direction.
+   * <p>
+   * Implementation details:
+   * <ul>
+   * <li> If the element is a block element, sets its dir attribute according
+   * to the given direction.
+   * <li> Otherwise (i.e. the element is inline), the direction is set using a
+   * nested &lt;span dir=...&gt; element which holds the content of the element.
+   * This nested span may be followed by a zero-width Unicode direction
+   * character (LRM or RLM). This manipulation is necessary to prevent garbling
+   * in case the direction of the element is opposite to the direction of its
+   * context. See {@link com.google.gwt.i18n.shared.BidiFormatter} for more
+   * details.
+   * </ul>
+   *
+   * @param content the element's new content
+   * @param dir the content's direction
+   * @param isHtml whether the content is HTML
+   */
+  public void setTextOrHtml(String content, Direction dir, boolean isHtml) {
+    textDir = dir;
+    // Set the text and the direction.
+    if (isElementInline) {
+      isSpanWrapped = true;
+      element.setInnerHTML(BidiFormatter.getInstanceForCurrentLocale(
+          true /* alwaysSpan */).spanWrapWithKnownDir(dir, content, isHtml));
+    } else {
+      isSpanWrapped = false;
+      BidiUtils.setDirectionOnElement(element, dir);
+      setInnerTextOrHtml(content, isHtml);
+    }
+    isDirectionExplicitlySet = true;
+  }
+
+  private void setInnerTextOrHtml(String content, boolean isHtml) {
+    if (isHtml) {
+      element.setInnerHTML(content);
+    } else {
+      element.setInnerText(content);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/HTML.java b/user/src/com/google/gwt/user/client/ui/HTML.java
index 98c7fd3..fe4e6ac 100644
--- a/user/src/com/google/gwt/user/client/ui/HTML.java
+++ b/user/src/com/google/gwt/user/client/ui/HTML.java
@@ -17,8 +17,6 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
-import com.google.gwt.i18n.client.BidiUtils;
-import com.google.gwt.i18n.shared.BidiFormatter;
 import com.google.gwt.i18n.shared.DirectionEstimator;
 import com.google.gwt.safehtml.shared.SafeHtml;
 
@@ -35,6 +33,14 @@
  * used properly.
  * </p>
  *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-HTML { }</li>
@@ -89,18 +95,8 @@
   }
 
   /**
-   * Creates an HTML widget with the specified HTML contents.
-   *
-   * @param html the new widget's HTML contents
-   */
-  public HTML(String html) {
-    this();
-    setHTML(html);
-  }
-
-  /**
-   * Creates an HTML widget with the specified contents and with the
-   * specified direction.
+   * Creates an HTML widget with the specified contents and with the specified
+   * direction.
    *
    * @param html the new widget's SafeHtml contents
    * @param dir the content's direction. Note: {@code Direction.DEFAULT} means
@@ -111,6 +107,31 @@
   }
 
   /**
+   * Creates an HTML widget with the specified HTML contents and specifies a
+   * direction estimator.
+   *
+   * @param html the new widget's SafeHtml contents
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public HTML(SafeHtml html, DirectionEstimator directionEstimator) {
+    this();
+    setDirectionEstimator(directionEstimator);
+    setHTML(html);
+  }
+
+  /**
+   * Creates an HTML widget with the specified HTML contents.
+   *
+   * @param html the new widget's HTML contents
+   */
+  public HTML(String html) {
+    this();
+    setHTML(html);
+  }
+
+  /**
    * Creates an HTML widget with the specified HTML contents and with the
    * specified direction.
    *
@@ -122,7 +143,7 @@
     this();
     setHTML(html, dir);
   }
-
+  
   /**
    * Creates an HTML widget with the specified contents, optionally treating it
    * as HTML, and optionally disabling word wrapping.
@@ -148,39 +169,7 @@
   }
 
   public String getHTML() {
-    return getTextOrHtml(true);
-  }
-
-  /**
-   * Sets the widget element's direction.
-   * @deprecated Use {@link #setDirectionEstimator} and / or pass explicit
-   * direction to {@link #setText} instead
-   */
-  @Deprecated
-  public void setDirection(Direction direction) {
-    BidiUtils.setDirectionOnElement(getElement(), direction);
-    initialElementDir = direction;
-
-    // For backwards compatibility, assure there's no span wrap, and update the
-    // content direction.
-    setInnerTextOrHtml(getTextOrHtml(true), true);
-    isSpanWrapped = false;
-    textDir = initialElementDir;
-    updateHorizontalAlignment();
-  }
-
-  /**
-   * {@inheritDoc}
-   * <p>
-   * Note: if the widget already has non-empty content, this will update
-   * its direction according to the new estimator's result. This may cause
-   * flicker, and thus should be avoided; DirectionEstimator should be set
-   * before the widget has any content.
-   */
-  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
-    this.directionEstimator = directionEstimator;
-    // Refresh appearance
-    setHTML(getTextOrHtml(true));
+    return directionalTextHelper.getTextOrHtml(true);
   }
 
   /**
@@ -191,48 +180,22 @@
    * @param html the new widget's HTML content
    */
   public void setHTML(String html) {
-    if (directionEstimator == null) {
-      isSpanWrapped = false;
-      getElement().setInnerHTML(html);
-
-      // Preserves the initial direction of the widget. This is different from
-      // passing the direction parameter explicitly as DEFAULT, which forces the
-      // widget to inherit the direction from its parent.
-      if (textDir != initialElementDir) {
-        textDir = initialElementDir;
-        BidiUtils.setDirectionOnElement(getElement(), initialElementDir);
-        updateHorizontalAlignment();
-      }
-    } else {
-      setHTML(html, directionEstimator.estimateDirection(html, true));
-    }
+    directionalTextHelper.setTextOrHtml(html, true);
+    updateHorizontalAlignment();
   }
 
   /**
    * Sets the label's content to the given HTML, applying the given direction.
    * See
-   * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction)
-   * setText(String, Direction)} for details on potential effects on alignment.
+   * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) setText(String, Direction)}
+   * for details on potential effects on alignment.
    * 
    * @param html the new widget's HTML content
    * @param dir the content's direction. Note: {@code Direction.DEFAULT} means
    *          direction should be inherited from the widget's parent element.
    */
   public void setHTML(String html, Direction dir) {
-    textDir = dir;
-
-    // Set the text and the direction.
-    if (isElementInline) {
-      isSpanWrapped = true;
-      getElement().setInnerHTML(BidiFormatter.getInstanceForCurrentLocale(
-          true /* alwaysSpan */).spanWrapWithKnownDir(dir, html, true));
-    } else {
-      isSpanWrapped = false;
-      BidiUtils.setDirectionOnElement(getElement(), dir);
-      getElement().setInnerHTML(html);
-    }
-
-    // Update the horizontal alignment if needed.
+    directionalTextHelper.setTextOrHtml(html, dir, true);
     updateHorizontalAlignment();
   }
 
@@ -251,16 +214,6 @@
   }
 
   protected String getTextOrHtml(boolean isHtml) {
-    Element element = isSpanWrapped ? getElement().getFirstChildElement()
-        : getElement();
-    return isHtml ? element.getInnerHTML() : element.getInnerText();
-  }
-
-  private void setInnerTextOrHtml(String content, boolean isHtml) {
-    if (isHtml) {
-      getElement().setInnerHTML(content);
-    } else {
-      getElement().setInnerText(content);
-    }
+    return directionalTextHelper.getTextOrHtml(isHtml);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/Hyperlink.java b/user/src/com/google/gwt/user/client/ui/Hyperlink.java
index 9d8f2e0..08e25f8 100644
--- a/user/src/com/google/gwt/user/client/ui/Hyperlink.java
+++ b/user/src/com/google/gwt/user/client/ui/Hyperlink.java
@@ -20,7 +20,9 @@
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.HasClickHandlers;
 import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.safehtml.client.HasSafeHtml;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
@@ -46,6 +48,14 @@
  * </p>
  * 
  * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
+ * <p>
  * <img class='gallery' src='doc-files/Hyperlink.png'/>
  * </p>
  * 
@@ -62,10 +72,14 @@
  */
 @SuppressWarnings("deprecation")
 public class Hyperlink extends Widget implements HasHTML, SourcesClickEvents,
-  HasClickHandlers, HasSafeHtml {
+    HasClickHandlers, HasDirectionEstimator, HasDirectionalSafeHtml {
+
+  public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR =
+      DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR;
 
   private static HyperlinkImpl impl = GWT.create(HyperlinkImpl.class);
-  
+
+  protected final DirectionalTextHelper directionalTextHelper;
   private final Element anchorElem = DOM.createAnchor();
   private String targetHistoryToken;
 
@@ -88,21 +102,30 @@
   }
 
   /**
-   * Creates a hyperlink with its text and target history token specified.
+   * Creates a hyperlink with its html and target history token specified.
    *
-   * @param text the hyperlink's text
-   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param html the hyperlink's safe html
+   * @param dir the html's direction
    * @param targetHistoryToken the history token to which it will link
    * @see #setTargetHistoryToken
    */
-  public Hyperlink(String text, boolean asHTML, String targetHistoryToken) {
-    this();
-    if (asHTML) {
-      setHTML(text);
-    } else {
-      setText(text);
-    }
-    setTargetHistoryToken(targetHistoryToken);
+  public Hyperlink(SafeHtml html, Direction dir, String targetHistoryToken) {
+    this(html.asString(), true, dir, targetHistoryToken);
+  }
+  
+  /**
+   * Creates a hyperlink with its html and target history token specified.
+   *
+   * @param html the hyperlink's safe html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  public Hyperlink(SafeHtml html, DirectionEstimator directionEstimator,
+      String targetHistoryToken) {
+    this(html.asString(), true, directionEstimator, targetHistoryToken);
   }
 
   /**
@@ -114,8 +137,49 @@
    *          history processing)
    */
   public Hyperlink(String text, String targetHistoryToken) {
+    this(text, false, targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   * 
+   * @param text the hyperlink's text
+   * @param dir the text's direction
+   * @param targetHistoryToken the history token to which it will link, which
+   *          may not be null (use {@link Anchor} instead if you don't need
+   *          history processing)
+   */
+  public Hyperlink(String text, Direction dir, String targetHistoryToken) {
+    this(text, false, dir, targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   * 
+   * @param text the hyperlink's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link, which
+   *          may not be null (use {@link Anchor} instead if you don't need
+   *          history processing)
+   */
+  public Hyperlink(String text, DirectionEstimator directionEstimator,
+      String targetHistoryToken) {
+    this(text, false, directionEstimator, targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  public Hyperlink(String text, boolean asHTML, String targetHistoryToken) {
     this();
-    setText(text);
+    directionalTextHelper.setTextOrHtml(text, asHTML);
     setTargetHistoryToken(targetHistoryToken);
   }
   
@@ -129,6 +193,43 @@
 
     sinkEvents(Event.ONCLICK);
     setStyleName("gwt-Hyperlink");
+    directionalTextHelper = new DirectionalTextHelper(anchorElem,
+        /* is inline */ true);
+  }
+
+  /**
+   * Creates a hyperlink with its text target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param dir the text's direction
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  private Hyperlink(String text, boolean asHTML, Direction dir,
+      String targetHistoryToken) {
+    this();
+    directionalTextHelper.setTextOrHtml(text, dir, asHTML);
+    setTargetHistoryToken(targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  private Hyperlink(String text, boolean asHTML,
+      DirectionEstimator directionEstimator, String targetHistoryToken) {
+    this();
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
+    directionalTextHelper.setTextOrHtml(text, asHTML);
+    setTargetHistoryToken(targetHistoryToken);
   }
 
   /**
@@ -151,8 +252,12 @@
     ListenerWrapper.WrappedClickListener.add(this, listener);
   }
 
+  public DirectionEstimator getDirectionEstimator() {
+    return directionalTextHelper.getDirectionEstimator();
+  }
+
   public String getHTML() {
-    return DOM.getInnerHTML(anchorElem);
+    return directionalTextHelper.getTextOrHtml(true);
   }
 
   /**
@@ -166,7 +271,11 @@
   }
 
   public String getText() {
-    return DOM.getInnerText(anchorElem);
+    return directionalTextHelper.getTextOrHtml(false);
+  }
+
+  public Direction getTextDirection() {
+    return directionalTextHelper.getTextDirection();
   }
 
   @Override
@@ -187,14 +296,40 @@
     ListenerWrapper.WrappedClickListener.remove(this, listener);
   }
 
-  public void setHTML(String html) {
-    DOM.setInnerHTML(anchorElem, html);
+  /**
+   * {@inheritDoc}
+   * <p>
+   * See note at {@link #setDirectionEstimator(DirectionEstimator)}.
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    directionalTextHelper.setDirectionEstimator(enabled);
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * Note: DirectionEstimator should be set before the widget has any content;
+   * it's highly recommended to set it using a constructor. Reason: if the
+   * widget already has non-empty content, this will update its direction
+   * according to the new estimator's result. This may cause flicker, and thus
+   * should be avoided.
+   */
+  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
   }
 
   public void setHTML(SafeHtml html) {
     setHTML(html.asString());
   }
 
+  public void setHTML(String html) {
+    directionalTextHelper.setTextOrHtml(html, true);
+  }
+
+  public void setHTML(SafeHtml html, Direction dir) {
+    directionalTextHelper.setTextOrHtml(html.asString(), dir, true);
+  }
+
   /**
    * Sets the history token referenced by this hyperlink. This is the history
    * token that will be passed to {@link History#newItem} when this link is
@@ -211,7 +346,11 @@
   }
 
   public void setText(String text) {
-    DOM.setInnerText(anchorElem, text);
+    directionalTextHelper.setTextOrHtml(text, false);
+  }
+
+  public void setText(String text, Direction dir) {
+    directionalTextHelper.setTextOrHtml(text, dir, false);
   }
 
   /**
@@ -228,4 +367,3 @@
     ensureDebugId(getElement(), baseID, "wrapper");
   }
 }
-
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHTML.java b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
index a1a0bc1..09f8f98 100644
--- a/user/src/com/google/gwt/user/client/ui/InlineHTML.java
+++ b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
@@ -17,6 +17,9 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+
+
+import com.google.gwt.i18n.shared.DirectionEstimator;
 import com.google.gwt.safehtml.shared.SafeHtml;
 
 /**
@@ -32,6 +35,14 @@
  * used properly.
  * </p>
  *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-InlineHTML { }</li>
@@ -80,16 +91,6 @@
   }
 
   /**
-   * Creates an HTML widget with the specified HTML contents.
-   *
-   * @param html the new widget's HTML contents
-   */
-  public InlineHTML(String html) {
-    this();
-    setHTML(html);
-  }
-
-  /**
    * Creates an HTML widget with the specified contents and with the
    * specified direction.
    *
@@ -102,6 +103,31 @@
   }
 
   /**
+   * Creates an HTML widget with the specified HTML contents and with a default
+   * direction estimator.
+   *
+   * @param html the new widget's SafeHtml contents
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public InlineHTML(SafeHtml html, DirectionEstimator directionEstimator) {
+    this();
+    setDirectionEstimator(directionEstimator);
+    setHTML(html);
+  }
+
+  /**
+   * Creates an HTML widget with the specified HTML contents.
+   *
+   * @param html the new widget's HTML contents
+   */
+  public InlineHTML(String html) {
+    this();
+    setHTML(html);
+  }
+
+  /**
    * Creates an HTML widget with the specified HTML contents and with the
    * specified direction.
    *
@@ -113,7 +139,7 @@
     this();
     setHTML(html, dir);
   }
-
+  
   /**
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be either a &lt;div&gt; &lt;span&gt; element.
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java b/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java
index 663d6f9..43b20be 100644
--- a/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java
+++ b/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java
@@ -16,6 +16,8 @@
 
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.DirectionEstimator;
 import com.google.gwt.safehtml.shared.SafeHtml;
 
 /**
@@ -24,6 +26,14 @@
  * {@link com.google.gwt.user.client.ui.Hyperlink}, save that it lays out
  * as an inline element, not block.
  * 
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-InlineHyperlink { }</li>
@@ -52,22 +62,31 @@
   }
 
   /**
-   * Creates a hyperlink with its text and target history token specified.
-   *
-   * @param text the hyperlink's text
-   * @param asHTML <code>true</code> to treat the specified text as html
+   * Creates a hyperlink with its html and target history token specified.
+   * 
+   * @param html the hyperlink's html
+   * @param dir the html's direction
    * @param targetHistoryToken the history token to which it will link
    * @see #setTargetHistoryToken
    */
-  public InlineHyperlink(String text, boolean asHTML, String targetHistoryToken) {
-    this();
-
-    if (asHTML) {
-      setHTML(text);
-    } else {
-      setText(text);
-    }
-    setTargetHistoryToken(targetHistoryToken);
+  public InlineHyperlink(SafeHtml html, Direction dir,
+      String targetHistoryToken) {
+    this(html.asString(), true, dir, targetHistoryToken);
+  }
+  
+  /**
+   * Creates a hyperlink with its html and target history token specified.
+   *
+   * @param html the hyperlink's html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  public InlineHyperlink(SafeHtml html, DirectionEstimator directionEstimator,
+      String targetHistoryToken) {
+    this(html.asString(), true, directionEstimator, targetHistoryToken);
   }
 
   /**
@@ -79,4 +98,80 @@
   public InlineHyperlink(String text, String targetHistoryToken) {
     this(text, false, targetHistoryToken);
   }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   * 
+   * @param text the hyperlink's text
+   * @param dir the text's direction
+   * @param targetHistoryToken the history token to which it will link
+   */
+  public InlineHyperlink(String text, Direction dir,
+      String targetHistoryToken) {
+    this(text, false, dir, targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   * 
+   * @param text the hyperlink's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link
+   */
+  public InlineHyperlink(String text, DirectionEstimator directionEstimator,
+      String targetHistoryToken) {
+    this(text, false, directionEstimator, targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  public InlineHyperlink(String text, boolean asHTML,
+      String targetHistoryToken) {
+    this();
+    directionalTextHelper.setTextOrHtml(text, asHTML);
+    setTargetHistoryToken(targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param dir the text's direction
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  private InlineHyperlink(String text, boolean asHTML, Direction dir,
+      String targetHistoryToken) {
+    this();
+    directionalTextHelper.setTextOrHtml(text, dir, asHTML);
+    setTargetHistoryToken(targetHistoryToken);
+  }
+
+  /**
+   * Creates a hyperlink with its text and target history token specified.
+   *
+   * @param text the hyperlink's text
+   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   * @param targetHistoryToken the history token to which it will link
+   * @see #setTargetHistoryToken
+   */
+  private InlineHyperlink(String text, boolean asHTML,
+      DirectionEstimator directionEstimator, String targetHistoryToken) {
+    this();
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
+    directionalTextHelper.setTextOrHtml(text, asHTML);
+    setTargetHistoryToken(targetHistoryToken);
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/InlineLabel.java b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
index 75bafab..5a33856 100644
--- a/user/src/com/google/gwt/user/client/ui/InlineLabel.java
+++ b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
@@ -18,12 +18,23 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 
+
+import com.google.gwt.i18n.shared.DirectionEstimator;
+
 /**
  * A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
  *
  * This widget uses a &lt;span&gt; element, causing it to be displayed with
  * inline layout.
  *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-InlineLabel { }</li>
@@ -85,6 +96,20 @@
   }
 
   /**
+   * Creates a label with the specified text and a default direction estimator.
+   *
+   * @param text the new label's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public InlineLabel(String text, DirectionEstimator directionEstimator) {
+    this();
+    setDirectionEstimator(directionEstimator);
+    setText(text);
+  }
+  
+  /**
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be either a &lt;div&gt; &lt;span&gt; element.
    *
diff --git a/user/src/com/google/gwt/user/client/ui/Label.java b/user/src/com/google/gwt/user/client/ui/Label.java
index b689c3b..ab4792e 100644
--- a/user/src/com/google/gwt/user/client/ui/Label.java
+++ b/user/src/com/google/gwt/user/client/ui/Label.java
@@ -42,10 +42,8 @@
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.i18n.shared.BidiFormatter;
 import com.google.gwt.i18n.shared.DirectionEstimator;
 import com.google.gwt.i18n.shared.HasDirectionEstimator;
-import com.google.gwt.i18n.shared.WordCountDirectionEstimator;
 
 /**
  * A widget that contains arbitrary text, <i>not</i> interpreted as HTML.
@@ -53,6 +51,14 @@
  * This widget uses a &lt;div&gt; element, causing it to be displayed with block
  * layout.
  *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to
+ * its content. This feature is controlled by {@link #setDirectionEstimator} or
+ * passing a DirectionEstimator parameter to the constructor, and is off by
+ * default.
+ * </p>
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-Label { }</li>
@@ -69,6 +75,9 @@
     SourcesMouseEvents, HasAllMouseHandlers, HasDirectionEstimator,
     HasAutoHorizontalAlignment, IsEditor<LeafValueEditor<String>> {
 
+  public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR =
+      DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR;
+
   /**
    * Creates a Label widget that wraps an existing &lt;div&gt; or &lt;span&gt;
    * element.
@@ -93,53 +102,16 @@
   }
 
   /**
+   * The widget's DirectionalTextHelper object.
+   */
+  final DirectionalTextHelper directionalTextHelper;
+
+  /**
    * The widget's auto horizontal alignment policy.
    * @see HasAutoHorizontalAlignment
    */
   private AutoHorizontalAlignmentConstant autoHorizontalAlignment;
-
-  /**
-   * The direction of the widget's content.
-   * Note: this may not match the direction of the widget's top DOM element
-   * ({@code getElement()}).
-   * See {@link #setText(String, Direction)} for details.
-   */
-  Direction textDir;
-
-  /**
-   * The widget's DirectionEstimator object.
-   */
-  DirectionEstimator directionEstimator;
-
-  /**
-   * The initial direction of the widget's element.
-   */
-  Direction initialElementDir;
-
-  /**
-   * Whether the widget is inline (a &lt;span&gt; element).
-   * This is needed because direction is handled differently for inline elements
-   * and for non-inline elements.
-   * <p>
-   * In case Label supports types of elements other than span and div, this
-   * should get true for any element that is inline by default. Another approach
-   * could be calculating the element's display property, but this may have some
-   * overhead, and is problematic when the element is yet unattached.
-   */
-  boolean isElementInline;
-
-  /**
-   * Whether the widget contains a nested &lt;span&gt; element used to
-   * indicate the content's direction.
-   * <p>
-   * The widget's top element is used for this purpose when it is a &lt;div&gt;,
-   * but doing so on an inline element often results in garbling what follows
-   * it. Thus, when the widget's top element is a &lt;span&gt;, a nested
-   * &lt;span&gt; must be used to carry the content's direction, with an LRM or
-   * RLM character afterwards to prevent the garbling.
-   */
-  boolean isSpanWrapped;
-
+  
   /**
    * The widget's horizontal alignment.
    */
@@ -151,10 +123,7 @@
   public Label() {
     setElement(Document.get().createDivElement());
     setStyleName("gwt-Label");
-    isElementInline = false;
-    isSpanWrapped = false;
-    textDir = Direction.DEFAULT;
-    initialElementDir = Direction.DEFAULT;
+    directionalTextHelper = new DirectionalTextHelper(getElement(), false);
   }
 
   /**
@@ -168,6 +137,32 @@
   }
 
   /**
+   * Creates a label with the specified text and direction.
+   * 
+   * @param text the new label's text
+   * @param dir the text's direction. Note that {@code DEFAULT} means direction
+   *          should be inherited from the widget's parent element.
+   */
+  public Label(String text, Direction dir) {
+    this();
+    setText(text, dir);
+  }
+
+  /**
+   * Creates a label with the specified text and a default direction estimator.
+   * 
+   * @param text the new label's text
+   * @param directionEstimator A DirectionEstimator object used for automatic
+   *          direction adjustment. For convenience,
+   *          {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used.
+   */
+  public Label(String text, DirectionEstimator directionEstimator) {
+    this();
+    setDirectionEstimator(directionEstimator);
+    setText(text);
+  }
+
+  /**
    * Creates a label with the specified text.
    *
    * @param text the new label's text
@@ -179,18 +174,6 @@
   }
 
   /**
-   * Creates a label with the specified text and direction.
-   *
-   * @param text the new label's text
-   * @param dir the text's direction. Note that {@code DEFAULT} means direction
-   *        should be inherited from the widget's parent element.
-   */
-  public Label(String text, Direction dir) {
-    this();
-    setText(text, dir);
-  }
-
-  /**
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be either a &lt;div&gt; or &lt;span&gt; element.
    *
@@ -199,11 +182,10 @@
   protected Label(Element element) {
     setElement(element);
     String tagName = element.getTagName();
-    isElementInline = tagName.equalsIgnoreCase("span");
+    boolean isElementInline = tagName.equalsIgnoreCase("span");
     assert isElementInline || tagName.equalsIgnoreCase("div");
-    isSpanWrapped = false;
-    initialElementDir = BidiUtils.getDirectionOnElement(element);
-    textDir = initialElementDir;
+    directionalTextHelper = new DirectionalTextHelper(getElement(),
+        isElementInline);
   }
 
   public HandlerRegistration addClickHandler(ClickHandler handler) {
@@ -285,7 +267,7 @@
   }
 
   public DirectionEstimator getDirectionEstimator() {
-    return directionEstimator;
+    return directionalTextHelper.getDirectionEstimator();
   }
 
   /**
@@ -296,13 +278,11 @@
   }
 
   public String getText() {
-    Element element = isSpanWrapped ? getElement().getFirstChildElement()
-        : getElement();
-    return element.getInnerText();
+    return directionalTextHelper.getTextOrHtml(false);
   }
 
   public Direction getTextDirection() {
-    return textDir;
+    return directionalTextHelper.getTextDirection();
   }
 
   public boolean getWordWrap() {
@@ -352,14 +332,7 @@
    */
   @Deprecated
   public void setDirection(Direction direction) {
-    BidiUtils.setDirectionOnElement(getElement(), direction);
-    initialElementDir = direction;
-
-    // For backwards compatibility, assure there's no span wrap, and update the
-    // content direction.
-    getElement().setInnerText(getText());
-    isSpanWrapped = false;
-    textDir = initialElementDir;
+    directionalTextHelper.setDirection(direction);
     updateHorizontalAlignment();
   }
 
@@ -369,21 +342,22 @@
    * See note at {@link #setDirectionEstimator(DirectionEstimator)}.
    */
   public void setDirectionEstimator(boolean enabled) {
-    setDirectionEstimator(enabled ? WordCountDirectionEstimator.get() : null);
+    directionalTextHelper.setDirectionEstimator(enabled);
+    updateHorizontalAlignment();
   }
 
   /**
    * {@inheritDoc}
    * <p>
-   * Note: if the widget already has non-empty content, this will update
-   * its direction according to the new estimator's result. This may cause
-   * flicker, and thus should be avoided; DirectionEstimator should be set
-   * before the widget has any content.
+   * Note: DirectionEstimator should be set before the widget has any content;
+   * it's highly recommended to set it using a constructor. Reason: if the
+   * widget already has non-empty content, this will update its direction
+   * according to the new estimator's result. This may cause flicker, and thus
+   * should be avoided.
    */
   public void setDirectionEstimator(DirectionEstimator directionEstimator) {
-    this.directionEstimator = directionEstimator;
-    // Refresh appearance
-    setText(getText());
+    directionalTextHelper.setDirectionEstimator(directionEstimator);
+    updateHorizontalAlignment();
   }
 
   /**
@@ -406,27 +380,13 @@
    * Doesn't change the widget's direction or horizontal alignment if {@code
    * directionEstimator} is null. Otherwise, the widget's direction is set using
    * the estimator, and its alignment may therefore change as described in
-   * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction)
-   * setText(String, Direction)}.
+   * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) setText(String, Direction)}.
    * 
    * @param text the widget's new text
    */
   public void setText(String text) {
-    if (directionEstimator == null) {
-      isSpanWrapped = false;
-      getElement().setInnerText(text);
-
-      // Preserves the initial direction of the widget. This is different from
-      // passing the direction parameter explicitly as DEFAULT, which forces the
-      // widget to inherit the direction from its parent.
-      if (textDir != initialElementDir) {
-        textDir = initialElementDir;
-        BidiUtils.setDirectionOnElement(getElement(), initialElementDir);
-        updateHorizontalAlignment();
-      }
-    } else {
-      setText(text, directionEstimator.estimateDirection(text, false));
-    }
+    directionalTextHelper.setTextOrHtml(text, false);
+    updateHorizontalAlignment();
   }
 
   /**
@@ -450,20 +410,7 @@
    *        direction should be inherited from the widget's parent element.
    */
   public void setText(String text, Direction dir) {
-    textDir = dir;
-    
-    // Set the text and the direction.
-    if (isElementInline) {
-      isSpanWrapped = true;
-      getElement().setInnerHTML(BidiFormatter.getInstanceForCurrentLocale(
-          true /* alwaysSpan */).spanWrapWithKnownDir(dir, text, false));
-    } else {
-      isSpanWrapped = false;
-      BidiUtils.setDirectionOnElement(getElement(), dir);
-      getElement().setInnerText(text);
-    }
-
-    // Update the horizontal alignment if needed.
+    directionalTextHelper.setTextOrHtml(text, dir, false);
     updateHorizontalAlignment();
   }
 
@@ -474,7 +421,9 @@
 
   /**
    * Sets the horizontal alignment of the widget according to the current
-   * AutoHorizontalAlignment setting.
+   * AutoHorizontalAlignment setting. Should be invoked whenever the horizontal
+   * alignment may be affected, i.e. on every modification of the content or its
+   * direction.
    */
   protected void updateHorizontalAlignment() {
     HorizontalAlignmentConstant align;
@@ -486,8 +435,8 @@
       /* autoHorizontalAlignment is a truly automatic policy, i.e. either
       ALIGN_CONTENT_START or ALIGN_CONTENT_END */
       align = autoHorizontalAlignment == ALIGN_CONTENT_START ?
-          HorizontalAlignmentConstant.startOf(textDir) :
-          HorizontalAlignmentConstant.endOf(textDir);
+          HorizontalAlignmentConstant.startOf(getTextDirection()) :
+          HorizontalAlignmentConstant.endOf(getTextDirection());
     }
 
     if (align != horzAlign) {
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 200e004..02b6946 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -45,6 +45,7 @@
 import com.google.gwt.user.client.ui.DefaultSuggestionDisplayTest;
 import com.google.gwt.user.client.ui.DelegatingKeyboardListenerCollectionTest;
 import com.google.gwt.user.client.ui.DialogBoxTest;
+import com.google.gwt.user.client.ui.DirectionalTextHelperTest;
 import com.google.gwt.user.client.ui.DisclosurePanelTest;
 import com.google.gwt.user.client.ui.DockLayoutPanelRtlTest;
 import com.google.gwt.user.client.ui.DockLayoutPanelTest;
@@ -147,6 +148,7 @@
     suite.addTestSuite(DefaultSuggestionDisplayTest.class);
     suite.addTestSuite(DelegatingKeyboardListenerCollectionTest.class);
     suite.addTestSuite(DialogBoxTest.class);
+    suite.addTestSuite(DirectionalTextHelperTest.class);
     suite.addTestSuite(DisclosurePanelTest.class);
     suite.addTestSuite(DockLayoutPanelRtlTest.class);
     suite.addTestSuite(DockLayoutPanelTest.class);
@@ -220,4 +222,3 @@
     return suite;
   }
 }
-
diff --git a/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java b/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java
new file mode 100644
index 0000000..a5e9561
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.i18n.shared.AnyRtlDirectionEstimator;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests {@link DirectionalTextHelper}.
+ */
+public class DirectionalTextHelperTest extends GWTTestCase {
+
+  private final String EN_TEXT = "abc";
+  private final String IW_TEXT = "\u05e0\u05e1\u05e2";
+  private final String EN_HTML = "<b style=\"color: red\">" + EN_TEXT + "</b>";
+  private final String IW_HTML = "<b style=\"color: red\">" + IW_TEXT + "</b>";
+  private Element element;
+  private DirectionalTextHelper directionalTextHelper;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
+  // setDirection is deprecated; this only assures backwards compatibility.
+  public void testSetDirection() {
+    element = Document.get().createSpanElement();
+    directionalTextHelper = new DirectionalTextHelper(element,
+        /* is inline? */ true);
+
+    directionalTextHelper.setDirection(Direction.RTL);
+    assertDirection("element's direction is incorrect after setDirection",
+        Direction.RTL);
+
+    directionalTextHelper.setTextOrHtml(EN_TEXT, Direction.LTR, false);
+    assertDirection("target's direction is incorrect after setText with a" +
+        "specific direction", Direction.LTR);
+
+    directionalTextHelper.setTextOrHtml(EN_TEXT, false);
+    assertDirection("target's direction wasn't reverted to the direction set" +
+        "by last setDirection when calling setText with no direction argument" +
+        "and without a directionEstimator", Direction.RTL);
+
+    // We also specifically assert that the direction of the topmost element
+    // matches the last setDirection. (this is needed only for inline elements).
+    assertEquals("element's direction does not match the direction set by " +
+        "last setDirection when calling setText with no direction argument " +
+        "and without a directionEstimator", Direction.RTL,
+        BidiUtils.getDirectionOnElement(element));
+  }
+
+  public void testSetDirectionEstimator() {
+    element = Document.get().createSpanElement();
+    BidiUtils.setDirectionOnElement(element, Direction.LTR);
+    directionalTextHelper = new DirectionalTextHelper(element,
+        /* is inline? */ true);
+    directionalTextHelper.setDirectionEstimator(true);
+    
+    // If the element is span-wrapped, a redundant refresh occurred.
+    assertFalse("setDirectionEstimator(true) refreshed appearance before text" +
+        "had been received", isSpanWrapped());
+    
+    directionalTextHelper.setDirectionEstimator(false);
+    directionalTextHelper.setTextOrHtml(IW_TEXT, false);
+    assertDirection("Original element's direction (LTR) was modified with no" +
+        "apparent reason", Direction.LTR);
+
+    directionalTextHelper.setDirectionEstimator(true);
+    assertDirection("Direction was not refreshed on " +
+        "setDirectionEstimator(true) after receiving text with no explicit " +
+        "direction", Direction.RTL);
+    
+    directionalTextHelper.setTextOrHtml(IW_TEXT, Direction.LTR, false);
+    directionalTextHelper.setDirectionEstimator(
+        AnyRtlDirectionEstimator.get());
+    assertDirection("Direction was refreshed on setDirectionEstimator after " +
+        "receiving text with explicit direction", Direction.LTR);
+    
+    directionalTextHelper.setTextOrHtml(IW_TEXT, false);
+    directionalTextHelper.setDirectionEstimator(false);
+    assertDirection("Direction was not reset to the initial element direction" +
+        " on turning off direction estimation when last call to setTextOrHtml" +
+        " did not declare explicit direction.", Direction.LTR);
+  }
+
+  public void testSetDirectionEstimatorAndSetHtml() {
+    testSetTextOrHtml(true);
+  }
+
+  public void testSetDirectionEstimatorAndSetText() {
+    testSetTextOrHtml(false);
+  }
+
+  /**
+   * Asserts that both the {@link HasDirectionalText#getTextDirection} and the
+   * physical dir attribute match the expected direction.
+   *
+   * @param message Assertion message
+   * @param expected Expected direction
+   */
+  private void assertDirection(String message, Direction expected) {
+    assertTrue("dir attribute mismatch: " + message,
+        expected == getElementDirection() ||
+        /* For inline elements, empty dir attribute is acceptable if LTR is
+         * expected and the locale is not RTL. */
+        isSpanWrapped() && getElementDirection() == Direction.DEFAULT &&
+        (expected == Direction.RTL) == LocaleInfo.getCurrentLocale().isRTL());
+
+    assertEquals("textDir mismatch: " + message, expected,
+        directionalTextHelper.getTextDirection());
+  }
+
+  private Direction getElementDirection() {
+    Element elem = isSpanWrapped() ? element.getFirstChildElement() : element;
+    return BidiUtils.getDirectionOnElement(elem);
+  }
+
+  // This will not work generally. It assumes that the widget's content isn't
+  // consist of a span tag.
+  private boolean isSpanWrapped() {
+    Element inner = element.getFirstChildElement();
+    return inner != null && inner.getTagName().equalsIgnoreCase("span");
+  }
+
+  private void testSetTextOrHtml(boolean isHtml) {
+    String enContent = isHtml ? EN_HTML : EN_TEXT;
+    String iwContent = isHtml ? IW_HTML : IW_TEXT;
+    for (int i = 0; i < 2; i++) {
+      boolean isDiv = i == 0;
+      String id = isDiv ? "div widget: " : "span widget: ";
+      element = isDiv ? Document.get().createDivElement() :
+          Document.get().createSpanElement();
+      directionalTextHelper = new DirectionalTextHelper(element,
+          /* is inline? */ !isDiv);
+
+      directionalTextHelper.setTextOrHtml(enContent, isHtml);
+      assertDirection(id + "widget's direction is not DEFAULT upon " +
+          "standard initialization", Direction.DEFAULT);
+
+      directionalTextHelper.setTextOrHtml(iwContent, Direction.RTL, isHtml);
+      assertDirection(id + "widget's direction is not RTL after it was" +
+          " explicitly set to RTL", Direction.RTL);
+
+      directionalTextHelper.setTextOrHtml(enContent, isHtml);
+      assertDirection(id + "widget's direction was not specified, and no" +
+          " estimator specified, thus should return to initial value (DEFAULT)",
+          Direction.DEFAULT);
+
+      // Toggling on direction estimation from now on.    
+      directionalTextHelper.setDirectionEstimator(true);
+      
+      assertDirection(id + "widget's direction wasn't instantly updated" +
+          " to LTR on switching direction estimation on", Direction.LTR);
+
+      directionalTextHelper.setTextOrHtml(iwContent, isHtml);
+      assertDirection(id + "widget's direction wasn't estimated as RTL",
+          Direction.RTL);
+
+      directionalTextHelper.setTextOrHtml(iwContent, Direction.LTR, isHtml);
+      assertDirection(id + "widget's direction is not LTR after it was" +
+          " explicitly set to LTR (direction estimation is on)", Direction.LTR);
+
+      directionalTextHelper.setTextOrHtml(iwContent, Direction.DEFAULT, isHtml);
+      assertDirection(id + "widget's direction is not DEFAULT after it" +
+          " was explicitly set to DEFAULT (direction estimation is on)",
+          Direction.DEFAULT);
+
+      // TODO(jlabanca): Need a cross-browser way to test innerHTML.
+      // assertEquals(id + "retreived html is incorrect", iwContent,
+      //     directionalTextHelper.getTextOrHtml(true).toLowerCase());
+      assertEquals(id + "retreived text is incorrect", IW_TEXT,
+          directionalTextHelper.getTextOrHtml(false).toLowerCase());
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLTest.java b/user/test/com/google/gwt/user/client/ui/HTMLTest.java
index bdb50b9..128f9bb 100644
--- a/user/test/com/google/gwt/user/client/ui/HTMLTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HTMLTest.java
@@ -15,10 +15,7 @@
  */
 package com.google.gwt.user.client.ui;
 
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection.Direction;
-import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 
 /**
@@ -28,9 +25,6 @@
 public class HTMLTest extends LabelTest {
 
   private static final String html = "<b>hello</b><i>world</i>";
-  private final String EN_HTML = "<b style=\"color: red\">" + EN_TEXT + "</b>";
-  private final String IW_HTML = "<b style=\"color: red\">" + IW_TEXT + "</b>";
-  private HTML label;
 
   @Override
   public String getModuleName() {
@@ -65,43 +59,6 @@
     assertEquals(Direction.RTL, htmlElementRTL.getTextDirection());
   }
 
-  // setDirection is deprecated; this only assures backwards compatibility.
-  public void testSetDirection() {
-    for (int i = 0; i < 2; i++) {
-      String id = i == 0 ? "div label: " : "span label: ";
-      label = HTML.wrap(i == 0 ? createAttachedDivElement() :
-          createAttachedSpanElement());
-      label.setDirection(Direction.RTL);
-      assertLabelDirection(id + "label's direction is incorrect after " +
-          "setDirection", Direction.RTL);
-
-      label.setText(EN_TEXT, Direction.LTR);
-      assertLabelDirection(id + "label's direction is incorrect after " +
-          "setText with a specific direction", Direction.LTR);
-
-      label.setText(EN_TEXT);
-      assertLabelDirection(id + "label's direction wasn't reverted to the " +
-          "direction set by last setDirection when calling setText with no " +
-          "direction argument and without a directionEstimator", Direction.RTL);
-      if (i == 1) {
-        // For span element, we also specifically assert that the direction of
-        // the topmost element matches the last setDirection.
-        assertEquals(id + "element's direction does not match the direction " +
-            "set by last setDirection when calling setText with no direction " +
-            "argument and without a directionEstimator", Direction.RTL,
-            BidiUtils.getDirectionOnElement(label.getElement()));
-      }
-    }
-  }
-
-  public void testSetDirectionEstimatorAndSetHtml() {
-    testSetDirectionEstimatorAndSetTextOrHtml(true);
-  }
-
-  public void testSetDirectionEstimatorAndSetText() {
-    testSetDirectionEstimatorAndSetTextOrHtml(false);
-  }
-
   public void testSetSafeHtml() {
     HTML htmlElement = new HTML("<b>foo</b>");
     htmlElement.setHTML(SafeHtmlUtils.fromSafeConstant(html));
@@ -129,99 +86,4 @@
     html2.setText("<b>foo</b>", Direction.RTL);
     assertEquals("<b>foo</b>", html2.getText());
   }
-
-  /**
-   * Asserts that both the {@link Label#getTextDirection} and the physical dir
-   * attribute match the expected direction.
-   *
-   * @param message Assertion message
-   * @param expected Expected direction
-   */
-  private void assertLabelDirection(String message, Direction expected) {
-    assertTrue("attribute mismatch: " + message,
-        expected == getLabelDirection() ||
-        /* For inline elements, empty dir attribute is acceptable if LTR is
-         * expected and the locale is not RTL. */
-        isSpanWrapped() && getLabelDirection() == Direction.DEFAULT &&
-        expected == Direction.LTR && !LocaleInfo.getCurrentLocale().isRTL());
-
-    assertEquals("textDir mismatch: " + message, expected,
-        label.getTextDirection());
-  }
-
-  private Direction getLabelDirection() {
-    Element element = isSpanWrapped() ?
-        label.getElement().getFirstChildElement() : label.getElement();
-
-    return BidiUtils.getDirectionOnElement(element);
-  }
-
-  // This will not work generally. It assumes that the label's content isn't
-  // consist of a span tag.
-  private boolean isSpanWrapped() {
-    Element inner = label.getElement().getFirstChildElement();
-    return inner != null && inner.getTagName().equalsIgnoreCase("span");
-  }
-
-  private void setLabelTextOrHtml(String content, boolean isHtml) {
-    if (isHtml) {
-      label.setHTML(content);
-    } else {
-      label.setText(content);
-    }
-  }
-
-  private void setLabelTextOrHtml(String content, Direction dir, boolean isHtml) {
-    if (isHtml) {
-      label.setHTML(content, dir);
-    } else {
-      label.setText(content, dir);
-    }
-  }
-
-  private void testSetDirectionEstimatorAndSetTextOrHtml(boolean isHtml) {
-    String enContent = isHtml ? EN_HTML : EN_TEXT;
-    String iwContent = isHtml ? IW_HTML : IW_TEXT;
-    for (int i = 0; i < 2; i++) {
-      String id = i == 0 ? "div label: " : "span label: ";
-      label = HTML.wrap(i == 0 ? createAttachedDivElement() :
-          createAttachedSpanElement());
-
-      setLabelTextOrHtml(enContent, isHtml);
-      assertLabelDirection(id + "label's direction is not DEFAULT upon " +
-          "standard initialization", Direction.DEFAULT);
-
-      setLabelTextOrHtml(iwContent, Direction.RTL, isHtml);
-      assertLabelDirection(id + "label's direction is not RTL after it was" +
-          " explicitly set to RTL", Direction.RTL);
-
-      setLabelTextOrHtml(enContent, isHtml);
-      assertLabelDirection(id + "label's direction was not specified, and no" +
-          " estimator specified, thus should return to initial value (DEFAULT)",
-          Direction.DEFAULT);
-
-      label.setDirectionEstimator(true);
-      assertLabelDirection(id + "label's direction wasn't instantly updated" +
-          " to LTR on switching direction estimation on", Direction.LTR);
-
-      setLabelTextOrHtml(iwContent, isHtml);
-      assertLabelDirection(id + "label's direction wasn't estimated as RTL",
-          Direction.RTL);
-
-      setLabelTextOrHtml(iwContent, Direction.LTR, isHtml);
-      assertLabelDirection(id + "label's direction is not LTR after it was" +
-          " explicitly set to LTR (direction estimation is on)", Direction.LTR);
-
-      setLabelTextOrHtml(iwContent, Direction.DEFAULT, isHtml);
-      assertLabelDirection(id + "label's direction is not DEFAULT after it" +
-          " was explicitly set to DEFAULT (direction estimation is on)",
-          Direction.DEFAULT);
-
-      // TODO(jlabanca): Need a cross-browser way to test innerHTML.
-      // assertEquals(id + "retreived html is incorrect", iwContent,
-      //     label.getHTML().toLowerCase());
-      assertEquals(id + "retreived text is incorrect", IW_TEXT,
-          label.getText().toLowerCase());
-    }
-  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/LabelTest.java b/user/test/com/google/gwt/user/client/ui/LabelTest.java
index 98276d0..43e2879 100644
--- a/user/test/com/google/gwt/user/client/ui/LabelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/LabelTest.java
@@ -22,6 +22,7 @@
 import com.google.gwt.i18n.client.BidiUtils;
 import com.google.gwt.i18n.client.HasDirection.Direction;
 import com.google.gwt.junit.client.GWTTestCase;
+
 import com.google.gwt.user.client.ui.HasHorizontalAlignment.AutoHorizontalAlignmentConstant;
 import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
 
@@ -91,7 +92,7 @@
     // Initialize the div with a specific direction, to verify it remembers its
     // original direction on turning direction estimator off.
     BidiUtils.setDirectionOnElement(elem, Direction.LTR);
-    label = Label.wrap(createAttachedDivElement());
+    label = Label.wrap(elem);
 
     label.setAutoHorizontalAlignment(
         HasAutoHorizontalAlignment.ALIGN_CONTENT_END);
@@ -117,32 +118,13 @@
         HasAutoHorizontalAlignment.ALIGN_CONTENT_START);
 
     label.setDirectionEstimator(false);
-    assertAlign("direction was supposed to be reset to the original " +
-        "ALIGN_LEFT after turning off direction estimator, and automatic " +
-        "horizontal alignment was to ALIGN_CONTENT_START",
+    assertAlign("horizontal alignment was supposed to be reset to the " +
+        "original ALIGN_LEFT after turning off direction estimator, and " +
+        "automatic horizontal alignment was to ALIGN_CONTENT_START",
         HasHorizontalAlignment.ALIGN_LEFT,
         HasAutoHorizontalAlignment.ALIGN_CONTENT_START);
   }
 
-  public void testSetDirection() {
-    Label label = new Label(createAttachedSpanElement());
-    label.setDirectionEstimator(true);
-    label.setText(IW_TEXT);
-
-    // Should be span wrapped.
-    assertTrue(label.getElement().getInnerHTML().toLowerCase().contains("span"));
-
-    // Should not be span wrapped.
-    label.setDirection(Direction.RTL);
-    assertEquals(Direction.RTL, label.getDirection());
-    assertFalse(label.getElement().getInnerHTML().toLowerCase().contains("span"));
-
-    // Should not be span wrapped.
-    label.setDirection(Direction.LTR);
-    assertEquals(Direction.LTR, label.getDirection());
-    assertFalse(label.getElement().getInnerHTML().toLowerCase().contains("span"));
-  }
-
   /**
    * Create a div and attach it to the {@link RootPanel}.
    *