Bug 312156 - Implement CSS3 text-overflow. r=roc
authorMats Palmgren <matspal@gmail.com>
Wed, 22 Jun 2011 20:11:48 +0200
changeset 71550 451c13333f149ae989e6f627fc5c258fa6577719
parent 71549 847fc92f36eeea85ef92cb4600b37240fde730d0
child 71551 e6d61d90c72fac4f79f7f54fafb488e4a93c71f1
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs312156
milestone7.0a1
Bug 312156 - Implement CSS3 text-overflow. r=roc
layout/base/nsCaret.cpp
layout/base/nsCaret.h
layout/base/nsDisplayItemTypes.h
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/Makefile.in
layout/generic/TextOverflow.cpp
layout/generic/TextOverflow.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/generic/nsHTMLContainerFrame.cpp
layout/generic/nsHTMLContainerFrame.h
layout/generic/nsTextFrame.h
layout/generic/nsTextFrameThebes.cpp
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -627,20 +627,40 @@ nsresult nsCaret::PrimeTimer()
 
     mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate,
                                       nsITimer::TYPE_REPEATING_SLACK);
   }
 
   return NS_OK;
 }
 
+void nsCaret::InvalidateTextOverflowBlock()
+{
+  // If the nearest block has a potential 'text-overflow' marker then
+  // invalidate it.
+  if (mLastContent) {
+    nsIFrame* caretFrame = mLastContent->GetPrimaryFrame();
+    if (caretFrame) {
+      nsIFrame* block = nsLayoutUtils::GetAsBlock(caretFrame) ? caretFrame :
+        nsLayoutUtils::FindNearestBlockAncestor(caretFrame);
+      if (block) {
+        const nsStyleTextReset* style = block->GetStyleTextReset();
+        if (style->mTextOverflow.mType != NS_STYLE_TEXT_OVERFLOW_CLIP) {
+          block->InvalidateOverflowRect();
+        }
+      }
+    }
+  }
+}
 
 //-----------------------------------------------------------------------------
 void nsCaret::StartBlinking()
 {
+  InvalidateTextOverflowBlock();
+
   if (mReadOnly) {
     // Make sure the one draw command we use for a readonly caret isn't
     // done until the selection is set
     DrawCaretAfterBriefDelay();
     return;
   }
   PrimeTimer();
 
@@ -654,16 +674,18 @@ void nsCaret::StartBlinking()
 
   DrawCaret(PR_TRUE);    // draw it right away
 }
 
 
 //-----------------------------------------------------------------------------
 void nsCaret::StopBlinking()
 {
+  InvalidateTextOverflowBlock();
+
   if (mDrawn)     // erase the caret if necessary
     DrawCaret(PR_TRUE);
 
   NS_ASSERTION(!mDrawn, "Caret still drawn after StopBlinking().");
   KillTimer();
 }
 
 PRBool
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -199,16 +199,20 @@ class nsCaret : public nsISelectionListe
 
 protected:
 
     void          KillTimer();
     nsresult      PrimeTimer();
 
     void          StartBlinking();
     void          StopBlinking();
