layout/generic/TextOverflow.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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

mercurial