1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TextOverflow.h"
10 // Please maintain alphabetical order below
11 #include "nsBlockFrame.h"
13 #include "nsContentUtils.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsFontMetrics.h"
16 #include "nsGfxScrollFrame.h"
17 #include "nsIScrollableFrame.h"
18 #include "nsLayoutUtils.h"
19 #include "nsPresContext.h"
21 #include "nsRenderingContext.h"
22 #include "nsTextFrame.h"
23 #include "nsIFrameInlines.h"
24 #include "mozilla/ArrayUtils.h"
25 #include "mozilla/Likely.h"
26 #include "nsISelection.h"
31 class LazyReferenceRenderingContextGetterFromFrame MOZ_FINAL
:
32 public gfxFontGroup::LazyReferenceContextGetter
{
34 explicit LazyReferenceRenderingContextGetterFromFrame(nsIFrame
* aFrame
)
36 virtual already_AddRefed
<gfxContext
> GetRefContext() MOZ_OVERRIDE
38 return mFrame
->PresContext()->PresShell()->CreateReferenceRenderingContext();
45 GetEllipsisTextRun(nsIFrame
* aFrame
)
47 nsRefPtr
<nsFontMetrics
> fm
;
48 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(fm
),
49 nsLayoutUtils::FontSizeInflationFor(aFrame
));
50 LazyReferenceRenderingContextGetterFromFrame
lazyRefContextGetter(aFrame
);
51 return fm
->GetThebesFontGroup()->GetEllipsisTextRun(
52 aFrame
->PresContext()->AppUnitsPerDevPixel(), lazyRefContextGetter
);
56 GetSelfOrNearestBlock(nsIFrame
* aFrame
)
58 return nsLayoutUtils::GetAsBlock(aFrame
) ? aFrame
:
59 nsLayoutUtils::FindNearestBlockAncestor(aFrame
);
62 // Return true if the frame is an atomic inline-level element.
63 // It's not supposed to be called for block frames since we never
64 // process block descendants for text-overflow.
66 IsAtomicElement(nsIFrame
* aFrame
, const nsIAtom
* aFrameType
)
68 NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame
) ||
69 !aFrame
->IsBlockOutside(),
70 "unexpected block frame");
71 NS_PRECONDITION(aFrameType
!= nsGkAtoms::placeholderFrame
,
72 "unexpected placeholder frame");
73 return !aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
);
77 IsFullyClipped(nsTextFrame
* aFrame
, nscoord aLeft
, nscoord aRight
,
78 nscoord
* aSnappedLeft
, nscoord
* aSnappedRight
)
80 *aSnappedLeft
= aLeft
;
81 *aSnappedRight
= aRight
;
82 if (aLeft
<= 0 && aRight
<= 0) {
85 return !aFrame
->MeasureCharClippedText(aLeft
, aRight
,
86 aSnappedLeft
, aSnappedRight
);
90 IsHorizontalOverflowVisible(nsIFrame
* aFrame
)
92 NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame
) != nullptr,
93 "expected a block frame");
96 while (f
&& f
->StyleContext()->GetPseudo() &&
97 f
->GetType() != nsGkAtoms::scrollFrame
) {
100 return !f
|| f
->StyleDisplay()->mOverflowX
== NS_STYLE_OVERFLOW_VISIBLE
;
104 ClipMarker(const nsRect
& aContentArea
,
105 const nsRect
& aMarkerRect
,
106 DisplayListClipState::AutoSaveRestore
& aClipState
)
108 nscoord rightOverflow
= aMarkerRect
.XMost() - aContentArea
.XMost();
109 nsRect markerRect
= aMarkerRect
;
110 if (rightOverflow
> 0) {
111 // Marker overflows on the right side (content width < marker width).
112 markerRect
.width
-= rightOverflow
;
113 aClipState
.ClipContentDescendants(markerRect
);
115 nscoord leftOverflow
= aContentArea
.x
- aMarkerRect
.x
;
116 if (leftOverflow
> 0) {
117 // Marker overflows on the left side
118 markerRect
.width
-= leftOverflow
;
119 markerRect
.x
+= leftOverflow
;
120 aClipState
.ClipContentDescendants(markerRect
);
126 InflateLeft(nsRect
* aRect
, nscoord aDelta
)
128 nscoord xmost
= aRect
->XMost();
130 aRect
->width
= std::max(xmost
- aRect
->x
, 0);
134 InflateRight(nsRect
* aRect
, nscoord aDelta
)
136 aRect
->width
= std::max(aRect
->width
+ aDelta
, 0);
140 IsFrameDescendantOfAny(nsIFrame
* aChild
,
141 const TextOverflow::FrameHashtable
& aSetOfFrames
,
142 nsIFrame
* aCommonAncestor
)
144 for (nsIFrame
* f
= aChild
; f
&& f
!= aCommonAncestor
;
145 f
= nsLayoutUtils::GetCrossDocParentFrame(f
)) {
146 if (aSetOfFrames
.GetEntry(f
)) {
153 class nsDisplayTextOverflowMarker
: public nsDisplayItem
156 nsDisplayTextOverflowMarker(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
157 const nsRect
& aRect
, nscoord aAscent
,
158 const nsStyleTextOverflowSide
* aStyle
,
160 : nsDisplayItem(aBuilder
, aFrame
), mRect(aRect
),
161 mStyle(aStyle
), mAscent(aAscent
), mIndex(aIndex
) {
162 MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker
);
164 #ifdef NS_BUILD_REFCNT_LOGGING
165 virtual ~nsDisplayTextOverflowMarker() {
166 MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker
);
169 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
170 bool* aSnap
) MOZ_OVERRIDE
{
173 nsLayoutUtils::GetTextShadowRectsUnion(mRect
, mFrame
);
174 return mRect
.Union(shadowRect
);
176 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
177 nsRenderingContext
* aCtx
) MOZ_OVERRIDE
;
179 virtual uint32_t GetPerFrameKey() MOZ_OVERRIDE
{
180 return (mIndex
<< nsDisplayItem::TYPE_BITS
) | nsDisplayItem::GetPerFrameKey();
182 void PaintTextToContext(nsRenderingContext
* aCtx
,
183 nsPoint aOffsetFromRect
);
184 NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW
)
186 nsRect mRect
; // in reference frame coordinates
187 const nsStyleTextOverflowSide
* mStyle
;
188 nscoord mAscent
; // baseline for the marker text in mRect
193 PaintTextShadowCallback(nsRenderingContext
* aCtx
,
194 nsPoint aShadowOffset
,
195 const nscolor
& aShadowColor
,
198 reinterpret_cast<nsDisplayTextOverflowMarker
*>(aData
)->
199 PaintTextToContext(aCtx
, aShadowOffset
);
203 nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder
* aBuilder
,
204 nsRenderingContext
* aCtx
)
206 nscolor foregroundColor
=
207 nsLayoutUtils::GetColor(mFrame
, eCSSProperty_color
);
209 // Paint the text-shadows for the overflow marker
210 nsLayoutUtils::PaintTextShadow(mFrame
, aCtx
, mRect
, mVisibleRect
,
211 foregroundColor
, PaintTextShadowCallback
,
213 aCtx
->ThebesContext()->SetColor(foregroundColor
);
214 PaintTextToContext(aCtx
, nsPoint(0, 0));
218 nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext
* aCtx
,
219 nsPoint aOffsetFromRect
)
221 gfxFloat y
= nsLayoutUtils::GetSnappedBaselineY(mFrame
, aCtx
->ThebesContext(),
223 nsPoint
baselinePt(mRect
.x
, NSToCoordFloor(y
));
224 nsPoint pt
= baselinePt
+ aOffsetFromRect
;
226 if (mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_ELLIPSIS
) {
227 gfxTextRun
* textRun
= GetEllipsisTextRun(mFrame
);
229 NS_ASSERTION(!textRun
->IsRightToLeft(),
230 "Ellipsis textruns should always be LTR!");
231 gfxPoint
gfxPt(pt
.x
, pt
.y
);
232 textRun
->Draw(aCtx
->ThebesContext(), gfxPt
, DrawMode::GLYPH_FILL
,
233 0, textRun
->GetLength(), nullptr, nullptr, nullptr);
236 nsRefPtr
<nsFontMetrics
> fm
;
237 nsLayoutUtils::GetFontMetricsForFrame(mFrame
, getter_AddRefs(fm
),
238 nsLayoutUtils::FontSizeInflationFor(mFrame
));
239 nsLayoutUtils::DrawString(mFrame
, *fm
, aCtx
, mStyle
->mString
.get(),
240 mStyle
->mString
.Length(), pt
);
245 TextOverflow::Init(nsDisplayListBuilder
* aBuilder
,
246 nsIFrame
* aBlockFrame
)
249 mBlock
= aBlockFrame
;
250 mContentArea
= aBlockFrame
->GetContentRectRelativeToSelf();
251 mScrollableFrame
= nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
);
252 uint8_t direction
= aBlockFrame
->StyleVisibility()->mDirection
;
253 mBlockIsRTL
= direction
== NS_STYLE_DIRECTION_RTL
;
254 mAdjustForPixelSnapping
= false;
256 if (!mScrollableFrame
) {
257 nsIAtom
* pseudoType
= aBlockFrame
->StyleContext()->GetPseudo();
258 if (pseudoType
== nsCSSAnonBoxes::mozXULAnonymousBlock
) {
260 nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
->GetParent());
261 // nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
262 // for RTL blocks (also for overflow:hidden), so we need to move
263 // the edges 1px outward in ExamineLineFrames to avoid triggering
264 // a text-overflow marker in this case.
265 mAdjustForPixelSnapping
= mBlockIsRTL
;
269 mCanHaveHorizontalScrollbar
= false;
270 if (mScrollableFrame
) {
271 mCanHaveHorizontalScrollbar
=
272 mScrollableFrame
->GetScrollbarStyles().mHorizontal
!= NS_STYLE_OVERFLOW_HIDDEN
;
273 if (!mAdjustForPixelSnapping
) {
274 // Scrolling to the end position can leave some text still overflowing due
275 // to pixel snapping behaviour in our scrolling code.
276 mAdjustForPixelSnapping
= mCanHaveHorizontalScrollbar
;
278 mContentArea
.MoveBy(mScrollableFrame
->GetScrollPosition());
279 nsIFrame
* scrollFrame
= do_QueryFrame(mScrollableFrame
);
280 scrollFrame
->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL
);
282 const nsStyleTextReset
* style
= aBlockFrame
->StyleTextReset();
283 mLeft
.Init(style
->mTextOverflow
.GetLeft(direction
));
284 mRight
.Init(style
->mTextOverflow
.GetRight(direction
));
285 // The left/right marker string is setup in ExamineLineFrames when a line
286 // has overflow on that side.
289 /* static */ TextOverflow
*
290 TextOverflow::WillProcessLines(nsDisplayListBuilder
* aBuilder
,
291 nsIFrame
* aBlockFrame
)
293 if (!CanHaveTextOverflow(aBuilder
, aBlockFrame
)) {
296 nsIScrollableFrame
* scrollableFrame
= nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
);
297 if (scrollableFrame
&& scrollableFrame
->IsTransformingByAPZ()) {
298 // If the APZ is actively scrolling this, don't bother with markers.
301 nsAutoPtr
<TextOverflow
> textOverflow(new TextOverflow
);
302 textOverflow
->Init(aBuilder
, aBlockFrame
);
303 return textOverflow
.forget();
307 TextOverflow::ExamineFrameSubtree(nsIFrame
* aFrame
,
308 const nsRect
& aContentArea
,
309 const nsRect
& aInsideMarkersArea
,
310 FrameHashtable
* aFramesToHide
,
311 AlignmentEdges
* aAlignmentEdges
,
312 bool* aFoundVisibleTextOrAtomic
,
313 InnerClipEdges
* aClippedMarkerEdges
)
315 const nsIAtom
* frameType
= aFrame
->GetType();
316 if (frameType
== nsGkAtoms::brFrame
||
317 frameType
== nsGkAtoms::placeholderFrame
) {
320 const bool isAtomic
= IsAtomicElement(aFrame
, frameType
);
321 if (aFrame
->StyleVisibility()->IsVisible()) {
322 nsRect childRect
= aFrame
->GetScrollableOverflowRect() +
323 aFrame
->GetOffsetTo(mBlock
);
324 bool overflowLeft
= childRect
.x
< aContentArea
.x
;
325 bool overflowRight
= childRect
.XMost() > aContentArea
.XMost();
327 mLeft
.mHasOverflow
= true;
330 mRight
.mHasOverflow
= true;
332 if (isAtomic
&& ((mLeft
.mActive
&& overflowLeft
) ||
333 (mRight
.mActive
&& overflowRight
))) {
334 aFramesToHide
->PutEntry(aFrame
);
335 } else if (isAtomic
|| frameType
== nsGkAtoms::textFrame
) {
336 AnalyzeMarkerEdges(aFrame
, frameType
, aInsideMarkersArea
,
337 aFramesToHide
, aAlignmentEdges
,
338 aFoundVisibleTextOrAtomic
,
339 aClippedMarkerEdges
);
346 nsIFrame
* child
= aFrame
->GetFirstPrincipalChild();
348 ExamineFrameSubtree(child
, aContentArea
, aInsideMarkersArea
,
349 aFramesToHide
, aAlignmentEdges
,
350 aFoundVisibleTextOrAtomic
,
351 aClippedMarkerEdges
);
352 child
= child
->GetNextSibling();
357 TextOverflow::AnalyzeMarkerEdges(nsIFrame
* aFrame
,
358 const nsIAtom
* aFrameType
,
359 const nsRect
& aInsideMarkersArea
,
360 FrameHashtable
* aFramesToHide
,
361 AlignmentEdges
* aAlignmentEdges
,
362 bool* aFoundVisibleTextOrAtomic
,
363 InnerClipEdges
* aClippedMarkerEdges
)
365 nsRect
borderRect(aFrame
->GetOffsetTo(mBlock
), aFrame
->GetSize());
366 nscoord leftOverlap
=
367 std::max(aInsideMarkersArea
.x
- borderRect
.x
, 0);
368 nscoord rightOverlap
=
369 std::max(borderRect
.XMost() - aInsideMarkersArea
.XMost(), 0);
370 bool insideLeftEdge
= aInsideMarkersArea
.x
<= borderRect
.XMost();
371 bool insideRightEdge
= borderRect
.x
<= aInsideMarkersArea
.XMost();
373 if (leftOverlap
> 0) {
374 aClippedMarkerEdges
->AccumulateLeft(borderRect
);
375 if (!mLeft
.mActive
) {
379 if (rightOverlap
> 0) {
380 aClippedMarkerEdges
->AccumulateRight(borderRect
);
381 if (!mRight
.mActive
) {
386 if ((leftOverlap
> 0 && insideLeftEdge
) ||
387 (rightOverlap
> 0 && insideRightEdge
)) {
388 if (aFrameType
== nsGkAtoms::textFrame
) {
389 if (aInsideMarkersArea
.x
< aInsideMarkersArea
.XMost()) {
390 // a clipped text frame and there is some room between the markers
391 nscoord snappedLeft
, snappedRight
;
392 bool isFullyClipped
=
393 IsFullyClipped(static_cast<nsTextFrame
*>(aFrame
),
394 leftOverlap
, rightOverlap
, &snappedLeft
, &snappedRight
);
395 if (!isFullyClipped
) {
396 nsRect snappedRect
= borderRect
;
397 if (leftOverlap
> 0) {
398 snappedRect
.x
+= snappedLeft
;
399 snappedRect
.width
-= snappedLeft
;
401 if (rightOverlap
> 0) {
402 snappedRect
.width
-= snappedRight
;
404 aAlignmentEdges
->Accumulate(snappedRect
);
405 *aFoundVisibleTextOrAtomic
= true;
409 aFramesToHide
->PutEntry(aFrame
);
411 } else if (!insideLeftEdge
|| !insideRightEdge
) {
413 if (IsAtomicElement(aFrame
, aFrameType
)) {
414 aFramesToHide
->PutEntry(aFrame
);
418 aAlignmentEdges
->Accumulate(borderRect
);
419 *aFoundVisibleTextOrAtomic
= true;
424 TextOverflow::ExamineLineFrames(nsLineBox
* aLine
,
425 FrameHashtable
* aFramesToHide
,
426 AlignmentEdges
* aAlignmentEdges
)
428 // No ellipsing for 'clip' style.
429 bool suppressLeft
= mLeft
.mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
;
430 bool suppressRight
= mRight
.mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
;
431 if (mCanHaveHorizontalScrollbar
) {
432 nsPoint pos
= mScrollableFrame
->GetScrollPosition();
433 nsRect scrollRange
= mScrollableFrame
->GetScrollRange();
434 // No ellipsing when nothing to scroll to on that side (this includes
435 // overflow:auto that doesn't trigger a horizontal scrollbar).
436 if (pos
.x
<= scrollRange
.x
) {
439 if (pos
.x
>= scrollRange
.XMost()) {
440 suppressRight
= true;
444 nsRect contentArea
= mContentArea
;
445 const nscoord scrollAdjust
= mAdjustForPixelSnapping
?
446 mBlock
->PresContext()->AppUnitsPerDevPixel() : 0;
447 InflateLeft(&contentArea
, scrollAdjust
);
448 InflateRight(&contentArea
, scrollAdjust
);
449 nsRect lineRect
= aLine
->GetScrollableOverflowArea();
450 const bool leftOverflow
=
451 !suppressLeft
&& lineRect
.x
< contentArea
.x
;
452 const bool rightOverflow
=
453 !suppressRight
&& lineRect
.XMost() > contentArea
.XMost();
454 if (!leftOverflow
&& !rightOverflow
) {
455 // The line does not overflow on a side we should ellipsize.
460 bool retryEmptyLine
= true;
461 bool guessLeft
= leftOverflow
;
462 bool guessRight
= rightOverflow
;
463 mLeft
.mActive
= leftOverflow
;
464 mRight
.mActive
= rightOverflow
;
465 bool clippedLeftMarker
= false;
466 bool clippedRightMarker
= false;
468 // Setup marker strings as needed.
470 mLeft
.SetupString(mBlock
);
473 mRight
.SetupString(mBlock
);
476 // If there is insufficient space for both markers then keep the one on the
477 // end side per the block's 'direction'.
478 nscoord rightMarkerWidth
= mRight
.mActive
? mRight
.mWidth
: 0;
479 nscoord leftMarkerWidth
= mLeft
.mActive
? mLeft
.mWidth
: 0;
480 if (leftMarkerWidth
&& rightMarkerWidth
&&
481 leftMarkerWidth
+ rightMarkerWidth
> contentArea
.width
) {
483 rightMarkerWidth
= 0;
489 // Calculate the area between the potential markers aligned at the
491 nsRect insideMarkersArea
= mContentArea
;
493 InflateLeft(&insideMarkersArea
, -leftMarkerWidth
);
496 InflateRight(&insideMarkersArea
, -rightMarkerWidth
);
499 // Analyze the frames on aLine for the overflow situation at the content
500 // edges and at the edges of the area between the markers.
501 bool foundVisibleTextOrAtomic
= false;
502 int32_t n
= aLine
->GetChildCount();
503 nsIFrame
* child
= aLine
->mFirstChild
;
504 InnerClipEdges clippedMarkerEdges
;
505 for (; n
-- > 0; child
= child
->GetNextSibling()) {
506 ExamineFrameSubtree(child
, contentArea
, insideMarkersArea
,
507 aFramesToHide
, aAlignmentEdges
,
508 &foundVisibleTextOrAtomic
,
509 &clippedMarkerEdges
);
511 if (!foundVisibleTextOrAtomic
&& retryEmptyLine
) {
512 aAlignmentEdges
->mAssigned
= false;
513 aFramesToHide
->Clear();
515 if (mLeft
.IsNeeded() && mLeft
.mActive
&& !clippedLeftMarker
) {
516 if (clippedMarkerEdges
.mAssignedLeft
&&
517 clippedMarkerEdges
.mLeft
- mContentArea
.X() > 0) {
518 mLeft
.mWidth
= clippedMarkerEdges
.mLeft
- mContentArea
.X();
519 NS_ASSERTION(mLeft
.mWidth
< mLeft
.mIntrinsicISize
,
520 "clipping a marker should make it strictly smaller");
521 clippedLeftMarker
= true;
523 mLeft
.mActive
= guessLeft
= false;
527 if (mRight
.IsNeeded() && mRight
.mActive
&& !clippedRightMarker
) {
528 if (clippedMarkerEdges
.mAssignedRight
&&
529 mContentArea
.XMost() - clippedMarkerEdges
.mRight
> 0) {
530 mRight
.mWidth
= mContentArea
.XMost() - clippedMarkerEdges
.mRight
;
531 NS_ASSERTION(mRight
.mWidth
< mRight
.mIntrinsicISize
,
532 "clipping a marker should make it strictly smaller");
533 clippedRightMarker
= true;
535 mRight
.mActive
= guessRight
= false;
539 // The line simply has no visible content even without markers,
540 // so examine the line again without suppressing markers.
541 retryEmptyLine
= false;
542 mLeft
.mWidth
= mLeft
.mIntrinsicISize
;
543 mLeft
.mActive
= guessLeft
= leftOverflow
;
544 mRight
.mWidth
= mRight
.mIntrinsicISize
;
545 mRight
.mActive
= guessRight
= rightOverflow
;
548 if (guessLeft
== (mLeft
.mActive
&& mLeft
.IsNeeded()) &&
549 guessRight
== (mRight
.mActive
&& mRight
.IsNeeded())) {
552 guessLeft
= mLeft
.mActive
&& mLeft
.IsNeeded();
553 guessRight
= mRight
.mActive
&& mRight
.IsNeeded();
556 aFramesToHide
->Clear();
558 NS_ASSERTION(pass
== 0, "2nd pass should never guess wrong");
559 } while (++pass
!= 2);
560 if (!leftOverflow
|| !mLeft
.mActive
) {
563 if (!rightOverflow
|| !mRight
.mActive
) {
569 TextOverflow::ProcessLine(const nsDisplayListSet
& aLists
,
572 NS_ASSERTION(mLeft
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
||
573 mRight
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
,
574 "TextOverflow with 'clip' for both sides");
576 mLeft
.mActive
= mLeft
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
;
578 mRight
.mActive
= mRight
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
;
580 FrameHashtable
framesToHide(64);
581 AlignmentEdges alignmentEdges
;
582 ExamineLineFrames(aLine
, &framesToHide
, &alignmentEdges
);
583 bool needLeft
= mLeft
.IsNeeded();
584 bool needRight
= mRight
.IsNeeded();
585 if (!needLeft
&& !needRight
) {
588 NS_ASSERTION(mLeft
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
||
589 !needLeft
, "left marker for 'clip'");
590 NS_ASSERTION(mRight
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
||
591 !needRight
, "right marker for 'clip'");
593 // If there is insufficient space for both markers then keep the one on the
594 // end side per the block's 'direction'.
595 if (needLeft
&& needRight
&&
596 mLeft
.mWidth
+ mRight
.mWidth
> mContentArea
.width
) {
603 nsRect insideMarkersArea
= mContentArea
;
605 InflateLeft(&insideMarkersArea
, -mLeft
.mWidth
);
608 InflateRight(&insideMarkersArea
, -mRight
.mWidth
);
610 if (!mCanHaveHorizontalScrollbar
&& alignmentEdges
.mAssigned
) {
611 nsRect alignmentRect
= nsRect(alignmentEdges
.x
, insideMarkersArea
.y
,
612 alignmentEdges
.Width(), 1);
613 insideMarkersArea
.IntersectRect(insideMarkersArea
, alignmentRect
);
616 // Clip and remove display items as needed at the final marker edges.
617 nsDisplayList
* lists
[] = { aLists
.Content(), aLists
.PositionedDescendants() };
618 for (uint32_t i
= 0; i
< ArrayLength(lists
); ++i
) {
619 PruneDisplayListContents(lists
[i
], framesToHide
, insideMarkersArea
);
621 CreateMarkers(aLine
, needLeft
, needRight
, insideMarkersArea
);
625 TextOverflow::PruneDisplayListContents(nsDisplayList
* aList
,
626 const FrameHashtable
& aFramesToHide
,
627 const nsRect
& aInsideMarkersArea
)
631 while ((item
= aList
->RemoveBottom())) {
632 nsIFrame
* itemFrame
= item
->Frame();
633 if (IsFrameDescendantOfAny(itemFrame
, aFramesToHide
, mBlock
)) {
634 item
->~nsDisplayItem();
638 nsDisplayList
* wrapper
= item
->GetSameCoordinateSystemChildren();
640 if (!itemFrame
|| GetSelfOrNearestBlock(itemFrame
) == mBlock
) {
641 PruneDisplayListContents(wrapper
, aFramesToHide
, aInsideMarkersArea
);
645 nsCharClipDisplayItem
* charClip
= itemFrame
?
646 nsCharClipDisplayItem::CheckCast(item
) : nullptr;
647 if (charClip
&& GetSelfOrNearestBlock(itemFrame
) == mBlock
) {
648 nsRect rect
= itemFrame
->GetScrollableOverflowRect() +
649 itemFrame
->GetOffsetTo(mBlock
);
650 if (mLeft
.IsNeeded() && rect
.x
< aInsideMarkersArea
.x
) {
651 nscoord left
= aInsideMarkersArea
.x
- rect
.x
;
652 if (MOZ_UNLIKELY(left
< 0)) {
653 item
->~nsDisplayItem();
656 charClip
->mLeftEdge
= left
;
658 if (mRight
.IsNeeded() && rect
.XMost() > aInsideMarkersArea
.XMost()) {
659 nscoord right
= rect
.XMost() - aInsideMarkersArea
.XMost();
660 if (MOZ_UNLIKELY(right
< 0)) {
661 item
->~nsDisplayItem();
664 charClip
->mRightEdge
= right
;
668 saved
.AppendToTop(item
);
670 aList
->AppendToTop(&saved
);
674 TextOverflow::HasClippedOverflow(nsIFrame
* aBlockFrame
)
676 const nsStyleTextReset
* style
= aBlockFrame
->StyleTextReset();
677 return style
->mTextOverflow
.mLeft
.mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
&&
678 style
->mTextOverflow
.mRight
.mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
;
682 TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder
* aBuilder
,
683 nsIFrame
* aBlockFrame
)
685 // Nothing to do for text-overflow:clip or if 'overflow-x:visible' or if
686 // we're just building items for event processing or image visibility.
687 if (HasClippedOverflow(aBlockFrame
) ||
688 IsHorizontalOverflowVisible(aBlockFrame
) ||
689 aBuilder
->IsForEventDelivery() || aBuilder
->IsForImageVisibility()) {
693 // Skip ComboboxControlFrame because it would clip the drop-down arrow.
694 // Its anon block inherits 'text-overflow' and does what is expected.
695 if (aBlockFrame
->GetType() == nsGkAtoms::comboboxControlFrame
) {
699 // Inhibit the markers if a descendant content owns the caret.
700 nsRefPtr
<nsCaret
> caret
= aBlockFrame
->PresContext()->PresShell()->GetCaret();
701 if (caret
&& caret
->IsVisible()) {
702 nsCOMPtr
<nsISelection
> domSelection
= caret
->GetSelection();
704 nsCOMPtr
<nsIDOMNode
> node
;
705 domSelection
->GetFocusNode(getter_AddRefs(node
));
706 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(node
);
707 if (content
&& nsContentUtils::ContentIsDescendantOf(content
,
708 aBlockFrame
->GetContent())) {
717 TextOverflow::CreateMarkers(const nsLineBox
* aLine
,
720 const nsRect
& aInsideMarkersArea
)
723 DisplayListClipState::AutoSaveRestore
clipState(mBuilder
);
725 //XXX Needs vertical text love
726 nsRect markerRect
= nsRect(aInsideMarkersArea
.x
- mLeft
.mIntrinsicISize
,
728 mLeft
.mIntrinsicISize
, aLine
->BSize());
729 markerRect
+= mBuilder
->ToReferenceFrame(mBlock
);
730 ClipMarker(mContentArea
+ mBuilder
->ToReferenceFrame(mBlock
),
731 markerRect
, clipState
);
732 nsDisplayItem
* marker
= new (mBuilder
)
733 nsDisplayTextOverflowMarker(mBuilder
, mBlock
, markerRect
,
734 aLine
->GetLogicalAscent(), mLeft
.mStyle
, 0);
735 mMarkerList
.AppendNewToTop(marker
);
739 DisplayListClipState::AutoSaveRestore
clipState(mBuilder
);
741 nsRect markerRect
= nsRect(aInsideMarkersArea
.XMost(),
743 mRight
.mIntrinsicISize
, aLine
->BSize());
744 markerRect
+= mBuilder
->ToReferenceFrame(mBlock
);
745 ClipMarker(mContentArea
+ mBuilder
->ToReferenceFrame(mBlock
),
746 markerRect
, clipState
);
747 nsDisplayItem
* marker
= new (mBuilder
)
748 nsDisplayTextOverflowMarker(mBuilder
, mBlock
, markerRect
,
749 aLine
->GetLogicalAscent(), mRight
.mStyle
, 1);
750 mMarkerList
.AppendNewToTop(marker
);
755 TextOverflow::Marker::SetupString(nsIFrame
* aFrame
)
761 if (mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_ELLIPSIS
) {
762 gfxTextRun
* textRun
= GetEllipsisTextRun(aFrame
);
764 mWidth
= textRun
->GetAdvanceWidth(0, textRun
->GetLength(), nullptr);
769 nsRenderingContext
rc(
770 aFrame
->PresContext()->PresShell()->CreateReferenceRenderingContext());
771 nsRefPtr
<nsFontMetrics
> fm
;
772 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(fm
),
773 nsLayoutUtils::FontSizeInflationFor(aFrame
));
774 mWidth
= nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle
->mString
, aFrame
,
777 mIntrinsicISize
= mWidth
;
782 } // namespace mozilla