+
+    // If the nearest block has a potential 'text-overflow' marker then
+    // invalidate it.
+    void          InvalidateTextOverflowBlock();
     
     PRBool        DrawAtPositionWithHint(nsIDOMNode* aNode,
                                          PRInt32 aOffset,
                                          nsFrameSelection::HINT aFrameHint,
                                          PRUint8 aBidiLevel,
                                          PRBool aInvalidate);
 
     struct Metrics {
--- a/layout/base/nsDisplayItemTypes.h
+++ b/layout/base/nsDisplayItemTypes.h
@@ -61,16 +61,17 @@ enum Type {
   TYPE_CHECKED_CHECKBOX,
   TYPE_CHECKED_RADIOBUTTON,
   TYPE_CLIP,
   TYPE_CLIP_ROUNDED_RECT,
   TYPE_COLUMN_RULE,
   TYPE_COMBOBOX_FOCUS,
   TYPE_EVENT_RECEIVER,
   TYPE_FIELDSET_BORDER_BACKGROUND,
+  TYPE_FORCEPAINTONSCROLL,
   TYPE_FRAMESET_BORDER,
   TYPE_FRAMESET_BLANK,
   TYPE_HEADER_FOOTER,
   TYPE_IMAGE,
   TYPE_LIST_FOCUS,
   TYPE_OPACITY,
   TYPE_OPTION_EVENT_GRABBER,
   TYPE_OUTLINE,
@@ -90,16 +91,17 @@ enum Type {
   TYPE_SVG_EVENT_RECEIVER,
   TYPE_TABLE_CELL_BACKGROUND,
   TYPE_TABLE_CELL_SELECTION,
   TYPE_TABLE_ROW_BACKGROUND,
   TYPE_TABLE_ROW_GROUP_BACKGROUND,
   TYPE_TABLE_BORDER_BACKGROUND,
   TYPE_TEXT,
   TYPE_TEXT_DECORATION,
+  TYPE_TEXT_OVERFLOW,
   TYPE_TEXT_SHADOW,
   TYPE_TRANSFORM,
   TYPE_VIDEO,
   TYPE_WRAP_LIST,
   TYPE_ZOOM,
   TYPE_EXCLUDE_GLASS_FRAME,
 
 #if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF)
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2740,8 +2740,25 @@ PRBool nsDisplaySVGEffects::TryMerge(nsD
   if (aItem->GetUnderlyingFrame()->GetContent() != mFrame->GetContent())
     return PR_FALSE;
   nsDisplaySVGEffects* other = static_cast<nsDisplaySVGEffects*>(aItem);
   mList.AppendToBottom(&other->mList);
   mBounds.UnionRect(mBounds,
     other->mBounds + other->mEffectsFrame->GetOffsetTo(mEffectsFrame));
   return PR_TRUE;
 }
+
+nsDisplayForcePaintOnScroll::nsDisplayForcePaintOnScroll(
+    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+  : nsDisplayItem(aBuilder, aFrame) {
+  MOZ_COUNT_CTOR(nsDisplayForcePaintOnScroll);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayForcePaintOnScroll::~nsDisplayForcePaintOnScroll() {
+  MOZ_COUNT_DTOR(nsDisplayForcePaintOnScroll);
+}
+#endif
+
+PRBool nsDisplayForcePaintOnScroll::IsVaryingRelativeToMovingFrame(
+         nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
+  return PR_TRUE;
+}
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -182,25 +182,25 @@ public:
    * @return the selection that painting should be restricted to (or nsnull
    * in the normal unrestricted case)
    */
   nsISelection* GetBoundingSelection() { return mBoundingSelection; }
   /**
    * @return the root of the display list's frame (sub)tree, whose origin
    * establishes the coordinate system for the display list
    */
-  nsIFrame* ReferenceFrame() { return mReferenceFrame; }
+  nsIFrame* ReferenceFrame() const { return mReferenceFrame; }
   /**
    * @return a point pt such that adding pt to a coordinate relative to aFrame
    * makes it relative to ReferenceFrame(), i.e., returns 
    * aFrame->GetOffsetToCrossDoc(ReferenceFrame()). The returned point is in
    * the appunits of aFrame. It may be optimized to be faster than
    * aFrame->GetOffsetToCrossDoc(ReferenceFrame()) (but currently isn't).
    */
-  nsPoint ToReferenceFrame(const nsIFrame* aFrame) {
+  nsPoint ToReferenceFrame(const nsIFrame* aFrame) const {
     return aFrame->GetOffsetToCrossDoc(ReferenceFrame());
   }
   /**
    * When building the display list, the scrollframe aFrame will be "ignored"
    * for the purposes of clipping, and its scrollbars will be hidden. We use
    * this to allow RenderOffscreen to render a whole document without beign
    * clipped by the viewport or drawing the viewport scrollbars.
    */
@@ -631,17 +631,17 @@ public:
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {}
   /**
    * @return the frame that this display item is based on. This is used to sort
    * items by z-index and content order and for some other uses. For some items
    * that wrap item lists, this could return nsnull because there is no single
    * underlying frame; for leaf items it will never return nsnull.
    */
-  inline nsIFrame* GetUnderlyingFrame() { return mFrame; }
+  inline nsIFrame* GetUnderlyingFrame() const { return mFrame; }
   /**
    * The default bounds is the frame border rect.
    * @return a rectangle relative to aBuilder->ReferenceFrame() that
    * contains the area drawn by this display item
    */
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) {
     return nsRect(ToReferenceFrame(), GetUnderlyingFrame()->GetSize());
   }
@@ -808,17 +808,17 @@ public:
    * -- Subtracts bounds from aVisibleRegion if the item is opaque
    */
   PRBool RecomputeVisibility(nsDisplayListBuilder* aBuilder,
                              nsRegion* aVisibleRegion);
 
   /**
    * Returns the result of aBuilder->ToReferenceFrame(GetUnderlyingFrame())
    */
-  const nsPoint& ToReferenceFrame() {
+  const nsPoint& ToReferenceFrame() const {
     NS_ASSERTION(mFrame, "No frame?");
     return mToReferenceFrame;
   }
 
   /**
    * Checks if this display item (or any children) contains content that might
    * be rendered with component alpha (e.g. subpixel antialiasing). Returns the
    * bounds of the area that needs component alpha, or an empty rect if nothing
@@ -2182,9 +2182,74 @@ public:
                                                const nsPoint& aOrigin,
                                                float aFactor,
                                                const nsRect* aBoundsOverride = nsnull);
 
 private:
   nsDisplayWrapList mStoredList;
 };
 
+/**
+ * This class adds basic support for limiting the rendering to the part inside
+ * the specified edges.  It's a base class for the display item classes that
+ * does the actual work.  The two members, mLeftEdge and mRightEdge, are
+ * relative to the edges of the frame's scrollable overflow rectangle and is
+ * the amount to suppress on each side.
+ *
+ * Setting none, both or only one edge is allowed.
+ * The values must be non-negative.
+ * The default value for both edges is zero, which means everything is painted.
+ */
+class nsCharClipDisplayItem : public nsDisplayItem {
+public:
+  nsCharClipDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+    : nsDisplayItem(aBuilder, aFrame), mLeftEdge(0), mRightEdge(0) {}
+
+  struct ClipEdges {
+    ClipEdges(const nsDisplayItem& aItem,
+              nscoord aLeftEdge, nscoord aRightEdge) {
+      nsRect r = aItem.GetUnderlyingFrame()->GetScrollableOverflowRect() +
+                 aItem.ToReferenceFrame();
+      mX = aLeftEdge > 0 ? r.x + aLeftEdge : nscoord_MIN;
+      mXMost = aRightEdge > 0 ? NS_MAX(r.XMost() - aRightEdge, mX) : nscoord_MAX;
+    }
+    void Intersect(nscoord* aX, nscoord* aWidth) const {
+      nscoord xmost1 = *aX + *aWidth;
+      *aX = NS_MAX(*aX, mX);
+      *aWidth = NS_MAX(NS_MIN(xmost1, mXMost) - *aX, 0);
+    }
+    nscoord mX;
+    nscoord mXMost;
+  };
+
+  ClipEdges Edges() const { return ClipEdges(*this, mLeftEdge, mRightEdge); }
+
+  static nsCharClipDisplayItem* CheckCast(nsDisplayItem* aItem) {
+    nsDisplayItem::Type t = aItem->GetType();
+    return (t == nsDisplayItem::TYPE_TEXT ||
+            t == nsDisplayItem::TYPE_TEXT_DECORATION ||
+            t == nsDisplayItem::TYPE_TEXT_SHADOW)
+      ? static_cast<nsCharClipDisplayItem*>(aItem) : nsnull;
+  }
+
+  nscoord mLeftEdge;  // length from the left side
+  nscoord mRightEdge; // length from the right side
+};
+
+
+/**
+ * This is a dummy item that reports true for IsVaryingRelativeToMovingFrame.
+ * It forces the bounds of its frame to be repainted every time it is scrolled.
+ * It is transparent to events and does not paint anything.
+ */
+class nsDisplayForcePaintOnScroll : public nsDisplayItem
+{
+public:
+  nsDisplayForcePaintOnScroll(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayForcePaintOnScroll();
+#endif
+  NS_DISPLAY_DECL_NAME("ForcePaintOnScroll", TYPE_FORCEPAINTONSCROLL)
+  virtual PRBool IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder,
+                                                nsIFrame* aFrame);
+};
+
 #endif /*NSDISPLAYLIST_H_*/
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -17,17 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
- *   Mats Palmgren <mats.palmgren@bredband.net>
+ *   Mats Palmgren <matspal@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -45,16 +45,17 @@
 #include "nsIDOMDocument.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsFrameList.h"
 #include "nsGkAtoms.h"
 #include "nsIAtom.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSAnonBoxes.h"
+#include "nsCSSColorUtils.h"
 #include "nsIView.h"
 #include "nsPlaceholderFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIDOMEvent.h"
 #include "nsGUIEvent.h"
 #include "nsDisplayList.h"
@@ -2785,16 +2786,59 @@ nsLayoutUtils::PrefWidthFromInline(nsIFr
 {
   nsIFrame::InlinePrefWidthData data;
   DISPLAY_PREF_WIDTH(aFrame, data.prevLines);
   aFrame->AddInlinePrefWidth(aRenderingContext, &data);
   data.ForceBreak(aRenderingContext);
   return data.prevLines;
 }
 
+static nscolor
+DarkenColor(nscolor aColor)
+{
+  PRUint16  hue, sat, value;
+  PRUint8 alpha;
+
+  // convert the RBG to HSV so we can get the lightness (which is the v)
+  NS_RGB2HSV(aColor, hue, sat, value, alpha);
+
+  // The goal here is to send white to black while letting colored
+  // stuff stay colored... So we adopt the following approach.
+  // Something with sat = 0 should end up with value = 0.  Something
+  // with a high sat can end up with a high value and it's ok.... At
+  // the same time, we don't want to make things lighter.  Do
+  // something simple, since it seems to work.
+  if (value > sat) {
+    value = sat;
+    // convert this color back into the RGB color space.
+    NS_HSV2RGB(aColor, hue, sat, value, alpha);
+  }
+  return aColor;
+}
+
+// Check whether we should darken text colors. We need to do this if
+// background images and colors are being suppressed, because that means
+// light text will not be visible against the (presumed light-colored) background.
+static PRBool
+ShouldDarkenColors(nsPresContext* aPresContext)
+{
+  return !aPresContext->GetBackgroundColorDraw() &&
+    !aPresContext->GetBackgroundImageDraw();
+}
+
+nscolor
+nsLayoutUtils::GetTextColor(nsIFrame* aFrame)
+{
+  nscolor color = aFrame->GetVisitedDependentColor(eCSSProperty_color);
+  if (ShouldDarkenColors(aFrame->PresContext())) {
+    color = DarkenColor(color);
+  }
+  return color;
+}
+
 void
 nsLayoutUtils::DrawString(const nsIFrame*      aFrame,
                           nsRenderingContext* aContext,
                           const PRUnichar*     aString,
                           PRInt32              aLength,
                           nsPoint              aPoint,
                           PRUint8              aDirection)
 {
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -17,17 +17,17 @@
  * The Initial Developer of the Original Code is
  * Boris Zbarsky <bzbarsky@mit.edu>.
  * Portions created by the Initial Developer are Copyright (C) 2002
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Boris Zbarsky <bzbarsky@mit.edu> (original author)
  *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
- *   Mats Palmgren <mats.palmgren@bredband.net>
+ *   Mats Palmgren <matspal@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -926,16 +926,19 @@ public:
   // Implement nsIFrame::GetPrefWidth in terms of nsIFrame::AddInlinePrefWidth
   static nscoord PrefWidthFromInline(nsIFrame* aFrame,
                                      nsRenderingContext* aRenderingContext);
 
   // Implement nsIFrame::GetMinWidth in terms of nsIFrame::AddInlineMinWidth
   static nscoord MinWidthFromInline(nsIFrame* aFrame,
                                     nsRenderingContext* aRenderingContext);
 
+  // Get a suitable foreground color for painting text for the frame.
+  static nscolor GetTextColor(nsIFrame* aFrame);
+
   static void DrawString(const nsIFrame*      aFrame,
                          nsRenderingContext* aContext,
                          const PRUnichar*     aString,
                          PRInt32              aLength,
                          nsPoint              aPoint,
                          PRUint8              aDirection = NS_STYLE_DIRECTION_INHERIT);
 
   static nscoord GetStringWidth(const nsIFrame*      aFrame,
--- a/layout/generic/Makefile.in
+++ b/layout/generic/Makefile.in
@@ -104,16 +104,17 @@ CPPSRCS		= \
 		nsPageFrame.cpp \
 		nsPlaceholderFrame.cpp \
 		nsSelection.cpp \
 		nsSimplePageSequence.cpp \
 		nsSplittableFrame.cpp \
 		nsSubDocumentFrame.cpp \
 		nsTextFrameThebes.cpp \
 		nsTextFrameUtils.cpp \
+		TextOverflow.cpp \
 		nsTextRunTransformations.cpp \
 		nsViewportFrame.cpp \
 		$(NULL)
 
 ifdef MOZ_MEDIA
 CPPSRCS		+= \
 		nsVideoFrame.cpp \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/layout/generic/TextOverflow.cpp
@@ -0,0 +1,634 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is an implementation of CSS3 text-overflow.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mats Palmgren <matspal@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "TextOverflow.h"
+
+// Please maintain alphabetical order below
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsRenderingContext.h"
+#include "nsTextFrame.h"
+
+namespace mozilla {
+namespace css {
+
+static const PRUnichar kEllipsisChar[] = { 0x2026, 0x0 };
+static const PRUnichar kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 };
+
+// Return an ellipsis if the font supports it,
+// otherwise use three ASCII periods as fallback.
+static nsDependentString GetEllipsis(nsIFrame* aFrame)
+{
+  // Check if the first font supports Unicode ellipsis.
+  nsRefPtr<nsFontMetrics> fm;
+  nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
+  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+  gfxFont* firstFont = fontGroup->GetFontAt(0);
+  return firstFont && firstFont->HasCharacter(kEllipsisChar[0])
+    ? nsDependentString(kEllipsisChar,
+                        NS_ARRAY_LENGTH(kEllipsisChar) - 1)
+    : nsDependentString(kASCIIPeriodsChar,
+                        NS_ARRAY_LENGTH(kASCIIPeriodsChar) - 1);
+}
+
+static nsIFrame*
+GetSelfOrNearestBlock(nsIFrame* aFrame)
+{
+  return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame :
+         nsLayoutUtils::FindNearestBlockAncestor(aFrame);
+}
+
+// Return true if the frame is an atomic inline-level element.
+// It's not supposed to be called for block frames since we never
+// process block descendants for text-overflow.
+static bool
+IsAtomicElement(nsIFrame* aFrame, const nsIAtom* aFrameType)
+{
+  NS_PRECONDITION(!aFrame->GetStyleDisplay()->IsBlockOutside(),
+                  "unexpected block frame");
+
+  if (aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
+    if (aFrameType != nsGkAtoms::textFrame &&
+        aFrameType != nsGkAtoms::brFrame) {
+      return true;
+    }
+  }
+  return aFrame->GetStyleDisplay()->mDisplay != NS_STYLE_DISPLAY_INLINE;
+}
+
+static bool
+IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
+               nscoord* aSnappedLeft, nscoord* aSnappedRight)
+{
+  *aSnappedLeft = aLeft;
+  *aSnappedRight = aRight;
+  if (aLeft <= 0 && aRight <= 0) {
+    return false;
+  }
+  nsRefPtr<nsRenderingContext> rc =
+    aFrame->PresContext()->PresShell()->GetReferenceRenderingContext();
+  return rc &&
+    !aFrame->MeasureCharClippedText(rc->ThebesContext(), aLeft, aRight,
+                                    aSnappedLeft, aSnappedRight);
+}
+
+static bool
+IsHorizontalOverflowVisible(nsIFrame* aFrame)
+{
+  NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nsnull,
+                  "expected a block frame");
+
+  nsIFrame* f = aFrame;
+  while (f && f->GetStyleContext()->GetPseudo()) {
+    f = f->GetParent();
+  }
+  return !f || f->GetStyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE;
+}
+
+static nsDisplayItem*
+ClipMarker(nsDisplayListBuilder* aBuilder,
+           nsIFrame*             aFrame,
+           nsDisplayItem*        aMarker,
+           const nsRect&         aContentArea,
+           nsRect*               aMarkerRect)
+{
+  nsDisplayItem* item = aMarker;
+  nscoord rightOverflow = aMarkerRect->XMost() - aContentArea.XMost();
+  if (rightOverflow > 0) {
+    // Marker overflows on the right side (content width < marker width).
+    aMarkerRect->width -= rightOverflow;
+    item = new (aBuilder)
+      nsDisplayClip(aBuilder, aFrame, aMarker, *aMarkerRect);
+  } else {
+    nscoord leftOverflow = aContentArea.x - aMarkerRect->x;
+    if (leftOverflow > 0) {
+      // Marker overflows on the left side
+      aMarkerRect->width -= leftOverflow;
+      aMarkerRect->x += leftOverflow;
+      item = new (aBuilder)
+        nsDisplayClip(aBuilder, aFrame, aMarker, *aMarkerRect);
+    }
+  }
+  return item;
+}
+
+static void
+InflateLeft(nsRect* aRect, bool aInfinity, nscoord aDelta)
+{
+  nscoord xmost = aRect->XMost();
+  if (aInfinity) {
+    aRect->x = nscoord_MIN;
+  } else {
+    aRect->x -= aDelta;
+  }
+  aRect->width = NS_MAX(xmost - aRect->x, 0);
+}
+
+static void
+InflateRight(nsRect* aRect, bool aInfinity, nscoord aDelta)
+{
+  if (aInfinity) {
+    aRect->width = nscoord_MAX;
+  } else {
+    aRect->width = NS_MAX(aRect->width + aDelta, 0);
+  }
+}
+
+static bool
+IsFrameDescendantOfAny(nsIFrame* aChild,
+                       const TextOverflow::FrameHashtable& aSetOfFrames,
+                       nsIFrame* aCommonAncestor)
+{
+  for (nsIFrame* f = aChild; f != aCommonAncestor;
+       f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
+    if (aSetOfFrames.GetEntry(f)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+class nsDisplayTextOverflowMarker : public nsDisplayItem
+{
+public:
+  nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+                              const nsRect& aRect, nscoord aAscent,
+                              const nsString& aString)
+    : nsDisplayItem(aBuilder, aFrame), mRect(aRect), mString(aString),
+      mAscent(aAscent) {
+    MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayTextOverflowMarker() {
+    MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
+  }
+#endif
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) {
+    return mRect;
+  }
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsRenderingContext* aCtx);
+  NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
+private:
+  nsRect          mRect;   // in reference frame coordinates
+  const nsString  mString; // the marker text
+  nscoord         mAscent; // baseline for the marker text in mRect
+};
+
+void
+nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
+                                   nsRenderingContext*   aCtx)
+{
+  nsStyleContext* sc = mFrame->GetStyleContext();
+  nsLayoutUtils::SetFontFromStyle(aCtx, sc);
+  aCtx->SetColor(nsLayoutUtils::GetTextColor(mFrame));
+  nsPoint baselinePt = mRect.TopLeft();
+  baselinePt.y += mAscent;
+  nsLayoutUtils::DrawString(mFrame, aCtx, mString.get(), mString.Length(),
+                            baselinePt);
+}
+
+/* static */ TextOverflow*
+TextOverflow::WillProcessLines(nsDisplayListBuilder*   aBuilder,
+                               const nsDisplayListSet& aLists,
+                               nsIFrame*               aBlockFrame)
+{
+  if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) {
+    return nsnull;
+  }
+
+  nsAutoPtr<TextOverflow> textOverflow(new TextOverflow);
+  textOverflow->mBuilder = aBuilder;
+  textOverflow->mBlock = aBlockFrame;
+  textOverflow->mMarkerList = aLists.PositionedDescendants();
+  textOverflow->mContentArea = aBlockFrame->GetContentRectRelativeToSelf();
+  nsIScrollableFrame* scroll =
+    nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
+  textOverflow->mCanHaveHorizontalScrollbar = false;
+  if (scroll) {
+    textOverflow->mCanHaveHorizontalScrollbar =
+      scroll->GetScrollbarStyles().mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
+    textOverflow->mContentArea.MoveBy(scroll->GetScrollPosition());
+  }
+  textOverflow->mBlockIsRTL =
+    aBlockFrame->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+  const nsStyleTextReset* style = aBlockFrame->GetStyleTextReset();
+  textOverflow->mLeft.Init(style->mTextOverflow);
+  textOverflow->mRight.Init(style->mTextOverflow);
+  // The left/right marker string is setup in ExamineLineFrames when a line
+  // has overflow on that side.
+
+  return textOverflow.forget();
+}
+
+void
+TextOverflow::DidProcessLines()
+{
+  nsIScrollableFrame* scroll = nsLayoutUtils::GetScrollableFrameFor(mBlock);
+  if (scroll) {
+    // Create a dummy item covering the entire area, it doesn't paint
+    // but reports true for IsVaryingRelativeToMovingFrame().
+    nsIFrame* scrollFrame = do_QueryFrame(scroll);
+    nsDisplayItem* marker = new (mBuilder)
+      nsDisplayForcePaintOnScroll(mBuilder, scrollFrame);
+    if (marker) {
+      mMarkerList->AppendNewToBottom(marker);
+      mBlock->PresContext()->SetHasFixedBackgroundFrame();
+    }
+  }
+}
+
+void
+TextOverflow::ExamineFrameSubtree(nsIFrame*       aFrame,
+                                  const nsRect&   aContentArea,
+                                  const nsRect&   aInsideMarkersArea,
+                                  FrameHashtable* aFramesToHide,
+                                  AlignmentEdges* aAlignmentEdges)
+{
+  const nsIAtom* frameType = aFrame->GetType();
+  if (frameType == nsGkAtoms::brFrame) {
+    return;
+  }
+  const bool isAtomic = IsAtomicElement(aFrame, frameType);
+  if (aFrame->GetStyleVisibility()->IsVisible()) {
+    nsRect childRect = aFrame->GetScrollableOverflowRect() +
+                       aFrame->GetOffsetTo(mBlock);
+    bool overflowLeft = childRect.x < aContentArea.x;
+    bool overflowRight = childRect.XMost() > aContentArea.XMost();
+    if (overflowLeft) {
+      mLeft.mHasOverflow = true;
+    }
+    if (overflowRight) {
+      mRight.mHasOverflow = true;
+    }
+    if (isAtomic && (overflowLeft || overflowRight)) {
+      aFramesToHide->PutEntry(aFrame);
+    } else if (isAtomic || frameType == nsGkAtoms::textFrame) {
+      AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
+                         aFramesToHide, aAlignmentEdges);
+    }
+  }
+  if (isAtomic) {
+    return;
+  }
+
+  nsIFrame* child = aFrame->GetFirstChild(nsnull);
+  while (child) {
+    ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
+                        aFramesToHide, aAlignmentEdges);
+    child = child->GetNextSibling();
+  }
+}
+
+void
+TextOverflow::AnalyzeMarkerEdges(nsIFrame*       aFrame,
+                                 const nsIAtom*  aFrameType,
+                                 const nsRect&   aInsideMarkersArea,
+                                 FrameHashtable* aFramesToHide,
+                                 AlignmentEdges* aAlignmentEdges)
+{
+  nsRect borderRect(aFrame->GetOffsetTo(mBlock), aFrame->GetSize());
+  nscoord leftOverlap =
+    NS_MAX(aInsideMarkersArea.x - borderRect.x, 0);
+  nscoord rightOverlap =
+    NS_MAX(borderRect.XMost() - aInsideMarkersArea.XMost(), 0);
+  bool insideLeftEdge = aInsideMarkersArea.x <= borderRect.XMost();
+  bool insideRightEdge = borderRect.x <= aInsideMarkersArea.XMost();
+
+  if ((leftOverlap > 0 && insideLeftEdge) ||
+      (rightOverlap > 0 && insideRightEdge)) {
+    if (aFrameType == nsGkAtoms::textFrame &&
+        aInsideMarkersArea.x < aInsideMarkersArea.XMost()) {
+      // a clipped text frame and there is some room between the markers
+      nscoord snappedLeft, snappedRight;
+      bool isFullyClipped =
+        IsFullyClipped(static_cast<nsTextFrame*>(aFrame),
+                       leftOverlap, rightOverlap, &snappedLeft, &snappedRight);
+      if (!isFullyClipped) {
+        nsRect snappedRect = borderRect;
+        if (leftOverlap > 0) {
+          snappedRect.x += snappedLeft;
+          snappedRect.width -= snappedLeft;
+        }
+        if (rightOverlap > 0) {
+          snappedRect.width -= snappedRight;
+        }
+        aAlignmentEdges->Accumulate(snappedRect);
+      }
+    } else if (IsAtomicElement(aFrame, aFrameType)) {
+      aFramesToHide->PutEntry(aFrame);
+    }
+  } else if (!insideLeftEdge || !insideRightEdge) {
+    // frame is outside
+    if (IsAtomicElement(aFrame, aFrameType)) {
+      aFramesToHide->PutEntry(aFrame);
+    }
+  } else {
+    // frame is inside
+    aAlignmentEdges->Accumulate(borderRect);
+  }
+}
+
+void
+TextOverflow::ExamineLineFrames(nsLineBox*      aLine,
+                                FrameHashtable* aFramesToHide,
+                                AlignmentEdges* aAlignmentEdges)
+{
+  // Scrolling to the end position can leave some text still overflowing due to
+  // pixel snapping behaviour in our scrolling code so we move the edges 1px
+  // outward to avoid triggering a text-overflow marker for such overflow.
+  nsRect contentArea = mContentArea;
+  const nscoord scrollAdjust = mCanHaveHorizontalScrollbar ?
+    mBlock->PresContext()->AppUnitsPerDevPixel() : 0;
+  InflateLeft(&contentArea,
+              mLeft.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP,
+              scrollAdjust);
+  InflateRight(&contentArea,
+               mRight.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP,
+               scrollAdjust);
+  nsRect lineRect = aLine->GetScrollableOverflowArea();
+  const bool leftOverflow = lineRect.x < contentArea.x;
+  const bool rightOverflow = lineRect.XMost() > contentArea.XMost();
+  if (!leftOverflow && !rightOverflow) {
+    // The line does not overflow - no need to traverse the frame tree.
+    return;
+  }
+
+  PRUint32 pass = 0;
+  bool guessLeft =
+    mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP && leftOverflow;
+  bool guessRight =
+    mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP && rightOverflow;
+  do {
+    // Setup marker strings as needed.
+    if (guessLeft || guessRight) {
+      mLeft.SetupString(mBlock);
+      mRight.mMarkerString = mLeft.mMarkerString;
+      mRight.mWidth = mLeft.mWidth;
+      mRight.mInitialized = mLeft.mInitialized;
+    }
+    
+    // If there is insufficient space for both markers then keep the one on the
+    // end side per the block's 'direction'.
+    nscoord rightMarkerWidth = mRight.mWidth;
+    nscoord leftMarkerWidth = mLeft.mWidth;
+    if (leftOverflow && rightOverflow &&
+        leftMarkerWidth + rightMarkerWidth > contentArea.width) {
+      if (mBlockIsRTL) {
+        rightMarkerWidth = 0;
+      } else {
+        leftMarkerWidth = 0;
+      }
+    }
+
+    // Calculate the area between the potential markers aligned at the
+    // block's edge.
+    nsRect insideMarkersArea = mContentArea;
+    InflateLeft(&insideMarkersArea, !guessLeft, -leftMarkerWidth);
+    InflateRight(&insideMarkersArea, !guessRight, -rightMarkerWidth);
+
+    // Analyze the frames on aLine for the overflow situation at the content
+    // edges and at the edges of the area between the markers.
+    PRInt32 n = aLine->GetChildCount();
+    nsIFrame* child = aLine->mFirstChild;
+    for (; n-- > 0; child = child->GetNextSibling()) {
+      ExamineFrameSubtree(child, contentArea, insideMarkersArea,
+                          aFramesToHide, aAlignmentEdges);
+    }
+    if (guessLeft == mLeft.IsNeeded() && guessRight == mRight.IsNeeded()) {
+      break;
+    } else {
+      guessLeft = mLeft.IsNeeded();
+      guessRight = mRight.IsNeeded();
+      mLeft.Reset();
+      mRight.Reset();
+      aFramesToHide->Clear();
+    }
+    NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
+  } while (++pass != 2);
+}
+
+void
+TextOverflow::ProcessLine(const nsDisplayListSet& aLists,
+                          nsLineBox*              aLine)
+{
+  NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
+               mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
+               "TextOverflow with 'clip' for both sides");
+  mLeft.Reset();
+  mRight.Reset();
+  FrameHashtable framesToHide;
+  if (!framesToHide.Init(100)) {
+    return;
+  }
+  AlignmentEdges alignmentEdges;
+  ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
+  bool needLeft = mLeft.IsNeeded();
+  bool needRight = mRight.IsNeeded();
+  if (!needLeft && !needRight) {
+    return;
+  }
+  NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
+               !needLeft, "left marker for 'clip'");
+  NS_ASSERTION(mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
+               !needRight, "right marker for 'clip'");
+
+  // If there is insufficient space for both markers then keep the one on the
+  // end side per the block's 'direction'.
+  if (needLeft && needRight &&
+      mLeft.mWidth + mRight.mWidth > mContentArea.width) {
+    if (mBlockIsRTL) {
+      needRight = false;
+    } else {
+      needLeft = false;
+    }
+  }
+  nsRect insideMarkersArea = mContentArea;
+  if (needLeft) {
+    InflateLeft(&insideMarkersArea, false, -mLeft.mWidth);
+  }
+  if (needRight) {
+    InflateRight(&insideMarkersArea, false, -mRight.mWidth);
+  }
+  if (!mCanHaveHorizontalScrollbar && alignmentEdges.mAssigned) {
+    nsRect alignmentRect = nsRect(alignmentEdges.x, insideMarkersArea.y,
+                                  alignmentEdges.Width(), 1);
+    insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
+  }
+
+  // Clip and remove display items as needed at the final marker edges.
+  nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() };
+  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(lists); ++i) {
+    PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
+  }
+  CreateMarkers(aLine, needLeft, needRight, insideMarkersArea);
+}
+
+void
+TextOverflow::PruneDisplayListContents(nsDisplayList*        aList,
+                                       const FrameHashtable& aFramesToHide,
+                                       const nsRect&         aInsideMarkersArea)
+{
+  nsDisplayList saved;
+  nsDisplayItem* item;
+  while ((item = aList->RemoveBottom())) {
+    nsIFrame* itemFrame = item->GetUnderlyingFrame();
+    if (itemFrame && IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
+      item->~nsDisplayItem();
+      continue;
+    }
+
+    nsDisplayList* wrapper = item->GetList();
+    if (wrapper) {
+      if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
+        PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
+      }
+    }
+
+    nsCharClipDisplayItem* charClip = itemFrame ? 
+      nsCharClipDisplayItem::CheckCast(item) : nsnull;
+    if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
+      nsRect rect = itemFrame->GetScrollableOverflowRect() +
+                    itemFrame->GetOffsetTo(mBlock);
+      if (mLeft.IsNeeded() && rect.x < aInsideMarkersArea.x) {
+        charClip->mLeftEdge = aInsideMarkersArea.x - rect.x;
+      }
+      if (mRight.IsNeeded() && rect.XMost() > aInsideMarkersArea.XMost()) {
+        charClip->mRightEdge = rect.XMost() - aInsideMarkersArea.XMost();
+      }
+    }
+
+    saved.AppendToTop(item);
+  }
+  aList->AppendToTop(&saved);
+}
+
+/* static */ bool
+TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
+                                  nsIFrame*             aBlockFrame)
+{
+  const nsStyleTextReset* style = aBlockFrame->GetStyleTextReset();
+  // Nothing to do for text-overflow:clip or if 'overflow-x:visible'
+  // or if we're just building items for event processing.
+  if ((style->mTextOverflow.mType == NS_STYLE_TEXT_OVERFLOW_CLIP) ||
+      IsHorizontalOverflowVisible(aBlockFrame) ||
+      aBuilder->IsForEventDelivery()) {
+    return false;
+  }
+
+  // Inhibit the markers if a descendant content owns the caret.
+  nsRefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret();
+  PRBool visible = PR_FALSE;
+  if (caret && NS_SUCCEEDED(caret->GetCaretVisible(&visible)) && visible) {
+    nsCOMPtr<nsISelection> domSelection = caret->GetCaretDOMSelection();
+    if (domSelection) {
+      nsCOMPtr<nsIDOMNode> node;
+      domSelection->GetFocusNode(getter_AddRefs(node));
+      nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+      if (content && nsContentUtils::ContentIsDescendantOf(content,
+                       aBlockFrame->GetContent())) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+void
+TextOverflow::CreateMarkers(const nsLineBox* aLine,
+                            bool             aCreateLeft,
+                            bool             aCreateRight,
+                            const nsRect&    aInsideMarkersArea) const
+{
+  if (aCreateLeft) {
+    nsRect markerRect = nsRect(aInsideMarkersArea.x - mLeft.mWidth,
+                               aLine->mBounds.y,
+                               mLeft.mWidth, aLine->mBounds.height);
+    markerRect += mBuilder->ToReferenceFrame(mBlock);
+    nsDisplayItem* marker = new (mBuilder)
+      nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
+                                  aLine->GetAscent(), mLeft.mMarkerString);
+    if (marker) {
+      marker = ClipMarker(mBuilder, mBlock, marker,
+                          mContentArea + mBuilder->ToReferenceFrame(mBlock),
+                          &markerRect);
+    }
+    mMarkerList->AppendNewToTop(marker);
+  }
+
+  if (aCreateRight) {
+    nsRect markerRect = nsRect(aInsideMarkersArea.XMost(),
+                               aLine->mBounds.y,
+                               mRight.mWidth, aLine->mBounds.height);
+    markerRect += mBuilder->ToReferenceFrame(mBlock);
+    nsDisplayItem* marker = new (mBuilder)
+      nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
+                                  aLine->GetAscent(), mRight.mMarkerString);
+    if (marker) {
+      marker = ClipMarker(mBuilder, mBlock, marker,
+                          mContentArea + mBuilder->ToReferenceFrame(mBlock),
+                          &markerRect);
+    }
+    mMarkerList->AppendNewToTop(marker);
+  }
+}
+
+void
+TextOverflow::Marker::SetupString(nsIFrame* aFrame)
+{
+  if (mInitialized) {
+    return;
+  }
+  nsRefPtr<nsRenderingContext> rc =
+    aFrame->PresContext()->PresShell()->GetReferenceRenderingContext();
+  nsLayoutUtils::SetFontFromStyle(rc, aFrame->GetStyleContext());
+
+  mMarkerString = mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS ?
+                    GetEllipsis(aFrame) : mStyle->mString;
+  mWidth = nsLayoutUtils::GetStringWidth(aFrame, rc, mMarkerString.get(),
+                                         mMarkerString.Length());
+  mInitialized = true;
+}
+
+}  // namespace css
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/TextOverflow.h
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is an implementation of CSS3 text-overflow.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mats Palmgren <matspal@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef TextOverflow_h_
+#define TextOverflow_h_
+
+#include "nsDisplayList.h"
+#include "nsLineBox.h"
+#include "nsStyleStruct.h"
+#include "nsTHashtable.h"
+
+namespace mozilla {
+namespace css {
+
+/**
+ * A class for rendering CSS3 text-overflow.
+ * Usage:
+ *  1. allocate an object using WillProcessLines
+ *  2. then call ProcessLine for each line you are building display lists for
+ *  3. finally call DidProcessLines
+ */
+class TextOverflow {
+ public:
+  /**
+   * Allocate an object for text-overflow processing.
+   * @return nsnull if no processing is necessary.  The caller owns the object.
+   */
+  static TextOverflow* WillProcessLines(nsDisplayListBuilder*   aBuilder,
+                                        const nsDisplayListSet& aLists,
+                                        nsIFrame*               aBlockFrame);
+  /**
+   * Analyze the display lists for text overflow and what kind of item is at
+   * the content edges.  Add display items for text-overflow markers as needed
+   * and remove or clip items that would overlap a marker.
+   */
+  void ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine);
+
+  /**
+   * Do final processing, currently just adds a dummy item for scroll frames
+   * to make IsVaryingRelativeToMovingFrame() true for the entire area.
+   */
+  void DidProcessLines();
+
+  /**
+   * @return true if aBlockFrame needs analysis for text overflow.
+   */
+  static bool CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
+                                  nsIFrame*             aBlockFrame);
+
+  typedef nsTHashtable<nsPtrHashKey<nsIFrame> > FrameHashtable;
+
+ protected:
+  TextOverflow() {}
+
+  struct AlignmentEdges {
+    AlignmentEdges() : mAssigned(false) {}
+    void Accumulate(const nsRect& aRect) {
+      if (NS_LIKELY(mAssigned)) {
+        x = NS_MIN(x, aRect.X());
+        xmost = NS_MAX(xmost, aRect.XMost());
+      } else {
+        x = aRect.X();
+        xmost = aRect.XMost();
+        mAssigned = true;
+      }
+    }
+    nscoord Width() { return xmost - x; }
+    nscoord x;
+    nscoord xmost;
+    bool mAssigned;
+  };
+
+  /**
+   * Examines frames on the line to determine whether we should draw a left
+   * and/or right marker, and if so, which frames should be completely hidden
+   * and the bounds of what will be displayed between the markers.
+   * @param aLine the line we're processing
+   * @param aFramesToHide frames that should have their display items removed
+   * @param aAlignmentEdges the outermost edges of all text and atomic
+   *   inline-level frames that are inside the area between the markers
+   */
+  void ExamineLineFrames(nsLineBox*      aLine,
+                         FrameHashtable* aFramesToHide,
+                         AlignmentEdges* aAlignmentEdges);
+
+  /**
+   * LineHasOverflowingText calls this to analyze edges, both the block's
+   * content edges and the hypothetical marker edges aligned at the block edges.
+   * @param aFrame the descendant frame of mBlock that we're analyzing
+   * @param aContentArea the block's content area
+   * @param aInsideMarkersArea the rectangle between the markers
+   * @param aFramesToHide frames that should have their display items removed
+   * @param aAlignmentEdges the outermost edges of all text and atomic
+   *   inline-level frames that are inside the area between the markers
+   */
+  void ExamineFrameSubtree(nsIFrame*       aFrame,
+                           const nsRect&   aContentArea,
+                           const nsRect&   aInsideMarkersArea,
+                           FrameHashtable* aFramesToHide,
+                           AlignmentEdges* aAlignmentEdges);
+
+  /**
+   * ExamineFrameSubtree calls this to analyze a frame against the hypothetical
+   * marker edges (aInsideMarkersArea) for text frames and atomic inline-level
+   * elements.  A text frame adds its extent inside aInsideMarkersArea where
+   * grapheme clusters are fully visible.  An atomic adds its border box if
+   * it's fully inside aInsideMarkersArea, otherwise the frame is added to
+   * aFramesToHide.
+   * @param aFrame the descendant frame of mBlock that we're analyzing
+   * @param aFrameType aFrame's frame type
+   * @param aInsideMarkersArea the rectangle between the markers
+   * @param aFramesToHide frames that should have their display items removed
+   * @param aAlignmentEdges the outermost edges of all text and atomic
+   *   inline-level frames that are inside the area between the markers
+   *                       inside aInsideMarkersArea
+   */
+  void AnalyzeMarkerEdges(nsIFrame*       aFrame,
+                          const nsIAtom*  aFrameType,
+                          const nsRect&   aInsideMarkersArea,
+                          FrameHashtable* aFramesToHide,
+                          AlignmentEdges* aAlignmentEdges);
+
+  /**
+   * Clip or remove items given the final marker edges. ("clip" here just means
+   * assigning mLeftEdge/mRightEdge for any nsCharClipDisplayItem that needs it,
+   * see nsDisplayList.h for a description of that item).
+   * @param aFramesToHide remove display items for these frames
+   * @param aInsideMarkersArea is the area inside the markers
+   */
+  void PruneDisplayListContents(nsDisplayList*        aList,
+                                const FrameHashtable& aFramesToHide,
+                                const nsRect&         aInsideMarkersArea);
+
+  /**
+   * ProcessLine calls this to create display items for the markers and insert
+   * them into a display list for the block.
+   * @param aLine the line we're processing
+   * @param aCreateLeft if true, create a marker on the left side
+   * @param aCreateRight if true, create a marker on the right side
+   * @param aInsideMarkersArea is the area inside the markers
+   */
+  void CreateMarkers(const nsLineBox* aLine,
+                     bool             aCreateLeft,
+                     bool             aCreateRight,
+                     const nsRect&    aInsideMarkersArea) const;
+
+  nsRect                 mContentArea;
+  nsDisplayListBuilder*  mBuilder;
+  nsIFrame*              mBlock;
+  nsDisplayList*         mMarkerList;
+  bool                   mBlockIsRTL;
+  bool                   mCanHaveHorizontalScrollbar;
+
+  class Marker {
+  public:
+    void Init(const nsStyleTextOverflow& aStyle) {
+      mInitialized = false;
+      mWidth = 0;
+      mStyle = &aStyle;
+    }
+
+    /**
+     * Setup the marker string and calculate its size, if not done already.
+     */
+    void SetupString(nsIFrame* aFrame);
+
+    bool IsNeeded() const {
+      return mHasOverflow;
+    }
+    void Reset() {
+      mHasOverflow = false;
+    }
+
+    // The intrinsic width of the marker string.
+    nscoord                        mWidth;
+    // The marker text.
+    nsString                       mMarkerString;
+    // The style for this side.
+    const nsStyleTextOverflow*     mStyle;
+    // True if there is visible overflowing inline content on this side.
+    bool                           mHasOverflow;
+    // True if mMarkerString and mWidth have been setup from style.
+    bool                           mInitialized;
+  };
+
+  Marker mLeft;  // the horizontal left marker
+  Marker mRight; // the horizontal right marker
+};
+
+} // namespace css
+} // namespace mozilla
+
+#endif /* !defined(TextOverflow_h_) */
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -84,16 +84,17 @@
 #include "nsLayoutUtils.h"
 #include "nsDisplayList.h"
 #include "nsContentErrors.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsCSSRendering.h"
 #include "FrameLayerBuilder.h"
 #include "nsRenderingContext.h"
