Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "TextOverflow.h"
8 : #include <algorithm>
9 :
10 : // Please maintain alphabetical order below
11 : #include "gfxContext.h"
12 : #include "nsBlockFrame.h"
13 : #include "nsCaret.h"
14 : #include "nsContentUtils.h"
15 : #include "nsCSSAnonBoxes.h"
16 : #include "nsFontMetrics.h"
17 : #include "nsGfxScrollFrame.h"
18 : #include "nsIScrollableFrame.h"
19 : #include "nsLayoutUtils.h"
20 : #include "nsPresContext.h"
21 : #include "nsRect.h"
22 : #include "nsTextFrame.h"
23 : #include "nsIFrameInlines.h"
24 : #include "mozilla/ArrayUtils.h"
25 : #include "mozilla/Likely.h"
26 : #include "nsISelection.h"
27 :
28 : namespace mozilla {
29 : namespace css {
30 :
31 : class LazyReferenceRenderingDrawTargetGetterFromFrame final :
32 : public gfxFontGroup::LazyReferenceDrawTargetGetter {
33 : public:
34 : typedef mozilla::gfx::DrawTarget DrawTarget;
35 :
36 0 : explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame* aFrame)
37 0 : : mFrame(aFrame) {}
38 0 : virtual already_AddRefed<DrawTarget> GetRefDrawTarget() override
39 : {
40 : RefPtr<gfxContext> ctx =
41 0 : mFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
42 0 : RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
43 0 : return dt.forget();
44 : }
45 : private:
46 : nsIFrame* mFrame;
47 : };
48 :
49 : static gfxTextRun*
50 0 : GetEllipsisTextRun(nsIFrame* aFrame)
51 : {
52 : RefPtr<nsFontMetrics> fm =
53 0 : nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
54 0 : LazyReferenceRenderingDrawTargetGetterFromFrame lazyRefDrawTargetGetter(aFrame);
55 0 : return fm->GetThebesFontGroup()->GetEllipsisTextRun(
56 : aFrame->PresContext()->AppUnitsPerDevPixel(),
57 : nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame->StyleContext()),
58 0 : lazyRefDrawTargetGetter);
59 : }
60 :
61 : static nsIFrame*
62 0 : GetSelfOrNearestBlock(nsIFrame* aFrame)
63 : {
64 0 : return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame :
65 0 : nsLayoutUtils::FindNearestBlockAncestor(aFrame);
66 : }
67 :
68 : // Return true if the frame is an atomic inline-level element.
69 : // It's not supposed to be called for block frames since we never
70 : // process block descendants for text-overflow.
71 : static bool
72 0 : IsAtomicElement(nsIFrame* aFrame, LayoutFrameType aFrameType)
73 : {
74 0 : NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame) ||
75 : !aFrame->IsBlockOutside(),
76 : "unexpected block frame");
77 0 : NS_PRECONDITION(aFrameType != LayoutFrameType::Placeholder,
78 : "unexpected placeholder frame");
79 0 : return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
80 : }
81 :
82 : static bool
83 0 : IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
84 : nscoord* aSnappedLeft, nscoord* aSnappedRight)
85 : {
86 0 : *aSnappedLeft = aLeft;
87 0 : *aSnappedRight = aRight;
88 0 : if (aLeft <= 0 && aRight <= 0) {
89 0 : return false;
90 : }
91 0 : return !aFrame->MeasureCharClippedText(aLeft, aRight,
92 0 : aSnappedLeft, aSnappedRight);
93 : }
94 :
95 : static bool
96 0 : IsInlineAxisOverflowVisible(nsIFrame* aFrame)
97 : {
98 0 : NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr,
99 : "expected a block frame");
100 :
101 0 : nsIFrame* f = aFrame;
102 0 : while (f && f->StyleContext()->GetPseudo() && !f->IsScrollFrame()) {
103 0 : f = f->GetParent();
104 : }
105 0 : if (!f) {
106 0 : return true;
107 : }
108 0 : auto overflow = aFrame->GetWritingMode().IsVertical() ?
109 0 : f->StyleDisplay()->mOverflowY : f->StyleDisplay()->mOverflowX;
110 0 : return overflow == NS_STYLE_OVERFLOW_VISIBLE;
111 : }
112 :
113 : static void
114 0 : ClipMarker(const nsRect& aContentArea,
115 : const nsRect& aMarkerRect,
116 : DisplayListClipState::AutoSaveRestore& aClipState)
117 : {
118 0 : nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost();
119 0 : nsRect markerRect = aMarkerRect;
120 0 : if (rightOverflow > 0) {
121 : // Marker overflows on the right side (content width < marker width).
122 0 : markerRect.width -= rightOverflow;
123 0 : aClipState.ClipContentDescendants(markerRect);
124 : } else {
125 0 : nscoord leftOverflow = aContentArea.x - aMarkerRect.x;
126 0 : if (leftOverflow > 0) {
127 : // Marker overflows on the left side
128 0 : markerRect.width -= leftOverflow;
129 0 : markerRect.x += leftOverflow;
130 0 : aClipState.ClipContentDescendants(markerRect);
131 : }
132 : }
133 0 : }
134 :
135 : static void
136 0 : InflateIStart(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
137 : {
138 0 : nscoord iend = aRect->IEnd(aWM);
139 0 : aRect->IStart(aWM) -= aDelta;
140 0 : aRect->ISize(aWM) = std::max(iend - aRect->IStart(aWM), 0);
141 0 : }
142 :
143 : static void
144 0 : InflateIEnd(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
145 : {
146 0 : aRect->ISize(aWM) = std::max(aRect->ISize(aWM) + aDelta, 0);
147 0 : }
148 :
149 : static bool
150 0 : IsFrameDescendantOfAny(nsIFrame* aChild,
151 : const TextOverflow::FrameHashtable& aSetOfFrames,
152 : nsIFrame* aCommonAncestor)
153 : {
154 0 : for (nsIFrame* f = aChild; f && f != aCommonAncestor;
155 : f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
156 0 : if (aSetOfFrames.GetEntry(f)) {
157 0 : return true;
158 : }
159 : }
160 0 : return false;
161 : }
162 :
163 : class nsDisplayTextOverflowMarker : public nsDisplayItem
164 : {
165 : public:
166 0 : nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
167 : const nsRect& aRect, nscoord aAscent,
168 : const nsStyleTextOverflowSide* aStyle,
169 : uint32_t aIndex)
170 0 : : nsDisplayItem(aBuilder, aFrame), mRect(aRect),
171 0 : mStyle(aStyle), mAscent(aAscent), mIndex(aIndex) {
172 0 : MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
173 0 : }
174 : #ifdef NS_BUILD_REFCNT_LOGGING
175 0 : virtual ~nsDisplayTextOverflowMarker() {
176 0 : MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
177 0 : }
178 : #endif
179 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
180 : bool* aSnap) override {
181 0 : *aSnap = false;
182 : nsRect shadowRect =
183 0 : nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
184 0 : return mRect.Union(shadowRect);
185 : }
186 :
187 0 : virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
188 : {
189 0 : if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
190 : // On OS X, web authors can turn off subpixel text rendering using the
191 : // CSS property -moz-osx-font-smoothing. If they do that, we don't need
192 : // to use component alpha layers for the affected text.
193 0 : if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
194 0 : return nsRect();
195 : }
196 : }
197 : bool snap;
198 0 : return GetBounds(aBuilder, &snap);
199 : }
200 :
201 : virtual void Paint(nsDisplayListBuilder* aBuilder,
202 : gfxContext* aCtx) override;
203 :
204 0 : virtual uint32_t GetPerFrameKey() override {
205 0 : return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
206 : }
207 : void PaintTextToContext(gfxContext* aCtx,
208 : nsPoint aOffsetFromRect);
209 0 : NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
210 : private:
211 : nsRect mRect; // in reference frame coordinates
212 : const nsStyleTextOverflowSide* mStyle;
213 : nscoord mAscent; // baseline for the marker text in mRect
214 : uint32_t mIndex;
215 : };
216 :
217 : static void
218 0 : PaintTextShadowCallback(gfxContext* aCtx,
219 : nsPoint aShadowOffset,
220 : const nscolor& aShadowColor,
221 : void* aData)
222 : {
223 : reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
224 0 : PaintTextToContext(aCtx, aShadowOffset);
225 0 : }
226 :
227 : void
228 0 : nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
229 : gfxContext* aCtx)
230 : {
231 : nscolor foregroundColor = nsLayoutUtils::
232 0 : GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
233 :
234 : // Paint the text-shadows for the overflow marker
235 0 : nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
236 : foregroundColor, PaintTextShadowCallback,
237 0 : (void*)this);
238 0 : aCtx->SetColor(gfx::Color::FromABGR(foregroundColor));
239 0 : PaintTextToContext(aCtx, nsPoint(0, 0));
240 0 : }
241 :
242 : void
243 0 : nsDisplayTextOverflowMarker::PaintTextToContext(gfxContext* aCtx,
244 : nsPoint aOffsetFromRect)
245 : {
246 0 : WritingMode wm = mFrame->GetWritingMode();
247 0 : nsPoint pt(mRect.x, mRect.y);
248 0 : if (wm.IsVertical()) {
249 0 : if (wm.IsVerticalLR()) {
250 0 : pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
251 : mFrame, aCtx, pt.x, mAscent));
252 : } else {
253 0 : pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
254 0 : mFrame, aCtx, pt.x + mRect.width, -mAscent));
255 : }
256 : } else {
257 0 : pt.y = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineY(
258 : mFrame, aCtx, pt.y, mAscent));
259 : }
260 0 : pt += aOffsetFromRect;
261 :
262 0 : if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
263 0 : gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
264 0 : if (textRun) {
265 0 : NS_ASSERTION(!textRun->IsRightToLeft(),
266 : "Ellipsis textruns should always be LTR!");
267 0 : gfxPoint gfxPt(pt.x, pt.y);
268 0 : textRun->Draw(gfxTextRun::Range(textRun), gfxPt,
269 0 : gfxTextRun::DrawParams(aCtx));
270 : }
271 : } else {
272 : RefPtr<nsFontMetrics> fm =
273 0 : nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame);
274 0 : nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle->mString.get(),
275 0 : mStyle->mString.Length(), pt);
276 : }
277 0 : }
278 :
279 0 : TextOverflow::TextOverflow(nsDisplayListBuilder* aBuilder,
280 0 : nsIFrame* aBlockFrame)
281 : : mContentArea(aBlockFrame->GetWritingMode(),
282 0 : aBlockFrame->GetContentRectRelativeToSelf(),
283 0 : aBlockFrame->GetSize())
284 : , mBuilder(aBuilder)
285 : , mBlock(aBlockFrame)
286 0 : , mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame))
287 : , mBlockSize(aBlockFrame->GetSize())
288 : , mBlockWM(aBlockFrame->GetWritingMode())
289 0 : , mAdjustForPixelSnapping(false)
290 : {
291 : #ifdef MOZ_XUL
292 0 : if (!mScrollableFrame) {
293 0 : nsIAtom* pseudoType = aBlockFrame->StyleContext()->GetPseudo();
294 0 : if (pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
295 0 : mScrollableFrame =
296 0 : nsLayoutUtils::GetScrollableFrameFor(aBlockFrame->GetParent());
297 : // nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
298 : // for RTL blocks (also for overflow:hidden), so we need to move
299 : // the edges 1px outward in ExamineLineFrames to avoid triggering
300 : // a text-overflow marker in this case.
301 0 : mAdjustForPixelSnapping = !mBlockWM.IsBidiLTR();
302 : }
303 : }
304 : #endif
305 0 : mCanHaveInlineAxisScrollbar = false;
306 0 : if (mScrollableFrame) {
307 0 : auto scrollbarStyle = mBlockWM.IsVertical() ?
308 0 : mScrollableFrame->GetScrollbarStyles().mVertical :
309 0 : mScrollableFrame->GetScrollbarStyles().mHorizontal;
310 0 : mCanHaveInlineAxisScrollbar = scrollbarStyle != NS_STYLE_OVERFLOW_HIDDEN;
311 0 : if (!mAdjustForPixelSnapping) {
312 : // Scrolling to the end position can leave some text still overflowing due
313 : // to pixel snapping behaviour in our scrolling code.
314 0 : mAdjustForPixelSnapping = mCanHaveInlineAxisScrollbar;
315 : }
316 : // Use a null containerSize to convert a vector from logical to physical.
317 0 : const nsSize nullContainerSize;
318 0 : mContentArea.MoveBy(mBlockWM,
319 0 : LogicalPoint(mBlockWM,
320 0 : mScrollableFrame->GetScrollPosition(),
321 0 : nullContainerSize));
322 0 : nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
323 0 : scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
324 : }
325 0 : uint8_t direction = aBlockFrame->StyleVisibility()->mDirection;
326 0 : const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
327 0 : if (mBlockWM.IsBidiLTR()) {
328 0 : mIStart.Init(style->mTextOverflow.GetLeft(direction));
329 0 : mIEnd.Init(style->mTextOverflow.GetRight(direction));
330 : } else {
331 0 : mIStart.Init(style->mTextOverflow.GetRight(direction));
332 0 : mIEnd.Init(style->mTextOverflow.GetLeft(direction));
333 : }
334 : // The left/right marker string is setup in ExamineLineFrames when a line
335 : // has overflow on that side.
336 0 : }
337 :
338 : /* static */ TextOverflow*
339 199 : TextOverflow::WillProcessLines(nsDisplayListBuilder* aBuilder,
340 : nsIFrame* aBlockFrame)
341 : {
342 : // Ignore 'text-overflow' for event and frame visibility processing.
343 597 : if (aBuilder->IsForEventDelivery() ||
344 398 : aBuilder->IsForFrameVisibility() ||
345 199 : !CanHaveTextOverflow(aBlockFrame)) {
346 199 : return nullptr;
347 : }
348 0 : nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
349 0 : if (scrollableFrame && scrollableFrame->IsTransformingByAPZ()) {
350 : // If the APZ is actively scrolling this, don't bother with markers.
351 0 : return nullptr;
352 : }
353 0 : return new TextOverflow(aBuilder, aBlockFrame);
354 : }
355 :
356 : void
357 0 : TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
358 : const LogicalRect& aContentArea,
359 : const LogicalRect& aInsideMarkersArea,
360 : FrameHashtable* aFramesToHide,
361 : AlignmentEdges* aAlignmentEdges,
362 : bool* aFoundVisibleTextOrAtomic,
363 : InnerClipEdges* aClippedMarkerEdges)
364 : {
365 0 : const LayoutFrameType frameType = aFrame->Type();
366 0 : if (frameType == LayoutFrameType::Br ||
367 : frameType == LayoutFrameType::Placeholder) {
368 0 : return;
369 : }
370 0 : const bool isAtomic = IsAtomicElement(aFrame, frameType);
371 0 : if (aFrame->StyleVisibility()->IsVisible()) {
372 : LogicalRect childRect =
373 0 : GetLogicalScrollableOverflowRectRelativeToBlock(aFrame);
374 : bool overflowIStart =
375 0 : childRect.IStart(mBlockWM) < aContentArea.IStart(mBlockWM);
376 : bool overflowIEnd =
377 0 : childRect.IEnd(mBlockWM) > aContentArea.IEnd(mBlockWM);
378 0 : if (overflowIStart) {
379 0 : mIStart.mHasOverflow = true;
380 : }
381 0 : if (overflowIEnd) {
382 0 : mIEnd.mHasOverflow = true;
383 : }
384 0 : if (isAtomic && ((mIStart.mActive && overflowIStart) ||
385 0 : (mIEnd.mActive && overflowIEnd))) {
386 0 : aFramesToHide->PutEntry(aFrame);
387 0 : } else if (isAtomic || frameType == LayoutFrameType::Text) {
388 : AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
389 : aFramesToHide, aAlignmentEdges,
390 : aFoundVisibleTextOrAtomic,
391 0 : aClippedMarkerEdges);
392 : }
393 : }
394 0 : if (isAtomic) {
395 0 : return;
396 : }
397 :
398 0 : for (nsIFrame* child : aFrame->PrincipalChildList()) {
399 : ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
400 : aFramesToHide, aAlignmentEdges,
401 : aFoundVisibleTextOrAtomic,
402 0 : aClippedMarkerEdges);
403 : }
404 : }
405 :
406 : void
407 0 : TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
408 : LayoutFrameType aFrameType,
409 : const LogicalRect& aInsideMarkersArea,
410 : FrameHashtable* aFramesToHide,
411 : AlignmentEdges* aAlignmentEdges,
412 : bool* aFoundVisibleTextOrAtomic,
413 : InnerClipEdges* aClippedMarkerEdges)
414 : {
415 : LogicalRect borderRect(mBlockWM,
416 0 : nsRect(aFrame->GetOffsetTo(mBlock),
417 0 : aFrame->GetSize()),
418 0 : mBlockSize);
419 : nscoord istartOverlap = std::max(
420 0 : aInsideMarkersArea.IStart(mBlockWM) - borderRect.IStart(mBlockWM), 0);
421 : nscoord iendOverlap = std::max(
422 0 : borderRect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM), 0);
423 : bool insideIStartEdge =
424 0 : aInsideMarkersArea.IStart(mBlockWM) <= borderRect.IEnd(mBlockWM);
425 : bool insideIEndEdge =
426 0 : borderRect.IStart(mBlockWM) <= aInsideMarkersArea.IEnd(mBlockWM);
427 :
428 0 : if (istartOverlap > 0) {
429 0 : aClippedMarkerEdges->AccumulateIStart(mBlockWM, borderRect);
430 0 : if (!mIStart.mActive) {
431 0 : istartOverlap = 0;
432 : }
433 : }
434 0 : if (iendOverlap > 0) {
435 0 : aClippedMarkerEdges->AccumulateIEnd(mBlockWM, borderRect);
436 0 : if (!mIEnd.mActive) {
437 0 : iendOverlap = 0;
438 : }
439 : }
440 :
441 0 : if ((istartOverlap > 0 && insideIStartEdge) ||
442 0 : (iendOverlap > 0 && insideIEndEdge)) {
443 0 : if (aFrameType == LayoutFrameType::Text) {
444 0 : if (aInsideMarkersArea.IStart(mBlockWM) <
445 0 : aInsideMarkersArea.IEnd(mBlockWM)) {
446 : // a clipped text frame and there is some room between the markers
447 : nscoord snappedIStart, snappedIEnd;
448 0 : auto textFrame = static_cast<nsTextFrame*>(aFrame);
449 0 : bool isFullyClipped = mBlockWM.IsBidiLTR() ?
450 : IsFullyClipped(textFrame, istartOverlap, iendOverlap,
451 : &snappedIStart, &snappedIEnd) :
452 : IsFullyClipped(textFrame, iendOverlap, istartOverlap,
453 0 : &snappedIEnd, &snappedIStart);
454 0 : if (!isFullyClipped) {
455 0 : LogicalRect snappedRect = borderRect;
456 0 : if (istartOverlap > 0) {
457 0 : snappedRect.IStart(mBlockWM) += snappedIStart;
458 0 : snappedRect.ISize(mBlockWM) -= snappedIStart;
459 : }
460 0 : if (iendOverlap > 0) {
461 0 : snappedRect.ISize(mBlockWM) -= snappedIEnd;
462 : }
463 0 : aAlignmentEdges->Accumulate(mBlockWM, snappedRect);
464 0 : *aFoundVisibleTextOrAtomic = true;
465 : }
466 : }
467 : } else {
468 0 : aFramesToHide->PutEntry(aFrame);
469 0 : }
470 0 : } else if (!insideIStartEdge || !insideIEndEdge) {
471 : // frame is outside
472 0 : if (IsAtomicElement(aFrame, aFrameType)) {
473 0 : aFramesToHide->PutEntry(aFrame);
474 : }
475 : } else {
476 : // frame is inside
477 0 : aAlignmentEdges->Accumulate(mBlockWM, borderRect);
478 0 : *aFoundVisibleTextOrAtomic = true;
479 : }
480 0 : }
481 :
482 : LogicalRect
483 0 : TextOverflow::ExamineLineFrames(nsLineBox* aLine,
484 : FrameHashtable* aFramesToHide,
485 : AlignmentEdges* aAlignmentEdges)
486 : {
487 : // No ellipsing for 'clip' style.
488 0 : bool suppressIStart = mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
489 0 : bool suppressIEnd = mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
490 0 : if (mCanHaveInlineAxisScrollbar) {
491 0 : LogicalPoint pos(mBlockWM, mScrollableFrame->GetScrollPosition(),
492 0 : mBlockSize);
493 0 : LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
494 0 : mBlockSize);
495 : // No ellipsing when nothing to scroll to on that side (this includes
496 : // overflow:auto that doesn't trigger a horizontal scrollbar).
497 0 : if (pos.I(mBlockWM) <= scrollRange.IStart(mBlockWM)) {
498 0 : suppressIStart = true;
499 : }
500 0 : if (pos.I(mBlockWM) >= scrollRange.IEnd(mBlockWM)) {
501 0 : suppressIEnd = true;
502 : }
503 : }
504 :
505 0 : LogicalRect contentArea = mContentArea;
506 0 : bool snapStart = true, snapEnd = true;
507 : nscoord startEdge, endEdge;
508 0 : if (aLine->GetFloatEdges(&startEdge, &endEdge)) {
509 : // Narrow the |contentArea| to account for any floats on this line, and
510 : // don't bother with the snapping quirk on whichever side(s) we narrow.
511 0 : nscoord delta = endEdge - contentArea.IEnd(mBlockWM);
512 0 : if (delta < 0) {
513 0 : nscoord newSize = contentArea.ISize(mBlockWM) + delta;
514 0 : contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
515 0 : snapEnd = false;
516 : }
517 0 : delta = startEdge - contentArea.IStart(mBlockWM);
518 0 : if (delta > 0) {
519 0 : contentArea.IStart(mBlockWM) = startEdge;
520 0 : nscoord newSize = contentArea.ISize(mBlockWM) - delta;
521 0 : contentArea.ISize(mBlockWM) = std::max(nscoord(0), newSize);
522 0 : snapStart = false;
523 : }
524 : }
525 : // Save the non-snapped area since that's what we want to use when placing
526 : // the markers (our return value). The snapped area is only for analysis.
527 0 : LogicalRect nonSnappedContentArea = contentArea;
528 0 : if (mAdjustForPixelSnapping) {
529 0 : const nscoord scrollAdjust = mBlock->PresContext()->AppUnitsPerDevPixel();
530 0 : if (snapStart) {
531 0 : InflateIStart(mBlockWM, &contentArea, scrollAdjust);
532 : }
533 0 : if (snapEnd) {
534 0 : InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
535 : }
536 : }
537 :
538 0 : LogicalRect lineRect(mBlockWM, aLine->GetScrollableOverflowArea(),
539 0 : mBlockSize);
540 : const bool istartOverflow =
541 0 : !suppressIStart && lineRect.IStart(mBlockWM) < contentArea.IStart(mBlockWM);
542 : const bool iendOverflow =
543 0 : !suppressIEnd && lineRect.IEnd(mBlockWM) > contentArea.IEnd(mBlockWM);
544 0 : if (!istartOverflow && !iendOverflow) {
545 : // The line does not overflow on a side we should ellipsize.
546 0 : return nonSnappedContentArea;
547 : }
548 :
549 0 : int pass = 0;
550 0 : bool retryEmptyLine = true;
551 0 : bool guessIStart = istartOverflow;
552 0 : bool guessIEnd = iendOverflow;
553 0 : mIStart.mActive = istartOverflow;
554 0 : mIEnd.mActive = iendOverflow;
555 0 : bool clippedIStartMarker = false;
556 0 : bool clippedIEndMarker = false;
557 0 : do {
558 : // Setup marker strings as needed.
559 0 : if (guessIStart) {
560 0 : mIStart.SetupString(mBlock);
561 : }
562 0 : if (guessIEnd) {
563 0 : mIEnd.SetupString(mBlock);
564 : }
565 :
566 : // If there is insufficient space for both markers then keep the one on the
567 : // end side per the block's 'direction'.
568 0 : nscoord istartMarkerISize = mIStart.mActive ? mIStart.mISize : 0;
569 0 : nscoord iendMarkerISize = mIEnd.mActive ? mIEnd.mISize : 0;
570 0 : if (istartMarkerISize && iendMarkerISize &&
571 0 : istartMarkerISize + iendMarkerISize > contentArea.ISize(mBlockWM)) {
572 0 : istartMarkerISize = 0;
573 : }
574 :
575 : // Calculate the area between the potential markers aligned at the
576 : // block's edge.
577 0 : LogicalRect insideMarkersArea = nonSnappedContentArea;
578 0 : if (guessIStart) {
579 0 : InflateIStart(mBlockWM, &insideMarkersArea, -istartMarkerISize);
580 : }
581 0 : if (guessIEnd) {
582 0 : InflateIEnd(mBlockWM, &insideMarkersArea, -iendMarkerISize);
583 : }
584 :
585 : // Analyze the frames on aLine for the overflow situation at the content
586 : // edges and at the edges of the area between the markers.
587 0 : bool foundVisibleTextOrAtomic = false;
588 0 : int32_t n = aLine->GetChildCount();
589 0 : nsIFrame* child = aLine->mFirstChild;
590 0 : InnerClipEdges clippedMarkerEdges;
591 0 : for (; n-- > 0; child = child->GetNextSibling()) {
592 : ExamineFrameSubtree(child, contentArea, insideMarkersArea,
593 : aFramesToHide, aAlignmentEdges,
594 : &foundVisibleTextOrAtomic,
595 0 : &clippedMarkerEdges);
596 : }
597 0 : if (!foundVisibleTextOrAtomic && retryEmptyLine) {
598 0 : aAlignmentEdges->mAssigned = false;
599 0 : aFramesToHide->Clear();
600 0 : pass = -1;
601 0 : if (mIStart.IsNeeded() && mIStart.mActive && !clippedIStartMarker) {
602 0 : if (clippedMarkerEdges.mAssignedIStart &&
603 0 : clippedMarkerEdges.mIStart > nonSnappedContentArea.IStart(mBlockWM)) {
604 0 : mIStart.mISize = clippedMarkerEdges.mIStart -
605 0 : nonSnappedContentArea.IStart(mBlockWM);
606 0 : NS_ASSERTION(mIStart.mISize < mIStart.mIntrinsicISize,
607 : "clipping a marker should make it strictly smaller");
608 0 : clippedIStartMarker = true;
609 : } else {
610 0 : mIStart.mActive = guessIStart = false;
611 : }
612 0 : continue;
613 : }
614 0 : if (mIEnd.IsNeeded() && mIEnd.mActive && !clippedIEndMarker) {
615 0 : if (clippedMarkerEdges.mAssignedIEnd &&
616 0 : nonSnappedContentArea.IEnd(mBlockWM) > clippedMarkerEdges.mIEnd) {
617 0 : mIEnd.mISize = nonSnappedContentArea.IEnd(mBlockWM) -
618 0 : clippedMarkerEdges.mIEnd;
619 0 : NS_ASSERTION(mIEnd.mISize < mIEnd.mIntrinsicISize,
620 : "clipping a marker should make it strictly smaller");
621 0 : clippedIEndMarker = true;
622 : } else {
623 0 : mIEnd.mActive = guessIEnd = false;
624 : }
625 0 : continue;
626 : }
627 : // The line simply has no visible content even without markers,
628 : // so examine the line again without suppressing markers.
629 0 : retryEmptyLine = false;
630 0 : mIStart.mISize = mIStart.mIntrinsicISize;
631 0 : mIStart.mActive = guessIStart = istartOverflow;
632 0 : mIEnd.mISize = mIEnd.mIntrinsicISize;
633 0 : mIEnd.mActive = guessIEnd = iendOverflow;
634 0 : continue;
635 : }
636 0 : if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
637 0 : guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
638 0 : break;
639 : } else {
640 0 : guessIStart = mIStart.mActive && mIStart.IsNeeded();
641 0 : guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
642 0 : mIStart.Reset();
643 0 : mIEnd.Reset();
644 0 : aFramesToHide->Clear();
645 : }
646 0 : NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
647 : } while (++pass != 2);
648 0 : if (!istartOverflow || !mIStart.mActive) {
649 0 : mIStart.Reset();
650 : }
651 0 : if (!iendOverflow || !mIEnd.mActive) {
652 0 : mIEnd.Reset();
653 : }
654 0 : return nonSnappedContentArea;
655 : }
656 :
657 : void
658 0 : TextOverflow::ProcessLine(const nsDisplayListSet& aLists,
659 : nsLineBox* aLine)
660 : {
661 0 : NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
662 : mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
663 : "TextOverflow with 'clip' for both sides");
664 0 : mIStart.Reset();
665 0 : mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
666 0 : mIEnd.Reset();
667 0 : mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
668 :
669 0 : FrameHashtable framesToHide(64);
670 0 : AlignmentEdges alignmentEdges;
671 : const LogicalRect contentArea =
672 0 : ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
673 0 : bool needIStart = mIStart.IsNeeded();
674 0 : bool needIEnd = mIEnd.IsNeeded();
675 0 : if (!needIStart && !needIEnd) {
676 0 : return;
677 : }
678 0 : NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
679 : !needIStart, "left marker for 'clip'");
680 0 : NS_ASSERTION(mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
681 : !needIEnd, "right marker for 'clip'");
682 :
683 : // If there is insufficient space for both markers then keep the one on the
684 : // end side per the block's 'direction'.
685 0 : if (needIStart && needIEnd &&
686 0 : mIStart.mISize + mIEnd.mISize > contentArea.ISize(mBlockWM)) {
687 0 : needIStart = false;
688 : }
689 0 : LogicalRect insideMarkersArea = contentArea;
690 0 : if (needIStart) {
691 0 : InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
692 : }
693 0 : if (needIEnd) {
694 0 : InflateIEnd(mBlockWM, &insideMarkersArea, -mIEnd.mISize);
695 : }
696 0 : if (!mCanHaveInlineAxisScrollbar && alignmentEdges.mAssigned) {
697 : LogicalRect alignmentRect(mBlockWM, alignmentEdges.mIStart,
698 0 : insideMarkersArea.BStart(mBlockWM),
699 0 : alignmentEdges.ISize(), 1);
700 0 : insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
701 : }
702 :
703 : // Clip and remove display items as needed at the final marker edges.
704 0 : nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() };
705 0 : for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
706 0 : PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
707 : }
708 0 : CreateMarkers(aLine, needIStart, needIEnd, insideMarkersArea, contentArea);
709 : }
710 :
711 : void
712 0 : TextOverflow::PruneDisplayListContents(nsDisplayList* aList,
713 : const FrameHashtable& aFramesToHide,
714 : const LogicalRect& aInsideMarkersArea)
715 : {
716 0 : nsDisplayList saved;
717 : nsDisplayItem* item;
718 0 : while ((item = aList->RemoveBottom())) {
719 0 : nsIFrame* itemFrame = item->Frame();
720 0 : if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
721 0 : item->~nsDisplayItem();
722 0 : continue;
723 : }
724 :
725 0 : nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
726 0 : if (wrapper) {
727 0 : if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
728 0 : PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
729 : }
730 : }
731 :
732 0 : nsCharClipDisplayItem* charClip = itemFrame ?
733 0 : nsCharClipDisplayItem::CheckCast(item) : nullptr;
734 0 : if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
735 : LogicalRect rect =
736 0 : GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
737 0 : if (mIStart.IsNeeded()) {
738 : nscoord istart =
739 0 : aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
740 0 : if (istart > 0) {
741 0 : (mBlockWM.IsBidiLTR() ?
742 0 : charClip->mVisIStartEdge : charClip->mVisIEndEdge) = istart;
743 : }
744 : }
745 0 : if (mIEnd.IsNeeded()) {
746 0 : nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
747 0 : if (iend > 0) {
748 0 : (mBlockWM.IsBidiLTR() ?
749 0 : charClip->mVisIEndEdge : charClip->mVisIStartEdge) = iend;
750 : }
751 : }
752 : }
753 :
754 0 : saved.AppendToTop(item);
755 : }
756 0 : aList->AppendToTop(&saved);
757 0 : }
758 :
759 : /* static */ bool
760 361 : TextOverflow::HasClippedOverflow(nsIFrame* aBlockFrame)
761 : {
762 361 : const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
763 722 : return style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
764 722 : style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
765 : }
766 :
767 : /* static */ bool
768 361 : TextOverflow::CanHaveTextOverflow(nsIFrame* aBlockFrame)
769 : {
770 : // Nothing to do for text-overflow:clip or if 'overflow-x/y:visible'.
771 361 : if (HasClippedOverflow(aBlockFrame) ||
772 0 : IsInlineAxisOverflowVisible(aBlockFrame)) {
773 361 : return false;
774 : }
775 :
776 : // Skip ComboboxControlFrame because it would clip the drop-down arrow.
777 : // Its anon block inherits 'text-overflow' and does what is expected.
778 0 : if (aBlockFrame->IsComboboxControlFrame()) {
779 0 : return false;
780 : }
781 :
782 : // Inhibit the markers if a descendant content owns the caret.
783 0 : RefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret();
784 0 : if (caret && caret->IsVisible()) {
785 0 : nsCOMPtr<nsISelection> domSelection = caret->GetSelection();
786 0 : if (domSelection) {
787 0 : nsCOMPtr<nsIDOMNode> node;
788 0 : domSelection->GetFocusNode(getter_AddRefs(node));
789 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(node);
790 0 : if (content && nsContentUtils::ContentIsDescendantOf(content,
791 0 : aBlockFrame->GetContent())) {
792 0 : return false;
793 : }
794 : }
795 : }
796 0 : return true;
797 : }
798 :
799 : void
800 0 : TextOverflow::CreateMarkers(const nsLineBox* aLine,
801 : bool aCreateIStart, bool aCreateIEnd,
802 : const LogicalRect& aInsideMarkersArea,
803 : const LogicalRect& aContentArea)
804 : {
805 0 : if (aCreateIStart) {
806 0 : DisplayListClipState::AutoSaveRestore clipState(mBuilder);
807 :
808 : LogicalRect markerLogicalRect(
809 0 : mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
810 0 : aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
811 0 : nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
812 : nsRect markerRect =
813 0 : markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
814 0 : ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
815 0 : markerRect, clipState);
816 : nsDisplayItem* marker = new (mBuilder)
817 : nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
818 0 : aLine->GetLogicalAscent(), mIStart.mStyle, 0);
819 0 : mMarkerList.AppendNewToTop(marker);
820 : }
821 :
822 0 : if (aCreateIEnd) {
823 0 : DisplayListClipState::AutoSaveRestore clipState(mBuilder);
824 :
825 : LogicalRect markerLogicalRect(
826 : mBlockWM, aInsideMarkersArea.IEnd(mBlockWM), aLine->BStart(),
827 0 : mIEnd.mIntrinsicISize, aLine->BSize());
828 0 : nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
829 : nsRect markerRect =
830 0 : markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
831 0 : ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
832 0 : markerRect, clipState);
833 : nsDisplayItem* marker = new (mBuilder)
834 : nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
835 0 : aLine->GetLogicalAscent(), mIEnd.mStyle, 1);
836 0 : mMarkerList.AppendNewToTop(marker);
837 : }
838 0 : }
839 :
840 : void
841 0 : TextOverflow::Marker::SetupString(nsIFrame* aFrame)
842 : {
843 0 : if (mInitialized) {
844 0 : return;
845 : }
846 :
847 0 : if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
848 0 : gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
849 0 : if (textRun) {
850 0 : mISize = textRun->GetAdvanceWidth();
851 : } else {
852 0 : mISize = 0;
853 : }
854 : } else {
855 : RefPtr<gfxContext> rc =
856 0 : aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
857 : RefPtr<nsFontMetrics> fm =
858 0 : nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
859 0 : mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
860 : *fm, *rc);
861 : }
862 0 : mIntrinsicISize = mISize;
863 0 : mInitialized = true;
864 : }
865 :
866 : } // namespace css
867 : } // namespace mozilla
|