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

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

mercurial