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