+#include "TextOverflow.h"
 #include "mozilla/Util.h" // for DebugOnly
 
 #ifdef IBMBIDI
 #include "nsBidiPresUtils.h"
 #endif // IBMBIDI
 
 #include "nsIDOMHTMLBodyElement.h"
 #include "nsIDOMHTMLHtmlElement.h"
@@ -102,16 +103,17 @@ static const int MIN_LINES_NEEDING_CURSO
 
 static const PRUnichar kDiscCharacter = 0x2022;
 static const PRUnichar kCircleCharacter = 0x25e6;
 static const PRUnichar kSquareCharacter = 0x25aa;
 
 #define DISABLE_FLOAT_BREAKING_IN_COLUMNS
 
 using namespace mozilla;
+using namespace mozilla::css;
 
 #ifdef DEBUG
 #include "nsPrintfCString.h"
 #include "nsBlockDebugFlags.h"
 
 PRBool nsBlockFrame::gLamePaintMetrics;
 PRBool nsBlockFrame::gLameReflowMetrics;
 PRBool nsBlockFrame::gNoisy;
@@ -6067,36 +6069,40 @@ nsBlockFrame::IsVisibleInSelection(nsISe
 
   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
   PRBool visible;
   nsresult rv = aSelection->ContainsNode(node, PR_TRUE, &visible);
   return NS_SUCCEEDED(rv) && visible;
 }
 
 /* virtual */ void
