|
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/. */ |
|
6 |
|
7 #include "TextOverflow.h" |
|
8 #include <algorithm> |
|
9 |
|
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" |
|
25 |
|
26 namespace mozilla { |
|
27 namespace css { |
|
28 |
|
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 }; |
|
44 |
|
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 } |
|
55 |
|
56 static nsIFrame* |
|
57 GetSelfOrNearestBlock(nsIFrame* aFrame) |
|
58 { |
|
59 return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame : |
|
60 nsLayoutUtils::FindNearestBlockAncestor(aFrame); |
|
61 } |
|
62 |
|
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 } |
|
76 |
|
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 } |
|
89 |
|
90 static bool |
|
91 IsHorizontalOverflowVisible(nsIFrame* aFrame) |
|
92 { |
|
93 NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr, |
|
94 "expected a block frame"); |
|
95 |
|
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 } |
|
103 |
|
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 } |
|
125 |
|
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 } |
|
133 |
|
134 static void |
|
135 InflateRight(nsRect* aRect, nscoord aDelta) |
|
136 { |
|
137 aRect->width = std::max(aRect->width + aDelta, 0); |
|
138 } |
|
139 |
|
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 } |
|
153 |
|
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; |
|
179 |
|
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 }; |
|
192 |
|
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 } |
|
202 |
|
203 void |
|
204 nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder, |
|
205 nsRenderingContext* aCtx) |
|
206 { |
|
207 nscolor foregroundColor = |
|
208 nsLayoutUtils::GetColor(mFrame, eCSSProperty_color); |
|
209 |
|
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 } |
|
217 |
|
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; |
|
226 |
|
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 } |
|
245 |
|
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 } |
|
290 |
|
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 } |
|
302 |
|
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 } |
|
342 |
|
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 } |
|
352 |
|
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(); |
|
369 |
|
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 } |
|
382 |
|
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 } |
|
419 |
|
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 } |
|
440 |
|
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 } |
|
455 |
|
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 } |
|
472 |
|
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 } |
|
485 |
|
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 } |
|
495 |
|
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 } |
|
564 |
|
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; |
|
576 |
|
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'"); |
|
589 |
|
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 } |
|
612 |
|
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 } |
|
620 |
|
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 } |
|
634 |
|
635 nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren(); |
|
636 if (wrapper) { |
|
637 if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) { |
|
638 PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea); |
|
639 } |
|
640 } |
|
641 |
|
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 } |
|
664 |
|
665 saved.AppendToTop(item); |
|
666 } |
|
667 aList->AppendToTop(&saved); |
|
668 } |
|
669 |
|
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 } |
|
683 |
|
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 } |
|
689 |
|
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 } |
|
707 |
|
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); |
|
716 |
|
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 } |
|
728 |
|
729 if (aCreateRight) { |
|
730 DisplayListClipState::AutoSaveRestore clipState(mBuilder); |
|
731 |
|
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 } |
|
744 |
|
745 void |
|
746 TextOverflow::Marker::SetupString(nsIFrame* aFrame) |
|
747 { |
|
748 if (mInitialized) { |
|
749 return; |
|
750 } |
|
751 |
|
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 } |
|
772 |
|
773 } // namespace css |
|
774 } // namespace mozilla |