1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "gfxContext.h"
12 #include "nsBlockFrame.h"
14 #include "nsContentUtils.h"
15 #include "nsCSSAnonBoxes.h"
16 #include "nsFontMetrics.h"
17 #include "nsGfxScrollFrame.h"
18 #include "nsIScrollableFrame.h"
19 #include "nsLayoutUtils.h"
20 #include "nsPresContext.h"
22 #include "nsTextFrame.h"
23 #include "nsIFrameInlines.h"
24 #include "mozilla/ArrayUtils.h"
25 #include "mozilla/Likely.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/dom/Selection.h"
28 #include "TextDrawTarget.h"
30 using mozilla::layout::TextDrawTarget
;
35 class LazyReferenceRenderingDrawTargetGetterFromFrame final
36 : public gfxFontGroup::LazyReferenceDrawTargetGetter
{
38 typedef mozilla::gfx::DrawTarget DrawTarget
;
40 explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame
* aFrame
)
42 virtual already_AddRefed
<DrawTarget
> GetRefDrawTarget() override
{
43 RefPtr
<gfxContext
> ctx
=
44 mFrame
->PresShell()->CreateReferenceRenderingContext();
45 RefPtr
<DrawTarget
> dt
= ctx
->GetDrawTarget();
53 static gfxTextRun
* GetEllipsisTextRun(nsIFrame
* aFrame
) {
54 RefPtr
<nsFontMetrics
> fm
=
55 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame
);
56 LazyReferenceRenderingDrawTargetGetterFromFrame
lazyRefDrawTargetGetter(
58 return fm
->GetThebesFontGroup()->GetEllipsisTextRun(
59 aFrame
->PresContext()->AppUnitsPerDevPixel(),
60 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame
->Style()),
61 lazyRefDrawTargetGetter
);
64 static nsIFrame
* GetSelfOrNearestBlock(nsIFrame
* aFrame
) {
66 return aFrame
->IsBlockFrameOrSubclass()
68 : nsLayoutUtils::FindNearestBlockAncestor(aFrame
);
71 // Return true if the frame is an atomic inline-level element.
72 // It's not supposed to be called for block frames since we never
73 // process block descendants for text-overflow.
74 static bool IsAtomicElement(nsIFrame
* aFrame
, LayoutFrameType aFrameType
) {
75 MOZ_ASSERT(!aFrame
->IsBlockFrameOrSubclass() || !aFrame
->IsBlockOutside(),
76 "unexpected block frame");
77 MOZ_ASSERT(aFrameType
!= LayoutFrameType::Placeholder
,
78 "unexpected placeholder frame");
79 return !aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
);
82 static bool IsFullyClipped(nsTextFrame
* aFrame
, nscoord aLeft
, nscoord aRight
,
83 nscoord
* aSnappedLeft
, nscoord
* aSnappedRight
) {
84 *aSnappedLeft
= aLeft
;
85 *aSnappedRight
= aRight
;
86 if (aLeft
<= 0 && aRight
<= 0) {
89 return !aFrame
->MeasureCharClippedText(aLeft
, aRight
, aSnappedLeft
,
93 static bool IsInlineAxisOverflowVisible(nsIFrame
* aFrame
) {
94 MOZ_ASSERT(aFrame
&& aFrame
->IsBlockFrameOrSubclass(),
95 "expected a block frame");
98 while (f
&& f
->Style()->IsAnonBox() && !f
->IsScrollFrame()) {
104 auto overflow
= aFrame
->GetWritingMode().IsVertical()
105 ? f
->StyleDisplay()->mOverflowY
106 : f
->StyleDisplay()->mOverflowX
;
107 return overflow
== StyleOverflow::Visible
;
110 static void ClipMarker(const nsRect
& aContentArea
, const nsRect
& aMarkerRect
,
111 DisplayListClipState::AutoSaveRestore
& aClipState
) {
112 nscoord rightOverflow
= aMarkerRect
.XMost() - aContentArea
.XMost();
113 nsRect markerRect
= aMarkerRect
;
114 if (rightOverflow
> 0) {
115 // Marker overflows on the right side (content width < marker width).
116 markerRect
.width
-= rightOverflow
;
117 aClipState
.ClipContentDescendants(markerRect
);
119 nscoord leftOverflow
= aContentArea
.x
- aMarkerRect
.x
;
120 if (leftOverflow
> 0) {
121 // Marker overflows on the left side
122 markerRect
.width
-= leftOverflow
;
123 markerRect
.x
+= leftOverflow
;
124 aClipState
.ClipContentDescendants(markerRect
);
129 static void InflateIStart(WritingMode aWM
, LogicalRect
* aRect
, nscoord aDelta
) {
130 nscoord iend
= aRect
->IEnd(aWM
);
131 aRect
->IStart(aWM
) -= aDelta
;
132 aRect
->ISize(aWM
) = std::max(iend
- aRect
->IStart(aWM
), 0);
135 static void InflateIEnd(WritingMode aWM
, LogicalRect
* aRect
, nscoord aDelta
) {
136 aRect
->ISize(aWM
) = std::max(aRect
->ISize(aWM
) + aDelta
, 0);
139 static bool IsFrameDescendantOfAny(
140 nsIFrame
* aChild
, const TextOverflow::FrameHashtable
& aSetOfFrames
,
141 nsIFrame
* aCommonAncestor
) {
142 for (nsIFrame
* f
= aChild
; f
&& f
!= aCommonAncestor
;
143 f
= nsLayoutUtils::GetCrossDocParentFrame(f
)) {
144 if (aSetOfFrames
.GetEntry(f
)) {
151 class nsDisplayTextOverflowMarker final
: public nsPaintedDisplayItem
{
153 nsDisplayTextOverflowMarker(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
154 const nsRect
& aRect
, nscoord aAscent
,
155 nsStyleTextOverflowSide aStyle
,
156 uint32_t aLineNumber
, uint16_t aIndex
)
157 : nsPaintedDisplayItem(aBuilder
, aFrame
),
161 mIndex((aLineNumber
<< 1) + 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
) const override
{
172 nsRect shadowRect
= nsLayoutUtils::GetTextShadowRectsUnion(mRect
, mFrame
);
173 return mRect
.Union(shadowRect
);
176 virtual nsRect
GetComponentAlphaBounds(
177 nsDisplayListBuilder
* aBuilder
) const override
{
178 if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
179 // On OS X, web authors can turn off subpixel text rendering using the
180 // CSS property -moz-osx-font-smoothing. If they do that, we don't need
181 // to use component alpha layers for the affected text.
182 if (mFrame
->StyleFont()->mFont
.smoothing
== NS_FONT_SMOOTHING_GRAYSCALE
) {
187 return GetBounds(aBuilder
, &snap
);
190 virtual void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
192 virtual uint16_t CalculatePerFrameKey() const override
{ return mIndex
; }
194 void PaintTextToContext(gfxContext
* aCtx
, nsPoint aOffsetFromRect
);
196 virtual bool CreateWebRenderCommands(
197 mozilla::wr::DisplayListBuilder
& aBuilder
,
198 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
199 const StackingContextHelper
& aSc
,
200 layers::RenderRootStateManager
* aManager
,
201 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
203 NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW
)
205 nsRect mRect
; // in reference frame coordinates
206 const nsStyleTextOverflowSide mStyle
;
207 nscoord mAscent
; // baseline for the marker text in mRect
211 static void PaintTextShadowCallback(gfxContext
* aCtx
, nsPoint aShadowOffset
,
212 const nscolor
& aShadowColor
, void* aData
) {
213 reinterpret_cast<nsDisplayTextOverflowMarker
*>(aData
)->PaintTextToContext(
214 aCtx
, aShadowOffset
);
217 void nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder
* aBuilder
,
219 DrawTargetAutoDisableSubpixelAntialiasing
disable(aCtx
->GetDrawTarget(),
220 IsSubpixelAADisabled());
222 nscolor foregroundColor
=
223 nsLayoutUtils::GetColor(mFrame
, &nsStyleText::mWebkitTextFillColor
);
225 // Paint the text-shadows for the overflow marker
226 nsLayoutUtils::PaintTextShadow(mFrame
, aCtx
, mRect
, GetPaintRect(),
227 foregroundColor
, PaintTextShadowCallback
,
229 aCtx
->SetColor(gfx::Color::FromABGR(foregroundColor
));
230 PaintTextToContext(aCtx
, nsPoint(0, 0));
233 void nsDisplayTextOverflowMarker::PaintTextToContext(gfxContext
* aCtx
,
234 nsPoint aOffsetFromRect
) {
235 WritingMode wm
= mFrame
->GetWritingMode();
236 nsPoint
pt(mRect
.x
, mRect
.y
);
237 if (wm
.IsVertical()) {
238 if (wm
.IsVerticalLR()) {
239 pt
.x
= NSToCoordFloor(
240 nsLayoutUtils::GetSnappedBaselineX(mFrame
, aCtx
, pt
.x
, mAscent
));
242 pt
.x
= NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
243 mFrame
, aCtx
, pt
.x
+ mRect
.width
, -mAscent
));
246 pt
.y
= NSToCoordFloor(
247 nsLayoutUtils::GetSnappedBaselineY(mFrame
, aCtx
, pt
.y
, mAscent
));
249 pt
+= aOffsetFromRect
;
251 if (mStyle
.mType
== NS_STYLE_TEXT_OVERFLOW_ELLIPSIS
) {
252 gfxTextRun
* textRun
= GetEllipsisTextRun(mFrame
);
254 NS_ASSERTION(!textRun
->IsRightToLeft(),
255 "Ellipsis textruns should always be LTR!");
256 gfx::Point
gfxPt(pt
.x
, pt
.y
);
257 textRun
->Draw(gfxTextRun::Range(textRun
), gfxPt
,
258 gfxTextRun::DrawParams(aCtx
));
261 RefPtr
<nsFontMetrics
> fm
=
262 nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame
);
263 nsLayoutUtils::DrawString(mFrame
, *fm
, aCtx
, mStyle
.mString
.get(),
264 mStyle
.mString
.Length(), pt
);
268 bool nsDisplayTextOverflowMarker::CreateWebRenderCommands(
269 mozilla::wr::DisplayListBuilder
& aBuilder
,
270 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
271 const StackingContextHelper
& aSc
, layers::RenderRootStateManager
* aManager
,
272 nsDisplayListBuilder
* aDisplayListBuilder
) {
274 nsRect bounds
= GetBounds(aDisplayListBuilder
, &snap
);
275 if (bounds
.IsEmpty()) {
279 // Run the rendering algorithm to capture the glyphs and shadows
280 RefPtr
<TextDrawTarget
> textDrawer
=
281 new TextDrawTarget(aBuilder
, aResources
, aSc
, aManager
, this, bounds
);
282 RefPtr
<gfxContext
> captureCtx
= gfxContext::CreateOrNull(textDrawer
);
283 Paint(aDisplayListBuilder
, captureCtx
);
284 textDrawer
->TerminateShadows();
286 return textDrawer
->Finish();
289 TextOverflow::TextOverflow(nsDisplayListBuilder
* aBuilder
,
290 nsIFrame
* aBlockFrame
)
291 : mContentArea(aBlockFrame
->GetWritingMode(),
292 aBlockFrame
->GetContentRectRelativeToSelf(),
293 aBlockFrame
->GetSize()),
296 mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
)),
297 mBlockSize(aBlockFrame
->GetSize()),
298 mBlockWM(aBlockFrame
->GetWritingMode()),
299 mAdjustForPixelSnapping(false) {
301 if (!mScrollableFrame
) {
302 auto pseudoType
= aBlockFrame
->Style()->GetPseudoType();
303 if (pseudoType
== PseudoStyleType::mozXULAnonymousBlock
) {
305 nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
->GetParent());
306 // nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
307 // for RTL blocks (also for overflow:hidden), so we need to move
308 // the edges 1px outward in ExamineLineFrames to avoid triggering
309 // a text-overflow marker in this case.
310 mAdjustForPixelSnapping
= !mBlockWM
.IsBidiLTR();
314 mCanHaveInlineAxisScrollbar
= false;
315 if (mScrollableFrame
) {
316 auto scrollbarStyle
= mBlockWM
.IsVertical()
317 ? mScrollableFrame
->GetScrollStyles().mVertical
318 : mScrollableFrame
->GetScrollStyles().mHorizontal
;
319 mCanHaveInlineAxisScrollbar
= scrollbarStyle
!= StyleOverflow::Hidden
;
320 if (!mAdjustForPixelSnapping
) {
321 // Scrolling to the end position can leave some text still overflowing due
322 // to pixel snapping behaviour in our scrolling code.
323 mAdjustForPixelSnapping
= mCanHaveInlineAxisScrollbar
;
325 // Use a null containerSize to convert a vector from logical to physical.
326 const nsSize nullContainerSize
;
328 mBlockWM
, LogicalPoint(mBlockWM
, mScrollableFrame
->GetScrollPosition(),
331 uint8_t direction
= aBlockFrame
->StyleVisibility()->mDirection
;
332 const nsStyleTextReset
* style
= aBlockFrame
->StyleTextReset();
333 if (mBlockWM
.IsBidiLTR()) {
334 mIStart
.Init(style
->mTextOverflow
.GetLeft(direction
));
335 mIEnd
.Init(style
->mTextOverflow
.GetRight(direction
));
337 mIStart
.Init(style
->mTextOverflow
.GetRight(direction
));
338 mIEnd
.Init(style
->mTextOverflow
.GetLeft(direction
));
340 // The left/right marker string is setup in ExamineLineFrames when a line
341 // has overflow on that side.
345 Maybe
<TextOverflow
> TextOverflow::WillProcessLines(
346 nsDisplayListBuilder
* aBuilder
, nsIFrame
* aBlockFrame
) {
347 // Ignore text-overflow and -webkit-line-clamp for event and frame visibility
349 if (aBuilder
->IsForEventDelivery() || aBuilder
->IsForFrameVisibility() ||
350 !CanHaveOverflowMarkers(aBlockFrame
)) {
353 nsIScrollableFrame
* scrollableFrame
=
354 nsLayoutUtils::GetScrollableFrameFor(aBlockFrame
);
355 if (scrollableFrame
&& scrollableFrame
->IsTransformingByAPZ()) {
356 // If the APZ is actively scrolling this, don't bother with markers.
359 return Some(TextOverflow(aBuilder
, aBlockFrame
));
362 void TextOverflow::ExamineFrameSubtree(nsIFrame
* aFrame
,
363 const LogicalRect
& aContentArea
,
364 const LogicalRect
& aInsideMarkersArea
,
365 FrameHashtable
* aFramesToHide
,
366 AlignmentEdges
* aAlignmentEdges
,
367 bool* aFoundVisibleTextOrAtomic
,
368 InnerClipEdges
* aClippedMarkerEdges
) {
369 const LayoutFrameType frameType
= aFrame
->Type();
370 if (frameType
== LayoutFrameType::Br
||
371 frameType
== LayoutFrameType::Placeholder
) {
374 const bool isAtomic
= IsAtomicElement(aFrame
, frameType
);
375 if (aFrame
->StyleVisibility()->IsVisible()) {
376 LogicalRect childRect
=
377 GetLogicalScrollableOverflowRectRelativeToBlock(aFrame
);
378 bool overflowIStart
=
379 childRect
.IStart(mBlockWM
) < aContentArea
.IStart(mBlockWM
);
380 bool overflowIEnd
= childRect
.IEnd(mBlockWM
) > aContentArea
.IEnd(mBlockWM
);
381 if (overflowIStart
) {
382 mIStart
.mHasOverflow
= true;
385 mIEnd
.mHasOverflow
= true;
387 if (isAtomic
&& ((mIStart
.mActive
&& overflowIStart
) ||
388 (mIEnd
.mActive
&& overflowIEnd
))) {
389 aFramesToHide
->PutEntry(aFrame
);
390 } else if (isAtomic
|| frameType
== LayoutFrameType::Text
) {
391 AnalyzeMarkerEdges(aFrame
, frameType
, aInsideMarkersArea
, aFramesToHide
,
392 aAlignmentEdges
, aFoundVisibleTextOrAtomic
,
393 aClippedMarkerEdges
);
400 for (nsIFrame
* child
: aFrame
->PrincipalChildList()) {
401 ExamineFrameSubtree(child
, aContentArea
, aInsideMarkersArea
, aFramesToHide
,
402 aAlignmentEdges
, aFoundVisibleTextOrAtomic
,
403 aClippedMarkerEdges
);
407 void TextOverflow::AnalyzeMarkerEdges(nsIFrame
* aFrame
,
408 LayoutFrameType aFrameType
,
409 const LogicalRect
& aInsideMarkersArea
,
410 FrameHashtable
* aFramesToHide
,
411 AlignmentEdges
* aAlignmentEdges
,
412 bool* aFoundVisibleTextOrAtomic
,
413 InnerClipEdges
* aClippedMarkerEdges
) {
414 MOZ_ASSERT(aFrameType
== LayoutFrameType::Text
||
415 IsAtomicElement(aFrame
, aFrameType
));
416 LogicalRect
borderRect(mBlockWM
,
417 nsRect(aFrame
->GetOffsetTo(mBlock
), aFrame
->GetSize()),
419 nscoord istartOverlap
= std::max(
420 aInsideMarkersArea
.IStart(mBlockWM
) - borderRect
.IStart(mBlockWM
), 0);
421 nscoord iendOverlap
= std::max(
422 borderRect
.IEnd(mBlockWM
) - aInsideMarkersArea
.IEnd(mBlockWM
), 0);
423 bool insideIStartEdge
=
424 aInsideMarkersArea
.IStart(mBlockWM
) <= borderRect
.IEnd(mBlockWM
);
425 bool insideIEndEdge
=
426 borderRect
.IStart(mBlockWM
) <= aInsideMarkersArea
.IEnd(mBlockWM
);
428 if (istartOverlap
> 0) {
429 aClippedMarkerEdges
->AccumulateIStart(mBlockWM
, borderRect
);
430 if (!mIStart
.mActive
) {
434 if (iendOverlap
> 0) {
435 aClippedMarkerEdges
->AccumulateIEnd(mBlockWM
, borderRect
);
436 if (!mIEnd
.mActive
) {
441 if ((istartOverlap
> 0 && insideIStartEdge
) ||
442 (iendOverlap
> 0 && insideIEndEdge
)) {
443 if (aFrameType
== LayoutFrameType::Text
) {
444 auto textFrame
= static_cast<nsTextFrame
*>(aFrame
);
445 if ((aInsideMarkersArea
.IStart(mBlockWM
) <
446 aInsideMarkersArea
.IEnd(mBlockWM
)) &&
447 textFrame
->HasNonSuppressedText()) {
448 // a clipped text frame and there is some room between the markers
449 nscoord snappedIStart
, snappedIEnd
;
450 bool isFullyClipped
=
452 ? IsFullyClipped(textFrame
, istartOverlap
, iendOverlap
,
453 &snappedIStart
, &snappedIEnd
)
454 : IsFullyClipped(textFrame
, iendOverlap
, istartOverlap
,
455 &snappedIEnd
, &snappedIStart
);
456 if (!isFullyClipped
) {
457 LogicalRect snappedRect
= borderRect
;
458 if (istartOverlap
> 0) {
459 snappedRect
.IStart(mBlockWM
) += snappedIStart
;
460 snappedRect
.ISize(mBlockWM
) -= snappedIStart
;
462 if (iendOverlap
> 0) {
463 snappedRect
.ISize(mBlockWM
) -= snappedIEnd
;
465 aAlignmentEdges
->AccumulateInner(mBlockWM
, snappedRect
);
466 *aFoundVisibleTextOrAtomic
= true;
470 aFramesToHide
->PutEntry(aFrame
);
472 } else if (!insideIStartEdge
|| !insideIEndEdge
) {
474 if (!insideIStartEdge
) {
475 aAlignmentEdges
->AccumulateOuter(mBlockWM
, borderRect
);
477 if (IsAtomicElement(aFrame
, aFrameType
)) {
478 aFramesToHide
->PutEntry(aFrame
);
482 aAlignmentEdges
->AccumulateInner(mBlockWM
, borderRect
);
483 if (aFrameType
== LayoutFrameType::Text
) {
484 auto textFrame
= static_cast<nsTextFrame
*>(aFrame
);
485 if (textFrame
->HasNonSuppressedText()) {
486 *aFoundVisibleTextOrAtomic
= true;
489 *aFoundVisibleTextOrAtomic
= true;
494 LogicalRect
TextOverflow::ExamineLineFrames(nsLineBox
* aLine
,
495 FrameHashtable
* aFramesToHide
,
496 AlignmentEdges
* aAlignmentEdges
) {
497 // No ellipsing for 'clip' style.
498 bool suppressIStart
= mIStart
.IsSuppressed();
499 bool suppressIEnd
= mIEnd
.IsSuppressed();
500 if (mCanHaveInlineAxisScrollbar
) {
501 LogicalPoint
pos(mBlockWM
, mScrollableFrame
->GetScrollPosition(),
503 LogicalRect
scrollRange(mBlockWM
, mScrollableFrame
->GetScrollRange(),
505 // No ellipsing when nothing to scroll to on that side (this includes
506 // overflow:auto that doesn't trigger a horizontal scrollbar).
507 if (pos
.I(mBlockWM
) <= scrollRange
.IStart(mBlockWM
)) {
508 suppressIStart
= true;
510 if (pos
.I(mBlockWM
) >= scrollRange
.IEnd(mBlockWM
)) {
511 // Except that we always want to display a -webkit-line-clamp ellipsis.
512 if (!mIEnd
.mHasBlockEllipsis
) {
518 LogicalRect contentArea
= mContentArea
;
519 bool snapStart
= true, snapEnd
= true;
520 nscoord startEdge
, endEdge
;
521 if (aLine
->GetFloatEdges(&startEdge
, &endEdge
)) {
522 // Narrow the |contentArea| to account for any floats on this line, and
523 // don't bother with the snapping quirk on whichever side(s) we narrow.
524 nscoord delta
= endEdge
- contentArea
.IEnd(mBlockWM
);
526 nscoord newSize
= contentArea
.ISize(mBlockWM
) + delta
;
527 contentArea
.ISize(mBlockWM
) = std::max(nscoord(0), newSize
);
530 delta
= startEdge
- contentArea
.IStart(mBlockWM
);
532 contentArea
.IStart(mBlockWM
) = startEdge
;
533 nscoord newSize
= contentArea
.ISize(mBlockWM
) - delta
;
534 contentArea
.ISize(mBlockWM
) = std::max(nscoord(0), newSize
);
538 // Save the non-snapped area since that's what we want to use when placing
539 // the markers (our return value). The snapped area is only for analysis.
540 LogicalRect nonSnappedContentArea
= contentArea
;
541 if (mAdjustForPixelSnapping
) {
542 const nscoord scrollAdjust
= mBlock
->PresContext()->AppUnitsPerDevPixel();
544 InflateIStart(mBlockWM
, &contentArea
, scrollAdjust
);
547 InflateIEnd(mBlockWM
, &contentArea
, scrollAdjust
);
551 LogicalRect
lineRect(mBlockWM
, aLine
->GetScrollableOverflowArea(),
553 const bool istartWantsMarker
=
555 lineRect
.IStart(mBlockWM
) < contentArea
.IStart(mBlockWM
);
556 const bool iendWantsTextOverflowMarker
=
557 !suppressIEnd
&& lineRect
.IEnd(mBlockWM
) > contentArea
.IEnd(mBlockWM
);
558 const bool iendWantsBlockEllipsisMarker
=
559 !suppressIEnd
&& mIEnd
.mHasBlockEllipsis
;
560 const bool iendWantsMarker
=
561 iendWantsTextOverflowMarker
|| iendWantsBlockEllipsisMarker
;
562 if (!istartWantsMarker
&& !iendWantsMarker
) {
563 // We don't need any markers on this line.
564 return nonSnappedContentArea
;
568 bool retryEmptyLine
= true;
569 bool guessIStart
= istartWantsMarker
;
570 bool guessIEnd
= iendWantsMarker
;
571 mIStart
.mActive
= istartWantsMarker
;
572 mIEnd
.mActive
= iendWantsMarker
;
573 mIStart
.mEdgeAligned
= mCanHaveInlineAxisScrollbar
&& istartWantsMarker
;
575 mCanHaveInlineAxisScrollbar
&& iendWantsTextOverflowMarker
;
576 bool clippedIStartMarker
= false;
577 bool clippedIEndMarker
= false;
579 // Setup marker strings as needed.
581 mIStart
.SetupString(mBlock
);
584 mIEnd
.SetupString(mBlock
);
587 // If there is insufficient space for both markers then keep the one on the
588 // end side per the block's 'direction'.
589 nscoord istartMarkerISize
= mIStart
.mActive
? mIStart
.mISize
: 0;
590 nscoord iendMarkerISize
= mIEnd
.mActive
? mIEnd
.mISize
: 0;
591 if (istartMarkerISize
&& iendMarkerISize
&&
592 istartMarkerISize
+ iendMarkerISize
> contentArea
.ISize(mBlockWM
)) {
593 istartMarkerISize
= 0;
596 // Calculate the area between the potential markers aligned at the
598 LogicalRect insideMarkersArea
= nonSnappedContentArea
;
600 InflateIStart(mBlockWM
, &insideMarkersArea
, -istartMarkerISize
);
603 InflateIEnd(mBlockWM
, &insideMarkersArea
, -iendMarkerISize
);
606 // Analyze the frames on aLine for the overflow situation at the content
607 // edges and at the edges of the area between the markers.
608 bool foundVisibleTextOrAtomic
= false;
609 int32_t n
= aLine
->GetChildCount();
610 nsIFrame
* child
= aLine
->mFirstChild
;
611 InnerClipEdges clippedMarkerEdges
;
612 for (; n
-- > 0; child
= child
->GetNextSibling()) {
613 ExamineFrameSubtree(child
, contentArea
, insideMarkersArea
, aFramesToHide
,
614 aAlignmentEdges
, &foundVisibleTextOrAtomic
,
615 &clippedMarkerEdges
);
617 if (!foundVisibleTextOrAtomic
&& retryEmptyLine
) {
618 aAlignmentEdges
->mAssignedInner
= false;
619 aAlignmentEdges
->mIEndOuter
= 0;
620 aFramesToHide
->Clear();
622 if (mIStart
.IsNeeded() && mIStart
.mActive
&& !clippedIStartMarker
) {
623 if (clippedMarkerEdges
.mAssignedIStart
&&
624 clippedMarkerEdges
.mIStart
>
625 nonSnappedContentArea
.IStart(mBlockWM
)) {
626 mIStart
.mISize
= clippedMarkerEdges
.mIStart
-
627 nonSnappedContentArea
.IStart(mBlockWM
);
628 NS_ASSERTION(mIStart
.mISize
< mIStart
.mIntrinsicISize
,
629 "clipping a marker should make it strictly smaller");
630 clippedIStartMarker
= true;
632 mIStart
.mActive
= guessIStart
= false;
636 if (mIEnd
.IsNeeded() && mIEnd
.mActive
&& !clippedIEndMarker
) {
637 if (clippedMarkerEdges
.mAssignedIEnd
&&
638 nonSnappedContentArea
.IEnd(mBlockWM
) > clippedMarkerEdges
.mIEnd
) {
640 nonSnappedContentArea
.IEnd(mBlockWM
) - clippedMarkerEdges
.mIEnd
;
641 NS_ASSERTION(mIEnd
.mISize
< mIEnd
.mIntrinsicISize
,
642 "clipping a marker should make it strictly smaller");
643 clippedIEndMarker
= true;
645 mIEnd
.mActive
= guessIEnd
= false;
649 // The line simply has no visible content even without markers,
650 // so examine the line again without suppressing markers.
651 retryEmptyLine
= false;
652 mIStart
.mISize
= mIStart
.mIntrinsicISize
;
653 mIStart
.mActive
= guessIStart
= istartWantsMarker
;
654 mIEnd
.mISize
= mIEnd
.mIntrinsicISize
;
655 mIEnd
.mActive
= guessIEnd
= iendWantsMarker
;
656 // If we wanted to place a block ellipsis but didn't, due to not having
657 // any visible content to align to or the line's content being scrolled
658 // out of view, then clip the ellipsis so that it looks like it is aligned
659 // with the out of view content.
660 if (mIEnd
.IsNeeded() && mIEnd
.mActive
&& mIEnd
.mHasBlockEllipsis
) {
661 NS_ASSERTION(nonSnappedContentArea
.IStart(mBlockWM
) >
662 aAlignmentEdges
->mIEndOuter
,
663 "Expected the alignment edge for the out of view content "
664 "to be before the start of the content area");
665 mIEnd
.mISize
= std::max(
666 mIEnd
.mIntrinsicISize
- (nonSnappedContentArea
.IStart(mBlockWM
) -
667 aAlignmentEdges
->mIEndOuter
),
672 if (guessIStart
== (mIStart
.mActive
&& mIStart
.IsNeeded()) &&
673 guessIEnd
== (mIEnd
.mActive
&& mIEnd
.IsNeeded())) {
676 guessIStart
= mIStart
.mActive
&& mIStart
.IsNeeded();
677 guessIEnd
= mIEnd
.mActive
&& mIEnd
.IsNeeded();
680 aFramesToHide
->Clear();
682 NS_ASSERTION(pass
== 0, "2nd pass should never guess wrong");
683 } while (++pass
!= 2);
684 if (!istartWantsMarker
|| !mIStart
.mActive
) {
687 if (!iendWantsMarker
|| !mIEnd
.mActive
) {
690 return nonSnappedContentArea
;
693 void TextOverflow::ProcessLine(const nsDisplayListSet
& aLists
, nsLineBox
* aLine
,
694 uint32_t aLineNumber
) {
695 if (mIStart
.mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
&&
696 mIEnd
.mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
&&
697 !aLine
->HasLineClampEllipsis()) {
702 mIStart
.mActive
= mIStart
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
;
704 mIEnd
.mHasBlockEllipsis
= aLine
->HasLineClampEllipsis();
705 mIEnd
.mActive
= mIEnd
.mStyle
->mType
!= NS_STYLE_TEXT_OVERFLOW_CLIP
||
706 aLine
->HasLineClampEllipsis();
708 FrameHashtable
framesToHide(64);
709 AlignmentEdges alignmentEdges
;
710 const LogicalRect contentArea
=
711 ExamineLineFrames(aLine
, &framesToHide
, &alignmentEdges
);
712 bool needIStart
= mIStart
.IsNeeded();
713 bool needIEnd
= mIEnd
.IsNeeded();
714 if (!needIStart
&& !needIEnd
) {
717 NS_ASSERTION(!mIStart
.IsSuppressed() || !needIStart
,
718 "left marker when not needed");
719 NS_ASSERTION(!mIEnd
.IsSuppressed() || !needIEnd
,
720 "right marker when not needed");
722 // If there is insufficient space for both markers then keep the one on the
723 // end side per the block's 'direction'.
724 if (needIStart
&& needIEnd
&&
725 mIStart
.mISize
+ mIEnd
.mISize
> contentArea
.ISize(mBlockWM
)) {
728 LogicalRect insideMarkersArea
= contentArea
;
730 InflateIStart(mBlockWM
, &insideMarkersArea
, -mIStart
.mISize
);
733 InflateIEnd(mBlockWM
, &insideMarkersArea
, -mIEnd
.mISize
);
736 if (alignmentEdges
.mAssignedInner
) {
737 if (mIStart
.mEdgeAligned
) {
738 alignmentEdges
.mIStart
= insideMarkersArea
.IStart(mBlockWM
);
740 if (mIEnd
.mEdgeAligned
) {
741 alignmentEdges
.mIEnd
= insideMarkersArea
.IEnd(mBlockWM
);
743 LogicalRect
alignmentRect(mBlockWM
, alignmentEdges
.mIStart
,
744 insideMarkersArea
.BStart(mBlockWM
),
745 alignmentEdges
.ISize(), 1);
746 insideMarkersArea
.IntersectRect(insideMarkersArea
, alignmentRect
);
748 // There was no content on the line that was visible at the current scolled
749 // position. If we wanted to place a block ellipsis but failed due to
750 // having no visible content to align it to, we still need to ensure it
751 // is displayed. It goes at the start of the line, even though it's an
752 // IEnd marker, since that is the side of the line that the content has
753 // been scrolled past. We set the insideMarkersArea to a zero-sized
754 // rectangle placed next to the scrolled-out-of-view content.
755 if (mIEnd
.mHasBlockEllipsis
) {
756 insideMarkersArea
= LogicalRect(mBlockWM
, alignmentEdges
.mIEndOuter
,
757 insideMarkersArea
.BStart(mBlockWM
), 0, 1);
761 // Clip and remove display items as needed at the final marker edges.
762 nsDisplayList
* lists
[] = {aLists
.Content(), aLists
.PositionedDescendants()};
763 for (uint32_t i
= 0; i
< ArrayLength(lists
); ++i
) {
764 PruneDisplayListContents(lists
[i
], framesToHide
, insideMarkersArea
);
766 CreateMarkers(aLine
, needIStart
, needIEnd
, insideMarkersArea
, contentArea
,
770 void TextOverflow::PruneDisplayListContents(
771 nsDisplayList
* aList
, const FrameHashtable
& aFramesToHide
,
772 const LogicalRect
& aInsideMarkersArea
) {
775 while ((item
= aList
->RemoveBottom())) {
776 nsIFrame
* itemFrame
= item
->Frame();
777 if (IsFrameDescendantOfAny(itemFrame
, aFramesToHide
, mBlock
)) {
778 item
->Destroy(mBuilder
);
782 nsDisplayList
* wrapper
= item
->GetSameCoordinateSystemChildren();
784 if (!itemFrame
|| GetSelfOrNearestBlock(itemFrame
) == mBlock
) {
785 PruneDisplayListContents(wrapper
, aFramesToHide
, aInsideMarkersArea
);
789 nsDisplayText
* textItem
=
790 itemFrame
? nsDisplayText::CheckCast(item
) : nullptr;
791 if (textItem
&& GetSelfOrNearestBlock(itemFrame
) == mBlock
) {
793 GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame
);
794 if (mIStart
.IsNeeded()) {
796 aInsideMarkersArea
.IStart(mBlockWM
) - rect
.IStart(mBlockWM
);
798 (mBlockWM
.IsBidiLTR() ? textItem
->VisIStartEdge()
799 : textItem
->VisIEndEdge()) = istart
;
802 if (mIEnd
.IsNeeded()) {
803 nscoord iend
= rect
.IEnd(mBlockWM
) - aInsideMarkersArea
.IEnd(mBlockWM
);
805 (mBlockWM
.IsBidiLTR() ? textItem
->VisIEndEdge()
806 : textItem
->VisIStartEdge()) = iend
;
811 saved
.AppendToTop(item
);
813 aList
->AppendToTop(&saved
);
817 bool TextOverflow::HasClippedTextOverflow(nsIFrame
* aBlockFrame
) {
818 const nsStyleTextReset
* style
= aBlockFrame
->StyleTextReset();
819 return style
->mTextOverflow
.mLeft
.mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
&&
820 style
->mTextOverflow
.mRight
.mType
== NS_STYLE_TEXT_OVERFLOW_CLIP
;
824 bool TextOverflow::HasBlockEllipsis(nsIFrame
* aBlockFrame
) {
825 nsBlockFrame
* f
= do_QueryFrame(aBlockFrame
);
826 return f
&& f
->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
);
830 bool TextOverflow::CanHaveOverflowMarkers(nsIFrame
* aBlockFrame
) {
831 // Treat a line with a -webkit-line-clamp ellipsis as a kind of text
833 if (aBlockFrame
->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS
)) {
837 // Nothing to do for text-overflow:clip or if 'overflow-x/y:visible'.
838 if (HasClippedTextOverflow(aBlockFrame
) ||
839 IsInlineAxisOverflowVisible(aBlockFrame
)) {
843 // Skip ComboboxControlFrame because it would clip the drop-down arrow.
844 // Its anon block inherits 'text-overflow' and does what is expected.
845 if (aBlockFrame
->IsComboboxControlFrame()) {
849 // Inhibit the markers if a descendant content owns the caret.
850 RefPtr
<nsCaret
> caret
= aBlockFrame
->PresShell()->GetCaret();
851 if (caret
&& caret
->IsVisible()) {
852 RefPtr
<dom::Selection
> domSelection
= caret
->GetSelection();
854 nsCOMPtr
<nsIContent
> content
=
855 nsIContent::FromNodeOrNull(domSelection
->GetFocusNode());
856 if (content
&& nsContentUtils::ContentIsDescendantOf(
857 content
, aBlockFrame
->GetContent())) {
865 void TextOverflow::CreateMarkers(const nsLineBox
* aLine
, bool aCreateIStart
,
867 const LogicalRect
& aInsideMarkersArea
,
868 const LogicalRect
& aContentArea
,
869 uint32_t aLineNumber
) {
871 DisplayListClipState::AutoSaveRestore
clipState(mBuilder
);
873 LogicalRect
markerLogicalRect(
874 mBlockWM
, aInsideMarkersArea
.IStart(mBlockWM
) - mIStart
.mIntrinsicISize
,
875 aLine
->BStart(), mIStart
.mIntrinsicISize
, aLine
->BSize());
876 nsPoint offset
= mBuilder
->ToReferenceFrame(mBlock
);
878 markerLogicalRect
.GetPhysicalRect(mBlockWM
, mBlockSize
) + offset
;
879 ClipMarker(aContentArea
.GetPhysicalRect(mBlockWM
, mBlockSize
) + offset
,
880 markerRect
, clipState
);
881 mMarkerList
.AppendNewToTop
<nsDisplayTextOverflowMarker
>(
882 mBuilder
, mBlock
, markerRect
, aLine
->GetLogicalAscent(),
883 *mIStart
.mStyle
, aLineNumber
, 0);
887 DisplayListClipState::AutoSaveRestore
clipState(mBuilder
);
889 LogicalRect
markerLogicalRect(mBlockWM
, aInsideMarkersArea
.IEnd(mBlockWM
),
890 aLine
->BStart(), mIEnd
.mIntrinsicISize
,
892 nsPoint offset
= mBuilder
->ToReferenceFrame(mBlock
);
894 markerLogicalRect
.GetPhysicalRect(mBlockWM
, mBlockSize
) + offset
;
895 ClipMarker(aContentArea
.GetPhysicalRect(mBlockWM
, mBlockSize
) + offset
,
896 markerRect
, clipState
);
897 mMarkerList
.AppendNewToTop
<nsDisplayTextOverflowMarker
>(
898 mBuilder
, mBlock
, markerRect
, aLine
->GetLogicalAscent(),
899 mIEnd
.mHasBlockEllipsis
? nsStyleTextOverflowSide::Ellipsis()
905 void TextOverflow::Marker::SetupString(nsIFrame
* aFrame
) {
910 // A limitation here is that at the IEnd of a line, we only ever render one of
911 // a text-overflow marker and a -webkit-line-clamp block ellipsis. Since we
912 // don't track the block ellipsis string and the text-overflow marker string
913 // separately, if both apply to the element, we will always use "…" as the
914 // string for text-overflow.
915 if (HasBlockEllipsis(aFrame
) ||
916 mStyle
->mType
== NS_STYLE_TEXT_OVERFLOW_ELLIPSIS
) {
917 gfxTextRun
* textRun
= GetEllipsisTextRun(aFrame
);
919 mISize
= textRun
->GetAdvanceWidth();
924 RefPtr
<gfxContext
> rc
=
925 aFrame
->PresShell()->CreateReferenceRenderingContext();
926 RefPtr
<nsFontMetrics
> fm
=
927 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame
);
928 mISize
= nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle
->mString
, aFrame
,
931 mIntrinsicISize
= mISize
;
936 } // namespace mozilla