Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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"
8 #include <algorithm>
10 // Please maintain alphabetical order below
11 #include "nsBlockFrame.h"
12 #include "nsCaret.h"
13 #include "nsContentUtils.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsGfxScrollFrame.h"
16 #include "nsIScrollableFrame.h"
17 #include "nsLayoutUtils.h"
18 #include "nsPresContext.h"
19 #include "nsRect.h"
20 #include "nsRenderingContext.h"
21 #include "nsTextFrame.h"
22 #include "nsIFrameInlines.h"
23 #include "mozilla/ArrayUtils.h"
24 #include "mozilla/Likely.h"
26 namespace mozilla {
27 namespace css {
29 class LazyReferenceRenderingContextGetterFromFrame MOZ_FINAL :
30 public gfxFontGroup::LazyReferenceContextGetter {
31 public:
32 LazyReferenceRenderingContextGetterFromFrame(nsIFrame* aFrame)
33 : mFrame(aFrame) {}
34 virtual already_AddRefed<gfxContext> GetRefContext() MOZ_OVERRIDE
35 {
36 nsRefPtr<nsRenderingContext> rc =
37 mFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
38 nsRefPtr<gfxContext> ctx = rc->ThebesContext();
39 return ctx.forget();
40 }
41 private:
42 nsIFrame* mFrame;
43 };
45 static gfxTextRun*
46 GetEllipsisTextRun(nsIFrame* aFrame)
47 {
48 nsRefPtr<nsFontMetrics> fm;
49 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
50 nsLayoutUtils::FontSizeInflationFor(aFrame));
51 LazyReferenceRenderingContextGetterFromFrame lazyRefContextGetter(aFrame);
52 return fm->GetThebesFontGroup()->GetEllipsisTextRun(
53 aFrame->PresContext()->AppUnitsPerDevPixel(), lazyRefContextGetter);
54 }
56 static nsIFrame*
57 GetSelfOrNearestBlock(nsIFrame* aFrame)
58 {
59 return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame :
60 nsLayoutUtils::FindNearestBlockAncestor(aFrame);
61 }
63 // Return true if the frame is an atomic inline-level element.
64 // It's not supposed to be called for block frames since we never
65 // process block descendants for text-overflow.
66 static bool
67 IsAtomicElement(nsIFrame* aFrame, const nsIAtom* aFrameType)
68 {
69 NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame) ||
70 !aFrame->IsBlockOutside(),
71 "unexpected block frame");
72 NS_PRECONDITION(aFrameType != nsGkAtoms::placeholderFrame,
73 "unexpected placeholder frame");
74 return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
75 }
77 static bool
78 IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
79 nscoord* aSnappedLeft, nscoord* aSnappedRight)
80 {
81 *aSnappedLeft = aLeft;
82 *aSnappedRight = aRight;
83 if (aLeft <= 0 && aRight <= 0) {
84 return false;
85 }
86 return !aFrame->MeasureCharClippedText(aLeft, aRight,
87 aSnappedLeft, aSnappedRight);
88 }
90 static bool
91 IsHorizontalOverflowVisible(nsIFrame* aFrame)
92 {
93 NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr,
94 "expected a block frame");
96 nsIFrame* f = aFrame;
97 while (f && f->StyleContext()->GetPseudo() &&
98 f->GetType() != nsGkAtoms::scrollFrame) {
99 f = f->GetParent();
100 }
101 return !f || f->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE;
102 }
104 static void
105 ClipMarker(const nsRect& aContentArea,
106 const nsRect& aMarkerRect,
107 DisplayListClipState::AutoSaveRestore& aClipState)
108 {
109 nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost();
110 nsRect markerRect = aMarkerRect;
111 if (rightOverflow > 0) {
112 // Marker overflows on the right side (content width < marker width).
113 markerRect.width -= rightOverflow;
114 aClipState.ClipContentDescendants(markerRect);
115 } else {
116 nscoord leftOverflow = aContentArea.x - aMarkerRect.x;
117 if (leftOverflow > 0) {
118 // Marker overflows on the left side
119 markerRect.width -= leftOverflow;
120 markerRect.x += leftOverflow;
121 aClipState.ClipContentDescendants(markerRect);
122 }
123 }
124 }
126 static void
127 InflateLeft(nsRect* aRect, nscoord aDelta)
128 {
129 nscoord xmost = aRect->XMost();
130 aRect->x -= aDelta;
131 aRect->width = std::max(xmost - aRect->x, 0);
132 }
134 static void
135 InflateRight(nsRect* aRect, nscoord aDelta)
136 {
137 aRect->width = std::max(aRect->width + aDelta, 0);
138 }
140 static bool
141 IsFrameDescendantOfAny(nsIFrame* aChild,
142 const TextOverflow::FrameHashtable& aSetOfFrames,
143 nsIFrame* aCommonAncestor)
144 {
145 for (nsIFrame* f = aChild; f && f != aCommonAncestor;
146 f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
147 if (aSetOfFrames.GetEntry(f)) {
148 return true;
149 }
150 }
151 return false;
152 }
154 class nsDisplayTextOverflowMarker : public nsDisplayItem
155 {
156 public:
157 nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
158 const nsRect& aRect, nscoord aAscent,
159 const nsStyleTextOverflowSide* aStyle,
160 uint32_t aIndex)
161 : nsDisplayItem(aBuilder, aFrame), mRect(aRect),
162 mStyle(aStyle), mAscent(aAscent), mIndex(aIndex) {
163 MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
164 }
165 #ifdef NS_BUILD_REFCNT_LOGGING
166 virtual ~nsDisplayTextOverflowMarker() {
167 MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
168 }
169 #endif
170 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
171 bool* aSnap) MOZ_OVERRIDE {
172 *aSnap = false;
173 nsRect shadowRect =
174 nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
175 return mRect.Union(shadowRect);
176 }
177 virtual void Paint(nsDisplayListBuilder* aBuilder,
178 nsRenderingContext* aCtx) MOZ_OVERRIDE;
180 virtual uint32_t GetPerFrameKey() MOZ_OVERRIDE {
181 return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
182 }
183 void PaintTextToContext(nsRenderingContext* aCtx,
184 nsPoint aOffsetFromRect);
185 NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
186 private:
187 nsRect mRect; // in reference frame coordinates
188 const nsStyleTextOverflowSide* mStyle;
189 nscoord mAscent; // baseline for the marker text in mRect
190 uint32_t mIndex;
191 };
193 static void
194 PaintTextShadowCallback(nsRenderingContext* aCtx,
195 nsPoint aShadowOffset,
196 const nscolor& aShadowColor,
197 void* aData)
198 {
199 reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
200 PaintTextToContext(aCtx, aShadowOffset);
201 }
203 void
204 nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
205 nsRenderingContext* aCtx)
206 {
207 nscolor foregroundColor =
208 nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
210 // Paint the text-shadows for the overflow marker
211 nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
212 foregroundColor, PaintTextShadowCallback,
213 (void*)this);
214 aCtx->SetColor(foregroundColor);
215 PaintTextToContext(aCtx, nsPoint(0, 0));
216 }
218 void
219 nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx,
220 nsPoint aOffsetFromRect)
221 {
222 gfxFloat y = nsLayoutUtils::GetSnappedBaselineY(mFrame, aCtx->ThebesContext(),
223 mRect.y, mAscent);
224 nsPoint baselinePt(mRect.x, NSToCoordFloor(y));
225 nsPoint pt = baselinePt + aOffsetFromRect;
227 if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
228 gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
229 if (textRun) {
230 NS_ASSERTION(!textRun->IsRightToLeft(),
231 "Ellipsis textruns should always be LTR!");
232 gfxPoint gfxPt(pt.x, pt.y);
233 textRun->Draw(aCtx->ThebesContext(), gfxPt, DrawMode::GLYPH_FILL,
234 0, textRun->GetLength(), nullptr, nullptr, nullptr);
235 }
236 } else {
237 nsRefPtr<nsFontMetrics> fm;
238 nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm),
239 nsLayoutUtils::FontSizeInflationFor(mFrame));
240 aCtx->SetFont(fm);
241 nsLayoutUtils::DrawString(mFrame, aCtx, mStyle->mString.get(),
242 mStyle->mString.Length(), pt);
243 }
244 }
246 void
247 TextOverflow::Init(nsDisplayListBuilder* aBuilder,
248 nsIFrame* aBlockFrame)
249 {
250 mBuilder = aBuilder;
251 mBlock = aBlockFrame;
252 mContentArea = aBlockFrame->GetContentRectRelativeToSelf();
253 mScrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
254 uint8_t direction = aBlockFrame->StyleVisibility()->mDirection;
255 mBlockIsRTL = direction == NS_STYLE_DIRECTION_RTL;
256 mAdjustForPixelSnapping = false;
257 #ifdef MOZ_XUL
258 if (!mScrollableFrame) {
259 nsIAtom* pseudoType = aBlockFrame->StyleContext()->GetPseudo();
260 if (pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
261 mScrollableFrame =
262 nsLayoutUtils::GetScrollableFrameFor(aBlockFrame->GetParent());
263 // nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
264 // for RTL blocks (also for overflow:hidden), so we need to move
265 // the edges 1px outward in ExamineLineFrames to avoid triggering
266 // a text-overflow marker in this case.
267 mAdjustForPixelSnapping = mBlockIsRTL;
268 }
269 }
270 #endif
271 mCanHaveHorizontalScrollbar = false;
272 if (mScrollableFrame) {
273 mCanHaveHorizontalScrollbar =
274 mScrollableFrame->GetScrollbarStyles().mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
275 if (!mAdjustForPixelSnapping) {
276 // Scrolling to the end position can leave some text still overflowing due
277 // to pixel snapping behaviour in our scrolling code.
278 mAdjustForPixelSnapping = mCanHaveHorizontalScrollbar;
279 }
280 mContentArea.MoveBy(mScrollableFrame->GetScrollPosition());
281 nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
282 scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
283 }
284 const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
285 mLeft.Init(style->mTextOverflow.GetLeft(direction));
286 mRight.Init(style->mTextOverflow.GetRight(direction));
287 // The left/right marker string is setup in ExamineLineFrames when a line
288 // has overflow on that side.
289 }
291 /* static */ TextOverflow*
292 TextOverflow::WillProcessLines(nsDisplayListBuilder* aBuilder,
293 nsIFrame* aBlockFrame)
294 {
295 if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) {
296 return nullptr;
297 }
298 nsAutoPtr<TextOverflow> textOverflow(new TextOverflow);
299 textOverflow->Init(aBuilder, aBlockFrame);
300 return textOverflow.forget();
301 }
303 void
304 TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
305 const nsRect& aContentArea,
306 const nsRect& aInsideMarkersArea,
307 FrameHashtable* aFramesToHide,
308 AlignmentEdges* aAlignmentEdges,
309 bool* aFoundVisibleTextOrAtomic,
310 InnerClipEdges* aClippedMarkerEdges)
311 {
312 const nsIAtom* frameType = aFrame->GetType();
313 if (frameType == nsGkAtoms::brFrame ||
314 frameType == nsGkAtoms::placeholderFrame) {
315 return;
316 }
317 const bool isAtomic = IsAtomicElement(aFrame, frameType);
318 if (aFrame->StyleVisibility()->IsVisible()) {
319 nsRect childRect = aFrame->GetScrollableOverflowRect() +
320 aFrame->GetOffsetTo(mBlock);
321 bool overflowLeft = childRect.x < aContentArea.x;
322 bool overflowRight = childRect.XMost() > aContentArea.XMost();
323 if (overflowLeft) {
324 mLeft.mHasOverflow = true;
325 }
326 if (overflowRight) {
327 mRight.mHasOverflow = true;
328 }
329 if (isAtomic && ((mLeft.mActive && overflowLeft) ||
330 (mRight.mActive && overflowRight))) {
331 aFramesToHide->PutEntry(aFrame);
332 } else if (isAtomic || frameType == nsGkAtoms::textFrame) {
333 AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
334 aFramesToHide, aAlignmentEdges,
335 aFoundVisibleTextOrAtomic,
336 aClippedMarkerEdges);
337 }
338 }
339 if (isAtomic) {
340 return;
341 }
343 nsIFrame* child = aFrame->GetFirstPrincipalChild();
344 while (child) {
345 ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
346 aFramesToHide, aAlignmentEdges,
347 aFoundVisibleTextOrAtomic,
348 aClippedMarkerEdges);
349 child = child->GetNextSibling();
350 }
351 }
353 void
354 TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
355 const nsIAtom* aFrameType,
356 const nsRect& aInsideMarkersArea,
357 FrameHashtable* aFramesToHide,
358 AlignmentEdges* aAlignmentEdges,
359 bool* aFoundVisibleTextOrAtomic,
360 InnerClipEdges* aClippedMarkerEdges)
361 {
362 nsRect borderRect(aFrame->GetOffsetTo(mBlock), aFrame->GetSize());
363 nscoord leftOverlap =
364 std::max(aInsideMarkersArea.x - borderRect.x, 0);
365 nscoord rightOverlap =
366 std::max(borderRect.XMost() - aInsideMarkersArea.XMost(), 0);
367 bool insideLeftEdge = aInsideMarkersArea.x <= borderRect.XMost();
368 bool insideRightEdge = borderRect.x <= aInsideMarkersArea.XMost();
370 if (leftOverlap > 0) {
371 aClippedMarkerEdges->AccumulateLeft(borderRect);
372 if (!mLeft.mActive) {
373 leftOverlap = 0;
374 }
375 }
376 if (rightOverlap > 0) {
377 aClippedMarkerEdges->AccumulateRight(borderRect);
378 if (!mRight.mActive) {
379 rightOverlap = 0;
380 }
381 }
383 if ((leftOverlap > 0 && insideLeftEdge) ||
384 (rightOverlap > 0 && insideRightEdge)) {
385 if (aFrameType == nsGkAtoms::textFrame) {
386 if (aInsideMarkersArea.x < aInsideMarkersArea.XMost()) {
387 // a clipped text frame and there is some room between the markers
388 nscoord snappedLeft, snappedRight;
389 bool isFullyClipped =
390 IsFullyClipped(static_cast<nsTextFrame*>(aFrame),
391 leftOverlap, rightOverlap, &snappedLeft, &snappedRight);
392 if (!isFullyClipped) {
393 nsRect snappedRect = borderRect;
394 if (leftOverlap > 0) {
395 snappedRect.x += snappedLeft;
396 snappedRect.width -= snappedLeft;
397 }
398 if (rightOverlap > 0) {
399 snappedRect.width -= snappedRight;
400 }
401 aAlignmentEdges->Accumulate(snappedRect);
402 *aFoundVisibleTextOrAtomic = true;
403 }
404 }
405 } else {
406 aFramesToHide->PutEntry(aFrame);
407 }
408 } else if (!insideLeftEdge || !insideRightEdge) {
409 // frame is outside
410 if (IsAtomicElement(aFrame, aFrameType)) {
411 aFramesToHide->PutEntry(aFrame);
412 }
413 } else {
414 // frame is inside
415 aAlignmentEdges->Accumulate(borderRect);
416 *aFoundVisibleTextOrAtomic = true;
417 }
418 }
420 void
421 TextOverflow::ExamineLineFrames(nsLineBox* aLine,
422 FrameHashtable* aFramesToHide,
423 AlignmentEdges* aAlignmentEdges)
424 {
425 // No ellipsing for 'clip' style.
426 bool suppressLeft = mLeft.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
427 bool suppressRight = mRight.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
428 if (mCanHaveHorizontalScrollbar) {
429 nsPoint pos = mScrollableFrame->GetScrollPosition();
430 nsRect scrollRange = mScrollableFrame->GetScrollRange();
431 // No ellipsing when nothing to scroll to on that side (this includes
432 // overflow:auto that doesn't trigger a horizontal scrollbar).
433 if (pos.x <= scrollRange.x) {
434 suppressLeft = true;
435 }
436 if (pos.x >= scrollRange.XMost()) {
437 suppressRight = true;
438 }
439 }
441 nsRect contentArea = mContentArea;
442 const nscoord scrollAdjust = mAdjustForPixelSnapping ?
443 mBlock->PresContext()->AppUnitsPerDevPixel() : 0;
444 InflateLeft(&contentArea, scrollAdjust);
445 InflateRight(&contentArea, scrollAdjust);
446 nsRect lineRect = aLine->GetScrollableOverflowArea();
447 const bool leftOverflow =
448 !suppressLeft && lineRect.x < contentArea.x;
449 const bool rightOverflow =
450 !suppressRight && lineRect.XMost() > contentArea.XMost();
451 if (!leftOverflow && !rightOverflow) {
452 // The line does not overflow on a side we should ellipsize.
453 return;
454 }
456 int pass = 0;
457 bool retryEmptyLine = true;
458 bool guessLeft = leftOverflow;
459 bool guessRight = rightOverflow;
460 mLeft.mActive = leftOverflow;
461 mRight.mActive = rightOverflow;
462 bool clippedLeftMarker = false;
463 bool clippedRightMarker = false;
464 do {
465 // Setup marker strings as needed.
466 if (guessLeft) {
467 mLeft.SetupString(mBlock);
468 }
469 if (guessRight) {
470 mRight.SetupString(mBlock);
471 }
473 // If there is insufficient space for both markers then keep the one on the
474 // end side per the block's 'direction'.
475 nscoord rightMarkerWidth = mRight.mActive ? mRight.mWidth : 0;
476 nscoord leftMarkerWidth = mLeft.mActive ? mLeft.mWidth : 0;
477 if (leftMarkerWidth && rightMarkerWidth &&
478 leftMarkerWidth + rightMarkerWidth > contentArea.width) {
479 if (mBlockIsRTL) {
480 rightMarkerWidth = 0;
481 } else {
482 leftMarkerWidth = 0;
483 }
484 }
486 // Calculate the area between the potential markers aligned at the
487 // block's edge.
488 nsRect insideMarkersArea = mContentArea;
489 if (guessLeft) {
490 InflateLeft(&insideMarkersArea, -leftMarkerWidth);
491 }
492 if (guessRight) {
493 InflateRight(&insideMarkersArea, -rightMarkerWidth);
494 }
496 // Analyze the frames on aLine for the overflow situation at the content
497 // edges and at the edges of the area between the markers.
498 bool foundVisibleTextOrAtomic = false;
499 int32_t n = aLine->GetChildCount();
500 nsIFrame* child = aLine->mFirstChild;
501 InnerClipEdges clippedMarkerEdges;
502 for (; n-- > 0; child = child->GetNextSibling()) {
503 ExamineFrameSubtree(child, contentArea, insideMarkersArea,
504 aFramesToHide, aAlignmentEdges,
505 &foundVisibleTextOrAtomic,
506 &clippedMarkerEdges);
507 }
508 if (!foundVisibleTextOrAtomic && retryEmptyLine) {
509 aAlignmentEdges->mAssigned = false;
510 aFramesToHide->Clear();
511 pass = -1;
512 if (mLeft.IsNeeded() && mLeft.mActive && !clippedLeftMarker) {
513 if (clippedMarkerEdges.mAssignedLeft &&
514 clippedMarkerEdges.mLeft - mContentArea.X() > 0) {
515 mLeft.mWidth = clippedMarkerEdges.mLeft - mContentArea.X();
516 NS_ASSERTION(mLeft.mWidth < mLeft.mIntrinsicWidth,
517 "clipping a marker should make it strictly smaller");
518 clippedLeftMarker = true;
519 } else {
520 mLeft.mActive = guessLeft = false;
521 }
522 continue;
523 }
524 if (mRight.IsNeeded() && mRight.mActive && !clippedRightMarker) {
525 if (clippedMarkerEdges.mAssignedRight &&
526 mContentArea.XMost() - clippedMarkerEdges.mRight > 0) {
527 mRight.mWidth = mContentArea.XMost() - clippedMarkerEdges.mRight;
528 NS_ASSERTION(mRight.mWidth < mRight.mIntrinsicWidth,
529 "clipping a marker should make it strictly smaller");
530 clippedRightMarker = true;
531 } else {
532 mRight.mActive = guessRight = false;
533 }
534 continue;
535 }
536 // The line simply has no visible content even without markers,
537 // so examine the line again without suppressing markers.
538 retryEmptyLine = false;
539 mLeft.mWidth = mLeft.mIntrinsicWidth;
540 mLeft.mActive = guessLeft = leftOverflow;
541 mRight.mWidth = mRight.mIntrinsicWidth;
542 mRight.mActive = guessRight = rightOverflow;
543 continue;
544 }
545 if (guessLeft == (mLeft.mActive && mLeft.IsNeeded()) &&
546 guessRight == (mRight.mActive && mRight.IsNeeded())) {
547 break;
548 } else {
549 guessLeft = mLeft.mActive && mLeft.IsNeeded();
550 guessRight = mRight.mActive && mRight.IsNeeded();
551 mLeft.Reset();
552 mRight.Reset();
553 aFramesToHide->Clear();
554 }
555 NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
556 } while (++pass != 2);
557 if (!leftOverflow || !mLeft.mActive) {
558 mLeft.Reset();
559 }
560 if (!rightOverflow || !mRight.mActive) {
561 mRight.Reset();
562 }
563 }
565 void
566 TextOverflow::ProcessLine(const nsDisplayListSet& aLists,
567 nsLineBox* aLine)
568 {
569 NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
570 mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
571 "TextOverflow with 'clip' for both sides");
572 mLeft.Reset();
573 mLeft.mActive = mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
574 mRight.Reset();
575 mRight.mActive = mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
577 FrameHashtable framesToHide(100);
578 AlignmentEdges alignmentEdges;
579 ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
580 bool needLeft = mLeft.IsNeeded();
581 bool needRight = mRight.IsNeeded();
582 if (!needLeft && !needRight) {
583 return;
584 }
585 NS_ASSERTION(mLeft.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
586 !needLeft, "left marker for 'clip'");
587 NS_ASSERTION(mRight.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
588 !needRight, "right marker for 'clip'");
590 // If there is insufficient space for both markers then keep the one on the
591 // end side per the block's 'direction'.
592 if (needLeft && needRight &&
593 mLeft.mWidth + mRight.mWidth > mContentArea.width) {
594 if (mBlockIsRTL) {
595 needRight = false;
596 } else {
597 needLeft = false;
598 }
599 }
600 nsRect insideMarkersArea = mContentArea;
601 if (needLeft) {
602 InflateLeft(&insideMarkersArea, -mLeft.mWidth);
603 }
604 if (needRight) {
605 InflateRight(&insideMarkersArea, -mRight.mWidth);
606 }
607 if (!mCanHaveHorizontalScrollbar && alignmentEdges.mAssigned) {
608 nsRect alignmentRect = nsRect(alignmentEdges.x, insideMarkersArea.y,
609 alignmentEdges.Width(), 1);
610 insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
611 }
613 // Clip and remove display items as needed at the final marker edges.
614 nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() };
615 for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
616 PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
617 }
618 CreateMarkers(aLine, needLeft, needRight, insideMarkersArea);
619 }
621 void
622 TextOverflow::PruneDisplayListContents(nsDisplayList* aList,
623 const FrameHashtable& aFramesToHide,
624 const nsRect& aInsideMarkersArea)
625 {
626 nsDisplayList saved;
627 nsDisplayItem* item;
628 while ((item = aList->RemoveBottom())) {
629 nsIFrame* itemFrame = item->Frame();
630 if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
631 item->~nsDisplayItem();
632 continue;
633 }
635 nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
636 if (wrapper) {
637 if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
638 PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
639 }
640 }
642 nsCharClipDisplayItem* charClip = itemFrame ?
643 nsCharClipDisplayItem::CheckCast(item) : nullptr;
644 if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
645 nsRect rect = itemFrame->GetScrollableOverflowRect() +
646 itemFrame->GetOffsetTo(mBlock);
647 if (mLeft.IsNeeded() && rect.x < aInsideMarkersArea.x) {
648 nscoord left = aInsideMarkersArea.x - rect.x;
649 if (MOZ_UNLIKELY(left < 0)) {
650 item->~nsDisplayItem();
651 continue;
652 }
653 charClip->mLeftEdge = left;
654 }
655 if (mRight.IsNeeded() && rect.XMost() > aInsideMarkersArea.XMost()) {
656 nscoord right = rect.XMost() - aInsideMarkersArea.XMost();
657 if (MOZ_UNLIKELY(right < 0)) {
658 item->~nsDisplayItem();
659 continue;
660 }
661 charClip->mRightEdge = right;
662 }
663 }
665 saved.AppendToTop(item);
666 }
667 aList->AppendToTop(&saved);
668 }
670 /* static */ bool
671 TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
672 nsIFrame* aBlockFrame)
673 {
674 const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
675 // Nothing to do for text-overflow:clip or if 'overflow-x:visible'
676 // or if we're just building items for event processing.
677 if ((style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
678 style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP) ||
679 IsHorizontalOverflowVisible(aBlockFrame) ||
680 aBuilder->IsForEventDelivery()) {
681 return false;
682 }
684 // Skip ComboboxControlFrame because it would clip the drop-down arrow.
685 // Its anon block inherits 'text-overflow' and does what is expected.
686 if (aBlockFrame->GetType() == nsGkAtoms::comboboxControlFrame) {
687 return false;
688 }
690 // Inhibit the markers if a descendant content owns the caret.
691 nsRefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret();
692 bool visible = false;
693 if (caret && NS_SUCCEEDED(caret->GetCaretVisible(&visible)) && visible) {
694 nsCOMPtr<nsISelection> domSelection = caret->GetCaretDOMSelection();
695 if (domSelection) {
696 nsCOMPtr<nsIDOMNode> node;
697 domSelection->GetFocusNode(getter_AddRefs(node));
698 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
699 if (content && nsContentUtils::ContentIsDescendantOf(content,
700 aBlockFrame->GetContent())) {
701 return false;
702 }
703 }
704 }
705 return true;
706 }
708 void
709 TextOverflow::CreateMarkers(const nsLineBox* aLine,
710 bool aCreateLeft,
711 bool aCreateRight,
712 const nsRect& aInsideMarkersArea)
713 {
714 if (aCreateLeft) {
715 DisplayListClipState::AutoSaveRestore clipState(mBuilder);
717 nsRect markerRect = nsRect(aInsideMarkersArea.x - mLeft.mIntrinsicWidth,
718 aLine->BStart(),
719 mLeft.mIntrinsicWidth, aLine->BSize());
720 markerRect += mBuilder->ToReferenceFrame(mBlock);
721 ClipMarker(mContentArea + mBuilder->ToReferenceFrame(mBlock),
722 markerRect, clipState);
723 nsDisplayItem* marker = new (mBuilder)
724 nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
725 aLine->GetAscent(), mLeft.mStyle, 0);
726 mMarkerList.AppendNewToTop(marker);
727 }
729 if (aCreateRight) {
730 DisplayListClipState::AutoSaveRestore clipState(mBuilder);
732 nsRect markerRect = nsRect(aInsideMarkersArea.XMost(),
733 aLine->BStart(),
734 mRight.mIntrinsicWidth, aLine->BSize());
735 markerRect += mBuilder->ToReferenceFrame(mBlock);
736 ClipMarker(mContentArea + mBuilder->ToReferenceFrame(mBlock),
737 markerRect, clipState);
738 nsDisplayItem* marker = new (mBuilder)
739 nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
740 aLine->GetAscent(), mRight.mStyle, 1);
741 mMarkerList.AppendNewToTop(marker);
742 }
743 }
745 void
746 TextOverflow::Marker::SetupString(nsIFrame* aFrame)
747 {
748 if (mInitialized) {
749 return;
750 }
752 if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
753 gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
754 if (textRun) {
755 mWidth = textRun->GetAdvanceWidth(0, textRun->GetLength(), nullptr);
756 } else {
757 mWidth = 0;
758 }
759 } else {
760 nsRefPtr<nsRenderingContext> rc =
761 aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
762 nsRefPtr<nsFontMetrics> fm;
763 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
764 nsLayoutUtils::FontSizeInflationFor(aFrame));
765 rc->SetFont(fm);
766 mWidth = nsLayoutUtils::GetStringWidth(aFrame, rc, mStyle->mString.get(),
767 mStyle->mString.Length());
768 }
769 mIntrinsicWidth = mWidth;
770 mInitialized = true;
771 }
773 } // namespace css
774 } // namespace mozilla