-nsBlockFrame::PaintTextDecorationLine(gfxContext* aCtx, 
-                                      const nsPoint& aPt,
-                                      nsLineBox* aLine,
-                                      nscolor aColor, 
-                                      PRUint8 aStyle,
-                                      gfxFloat aOffset, 
-                                      gfxFloat aAscent, 
-                                      gfxFloat aSize,
-                                      const PRUint8 aDecoration) 
+nsBlockFrame::PaintTextDecorationLine(
+                gfxContext* aCtx, 
+                const nsPoint& aPt,
+                nsLineBox* aLine,
+                nscolor aColor, 
+                PRUint8 aStyle,
+                gfxFloat aOffset, 
+                gfxFloat aAscent, 
+                gfxFloat aSize,
+                const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                const PRUint8 aDecoration) 
 {
   NS_ASSERTION(!aLine->IsBlock(), "Why did we ask for decorations on a block?");
 
   nscoord start = aLine->mBounds.x;
   nscoord width = aLine->mBounds.width;
 
   AdjustForTextIndent(aLine, start, width);
-      
+  nscoord x = start + aPt.x;
+  aClipEdges.Intersect(&x, &width);
+
   // Only paint if we have a positive width
   if (width > 0) {
-    gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x),
+    gfxPoint pt(PresContext()->AppUnitsToGfxUnits(x),
                 PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y));
     gfxSize size(PresContext()->AppUnitsToGfxUnits(width), aSize);
     nsCSSRendering::PaintDecorationLine(
       aCtx, aColor, pt, size,
       PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()),
       aOffset, aDecoration, aStyle);
   }
 }
