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 id20563
push usereakhgari@mozilla.com
push dateWed, 22 Jun 2011 23:17:45 +0000
treeherdermozilla-central@48e72227c2fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs312156
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)
 {