gecko/layout/generic/TextOverflow.cpp
L. David Baron 5eb8920348 Switch nsLayoutUtils inflation methods to the new setup with state on the pres context. (Bug 706609, patch 5) r=roc
This is the third of three patches to rework the way we handle getting
the font inflation container and width data during reflow, which are
needed so that we can sometimes honor inflation during intrinsic width
calculation (which we need to do to make some form controls inflate
correctly).
2012-01-24 17:21:29 -08:00

752 lines
27 KiB
C++

/* -*- 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)
* Michael Ventnor <m.ventnor@gmail.com>
*
* 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 "mozilla/Util.h"
#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"
#include "nsGfxScrollFrame.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(nsFontMetrics *aFontMetrics)
{
// Check if the first font supports Unicode ellipsis.
gfxFontGroup* fontGroup = aFontMetrics->GetThebesFontGroup();
gfxFont* firstFont = fontGroup->GetFontAt(0);
return firstFont && firstFont->HasCharacter(kEllipsisChar[0])
? nsDependentString(kEllipsisChar,
ArrayLength(kEllipsisChar) - 1)
: nsDependentString(kASCIIPeriodsChar,
ArrayLength(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(!nsLayoutUtils::GetAsBlock(aFrame) ||
!aFrame->GetStyleDisplay()->IsBlockOutside(),
"unexpected block frame");
NS_PRECONDITION(aFrameType != nsGkAtoms::placeholderFrame,
"unexpected placeholder frame");
return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
}
static bool
IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
nscoord* aSnappedLeft, nscoord* aSnappedRight)
{
*aSnappedLeft = aLeft;
*aSnappedRight = aRight;
if (aLeft <= 0 && aRight <= 0) {
return false;
}
return !aFrame->MeasureCharClippedText(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, nscoord aDelta)
{
nscoord xmost = aRect->XMost();
aRect->x -= aDelta;
aRect->width = NS_MAX(xmost - aRect->x, 0);
}
static void
InflateRight(nsRect* aRect, nscoord aDelta)
{
aRect->width = NS_MAX(aRect->width + aDelta, 0);
}
static bool
IsFrameDescendantOfAny(nsIFrame* aChild,
const TextOverflow::FrameHashtable& aSetOfFrames,
nsIFrame* aCommonAncestor)
{
for (nsIFrame* f = aChild; f && 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) {
nsRect shadowRect =
nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
return mRect.Union(shadowRect);
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx);
void PaintTextToContext(nsRenderingContext* aCtx,
nsPoint aOffsetFromRect);
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
};
static void
PaintTextShadowCallback(nsRenderingContext* aCtx,
nsPoint aShadowOffset,
const nscolor& aShadowColor,
void* aData)
{
reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
PaintTextToContext(aCtx, aShadowOffset);
}
void
nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx)
{
nscolor foregroundColor =
nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
// Paint the text-shadows for the overflow marker
nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
foregroundColor, PaintTextShadowCallback,
(void*)this);
aCtx->SetColor(foregroundColor);
PaintTextToContext(aCtx, nsPoint(0, 0));
}
void
nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx,
nsPoint aOffsetFromRect)
{
nsRefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(mFrame, nsLayoutUtils::eNotInReflow));
aCtx->SetFont(fm);
gfxFloat y = nsLayoutUtils::GetSnappedBaselineY(mFrame, aCtx->ThebesContext(),
mRect.y, mAscent);
nsPoint baselinePt(mRect.x, NSToCoordFloor(y));
nsLayoutUtils::DrawString(mFrame, aCtx, mString.get(),
mString.Length(), baselinePt + aOffsetFromRect);
}
/* 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());
nsIFrame* scrollFrame = do_QueryFrame(scroll);
scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
}
PRUint8 direction = aBlockFrame->GetStyleVisibility()->mDirection;
textOverflow->mBlockIsRTL = direction == NS_STYLE_DIRECTION_RTL;
const nsStyleTextReset* style = aBlockFrame->GetStyleTextReset();
textOverflow->mLeft.Init(style->mTextOverflow.GetLeft(direction));
textOverflow->mRight.Init(style->mTextOverflow.GetRight(direction));
// The left/right marker string is setup in ExamineLineFrames when a line
// has overflow on that side.
return textOverflow.forget();
}
void
TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
const nsRect& aContentArea,
const nsRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges)
{
const nsIAtom* frameType = aFrame->GetType();
if (frameType == nsGkAtoms::brFrame ||
frameType == nsGkAtoms::placeholderFrame) {
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 && ((mLeft.mActive && overflowLeft) ||
(mRight.mActive && overflowRight))) {
aFramesToHide->PutEntry(aFrame);
} else if (isAtomic || frameType == nsGkAtoms::textFrame) {
AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
aFramesToHide, aAlignmentEdges,
aFoundVisibleTextOrAtomic,
aClippedMarkerEdges);
}
}
if (isAtomic) {
return;
}
nsIFrame* child = aFrame->GetFirstPrincipalChild();
while (child) {
ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
aFramesToHide, aAlignmentEdges,
aFoundVisibleTextOrAtomic,
aClippedMarkerEdges);
child = child->GetNextSibling();
}
}
void
TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
const nsIAtom* aFrameType,
const nsRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges)
{
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) {
aClippedMarkerEdges->AccumulateLeft(borderRect);
if (!mLeft.mActive) {
leftOverlap = 0;
}
}
if (rightOverlap > 0) {
aClippedMarkerEdges->AccumulateRight(borderRect);
if (!mRight.mActive) {
rightOverlap = 0;
}
}
if ((leftOverlap > 0 && insideLeftEdge) ||
(rightOverlap > 0 && insideRightEdge)) {
if (aFrameType == nsGkAtoms::textFrame) {
if (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);
*aFoundVisibleTextOrAtomic = true;
}
}
} else {
aFramesToHide->PutEntry(aFrame);
}
} else if (!insideLeftEdge || !insideRightEdge) {
// frame is outside
if (IsAtomicElement(aFrame, aFrameType)) {
aFramesToHide->PutEntry(aFrame);
}
} else {
// frame is inside
aAlignmentEdges->Accumulate(borderRect);
*aFoundVisibleTextOrAtomic = true;
}
}
void
TextOverflow::ExamineLineFrames(nsLineBox* aLine,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges)
{
// No ellipsing for 'clip' style.
bool suppressLeft = mLeft.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
bool suppressRight = mRight.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
if (mCanHaveHorizontalScrollbar) {
nsIScrollableFrame* scroll = nsLayoutUtils::GetScrollableFrameFor(mBlock);
nsPoint pos = scroll->GetScrollPosition();
nsRect scrollRange = scroll->GetScrollRange();
// No ellipsing when nothing to scroll to on that side (this includes
// overflow:auto that doesn't trigger a horizontal scrollbar).
if (pos.x <= scrollRange.x) {
suppressLeft = true;
}
if (pos.x >= scrollRange.XMost()) {
suppressRight = true;
}
}
// 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, scrollAdjust);
InflateRight(&contentArea, scrollAdjust);
nsRect lineRect = aLine->GetScrollableOverflowArea();
const bool leftOverflow =
!suppressLeft && lineRect.x < contentArea.x;
const bool rightOverflow =
!suppressRight && lineRect.XMost() > contentArea.XMost();
if (!leftOverflow && !rightOverflow) {
// The line does not overflow on a side we should ellipsize.
return;
}
int pass = 0;
bool retryEmptyLine = true;
bool guessLeft = leftOverflow;
bool guessRight = rightOverflow;
mLeft.mActive = leftOverflow;
mRight.mActive = rightOverflow;
bool clippedLeftMarker = false;
bool clippedRightMarker = false;
do {
// Setup marker strings as needed.
if (guessLeft) {
mLeft.SetupString(mBlock);
}
if (guessRight) {
mRight.SetupString(mBlock);
}
// If there is insufficient space for both markers then keep the one on the
// end side per the block's 'direction'.
nscoord rightMarkerWidth = mRight.mActive ? mRight.mWidth : 0;
nscoord leftMarkerWidth = mLeft.mActive ? mLeft.mWidth : 0;
if (leftMarkerWidth && rightMarkerWidth &&
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;
if (guessLeft) {
InflateLeft(&insideMarkersArea, -leftMarkerWidth);
}
if (guessRight) {
InflateRight(&insideMarkersArea, -rightMarkerWidth);
}
// Analyze the frames on aLine for the overflow situation at the content
// edges and at the edges of the area between the markers.
bool foundVisibleTextOrAtomic = false;
PRInt32 n = aLine->GetChildCount();
nsIFrame* child = aLine->mFirstChild;
InnerClipEdges clippedMarkerEdges;
for (; n-- > 0; child = child->GetNextSibling()) {
ExamineFrameSubtree(child, contentArea, insideMarkersArea,
aFramesToHide, aAlignmentEdges,
&foundVisibleTextOrAtomic,
&clippedMarkerEdges);
}
if (!foundVisibleTextOrAtomic && retryEmptyLine) {
aAlignmentEdges->mAssigned = false;
aFramesToHide->Clear();
pass = -1;
if (mLeft.IsNeeded() && mLeft.mActive && !clippedLeftMarker) {
if (clippedMarkerEdges.mAssignedLeft &&
clippedMarkerEdges.mLeft - mContentArea.X() > 0) {
mLeft.mWidth = clippedMarkerEdges.mLeft - mContentArea.X();
NS_ASSERTION(mLeft.mWidth < mLeft.mIntrinsicWidth,
"clipping a marker should make it strictly smaller");
clippedLeftMarker = true;
} else {
mLeft.mActive = guessLeft = false;
}
continue;
}
if (mRight.IsNeeded() && mRight.mActive && !clippedRightMarker) {
if (clippedMarkerEdges.mAssignedRight &&
mContentArea.XMost() - clippedMarkerEdges.mRight > 0) {
mRight.mWidth = mContentArea.XMost() - clippedMarkerEdges.mRight;
NS_ASSERTION(mRight.mWidth < mRight.mIntrinsicWidth,
"clipping a marker should make it strictly smaller");
clippedRightMarker = true;
} else {
mRight.mActive = guessRight = false;
}
continue;
}
// The line simply has no visible content even without markers,
// so examine the line again without suppressing markers.
retryEmptyLine = false;
mLeft.mWidth = mLeft.mIntrinsicWidth;
mLeft.mActive = guessLeft = leftOverflow;
mRight.mWidth = mRight.mIntrinsicWidth;
mRight.mActive = guessRight = rightOverflow;
continue;
}
if (guessLeft == (mLeft.mActive && mLeft.IsNeeded()) &&
guessRight == (mRight.mActive && mRight.IsNeeded())) {
break;
} else {
guessLeft = mLeft.mActive && mLeft.IsNeeded();
guessRight = mRight.mActive && mRight.IsNeeded();
mLeft.Reset();
mRight.Reset();
aFramesToHide->Clear();
}
NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
} while (++pass != 2);
if (!leftOverflow || !mLeft.mActive) {
mLeft.Reset();
}
if (!rightOverflow || !mRight.mActive) {
mRight.Reset();
}
}
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();
mLeft.mActive = mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
mRight.Reset();
mRight.mActive = mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
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, -mLeft.mWidth);
}
if (needRight) {
InflateRight(&insideMarkersArea, -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 < ArrayLength(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) {
nscoord left = aInsideMarkersArea.x - rect.x;
if (NS_UNLIKELY(left < 0)) {
item->~nsDisplayItem();
continue;
}
charClip->mLeftEdge = left;
}
if (mRight.IsNeeded() && rect.XMost() > aInsideMarkersArea.XMost()) {
nscoord right = rect.XMost() - aInsideMarkersArea.XMost();
if (NS_UNLIKELY(right < 0)) {
item->~nsDisplayItem();
continue;
}
charClip->mRightEdge = right;
}
}
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.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
style->mTextOverflow.mRight.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();
bool visible = 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.mIntrinsicWidth,
aLine->mBounds.y,
mLeft.mIntrinsicWidth, 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.mIntrinsicWidth, 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();
nsRefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(aFrame, nsLayoutUtils::eNotInReflow));
rc->SetFont(fm);
mMarkerString = mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS ?
GetEllipsis(fm) : mStyle->mString;
mWidth = nsLayoutUtils::GetStringWidth(aFrame, rc, mMarkerString.get(),
mMarkerString.Length());
mIntrinsicWidth = mWidth;
mInitialized = true;
}
} // namespace css
} // namespace mozilla