@@ -6146,17 +6152,17 @@ static void DebugOutputDrawLine(PRInt32 
   }
 }
 #endif
 
 static nsresult
 DisplayLine(nsDisplayListBuilder* aBuilder, const nsRect& aLineArea,
             const nsRect& aDirtyRect, nsBlockFrame::line_iterator& aLine,
             PRInt32 aDepth, PRInt32& aDrawnLines, const nsDisplayListSet& aLists,
-            nsBlockFrame* aFrame) {
+            nsBlockFrame* aFrame, TextOverflow* aTextOverflow) {
   // If the line's combined area (which includes child frames that
   // stick outside of the line's bounding box or our bounding box)
   // intersects the dirty rect then paint the line.
   PRBool intersect = aLineArea.Intersects(aDirtyRect);
 #ifdef DEBUG
   if (nsBlockFrame::gLamePaintMetrics) {
     aDrawnLines++;
   }
@@ -6164,44 +6170,53 @@ DisplayLine(nsDisplayListBuilder* aBuild
 #endif
   // The line might contain a placeholder for a visible out-of-flow, in which
   // case we need to descend into it. If there is such a placeholder, we will
   // have NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO set.
   // In particular, we really want to check ShouldDescendIntoFrame()
   // on all the frames on the line, but that might be expensive.  So
   // we approximate it by checking it on aFrame; if it's true for any
   // frame in the line, it's also true for aFrame.
-  if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame))
+  PRBool lineInline = aLine->IsInline();
+  PRBool lineMayHaveTextOverflow = aTextOverflow && lineInline;
+  if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame) &&
+      !lineMayHaveTextOverflow)
     return NS_OK;
 
+  nsDisplayListCollection collection;
   nsresult rv;
   nsDisplayList aboveTextDecorations;
-  PRBool lineInline = aLine->IsInline();
   if (lineInline) {
     // Display the text-decoration for the hypothetical anonymous inline box
     // that wraps these inlines
-    rv = aFrame->DisplayTextDecorations(aBuilder, aLists.Content(),
+    rv = aFrame->DisplayTextDecorations(aBuilder, collection.Content(),
                                         &aboveTextDecorations, aLine);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Block-level child backgrounds go on the blockBorderBackgrounds list ...
   // Inline-level child backgrounds go on the regular child content list.
-  nsDisplayListSet childLists(aLists,
-      lineInline ? aLists.Content() : aLists.BlockBorderBackgrounds());
+  nsDisplayListSet childLists(collection,
+    lineInline ? collection.Content() : collection.BlockBorderBackgrounds());
   nsIFrame* kid = aLine->mFirstChild;
   PRInt32 n = aLine->GetChildCount();
   while (--n >= 0) {
     rv = aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, childLists,
                                           lineInline ? nsIFrame::DISPLAY_CHILD_INLINE : 0);
     NS_ENSURE_SUCCESS(rv, rv);
     kid = kid->GetNextSibling();
   }
   
-  aLists.Content()->AppendToTop(&aboveTextDecorations);
+  collection.Content()->AppendToTop(&aboveTextDecorations);
+
+  if (lineMayHaveTextOverflow) {
+    aTextOverflow->ProcessLine(collection, aLine.get());
+  }
+
+  collection.MoveTo(aLists);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBlockFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsRect&           aDirtyRect,
                                const nsDisplayListSet& aLists)
 {
@@ -6233,16 +6248,20 @@ nsBlockFrame::BuildDisplayList(nsDisplay
     for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) {
       if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT)
          BuildDisplayListForChild(aBuilder, f, aDirtyRect, aLists);
     }
   }
 
   aBuilder->MarkFramesForDisplayList(this, mFloats, aDirtyRect);
 
+  // Prepare for text-overflow processing.
+  nsAutoPtr<TextOverflow> textOverflow(
+    TextOverflow::WillProcessLines(aBuilder, aLists, this));
+
   // Don't use the line cursor if we might have a descendant placeholder ...
   // it might skip lines that contain placeholders but don't themselves
   // intersect with the dirty area.
   // In particular, we really want to check ShouldDescendIntoFrame()
   // on all our child frames, but that might be expensive.  So we
   // approximate it by checking it on |this|; if it's true for any
   // frame in our child list, it's also true for |this|.
   nsLineBox* cursor = aBuilder->ShouldDescendIntoFrame(this) ?
