1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/TextOverflow.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,774 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "TextOverflow.h" 1.11 +#include <algorithm> 1.12 + 1.13 +// Please maintain alphabetical order below 1.14 +#include "nsBlockFrame.h" 1.15 +#include "nsCaret.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "nsCSSAnonBoxes.h" 1.18 +#include "nsGfxScrollFrame.h" 1.19 +#include "nsIScrollableFrame.h" 1.20 +#include "nsLayoutUtils.h" 1.21 +#include "nsPresContext.h" 1.22 +#include "nsRect.h" 1.23 +#include "nsRenderingContext.h" 1.24 +#include "nsTextFrame.h" 1.25 +#include "nsIFrameInlines.h" 1.26 +#include "mozilla/ArrayUtils.h" 1.27 +#include "mozilla/Likely.h" 1.28 + 1.29 +namespace mozilla { 1.30 +namespace css { 1.31 + 1.32 +class LazyReferenceRenderingContextGetterFromFrame MOZ_FINAL : 1.33 + public gfxFontGroup::LazyReferenceContextGetter { 1.34 +public: 1.35 + LazyReferenceRenderingContextGetterFromFrame(nsIFrame* aFrame) 1.36 + : mFrame(aFrame) {} 1.37 + virtual already_AddRefed<gfxContext> GetRefContext() MOZ_OVERRIDE 1.38 + { 1.39 + nsRefPtr<nsRenderingContext> rc = 1.40 + mFrame->PresContext()->PresShell()->CreateReferenceRenderingContext(); 1.41 + nsRefPtr<gfxContext> ctx = rc->ThebesContext(); 1.42 + return ctx.forget(); 1.43 + } 1.44 +private: 1.45 + nsIFrame* mFrame; 1.46 +}; 1.47 + 1.48 +static gfxTextRun* 1.49 +GetEllipsisTextRun(nsIFrame* aFrame) 1.50 +{ 1.51 + nsRefPtr<nsFontMetrics> fm; 1.52 + nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm), 1.53 + nsLayoutUtils::FontSizeInflationFor(aFrame)); 1.54 + LazyReferenceRenderingContextGetterFromFrame lazyRefContextGetter(aFrame); 1.55 + return fm->GetThebesFontGroup()->GetEllipsisTextRun( 1.56 + aFrame->PresContext()->AppUnitsPerDevPixel(), lazyRefContextGetter); 1.57 +} 1.58 + 1.59 +static nsIFrame* 1.60 +GetSelfOrNearestBlock(nsIFrame* aFrame) 1.61 +{ 1.62 + return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame : 1.63 + nsLayoutUtils::FindNearestBlockAncestor(aFrame); 1.64 +} 1.65 + 1.66 +// Return true if the frame is an atomic inline-level element. 1.67 +// It's not supposed to be called for block frames since we never 1.68 +// process block descendants for text-overflow. 1.69 +static bool 1.70 +IsAtomicElement(nsIFrame* aFrame, const nsIAtom* aFrameType) 1.71 +{ 1.72 + NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame) || 1.73 + !aFrame->IsBlockOutside(), 1.74 + "unexpected block frame"); 1.75 + NS_PRECONDITION(aFrameType != nsGkAtoms::placeholderFrame, 1.76 + "unexpected placeholder frame"); 1.77 + return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant); 1.78 +} 1.79 + 1.80 +static bool 1.81 +IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight, 1.82 + nscoord* aSnappedLeft, nscoord* aSnappedRight) 1.83 +{ 1.84 + *aSnappedLeft = aLeft; 1.85 + *aSnappedRight = aRight; 1.86 + if (aLeft <= 0 && aRight <= 0) { 1.87 + return false; 1.88 + } 1.89 + return !aFrame->MeasureCharClippedText(aLeft, aRight, 1.90 + aSnappedLeft, aSnappedRight); 1.91 +} 1.92 + 1.93 +static bool 1.94 +IsHorizontalOverflowVisible(nsIFrame* aFrame) 1.95 +{ 1.96 + NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr, 1.97 + "expected a block frame"); 1.98 + 1.99 + nsIFrame* f = aFrame; 1.100 + while (f && f->StyleContext()->GetPseudo() && 1.101 + f->GetType() != nsGkAtoms::scrollFrame) { 1.102 + f = f->GetParent(); 1.103 + } 1.104 + return !f || f->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE; 1.105 +} 1.106 + 1.107 +static void 1.108 +ClipMarker(const nsRect& aContentArea, 1.109 + const nsRect& aMarkerRect, 1.110 + DisplayListClipState::AutoSaveRestore& aClipState) 1.111 +{ 1.112 + nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost(); 1.113 + nsRect markerRect = aMarkerRect; 1.114 + if (rightOverflow > 0) { 1.115 + // Marker overflows on the right side (content width < marker width). 1.116 + markerRect.width -= rightOverflow; 1.117 + aClipState.ClipContentDescendants(markerRect); 1.118 + } else { 1.119 + nscoord leftOverflow = aContentArea.x - aMarkerRect.x; 1.120 + if (leftOverflow > 0) { 1.121 + // Marker overflows on the left side 1.122 + markerRect.width -= leftOverflow; 1.123 + markerRect.x += leftOverflow; 1.124 + aClipState.ClipContentDescendants(markerRect); 1.125 + } 1.126 + } 1.127 +} 1.128 + 1.129 +static void 1.130 +InflateLeft(nsRect* aRect, nscoord aDelta) 1.131 +{ 1.132 + nscoord xmost = aRect->XMost(); 1.133 + aRect->x -= aDelta; 1.134 + aRect->width = std::max(xmost - aRect->x, 0); 1.135 +} 1.136 + 1.137 +static void 1.138 +InflateRight(nsRect* aRect, nscoord aDelta) 1.139 +{ 1.140 + aRect->width = std::max(aRect->width + aDelta, 0); 1.141 +} 1.142 + 1.143 +static bool 1.144 +IsFrameDescendantOfAny(nsIFrame* aChild, 1.145 + const TextOverflow::FrameHashtable& aSetOfFrames, 1.146 + nsIFrame* aCommonAncestor) 1.147 +{ 1.148 + for (nsIFrame* f = aChild; f && f != aCommonAncestor; 1.149 + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { 1.150 + if (aSetOfFrames.GetEntry(f)) { 1.151 + return true; 1.152 + } 1.153 + } 1.154 + return false; 1.155 +} 1.156 + 1.157 +class nsDisplayTextOverflowMarker : public nsDisplayItem 1.158 +{ 1.159 +public: 1.160 + nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, 1.161 + const nsRect& aRect, nscoord aAscent, 1.162 + const nsStyleTextOverflowSide* aStyle, 1.163 + uint32_t aIndex) 1.164 + : nsDisplayItem(aBuilder, aFrame), mRect(aRect), 1.165 + mStyle(aStyle), mAscent(aAscent), mIndex(aIndex) { 1.166 + MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker); 1.167 + } 1.168 +#ifdef NS_BUILD_REFCNT_LOGGING 1.169 + virtual ~nsDisplayTextOverflowMarker() { 1.170 + MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker); 1.171 + } 1.172 +#endif 1.173 + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, 1.174 + bool* aSnap) MOZ_OVERRIDE { 1.175 + *aSnap = false; 1.176 + nsRect shadowRect = 1.177 + nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame); 1.178 + return mRect.Union(shadowRect); 1.179 + } 1.180 + virtual void Paint(nsDisplayListBuilder* aBuilder, 1.181 + nsRenderingContext* aCtx) MOZ_OVERRIDE; 1.182 + 1.183 + virtual uint32_t GetPerFrameKey() MOZ_OVERRIDE { 1.184 + return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); 1.185 + } 1.186 + void PaintTextToContext(nsRenderingContext* aCtx, 1.187 + nsPoint aOffsetFromRect); 1.188 + NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW) 1.189 +private: 1.190 + nsRect mRect; // in reference frame coordinates 1.191 + const nsStyleTextOverflowSide* mStyle; 1.192 + nscoord mAscent; // baseline for the marker text in mRect 1.193 + uint32_t mIndex; 1.194 +}; 1.195 + 1.196 +static void 1.197 +PaintTextShadowCallback(nsRenderingContext* aCtx, 1.198 + nsPoint aShadowOffset, 1.199 + const nscolor& aShadowColor, 1.200 + void* aData) 1.201 +{ 1.202 + reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)-> 1.203 + PaintTextToContext(aCtx, aShadowOffset); 1.204 +} 1.205 + 1.206 +void 1.207 +nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder, 1.208 + nsRenderingContext* aCtx) 1.209 +{ 1.210 + nscolor foregroundColor = 1.211 + nsLayoutUtils::GetColor(mFrame, eCSSProperty_color); 1.212 + 1.213 + // Paint the text-shadows for the overflow marker 1.214 + nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect, 1.215 + foregroundColor, PaintTextShadowCallback, 1.216 + (void*)this); 1.217 + aCtx->SetColor(foregroundColor); 1.218 + PaintTextToContext(aCtx, nsPoint(0, 0)); 1.219 +} 1.220 + 1.221 +void 1.222 +nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx, 1.223 + nsPoint aOffsetFromRect) 1.224 +{ 1.225 + gfxFloat y = nsLayoutUtils::GetSnappedBaselineY(mFrame, aCtx->ThebesContext(), 1.226 + mRect.y, mAscent); 1.227 + nsPoint baselinePt(mRect.x, NSToCoordFloor(y)); 1.228 + nsPoint pt = baselinePt + aOffsetFromRect; 1.229 + 1.230 + if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) { 1.231 + gfxTextRun* textRun = GetEllipsisTextRun(mFrame); 1.232 + if (textRun) { 1.233 + NS_ASSERTION(!textRun->IsRightToLeft(), 1.234 + "Ellipsis textruns should always be LTR!"); 1.235 + gfxPoint gfxPt(pt.x, pt.y); 1.236 + textRun->Draw(aCtx->ThebesContext(), gfxPt, DrawMode::GLYPH_FILL, 1.237 + 0, textRun->GetLength(), nullptr, nullptr, nullptr); 1.238 + } 1.239 + } else { 1.240 + nsRefPtr<nsFontMetrics> fm; 1.241 + nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm), 1.242 + nsLayoutUtils::FontSizeInflationFor(mFrame)); 1.243 + aCtx->SetFont(fm); 1.244 + nsLayoutUtils::DrawString(mFrame, aCtx, mStyle->mString.get(), 1.245 + mStyle->mString.Length(), pt); 1.246 + } 1.247 +} 1.248 + 1.249 +void 1.250 +TextOverflow::Init(nsDisplayListBuilder* aBuilder, 1.251 + nsIFrame* aBlockFrame) 1.252 +{ 1.253 + mBuilder = aBuilder; 1.254 + mBlock = aBlockFrame; 1.255 + mContentArea = aBlockFrame->GetContentRectRelativeToSelf(); 1.256 + mScrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aBlockFrame); 1.257 + uint8_t direction = aBlockFrame->StyleVisibility()->mDirection; 1.258 + mBlockIsRTL = direction == NS_STYLE_DIRECTION_RTL; 1.259 + mAdjustForPixelSnapping = false; 1.260 +#ifdef MOZ_XUL 1.261 + if (!mScrollableFrame) { 1.262 + nsIAtom* pseudoType = aBlockFrame->StyleContext()->GetPseudo(); 1.263 + if (pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) { 1.264 + mScrollableFrame = 1.265 + nsLayoutUtils::GetScrollableFrameFor(aBlockFrame->GetParent()); 1.266 + // nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels 1.267 + // for RTL blocks (also for overflow:hidden), so we need to move 1.268 + // the edges 1px outward in ExamineLineFrames to avoid triggering 1.269 + // a text-overflow marker in this case. 1.270 + mAdjustForPixelSnapping = mBlockIsRTL; 1.271 + } 1.272 + } 1.273 +#endif 1.274 + mCanHaveHorizontalScrollbar = false; 1.275 + if (mScrollableFrame) { 1.276 + mCanHaveHorizontalScrollbar = 1.277 + mScrollableFrame->GetScrollbarStyles().mHorizontal != NS_STYLE_OVERFLOW_HIDDEN; 1.278 + if (!mAdjustForPixelSnapping) { 1.279 + // Scrolling to the end position can leave some text still overflowing due 1.280 + // to pixel snapping behaviour in our scrolling code. 1.281 + mAdjustForPixelSnapping = mCanHaveHorizontalScrollbar; 1.282 + } 1.283 + mContentArea.MoveBy(mScrollableFrame->GetScrollPosition()); 1.284 + nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame); 1.285 + scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL); 1.286 + } 1.287 + const nsStyleTextReset* style = aBlockFrame->StyleTextReset(); 1.288 + mLeft.Init(style->mTextOverflow.GetLeft(direction)); 1.289 + mRight.Init(style->mTextOverflow.GetRight(direction)); 1.290 + // The left/right marker string is setup in ExamineLineFrames when a line 1.291 + // has overflow on that side. 1.292 +} 1.293 + 1.294 +/* static */ TextOverflow* 1.295 +TextOverflow::WillProcessLines(nsDisplayListBuilder* aBuilder, 1.296 + nsIFrame* aBlockFrame) 1.297 +{ 1.298 + if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) { 1.299 + return nullptr; 1.300 + } 1.301 + nsAutoPtr<TextOverflow> textOverflow(new TextOverflow); 1.302 + textOverflow->Init(aBuilder, aBlockFrame); 1.303 + return textOverflow.forget(); 1.304 +} 1.305 + 1.306 +void 1.307 +TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame, 1.308 + const nsRect& aContentArea, 1.309 + const nsRect& aInsideMarkersArea, 1.310 + FrameHashtable* aFramesToHide, 1.311 + AlignmentEdges* aAlignmentEdges, 1.312 + bool* aFoundVisibleTextOrAtomic, 1.313 + InnerClipEdges* aClippedMarkerEdges) 1.314 +{ 1.315 + const nsIAtom* frameType = aFrame->GetType(); 1.316 + if (frameType == nsGkAtoms::brFrame || 1.317 + frameType == nsGkAtoms::placeholderFrame) { 1.318 + return; 1.319 + } 1.320 + const bool isAtomic = IsAtomicElement(aFrame, frameType); 1.321 + if (aFrame->StyleVisibility()->IsVisible()) { 1.322 + nsRect childRect = aFrame->GetScrollableOverflowRect() + 1.323 + aFrame->GetOffsetTo(mBlock); 1.324 + bool overflowLeft = childRect.x < aContentArea.x; 1.325 + bool overflowRight = childRect.XMost() > aContentArea.XMost(); 1.326 + if (overflowLeft) { 1.327 + mLeft.mHasOverflow = true; 1.328 + } 1.329 + if (overflowRight) { 1.330 + mRight.mHasOverflow = true; 1.331 + } 1.332 + if (isAtomic && ((mLeft.mActive && overflowLeft) || 1.333 + (mRight.mActive && overflowRight))) { 1.334 + aFramesToHide->PutEntry(aFrame); 1.335 + } else if (isAtomic || frameType == nsGkAtoms::textFrame) { 1.336 + AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea, 1.337 + aFramesToHide, aAlignmentEdges, 1.338 + aFoundVisibleTextOrAtomic, 1.339 + aClippedMarkerEdges); 1.340 + } 1.341 + } 1.342 + if (isAtomic) { 1.343 + return; 1.344 + } 1.345 + 1.346 + nsIFrame* child = aFrame->GetFirstPrincipalChild(); 1.347 + while (child) { 1.348 + ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea, 1.349 + aFramesToHide, aAlignmentEdges, 1.350 + aFoundVisibleTextOrAtomic, 1.351 + aClippedMarkerEdges); 1.352 + child = child->GetNextSibling(); 1.353 + } 1.354 +} 1.355 + 1.356 +void 1.357 +TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame, 1.358 + const nsIAtom* aFrameType, 1.359 + const nsRect& aInsideMarkersArea, 1.360 + FrameHashtable* aFramesToHide, 1.361 + AlignmentEdges* aAlignmentEdges, 1.362 + bool* aFoundVisibleTextOrAtomic, 1.363 + InnerClipEdges* aClippedMarkerEdges) 1.364 +{ 1.365 + nsRect borderRect(aFrame->GetOffsetTo(mBlock), aFrame->GetSize()); 1.366 + nscoord leftOverlap = 1.367 + std::max(aInsideMarkersArea.x - borderRect.x, 0); 1.368 + nscoord rightOverlap = 1.369 + std::max(borderRect.XMost() - aInsideMarkersArea.XMost(), 0); 1.370 + bool insideLeftEdge = aInsideMarkersArea.x <= borderRect.XMost(); 1.371 + bool insideRightEdge = borderRect.x <= aInsideMarkersArea.XMost(); 1.372 + 1.373 + if (leftOverlap > 0) { 1.374 + aClippedMarkerEdges->AccumulateLeft(borderRect); 1.375 + if (!mLeft.mActive) { 1.376 + leftOverlap = 0; 1.377 + } 1.378 + } 1.379 + if (rightOverlap > 0) { 1.380 + aClippedMarkerEdges->AccumulateRight(borderRect); 1.381 + if (!mRight.mActive) { 1.382 + rightOverlap = 0; 1.383 + } 1.384 + } 1.385 + 1.386 + if ((leftOverlap > 0 && insideLeftEdge) || 1.387 + (rightOverlap > 0 && insideRightEdge)) { 1.388 + if (aFrameType == nsGkAtoms::textFrame) { 1.389 + if (aInsideMarkersArea.x < aInsideMarkersArea.XMost()) { 1.390 + // a clipped text frame and there is some room between the markers 1.391 + nscoord snappedLeft, snappedRight; 1.392 + bool isFullyClipped = 1.393 + IsFullyClipped(static_cast<nsTextFrame*>(aFrame), 1.394 + leftOverlap, rightOverlap, &snappedLeft, &snappedRight); 1.395 + if (!isFullyClipped) { 1.396 + nsRect snappedRect = borderRect; 1.397 + if (leftOverlap > 0) { 1.398 + snappedRect.x += snappedLeft; 1.399 + snappedRect.width -= snappedLeft; 1.400 + } 1.401 + if (rightOverlap > 0) { 1.402 + snappedRect.width -= snappedRight; 1.403 + } 1.404 + aAlignmentEdges->Accumulate(snappedRect); 1.405 + *aFoundVisibleTextOrAtomic = true; 1.406 + } 1.407 + } 1.408 + } else { 1.409 + aFramesToHide->PutEntry(aFrame); 1.410 + } 1.411 + } else if (!insideLeftEdge || !insideRightEdge) { 1.412 + // frame is outside 1.413 + if (IsAtomicElement(aFrame, aFrameType)) { 1.414 + aFramesToHide->PutEntry(aFrame); 1.415 + } 1.416 + } else { 1.417 + // frame is inside 1.418 + aAlignmentEdges->Accumulate(borderRect); 1.419 + *aFoundVisibleTextOrAtomic = true; 1.420 + } 1.421 +} 1.422 + 1.423 +void 1.424 +TextOverflow::ExamineLineFrames(nsLineBox* aLine, 1.425 + FrameHashtable* aFramesToHide, 1.426 + AlignmentEdges* aAlignmentEdges) 1.427 +{ 1.428 + // No ellipsing for 'clip' style. 1.429 + bool suppressLeft = mLeft.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP; 1.430 + bool suppressRight = mRight.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP; 1.431 + if (mCanHaveHorizontalScrollbar) { 1.432 + nsPoint pos = mScrollableFrame->GetScrollPosition(); 1.433 + nsRect scrollRange = mScrollableFrame->GetScrollRange(); 1.434 + // No ellipsing when nothing to scroll to on that side (this includes 1.435 + // overflow:auto that doesn't trigger a horizontal scrollbar). 1.436 + if (pos.x <= scrollRange.x) { 1.437 + suppressLeft = true; 1.438 + } 1.439 + if (pos.x >= scrollRange.XMost()) { 1.440 + suppressRight = true; 1.441 + } 1.442 + } 1.443 + 1.444 + nsRect contentArea = mContentArea; 1.445 + const nscoord scrollAdjust = mAdjustForPixelSnapping ? 1.446 + mBlock->PresContext()->AppUnitsPerDevPixel() : 0; 1.447 + InflateLeft(&contentArea, scrollAdjust); 1.448 + InflateRight(&contentArea, scrollAdjust); 1.449 + nsRect lineRect = aLine->GetScrollableOverflowArea(); 1.450 + const bool leftOverflow = 1.451 + !suppressLeft && lineRect.x < contentArea.x; 1.452 + const bool rightOverflow = 1.453 + !suppressRight && lineRect.XMost() > contentArea.XMost(); 1.454 + if (!leftOverflow && !rightOverflow) { 1.455 + // The line does not overflow on a side we should ellipsize. 1.456 + return; 1.457 + } 1.458 + 1.459 + int pass = 0; 1.460 + bool retryEmptyLine = true; 1.461 + bool guessLeft = leftOverflow; 1.462 + bool guessRight = rightOverflow; 1.463 + mLeft.mActive = leftOverflow; 1.464 + mRight.mActive = rightOverflow; 1.465 + bool clippedLeftMarker = false; 1.466 + bool clippedRightMarker = false; 1.467 + do { 1.468 + // Setup marker strings as needed. 1.469 + if (guessLeft) { 1.470 + mLeft.SetupString(mBlock); 1.471 + } 1.472 + if (guessRight) { 1.473 + mRight.SetupString(mBlock); 1.474 + } 1.475 + 1.476 + // If there is insufficient space for both markers then keep the one on the 1.477 + // end side per the block's 'direction'. 1.478 + nscoord rightMarkerWidth = mRight.mActive ? mRight.mWidth : 0; 1.479 + nscoord leftMarkerWidth = mLeft.mActive ? mLeft.mWidth : 0; 1.480 + if (leftMarkerWidth && rightMarkerWidth && 1.481 + leftMarkerWidth + rightMarkerWidth > contentArea.width) { 1.482 + if (mBlockIsRTL) { 1.483 + rightMarkerWidth = 0; 1.484 + } else { 1.485 + leftMarkerWidth = 0; 1.486 + } 1.487 + } 1.488 + 1.489 + // Calculate the area between the potential markers aligned at the 1.490 + // block's edge. 1.491 + nsRect insideMarkersArea = mContentArea; 1.492 + if (guessLeft) { 1.493 + InflateLeft(&insideMarkersArea, -leftMarkerWidth); 1.494 + } 1.495 + if (guessRight) { 1.496 + InflateRight(&insideMarkersArea, -rightMarkerWidth); 1.497 + } 1.498 + 1.499 + // Analyze the frames on aLine for the overflow situation at the content 1.500 + // edges and at the edges of the area between the markers. 1.501 + bool foundVisibleTextOrAtomic = false; 1.502 + int32_t n = aLine->GetChildCount(); 1.503 + nsIFrame* child = aLine->mFirstChild; 1.504 + InnerClipEdges clippedMarkerEdges; 1.505 + for (; n-- > 0; child = child->GetNextSibling()) { 1.506 + ExamineFrameSubtree(child, contentArea, insideMarkersArea, 1.507 + aFramesToHide, aAlignmentEdges, 1.508 + &foundVisibleTextOrAtomic, 1.509 + &clippedMarkerEdges); 1.510 + } 1.511 + if (!foundVisibleTextOrAtomic && retryEmptyLine) { 1.512 + aAlignmentEdges->mAssigned = false; 1.513 + aFramesToHide->Clear(); 1.514 + pass = -1; 1.515 + if (mLeft.IsNeeded() && mLeft.mActive && !clippedLeftMarker) { 1.516 + if (clippedMarkerEdges.mAssignedLeft && 1.517 + clippedMarkerEdges.mLeft - mContentArea.X() > 0) { 1.518 + mLeft.mWidth = clippedMarkerEdges.mLeft - mContentArea.X(); 1.519 + NS_ASSERTION(mLeft.mWidth < mLeft.mIntrinsicWidth, 1.520 + "clipping a marker should make it strictly smaller"); 1.521 + clippedLeftMarker = true; 1.522 + } else { 1.523 + mLeft.mActive = guessLeft = false; 1.524 + } 1.525 + continue; 1.526 + } 1.527 + if (mRight.IsNeeded() && mRight.mActive && !clippedRightMarker) { 1.528 + if (clippedMarkerEdges.mAssignedRight && 1.529 + mContentArea.XMost() - clippedMarkerEdges.mRight > 0) { 1.530 + mRight.mWidth = mContentArea.XMost() - clippedMarkerEdges.mRight; 1.531 + NS_ASSERTION(mRight.mWidth < mRight.mIntrinsicWidth, 1.532 + "clipping a marker should make it strictly smaller"); 1.533 + clippedRightMarker = true; 1.534 + } else { 1.535 + mRight.mActive = guessRight = false; 1.536 + } 1.537 + continue; 1.538 + } 1.539 + // The line simply has no visible content even without markers, 1.540 + // so examine the line again without suppressing markers. 1.541 + retryEmptyLine = false; 1.542 + mLeft.mWidth = mLeft.mIntrinsicWidth; 1.543 + mLeft.mActive = guessLeft = leftOverflow; 1.544 + mRight.mWidth = mRight.mIntrinsicWidth; 1.545 + mRight.mActive = guessRight = rightOverflow; 1.546 + continue; 1.547 + } 1.548 + if (guessLeft == (mLeft.mActive && mLeft.IsNeeded()) && 1.549 + guessRight == (mRight.mActive && mRight.IsNeeded())) { 1.550 + break; 1.551 + } else { 1.552 + guessLeft = mLeft.mActive && mLeft.IsNeeded(); 1.553 + guessRight = mRight.mActive && mRight.IsNeeded(); 1.554 + mLeft.Reset(); 1.555 + mRight.Reset(); 1.556 + aFramesToHide->Clear(); 1.557 + } 1.558 + NS_ASSERTION(pass == 0, "2nd pass should never guess wrong"); 1.559 + } while (++pass != 2); 1.560 + if (!leftOverflow || !mLeft.mActive) { 1.561 + mLeft.Reset(); 1.562 + } 1.563 + if (!rightOverflow || !mRight.mActive) { 1.564 + mRight.Reset(); 1.565 + } 1.566 +} 1.567 + 1.568 +void 1.569 +TextOverflow::ProcessLine(const nsDisplayListSet& aLists, 1.570 + nsLineBox* aLine) 1.571 +{ 1.572 + NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP || 1.573 + mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP, 1.574 + "TextOverflow with 'clip' for both sides"); 1.575 + mLeft.Reset(); 1.576 + mLeft.mActive = mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP; 1.577 + mRight.Reset(); 1.578 + mRight.mActive = mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP; 1.579 + 1.580 + FrameHashtable framesToHide(100); 1.581 + AlignmentEdges alignmentEdges; 1.582 + ExamineLineFrames(aLine, &framesToHide, &alignmentEdges); 1.583 + bool needLeft = mLeft.IsNeeded(); 1.584 + bool needRight = mRight.IsNeeded(); 1.585 + if (!needLeft && !needRight) { 1.586 + return; 1.587 + } 1.588 + NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP || 1.589 + !needLeft, "left marker for 'clip'"); 1.590 + NS_ASSERTION(mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP || 1.591 + !needRight, "right marker for 'clip'"); 1.592 + 1.593 + // If there is insufficient space for both markers then keep the one on the 1.594 + // end side per the block's 'direction'. 1.595 + if (needLeft && needRight && 1.596 + mLeft.mWidth + mRight.mWidth > mContentArea.width) { 1.597 + if (mBlockIsRTL) { 1.598 + needRight = false; 1.599 + } else { 1.600 + needLeft = false; 1.601 + } 1.602 + } 1.603 + nsRect insideMarkersArea = mContentArea; 1.604 + if (needLeft) { 1.605 + InflateLeft(&insideMarkersArea, -mLeft.mWidth); 1.606 + } 1.607 + if (needRight) { 1.608 + InflateRight(&insideMarkersArea, -mRight.mWidth); 1.609 + } 1.610 + if (!mCanHaveHorizontalScrollbar && alignmentEdges.mAssigned) { 1.611 + nsRect alignmentRect = nsRect(alignmentEdges.x, insideMarkersArea.y, 1.612 + alignmentEdges.Width(), 1); 1.613 + insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect); 1.614 + } 1.615 + 1.616 + // Clip and remove display items as needed at the final marker edges. 1.617 + nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() }; 1.618 + for (uint32_t i = 0; i < ArrayLength(lists); ++i) { 1.619 + PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea); 1.620 + } 1.621 + CreateMarkers(aLine, needLeft, needRight, insideMarkersArea); 1.622 +} 1.623 + 1.624 +void 1.625 +TextOverflow::PruneDisplayListContents(nsDisplayList* aList, 1.626 + const FrameHashtable& aFramesToHide, 1.627 + const nsRect& aInsideMarkersArea) 1.628 +{ 1.629 + nsDisplayList saved; 1.630 + nsDisplayItem* item; 1.631 + while ((item = aList->RemoveBottom())) { 1.632 + nsIFrame* itemFrame = item->Frame(); 1.633 + if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) { 1.634 + item->~nsDisplayItem(); 1.635 + continue; 1.636 + } 1.637 + 1.638 + nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren(); 1.639 + if (wrapper) { 1.640 + if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) { 1.641 + PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea); 1.642 + } 1.643 + } 1.644 + 1.645 + nsCharClipDisplayItem* charClip = itemFrame ? 1.646 + nsCharClipDisplayItem::CheckCast(item) : nullptr; 1.647 + if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) { 1.648 + nsRect rect = itemFrame->GetScrollableOverflowRect() + 1.649 + itemFrame->GetOffsetTo(mBlock); 1.650 + if (mLeft.IsNeeded() && rect.x < aInsideMarkersArea.x) { 1.651 + nscoord left = aInsideMarkersArea.x - rect.x; 1.652 + if (MOZ_UNLIKELY(left < 0)) { 1.653 + item->~nsDisplayItem(); 1.654 + continue; 1.655 + } 1.656 + charClip->mLeftEdge = left; 1.657 + } 1.658 + if (mRight.IsNeeded() && rect.XMost() > aInsideMarkersArea.XMost()) { 1.659 + nscoord right = rect.XMost() - aInsideMarkersArea.XMost(); 1.660 + if (MOZ_UNLIKELY(right < 0)) { 1.661 + item->~nsDisplayItem(); 1.662 + continue; 1.663 + } 1.664 + charClip->mRightEdge = right; 1.665 + } 1.666 + } 1.667 + 1.668 + saved.AppendToTop(item); 1.669 + } 1.670 + aList->AppendToTop(&saved); 1.671 +} 1.672 + 1.673 +/* static */ bool 1.674 +TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder* aBuilder, 1.675 + nsIFrame* aBlockFrame) 1.676 +{ 1.677 + const nsStyleTextReset* style = aBlockFrame->StyleTextReset(); 1.678 + // Nothing to do for text-overflow:clip or if 'overflow-x:visible' 1.679 + // or if we're just building items for event processing. 1.680 + if ((style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP && 1.681 + style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP) || 1.682 + IsHorizontalOverflowVisible(aBlockFrame) || 1.683 + aBuilder->IsForEventDelivery()) { 1.684 + return false; 1.685 + } 1.686 + 1.687 + // Skip ComboboxControlFrame because it would clip the drop-down arrow. 1.688 + // Its anon block inherits 'text-overflow' and does what is expected. 1.689 + if (aBlockFrame->GetType() == nsGkAtoms::comboboxControlFrame) { 1.690 + return false; 1.691 + } 1.692 + 1.693 + // Inhibit the markers if a descendant content owns the caret. 1.694 + nsRefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret(); 1.695 + bool visible = false; 1.696 + if (caret && NS_SUCCEEDED(caret->GetCaretVisible(&visible)) && visible) { 1.697 + nsCOMPtr<nsISelection> domSelection = caret->GetCaretDOMSelection(); 1.698 + if (domSelection) { 1.699 + nsCOMPtr<nsIDOMNode> node; 1.700 + domSelection->GetFocusNode(getter_AddRefs(node)); 1.701 + nsCOMPtr<nsIContent> content = do_QueryInterface(node); 1.702 + if (content && nsContentUtils::ContentIsDescendantOf(content, 1.703 + aBlockFrame->GetContent())) { 1.704 + return false; 1.705 + } 1.706 + } 1.707 + } 1.708 + return true; 1.709 +} 1.710 + 1.711 +void 1.712 +TextOverflow::CreateMarkers(const nsLineBox* aLine, 1.713 + bool aCreateLeft, 1.714 + bool aCreateRight, 1.715 + const nsRect& aInsideMarkersArea) 1.716 +{ 1.717 + if (aCreateLeft) { 1.718 + DisplayListClipState::AutoSaveRestore clipState(mBuilder); 1.719 + 1.720 + nsRect markerRect = nsRect(aInsideMarkersArea.x - mLeft.mIntrinsicWidth, 1.721 + aLine->BStart(), 1.722 + mLeft.mIntrinsicWidth, aLine->BSize()); 1.723 + markerRect += mBuilder->ToReferenceFrame(mBlock); 1.724 + ClipMarker(mContentArea + mBuilder->ToReferenceFrame(mBlock), 1.725 + markerRect, clipState); 1.726 + nsDisplayItem* marker = new (mBuilder) 1.727 + nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect, 1.728 + aLine->GetAscent(), mLeft.mStyle, 0); 1.729 + mMarkerList.AppendNewToTop(marker); 1.730 + } 1.731 + 1.732 + if (aCreateRight) { 1.733 + DisplayListClipState::AutoSaveRestore clipState(mBuilder); 1.734 + 1.735 + nsRect markerRect = nsRect(aInsideMarkersArea.XMost(), 1.736 + aLine->BStart(), 1.737 + mRight.mIntrinsicWidth, aLine->BSize()); 1.738 + markerRect += mBuilder->ToReferenceFrame(mBlock); 1.739 + ClipMarker(mContentArea + mBuilder->ToReferenceFrame(mBlock), 1.740 + markerRect, clipState); 1.741 + nsDisplayItem* marker = new (mBuilder) 1.742 + nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect, 1.743 + aLine->GetAscent(), mRight.mStyle, 1); 1.744 + mMarkerList.AppendNewToTop(marker); 1.745 + } 1.746 +} 1.747 + 1.748 +void 1.749 +TextOverflow::Marker::SetupString(nsIFrame* aFrame) 1.750 +{ 1.751 + if (mInitialized) { 1.752 + return; 1.753 + } 1.754 + 1.755 + if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) { 1.756 + gfxTextRun* textRun = GetEllipsisTextRun(aFrame); 1.757 + if (textRun) { 1.758 + mWidth = textRun->GetAdvanceWidth(0, textRun->GetLength(), nullptr); 1.759 + } else { 1.760 + mWidth = 0; 1.761 + } 1.762 + } else { 1.763 + nsRefPtr<nsRenderingContext> rc = 1.764 + aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext(); 1.765 + nsRefPtr<nsFontMetrics> fm; 1.766 + nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm), 1.767 + nsLayoutUtils::FontSizeInflationFor(aFrame)); 1.768 + rc->SetFont(fm); 1.769 + mWidth = nsLayoutUtils::GetStringWidth(aFrame, rc, mStyle->mString.get(), 1.770 + mStyle->mString.Length()); 1.771 + } 1.772 + mIntrinsicWidth = mWidth; 1.773 + mInitialized = true; 1.774 +} 1.775 + 1.776 +} // namespace css 1.777 +} // namespace mozilla