Bug 1468402 - Part 3: Add test for subgrids in the grid list. r=pbro
[gecko.git] / layout / generic / TextOverflow.cpp
blob4a81be5d9752d5746ebba2091ec3383e5e462ca2
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"
8 #include <algorithm>
10 // Please maintain alphabetical order below
11 #include "gfxContext.h"
12 #include "nsBlockFrame.h"
13 #include "nsCaret.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"
21 #include "nsRect.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;
32 namespace mozilla {
33 namespace css {
35 class LazyReferenceRenderingDrawTargetGetterFromFrame final
36 : public gfxFontGroup::LazyReferenceDrawTargetGetter {
37 public:
38 typedef mozilla::gfx::DrawTarget DrawTarget;
40 explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame* aFrame)
41 : mFrame(aFrame) {}
42 virtual already_AddRefed<DrawTarget> GetRefDrawTarget() override {
43 RefPtr<gfxContext> ctx =
44 mFrame->PresShell()->CreateReferenceRenderingContext();
45 RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
46 return dt.forget();
49 private:
50 nsIFrame* mFrame;
53 static gfxTextRun* GetEllipsisTextRun(nsIFrame* aFrame) {
54 RefPtr<nsFontMetrics> fm =
55 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
56 LazyReferenceRenderingDrawTargetGetterFromFrame lazyRefDrawTargetGetter(
57 aFrame);
58 return fm->GetThebesFontGroup()->GetEllipsisTextRun(
59 aFrame->PresContext()->AppUnitsPerDevPixel(),
60 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame->Style()),
61 lazyRefDrawTargetGetter);
64 static nsIFrame* GetSelfOrNearestBlock(nsIFrame* aFrame) {
65 MOZ_ASSERT(aFrame);
66 return aFrame->IsBlockFrameOrSubclass()
67 ? aFrame
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) {
87 return false;
89 return !aFrame->MeasureCharClippedText(aLeft, aRight, aSnappedLeft,
90 aSnappedRight);
93 static bool IsInlineAxisOverflowVisible(nsIFrame* aFrame) {
94 MOZ_ASSERT(aFrame && aFrame->IsBlockFrameOrSubclass(),
95 "expected a block frame");
97 nsIFrame* f = aFrame;
98 while (f && f->Style()->IsAnonBox() && !f->IsScrollFrame()) {
99 f = f->GetParent();
101 if (!f) {
102 return true;
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);
118 } else {
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)) {
145 return true;
148 return false;
151 class nsDisplayTextOverflowMarker final : public nsPaintedDisplayItem {
152 public:
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),
158 mRect(aRect),
159 mStyle(aStyle),
160 mAscent(aAscent),
161 mIndex((aLineNumber << 1) + aIndex) {
162 MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
164 #ifdef NS_BUILD_REFCNT_LOGGING
165 virtual ~nsDisplayTextOverflowMarker() {
166 MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
168 #endif
169 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
170 bool* aSnap) const override {
171 *aSnap = false;
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) {
183 return nsRect();
186 bool snap;
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)
204 private:
205 nsRect mRect; // in reference frame coordinates
206 const nsStyleTextOverflowSide mStyle;
207 nscoord mAscent; // baseline for the marker text in mRect
208 uint16_t mIndex;
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,
218 gfxContext* aCtx) {
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,
228 (void*)this);
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));
241 } else {
242 pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
243 mFrame, aCtx, pt.x + mRect.width, -mAscent));
245 } else {
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);
253 if (textRun) {
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));
260 } else {
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) {
273 bool snap;
274 nsRect bounds = GetBounds(aDisplayListBuilder, &snap);
275 if (bounds.IsEmpty()) {
276 return true;
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()),
294 mBuilder(aBuilder),
295 mBlock(aBlockFrame),
296 mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame)),
297 mBlockSize(aBlockFrame->GetSize()),
298 mBlockWM(aBlockFrame->GetWritingMode()),
299 mAdjustForPixelSnapping(false) {
300 #ifdef MOZ_XUL
301 if (!mScrollableFrame) {
302 auto pseudoType = aBlockFrame->Style()->GetPseudoType();
303 if (pseudoType == PseudoStyleType::mozXULAnonymousBlock) {
304 mScrollableFrame =
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();
313 #endif
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;
327 mContentArea.MoveBy(
328 mBlockWM, LogicalPoint(mBlockWM, mScrollableFrame->GetScrollPosition(),
329 nullContainerSize));
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));
336 } else {
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.
344 /* static */
345 Maybe<TextOverflow> TextOverflow::WillProcessLines(
346 nsDisplayListBuilder* aBuilder, nsIFrame* aBlockFrame) {
347 // Ignore text-overflow and -webkit-line-clamp for event and frame visibility
348 // processing.
349 if (aBuilder->IsForEventDelivery() || aBuilder->IsForFrameVisibility() ||
350 !CanHaveOverflowMarkers(aBlockFrame)) {
351 return Nothing();
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.
357 return Nothing();
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) {
372 return;
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;
384 if (overflowIEnd) {
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);
396 if (isAtomic) {
397 return;
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()),
418 mBlockSize);
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) {
431 istartOverlap = 0;
434 if (iendOverlap > 0) {
435 aClippedMarkerEdges->AccumulateIEnd(mBlockWM, borderRect);
436 if (!mIEnd.mActive) {
437 iendOverlap = 0;
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 =
451 mBlockWM.IsBidiLTR()
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;
469 } else {
470 aFramesToHide->PutEntry(aFrame);
472 } else if (!insideIStartEdge || !insideIEndEdge) {
473 // frame is outside
474 if (!insideIStartEdge) {
475 aAlignmentEdges->AccumulateOuter(mBlockWM, borderRect);
477 if (IsAtomicElement(aFrame, aFrameType)) {
478 aFramesToHide->PutEntry(aFrame);
480 } else {
481 // frame is inside
482 aAlignmentEdges->AccumulateInner(mBlockWM, borderRect);
483 if (aFrameType == LayoutFrameType::Text) {
484 auto textFrame = static_cast<nsTextFrame*>(aFrame);
485 if (textFrame->HasNonSuppressedText()) {
486 *aFoundVisibleTextOrAtomic = true;
488 } else {
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(),
502 mBlockSize);
503 LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
504 mBlockSize);
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) {
513 suppressIEnd = true;
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);
525 if (delta < 0) {
526 nscoord newSize = contentArea.ISize(mBlockWM) + delta;
527 contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
528 snapEnd = false;
530 delta = startEdge - contentArea.IStart(mBlockWM);
531 if (delta > 0) {
532 contentArea.IStart(mBlockWM) = startEdge;
533 nscoord newSize = contentArea.ISize(mBlockWM) - delta;
534 contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
535 snapStart = false;
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();
543 if (snapStart) {
544 InflateIStart(mBlockWM, &contentArea, scrollAdjust);
546 if (snapEnd) {
547 InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
551 LogicalRect lineRect(mBlockWM, aLine->GetScrollableOverflowArea(),
552 mBlockSize);
553 const bool istartWantsMarker =
554 !suppressIStart &&
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;
567 int pass = 0;
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;
574 mIEnd.mEdgeAligned =
575 mCanHaveInlineAxisScrollbar && iendWantsTextOverflowMarker;
576 bool clippedIStartMarker = false;
577 bool clippedIEndMarker = false;
578 do {
579 // Setup marker strings as needed.
580 if (guessIStart) {
581 mIStart.SetupString(mBlock);
583 if (guessIEnd) {
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
597 // block's edge.
598 LogicalRect insideMarkersArea = nonSnappedContentArea;
599 if (guessIStart) {
600 InflateIStart(mBlockWM, &insideMarkersArea, -istartMarkerISize);
602 if (guessIEnd) {
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();
621 pass = -1;
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;
631 } else {
632 mIStart.mActive = guessIStart = false;
634 continue;
636 if (mIEnd.IsNeeded() && mIEnd.mActive && !clippedIEndMarker) {
637 if (clippedMarkerEdges.mAssignedIEnd &&
638 nonSnappedContentArea.IEnd(mBlockWM) > clippedMarkerEdges.mIEnd) {
639 mIEnd.mISize =
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;
644 } else {
645 mIEnd.mActive = guessIEnd = false;
647 continue;
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),
670 continue;
672 if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
673 guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
674 break;
675 } else {
676 guessIStart = mIStart.mActive && mIStart.IsNeeded();
677 guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
678 mIStart.Reset();
679 mIEnd.Reset();
680 aFramesToHide->Clear();
682 NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
683 } while (++pass != 2);
684 if (!istartWantsMarker || !mIStart.mActive) {
685 mIStart.Reset();
687 if (!iendWantsMarker || !mIEnd.mActive) {
688 mIEnd.Reset();
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()) {
698 return;
701 mIStart.Reset();
702 mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
703 mIEnd.Reset();
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) {
715 return;
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)) {
726 needIStart = false;
728 LogicalRect insideMarkersArea = contentArea;
729 if (needIStart) {
730 InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
732 if (needIEnd) {
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);
747 } else {
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,
767 aLineNumber);
770 void TextOverflow::PruneDisplayListContents(
771 nsDisplayList* aList, const FrameHashtable& aFramesToHide,
772 const LogicalRect& aInsideMarkersArea) {
773 nsDisplayList saved;
774 nsDisplayItem* item;
775 while ((item = aList->RemoveBottom())) {
776 nsIFrame* itemFrame = item->Frame();
777 if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
778 item->Destroy(mBuilder);
779 continue;
782 nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
783 if (wrapper) {
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) {
792 LogicalRect rect =
793 GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
794 if (mIStart.IsNeeded()) {
795 nscoord istart =
796 aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
797 if (istart > 0) {
798 (mBlockWM.IsBidiLTR() ? textItem->VisIStartEdge()
799 : textItem->VisIEndEdge()) = istart;
802 if (mIEnd.IsNeeded()) {
803 nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
804 if (iend > 0) {
805 (mBlockWM.IsBidiLTR() ? textItem->VisIEndEdge()
806 : textItem->VisIStartEdge()) = iend;
811 saved.AppendToTop(item);
813 aList->AppendToTop(&saved);
816 /* static */
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;
823 /* static */
824 bool TextOverflow::HasBlockEllipsis(nsIFrame* aBlockFrame) {
825 nsBlockFrame* f = do_QueryFrame(aBlockFrame);
826 return f && f->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
829 /* static */
830 bool TextOverflow::CanHaveOverflowMarkers(nsIFrame* aBlockFrame) {
831 // Treat a line with a -webkit-line-clamp ellipsis as a kind of text
832 // overflow.
833 if (aBlockFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
834 return true;
837 // Nothing to do for text-overflow:clip or if 'overflow-x/y:visible'.
838 if (HasClippedTextOverflow(aBlockFrame) ||
839 IsInlineAxisOverflowVisible(aBlockFrame)) {
840 return false;
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()) {
846 return false;
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();
853 if (domSelection) {
854 nsCOMPtr<nsIContent> content =
855 nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
856 if (content && nsContentUtils::ContentIsDescendantOf(
857 content, aBlockFrame->GetContent())) {
858 return false;
862 return true;
865 void TextOverflow::CreateMarkers(const nsLineBox* aLine, bool aCreateIStart,
866 bool aCreateIEnd,
867 const LogicalRect& aInsideMarkersArea,
868 const LogicalRect& aContentArea,
869 uint32_t aLineNumber) {
870 if (aCreateIStart) {
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);
877 nsRect markerRect =
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);
886 if (aCreateIEnd) {
887 DisplayListClipState::AutoSaveRestore clipState(mBuilder);
889 LogicalRect markerLogicalRect(mBlockWM, aInsideMarkersArea.IEnd(mBlockWM),
890 aLine->BStart(), mIEnd.mIntrinsicISize,
891 aLine->BSize());
892 nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
893 nsRect markerRect =
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()
900 : *mIEnd.mStyle,
901 aLineNumber, 1);
905 void TextOverflow::Marker::SetupString(nsIFrame* aFrame) {
906 if (mInitialized) {
907 return;
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);
918 if (textRun) {
919 mISize = textRun->GetAdvanceWidth();
920 } else {
921 mISize = 0;
923 } else {
924 RefPtr<gfxContext> rc =
925 aFrame->PresShell()->CreateReferenceRenderingContext();
926 RefPtr<nsFontMetrics> fm =
927 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
928 mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
929 *fm, *rc);
931 mIntrinsicISize = mISize;
932 mInitialized = true;
935 } // namespace css
936 } // namespace mozilla