@@ -6257,32 +6276,32 @@ nsBlockFrame::BuildDisplayList(nsDisplay
       nsRect lineArea = line->GetVisualOverflowArea();
       if (!lineArea.IsEmpty()) {
         // Because we have a cursor, the combinedArea.ys are non-decreasing.
         // Once we've passed aDirtyRect.YMost(), we can never see it again.
         if (lineArea.y >= aDirtyRect.YMost()) {
           break;
         }
         rv = DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines,
-                         aLists, this);
+                         aLists, this, textOverflow);
         if (NS_FAILED(rv))
           break;
       }
     }
   } else {
     PRBool nonDecreasingYs = PR_TRUE;
     PRInt32 lineCount = 0;
     nscoord lastY = PR_INT32_MIN;
     nscoord lastYMost = PR_INT32_MIN;
     for (line_iterator line = begin_lines();
          line != line_end;
          ++line) {
       nsRect lineArea = line->GetVisualOverflowArea();
       rv = DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines,
-                       aLists, this);
+                       aLists, this, textOverflow);
       if (NS_FAILED(rv))
         break;
       if (!lineArea.IsEmpty()) {
         if (lineArea.y < lastY
             || lineArea.YMost() < lastYMost) {
           nonDecreasingYs = PR_FALSE;
         }
         lastY = lineArea.y;
@@ -6291,16 +6310,21 @@ nsBlockFrame::BuildDisplayList(nsDisplay
       lineCount++;
     }
 
     if (NS_SUCCEEDED(rv) && nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
       SetupLineCursor();
     }
   }
 
+  // Finalize text-overflow processing.
+  if (textOverflow) {
+    textOverflow->DidProcessLines();
+  }
+
   if (NS_SUCCEEDED(rv) && (nsnull != mBullet) && HaveOutsideBullet()) {
     // Display outside bullets manually
     rv = BuildDisplayListForChild(aBuilder, mBullet, aDirtyRect, aLists);
   }
 
 #ifdef DEBUG
   if (gLamePaintMetrics) {
     PRTime end = PR_Now();
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -367,16 +367,17 @@ protected:
   virtual void PaintTextDecorationLine(gfxContext* aCtx,
                                        const nsPoint& aPt,
                                        nsLineBox* aLine,
                                        nscolor aColor,
                                        PRUint8 aStyle,
                                        gfxFloat aOffset,
                                        gfxFloat aAscent,
                                        gfxFloat aSize,
+                                       const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                                        const PRUint8 aDecoration);
 
   virtual void AdjustForTextIndent(const nsLineBox* aLine,
                                    nscoord& start,
                                    nscoord& width);
 
   void TryAllLines(nsLineList::iterator* aIterator,
                    nsLineList::iterator* aStartIterator,
--- a/layout/generic/nsHTMLContainerFrame.cpp
+++ b/layout/generic/nsHTMLContainerFrame.cpp
@@ -63,22 +63,22 @@
 #include "gfxFont.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsDisplayList.h"
 #include "nsBlockFrame.h"
 #include "nsLineBox.h"
 #include "nsDisplayList.h"
 #include "nsCSSRendering.h"
 
-class nsDisplayTextDecoration : public nsDisplayItem {
+class nsDisplayTextDecoration : public nsCharClipDisplayItem {
 public:
   nsDisplayTextDecoration(nsDisplayListBuilder* aBuilder,
                           nsHTMLContainerFrame* aFrame, PRUint8 aDecoration,
                           nscolor aColor, PRUint8 aStyle, nsLineBox* aLine)
-    : nsDisplayItem(aBuilder, aFrame), mLine(aLine), mColor(aColor),
+    : nsCharClipDisplayItem(aBuilder, aFrame), mLine(aLine), mColor(aColor),
       mDecoration(aDecoration), mStyle(aStyle) {
     MOZ_COUNT_CTOR(nsDisplayTextDecoration);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTextDecoration() {
     MOZ_COUNT_DTOR(nsDisplayTextDecoration);
   }
 #endif
@@ -129,42 +129,42 @@ nsDisplayTextDecoration::Paint(nsDisplay
   }
 
   nsPoint pt = ToReferenceFrame();
   nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame);
   if (mDecoration == NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
     gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
     f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
                                mStyle, underlineOffset, ascent,
-                               metrics.underlineSize, mDecoration);
+                               metrics.underlineSize, Edges(), mDecoration);
   } else if (mDecoration == NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
     f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
                                mStyle, metrics.maxAscent, ascent,
-                               metrics.underlineSize, mDecoration);
+                               metrics.underlineSize, Edges(), mDecoration);
   } else {
     f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
-                               mStyle, metrics.strikeoutOffset,
-                               ascent, metrics.strikeoutSize, mDecoration);
+                               mStyle, metrics.strikeoutOffset, ascent,
+                               metrics.strikeoutSize, Edges(), mDecoration);
   }
 }
 
 nsRect
 nsDisplayTextDecoration::GetBounds(nsDisplayListBuilder* aBuilder)
 {
   return mFrame->GetVisualOverflowRect() + ToReferenceFrame();
 }
 
-class nsDisplayTextShadow : public nsDisplayItem {
+class nsDisplayTextShadow : public nsCharClipDisplayItem {
 public:
   nsDisplayTextShadow(nsDisplayListBuilder* aBuilder,
                       nsHTMLContainerFrame* aFrame,
                       const PRUint8 aDecoration, PRUint8 aUnderlineStyle,
                       PRUint8 aOverlineStyle, PRUint8 aStrikeThroughStyle,
                       nsLineBox* aLine)
-    : nsDisplayItem(aBuilder, aFrame), mLine(aLine),
+    : nsCharClipDisplayItem(aBuilder, aFrame), mLine(aLine),
       mDecorationFlags(aDecoration), mUnderlineStyle(aUnderlineStyle),
       mOverlineStyle(aOverlineStyle), mStrikeThroughStyle(aStrikeThroughStyle) {
     MOZ_COUNT_CTOR(nsDisplayTextShadow);
   }
   virtual ~nsDisplayTextShadow() {
     MOZ_COUNT_DTOR(nsDisplayTextShadow);
   }
 
@@ -295,32 +295,33 @@ nsDisplayTextShadow::Paint(nsDisplayList
     nsContextBoxBlur contextBoxBlur;
     gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, 0, shadow->mRadius,
                                                 presContext->AppUnitsPerDevPixel(),
                                                 thebesCtx, mVisibleRect, nsnull);
     if (!shadowCtx) {
       continue;
     }
 
+    const nsCharClipDisplayItem::ClipEdges clipEdges = this->Edges();
     if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
       f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
                                  mUnderlineStyle, underlineOffset, ascent,
-                                 metrics.underlineSize,
+                                 metrics.underlineSize, clipEdges,
                                  NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE);
     }
     if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
       f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
                                  mOverlineStyle, metrics.maxAscent, ascent,
-                                 metrics.underlineSize,
+                                 metrics.underlineSize, clipEdges,
                                  NS_STYLE_TEXT_DECORATION_LINE_OVERLINE);
     }
     if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
       f->PaintTextDecorationLine(shadowCtx, pt, mLine, shadowColor,
                                  mStrikeThroughStyle, metrics.strikeoutOffset,
-                                 ascent, metrics.strikeoutSize,
+                                 ascent, metrics.strikeoutSize, clipEdges,
                                  NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH);
     }
 
     contextBoxBlur.DoPaint();
   }
 }
 
 nsRect
@@ -435,28 +436,31 @@ nsHTMLContainerFrame::PaintTextDecoratio
                    gfxContext* aCtx, 
                    const nsPoint& aPt,
                    nsLineBox* aLine,
                    nscolor aColor, 
                    PRUint8 aStyle,
                    gfxFloat aOffset, 
                    gfxFloat aAscent, 
                    gfxFloat aSize,
+                   const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                    const PRUint8 aDecoration) 
 {
   NS_ASSERTION(!aLine, "Should not have passed a linebox to a non-block frame");
   nsMargin bp = GetUsedBorderAndPadding();
   PRIntn skip = GetSkipSides();
   NS_FOR_CSS_SIDES(side) {
     if (skip & (1 << side)) {
       bp.Side(side) = 0;
     }
   }
+  nscoord x = aPt.x + bp.left;
   nscoord innerWidth = mRect.width - bp.left - bp.right;
-  gfxPoint pt(PresContext()->AppUnitsToGfxUnits(bp.left + aPt.x),
+  aClipEdges.Intersect(&x, &innerWidth);
+  gfxPoint pt(PresContext()->AppUnitsToGfxUnits(x),
               PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y));
   gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), aSize);
   nsCSSRendering::PaintDecorationLine(aCtx, aColor, pt, size, aAscent, aOffset,
                                       aDecoration, aStyle);
 }
 
 /*virtual*/ void
 nsHTMLContainerFrame::AdjustForTextIndent(const nsLineBox* aLine,
--- a/layout/generic/nsHTMLContainerFrame.h
+++ b/layout/generic/nsHTMLContainerFrame.h
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 /* base class #2 for rendering objects that have child lists */
 
 #ifndef nsHTMLContainerFrame_h___
 #define nsHTMLContainerFrame_h___
 
 #include "nsContainerFrame.h"
+#include "nsDisplayList.h"
 #include "gfxPoint.h"
 
 class nsString;
 class nsAbsoluteFrame;
 class nsPlaceholderFrame;
 struct nsStyleDisplay;
 struct nsStylePosition;
 struct nsHTMLReflowMetrics;
@@ -167,30 +168,33 @@ protected:
    *                                NS_STYLE_TEXT_DECORATION_STYLE_* consts.
    *    @param aAscent            ascent of the font from which the
    *                                text-decoration was derived. 
    *    @param aOffset            distance *above* baseline where the
    *                                text-decoration should be drawn,
    *                                i.e. negative offsets draws *below*
    *                                the baseline.
    *    @param aSize              the thickness of the line
+   *    @param aClipEdges         clip edges from the display item
    *    @param aDecoration        which line will be painted i.e.,
    *                              NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE or
    *                              NS_STYLE_TEXT_DECORATION_LINE_OVERLINE or
    *                              NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH.
    */
-  virtual void PaintTextDecorationLine(gfxContext* aCtx,
-                                       const nsPoint& aPt,
-                                       nsLineBox* aLine,
-                                       nscolor aColor,
-                                       PRUint8 aStyle,
-                                       gfxFloat aOffset,
-                                       gfxFloat aAscent,
-                                       gfxFloat aSize,
-                                       const PRUint8 aDecoration);
+  virtual void PaintTextDecorationLine(
+                 gfxContext* aCtx,
+                 const nsPoint& aPt,
+                 nsLineBox* aLine,
+                 nscolor aColor,
+                 PRUint8 aStyle,
+                 gfxFloat aOffset,
+                 gfxFloat aAscent,
+                 gfxFloat aSize,
+                 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                 const PRUint8 aDecoration);
 
   virtual void AdjustForTextIndent(const nsLineBox* aLine,
                                    nscoord& start,
                                    nscoord& width);
 
   friend class nsDisplayTextDecoration;
   friend class nsDisplayTextShadow;
 };
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -51,16 +51,17 @@
 #define nsTextFrame_h__
 
 #include "nsFrame.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxFont.h"
 #include "gfxSkipChars.h"
 #include "gfxContext.h"
+#include "nsDisplayList.h"
 
 class nsTextPaintStyle;
 class PropertyProvider;
 
 // This state bit is set on frames that have some non-collapsed characters after
 // reflow
 #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
 
@@ -267,36 +268,62 @@ public:
 
   void AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext,
                                 nsIFrame::InlineMinWidthData *aData);
   void AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext,
                                  InlinePrefWidthData *aData);
 
   gfxFloat GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY);
 
+  /**
+   * Calculate the horizontal bounds of the grapheme clusters that fit entirely
+   * inside the given left/right edges (which are positive lengths from the
+   * respective frame edge).  If an input value is zero it is ignored and the
+   * result for that edge is zero.  All out parameter values are undefined when
+   * the method returns false.
+   * @return true if at least one whole grapheme cluster fit between the edges
+   */
+  bool MeasureCharClippedText(gfxContext* aCtx,
+                              nscoord aLeftEdge, nscoord aRightEdge,
+                              nscoord* aSnappedLeftEdge,
+                              nscoord* aSnappedRightEdge);
+  /**
+   * Same as above; this method also the returns the corresponding text run
+   * offset and number of characters that fit.  All out parameter values are
+   * undefined when the method returns false.
+   * @return true if at least one whole grapheme cluster fit between the edges
+   */
+  bool MeasureCharClippedText(gfxContext* aCtx,
+                              PropertyProvider& aProvider,
+                              nscoord aLeftEdge, nscoord aRightEdge,
+                              PRUint32* aStartOffset, PRUint32* aMaxLength,
+                              nscoord* aSnappedLeftEdge,
+                              nscoord* aSnappedRightEdge);
   // primary frame paint method called from nsDisplayText
   // The private DrawText() is what applies the text to a graphics context
   void PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
-                 const nsRect& aDirtyRect);
+                 const nsRect& aDirtyRect, const nsCharClipDisplayItem& aItem);
   // helper: paint quirks-mode CSS text decorations
   void PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect,
                             const gfxPoint& aFramePt,
                             const gfxPoint& aTextBaselinePt,
                             nsTextPaintStyle& aTextStyle,
                             PropertyProvider& aProvider,
+                            const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                             const nscolor* aOverrideColor = nsnull);
   // helper: paint text frame when we're impacted by at least one selection.
   // Return PR_FALSE if the text was not painted and we should continue with
   // the fast path.
   PRBool PaintTextWithSelection(gfxContext* aCtx,
                                 const gfxPoint& aFramePt,
                                 const gfxPoint& aTextBaselinePt,
                                 const gfxRect& aDirtyRect,
                                 PropertyProvider& aProvider,
-                                nsTextPaintStyle& aTextPaintStyle);
+                                nsTextPaintStyle& aTextPaintStyle,
+                                const nsCharClipDisplayItem::ClipEdges& aClipEdges);
   // helper: paint text with foreground and background colors determined
   // by selection(s). Also computes a mask of all selection types applying to
   // our text, returned in aAllTypes.
   void PaintTextWithSelectionColors(gfxContext* aCtx,
                                     const gfxPoint& aFramePt,
                                     const gfxPoint& aTextBaselinePt,
                                     const gfxRect& aDirtyRect,
                                     PropertyProvider& aProvider,
@@ -423,17 +450,19 @@ protected:
   void PaintOneShadow(PRUint32 aOffset,
                       PRUint32 aLength,
                       nsCSSShadowItem* aShadowDetails,
                       PropertyProvider* aProvider,
                       const nsRect& aDirtyRect,
                       const gfxPoint& aFramePt,
                       const gfxPoint& aTextBaselinePt,
                       gfxContext* aCtx,
-                      const nscolor& aForegroundColor);
+                      const nscolor& aForegroundColor,
+                      const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                      nscoord aLeftSideOffset);
 
   struct TextDecorations {
     PRUint8 mDecorations;
     PRUint8 mOverStyle;
     PRUint8 mUnderStyle;
     PRUint8 mStrikeStyle;
     nscolor mOverColor;
     nscolor mUnderColor;
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -3203,49 +3203,16 @@ EnsureDifferentColors(nscolor colorA, ns
                  NS_GET_B(colorA) ^ 0xff);
     return res;
   }
   return colorA;
 }
 
 //-----------------------------------------------------------------------------
 
-static nscolor
-DarkenColor(nscolor aColor)
-{
-  PRUint16  hue, sat, value;
-  PRUint8 alpha;
-
-  // convert the RBG to HSV so we can get the lightness (which is the v)
-  NS_RGB2HSV(aColor, hue, sat, value, alpha);
-
-  // The goal here is to send white to black while letting colored
-  // stuff stay colored... So we adopt the following approach.
-  // Something with sat = 0 should end up with value = 0.  Something
-  // with a high sat can end up with a high value and it's ok.... At
-  // the same time, we don't want to make things lighter.  Do
-  // something simple, since it seems to work.
-  if (value > sat) {
-    value = sat;
-    // convert this color back into the RGB color space.
-    NS_HSV2RGB(aColor, hue, sat, value, alpha);
-  }
-  return aColor;
-}
-
-// Check whether we should darken text colors. We need to do this if
-// background images and colors are being suppressed, because that means
-// light text will not be visible against the (presumed light-colored) background.
-static PRBool
-ShouldDarkenColors(nsPresContext* aPresContext)
-{
-  return !aPresContext->GetBackgroundColorDraw() &&
-    !aPresContext->GetBackgroundImageDraw();
-}
-
 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
   : mFrame(aFrame),
     mPresContext(aFrame->PresContext()),
     mInitCommonColors(PR_FALSE),
     mInitSelectionColors(PR_FALSE)
 {
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mSelectionStyle); i++)
     mSelectionStyle[i].mInit = PR_FALSE;
@@ -3274,21 +3241,17 @@ nsTextPaintStyle::EnsureSufficientContra
     return PR_TRUE;
   }
   return PR_FALSE;
 }
 
 nscolor
 nsTextPaintStyle::GetTextColor()
 {
-  nscolor color = mFrame->GetVisitedDependentColor(eCSSProperty_color);
-  if (ShouldDarkenColors(mPresContext)) {
-    color = DarkenColor(color);
-  }
-  return color;
+  return nsLayoutUtils::GetTextColor(mFrame);
 }
 
 PRBool
 nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
                                      nscolor* aBackColor)
 {
   NS_ASSERTION(aForeColor, "aForeColor is null");
   NS_ASSERTION(aBackColor, "aBackColor is null");
@@ -4164,20 +4127,20 @@ nsTextFrame::CharacterDataChanged(Charac
 
 /* virtual */ void
 nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
 {
   nsFrame::DidSetStyleContext(aOldStyleContext);
   ClearTextRun(nsnull);
 } 
 
-class nsDisplayText : public nsDisplayItem {
+class nsDisplayText : public nsCharClipDisplayItem {
 public:
   nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
-    nsDisplayItem(aBuilder, aFrame),
+    nsCharClipDisplayItem(aBuilder, aFrame),
     mDisableSubpixelAA(PR_FALSE) {
     MOZ_COUNT_CTOR(nsDisplayText);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayText() {
     MOZ_COUNT_DTOR(nsDisplayText);
   }
 #endif
@@ -4213,17 +4176,19 @@ nsDisplayText::Paint(nsDisplayListBuilde
   // This is temporary until we do this in the actual calculation of text extents.
   nsRect extraVisible = mVisibleRect;
   nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
   extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel);
   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
 
   gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
                                                     mDisableSubpixelAA);
-  f->PaintText(aCtx, ToReferenceFrame(), extraVisible);
+  NS_ASSERTION(mLeftEdge >= 0, "illegal left edge");
+  NS_ASSERTION(mRightEdge >= 0, "illegal right edge");
+  f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this);
 }
 
 NS_IMETHODIMP
 nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
@@ -4429,22 +4394,24 @@ nsTextFrame::UnionTextDecorationOverflow
   nsRect decorationRect;
   if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT) ||
       !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
     return;
   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
 }
 
 void 
-nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect,
-                                  const gfxPoint& aFramePt,
-                                  const gfxPoint& aTextBaselinePt,
-                                  nsTextPaintStyle& aTextPaintStyle,
-                                  PropertyProvider& aProvider,
-                                  const nscolor* aOverrideColor)
+nsTextFrame::PaintTextDecorations(
+               gfxContext* aCtx, const gfxRect& aDirtyRect,
+               const gfxPoint& aFramePt,
+               const gfxPoint& aTextBaselinePt,
+               nsTextPaintStyle& aTextPaintStyle,
+               PropertyProvider& aProvider,
+               const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+               const nscolor* aOverrideColor)
 {
   TextDecorations decorations =
     GetTextDecorations(aTextPaintStyle.PresContext());
   if (!decorations.HasDecorationlines())
     return;
 
   // Hide text decorations if we're currently hiding @font-face fallback text
   if (aProvider.GetFontGroup()->ShouldSkipDrawing())
@@ -4452,18 +4419,21 @@ nsTextFrame::PaintTextDecorations(gfxCon
 
   gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0);
   if (!firstFont)
     return; // OOM
   const gfxFont::Metrics& fontMetrics = firstFont->GetMetrics();
   gfxFloat app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
 
   // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
-  gfxPoint pt(aFramePt.x / app, (aTextBaselinePt.y - mAscent) / app);
-  gfxSize size(GetRect().width / app, 0);
+  nscoord x = aFramePt.x;
+  nscoord width = GetRect().width;
+  aClipEdges.Intersect(&x, &width);
+  gfxPoint pt(x / app, (aTextBaselinePt.y - mAscent) / app);
+  gfxSize size(width / app, 0);
   gfxFloat ascent = gfxFloat(mAscent) / app;
 
   nscolor lineColor;
   if (decorations.HasOverline()) {
     lineColor = aOverrideColor ? *aOverrideColor : decorations.mOverColor;
     size.height = fontMetrics.underlineSize;
     nsCSSRendering::PaintDecorationLine(
       aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent,
@@ -4806,33 +4776,35 @@ AddHyphenToMetrics(nsTextFrame* aTextFra
   aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
 }
 
 void
 nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength,
                             nsCSSShadowItem* aShadowDetails,
                             PropertyProvider* aProvider, const nsRect& aDirtyRect,
                             const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
-                            gfxContext* aCtx, const nscolor& aForegroundColor)
+                            gfxContext* aCtx, const nscolor& aForegroundColor,
+                            const nsCharClipDisplayItem::ClipEdges& aClipEdges,
+                            nscoord aLeftSideOffset)
 {
   gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
   nscoord blurRadius = NS_MAX(aShadowDetails->mRadius, 0);
 
   gfxTextRun::Metrics shadowMetrics =
     mTextRun->MeasureText(aOffset, aLength, gfxFont::LOOSE_INK_EXTENTS,
                           nsnull, aProvider);
   if (GetStateBits() & TEXT_HYPHEN_BREAK) {
     AddHyphenToMetrics(this, mTextRun, &shadowMetrics, gfxFont::LOOSE_INK_EXTENTS, aCtx);
   }
 
   // This rect is the box which is equivalent to where the shadow will be painted.
   // The origin of mBoundingBox is the text baseline left, so we must translate it by
   // that much in order to make the origin the top-left corner of the text bounding box.
   gfxRect shadowGfxRect = shadowMetrics.mBoundingBox +
-     gfxPoint(aFramePt.x, aTextBaselinePt.y) + shadowOffset;
+    gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset;
   nsRect shadowRect(shadowGfxRect.X(), shadowGfxRect.Y(),
                     shadowGfxRect.Width(), shadowGfxRect.Height());
 
   nsContextBoxBlur contextBoxBlur;
   gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
                                                   PresContext()->AppUnitsPerDevPixel(),
                                                   aCtx, aDirtyRect, nsnull);
   if (!shadowContext)
@@ -4859,17 +4831,17 @@ nsTextFrame::PaintOneShadow(PRUint32 aOf
            (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
 
   // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
   // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
   // any code behaviour here.
   nsTextPaintStyle textPaintStyle(this);
   PaintTextDecorations(shadowContext, dirtyGfxRect, aFramePt + shadowOffset,
                        aTextBaselinePt + shadowOffset,
-                       textPaintStyle, *aProvider, &shadowColor);
+                       textPaintStyle, *aProvider, aClipEdges, &shadowColor);
 
   contextBoxBlur.DoPaint();
   aCtx->Restore();
 }
 
 // Paints selection backgrounds and text in the correct colors. Also computes
 // aAllTypes, the union of all selection types that are applying to this text.
 void
@@ -5037,27 +5009,28 @@ nsTextFrame::PaintTextSelectionDecoratio
     iterator.UpdateWithAdvance(advance);
   }
 }
 
 PRBool
 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
     const gfxPoint& aFramePt,
     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
-    PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle)
+    PropertyProvider& aProvider, nsTextPaintStyle& aTextPaintStyle,
+    const nsCharClipDisplayItem::ClipEdges& aClipEdges)
 {
   SelectionDetails* details = GetSelectionDetails();
   if (!details)
     return PR_FALSE;
 
   SelectionType allTypes;
   PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
                                aProvider, aTextPaintStyle, details, &allTypes);
   PaintTextDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt,
-                       aTextPaintStyle, aProvider);
+                       aTextPaintStyle, aProvider, aClipEdges);
   PRInt32 i;
   // Iterate through just the selection types that paint decorations and
   // paint decorations for any that actually occur in this frame. Paint
   // higher-numbered selection types below lower-numered ones on the
   // general principal that lower-numbered selections are higher priority.
   allTypes &= SelectionTypesWithDecorations;
   for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) {
     SelectionType type = 1 << (i - 1);
@@ -5131,74 +5104,185 @@ nsTextFrame::GetSnappedBaselineY(gfxCont
   gfxFloat appUnitsPerDevUnit = mTextRun->GetAppUnitsPerDevUnit();
   gfxFloat baseline = aY + mAscent;
   gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1);
   if (!aContext->UserToDevicePixelSnapped(putativeRect, PR_TRUE))
     return baseline;
   return aContext->DeviceToUser(putativeRect.TopLeft()).y*appUnitsPerDevUnit;
 }
 
+bool
+nsTextFrame::MeasureCharClippedText(gfxContext* aCtx,
+                                    nscoord aLeftEdge, nscoord aRightEdge,
+                                    nscoord* aSnappedLeftEdge,
+                                    nscoord* aSnappedRightEdge)
+{
+  // Don't pass in aRenderingContext here, because we need a *reference*
+  // context and aRenderingContext might have some transform in it
+  // XXX get the block and line passed to us somehow! This is slow!
+  gfxSkipCharsIterator iter = EnsureTextRun();
+  if (!mTextRun)
+    return false;
+
+  PropertyProvider provider(this, iter);
+  // Trim trailing whitespace
+  provider.InitializeForDisplay(PR_TRUE);
+
+  PRUint32 startOffset = provider.GetStart().GetSkippedOffset();
+  PRUint32 maxLength = ComputeTransformedLength(provider);
+  return MeasureCharClippedText(aCtx, provider, aLeftEdge, aRightEdge,
+                                &startOffset, &maxLength,
+                                aSnappedLeftEdge, aSnappedRightEdge);
+}
+
+static PRUint32 GetClusterLength(gfxTextRun* aTextRun,
+                                 PRUint32    aStartOffset,
+                                 PRUint32    aMaxLength,
+                                 bool        aIsRTL)
+{
+  PRUint32 clusterLength = aIsRTL ? 0 : 1;
+  while (clusterLength < aMaxLength) {
+    if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
+      if (aIsRTL) {
+        ++clusterLength;
+      }
+      break;
+    }
+    ++clusterLength;
+  }
+  return clusterLength;
+}
+
+bool
+nsTextFrame::MeasureCharClippedText(gfxContext* aCtx,
+                                    PropertyProvider& aProvider,
+                                    nscoord aLeftEdge, nscoord aRightEdge,
+                                    PRUint32* aStartOffset,
+                                    PRUint32* aMaxLength,
+                                    nscoord*  aSnappedLeftEdge,
+                                    nscoord*  aSnappedRightEdge)
+{
+  *aSnappedLeftEdge = 0;
+  *aSnappedRightEdge = 0;
+  if (aLeftEdge <= 0 && aRightEdge <= 0) {
+    return true;
+  }
+
+  PRUint32 offset = *aStartOffset;
+  PRUint32 maxLength = *aMaxLength;
+  const nscoord frameWidth = GetSize().width;
+  const PRBool rtl = mTextRun->IsRightToLeft();
+  gfxFloat advanceWidth = 0;
+  const nscoord startEdge = rtl ? aRightEdge : aLeftEdge;
+  if (startEdge > 0) {
+    const gfxFloat maxAdvance = gfxFloat(startEdge);
+    while (maxLength > 0) {
+      PRUint32 clusterLength =
+        GetClusterLength(mTextRun, offset, maxLength, rtl);
+      advanceWidth +=
+        mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
+      maxLength -= clusterLength;
+      offset += clusterLength;
+      if (advanceWidth >= maxAdvance) {
+        break;
+      }
+    }
+    nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge;
+    *snappedStartEdge = NSToCoordFloor(advanceWidth);
+    *aStartOffset = offset;
+  }
+
+  const nscoord endEdge = rtl ? aLeftEdge : aRightEdge;
+  if (endEdge > 0) {
+    const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge);
+    while (maxLength > 0) {
+      PRUint32 clusterLength =
+        GetClusterLength(mTextRun, offset, maxLength, rtl);
+      gfxFloat nextAdvance = advanceWidth +
+        mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
+      if (nextAdvance > maxAdvance) {
+        break;
+      }
+      // This cluster fits, include it.
+      advanceWidth = nextAdvance;
+      maxLength -= clusterLength;
+      offset += clusterLength;
+    }
+    maxLength = offset - *aStartOffset;
+    nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge;
+    *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth);
+  }
+  *aMaxLength = maxLength;
+  return maxLength != 0;
+}
+
 void
 nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
-                       const nsRect& aDirtyRect)
+                       const nsRect& aDirtyRect,
+                       const nsCharClipDisplayItem& aItem)
 {
   // Don't pass in aRenderingContext here, because we need a *reference*
   // context and aRenderingContext might have some transform in it
   // XXX get the block and line passed to us somehow! This is slow!
   gfxSkipCharsIterator iter = EnsureTextRun();
   if (!mTextRun)
     return;
 
-  nsTextPaintStyle textPaintStyle(this);
   PropertyProvider provider(this, iter);
   // Trim trailing whitespace
   provider.InitializeForDisplay(PR_TRUE);
 
   gfxContext* ctx = aRenderingContext->ThebesContext();
-
+  const PRBool rtl = mTextRun->IsRightToLeft();
+  const nscoord frameWidth = GetSize().width;
   gfxPoint framePt(aPt.x, aPt.y);
-  gfxPoint textBaselinePt(
-      mTextRun->IsRightToLeft() ? gfxFloat(aPt.x + GetSize().width) : framePt.x,
-      GetSnappedBaselineY(ctx, aPt.y));
-
-  gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
-                    aDirtyRect.width, aDirtyRect.height);
-
-  gfxFloat advanceWidth;
-  gfxRGBA foregroundColor = gfxRGBA(textPaintStyle.GetTextColor());
+  gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
+                          GetSnappedBaselineY(ctx, aPt.y));
+  PRUint32 startOffset = provider.GetStart().GetSkippedOffset();
+  PRUint32 maxLength = ComputeTransformedLength(provider);
+  nscoord snappedLeftEdge, snappedRightEdge;
+  if (!MeasureCharClippedText(ctx, provider, aItem.mLeftEdge, aItem.mRightEdge,
+         &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
+    return;
+  }
+  textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
+  nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
+                                             snappedRightEdge);
+  nsTextPaintStyle textPaintStyle(this);
+  nscolor foregroundColor = textPaintStyle.GetTextColor();
 
   // Paint the text shadow before doing any foreground stuff
   const nsStyleText* textStyle = GetStyleText();
   if (textStyle->mTextShadow) {
     // Text shadow happens with the last value being painted at the back,
     // ie. it is painted first.
     for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) {
-      PaintOneShadow(provider.GetStart().GetSkippedOffset(),
-                     ComputeTransformedLength(provider),
+      PaintOneShadow(startOffset, maxLength,
                      textStyle->mTextShadow->ShadowAt(i - 1), &provider,
                      aDirtyRect, framePt, textBaselinePt, ctx,
-                     textPaintStyle.GetTextColor());
-    }
-  }
-
+                     foregroundColor, clipEdges, snappedLeftEdge);
+    }
+  }
+
+  gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
+                    aDirtyRect.width, aDirtyRect.height);
   // Fork off to the (slower) paint-with-selection path if necessary.
   if (nsLayoutUtils::GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) {
     if (PaintTextWithSelection(ctx, framePt, textBaselinePt,
-                               dirtyRect, provider, textPaintStyle))
+                               dirtyRect, provider, textPaintStyle, clipEdges))
       return;
   }
 
-  ctx->SetColor(foregroundColor);
-
-  DrawText(ctx, textBaselinePt, provider.GetStart().GetSkippedOffset(),
-           ComputeTransformedLength(provider), &dirtyRect,
-           &provider, advanceWidth,
-           (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
+  ctx->SetColor(gfxRGBA(foregroundColor));
+
+  gfxFloat advanceWidth;
+  DrawText(ctx, textBaselinePt, startOffset, maxLength, &dirtyRect, &provider,
+           advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
   PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt,
-                       textPaintStyle, provider);
+                       textPaintStyle, provider, clipEdges);
 }
 
 void
 nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt,
                       PRUint32 aOffset, PRUint32 aLength,
                       const gfxRect* aDirtyRect, PropertyProvider* aProvider,
                       gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen)
 {