mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-12 22:28:59 +02:00
Although this makes some places more complicated, code should generally be simpler and clearer. It could slightly improve performance on x64 for functions with more than four parameters, since it makes more data be passed via registers rather than the stack. MozReview-Commit-ID: D0GM2Jyrr6W --HG-- extra : source : bd88a2e478e23edf1845f724a32fef908c8cc007
824 lines
30 KiB
C++
824 lines
30 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "TextOverflow.h"
|
|
#include <algorithm>
|
|
|
|
// Please maintain alphabetical order below
|
|
#include "nsBlockFrame.h"
|
|
#include "nsCaret.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCSSAnonBoxes.h"
|
|
#include "nsFontMetrics.h"
|
|
#include "nsGfxScrollFrame.h"
|
|
#include "nsIScrollableFrame.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsRect.h"
|
|
#include "nsRenderingContext.h"
|
|
#include "nsTextFrame.h"
|
|
#include "nsIFrameInlines.h"
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "nsISelection.h"
|
|
|
|
namespace mozilla {
|
|
namespace css {
|
|
|
|
class LazyReferenceRenderingDrawTargetGetterFromFrame final :
|
|
public gfxFontGroup::LazyReferenceDrawTargetGetter {
|
|
public:
|
|
typedef mozilla::gfx::DrawTarget DrawTarget;
|
|
|
|
explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame* aFrame)
|
|
: mFrame(aFrame) {}
|
|
virtual already_AddRefed<DrawTarget> GetRefDrawTarget() override
|
|
{
|
|
RefPtr<gfxContext> ctx =
|
|
mFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
|
|
RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
|
|
return dt.forget();
|
|
}
|
|
private:
|
|
nsIFrame* mFrame;
|
|
};
|
|
|
|
static gfxTextRun*
|
|
GetEllipsisTextRun(nsIFrame* aFrame)
|
|
{
|
|
RefPtr<nsFontMetrics> fm;
|
|
nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
|
|
nsLayoutUtils::FontSizeInflationFor(aFrame));
|
|
LazyReferenceRenderingDrawTargetGetterFromFrame lazyRefDrawTargetGetter(aFrame);
|
|
return fm->GetThebesFontGroup()->GetEllipsisTextRun(
|
|
aFrame->PresContext()->AppUnitsPerDevPixel(),
|
|
nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame->StyleContext()),
|
|
lazyRefDrawTargetGetter);
|
|
}
|
|
|
|
static nsIFrame*
|
|
GetSelfOrNearestBlock(nsIFrame* aFrame)
|
|
{
|
|
return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame :
|
|
nsLayoutUtils::FindNearestBlockAncestor(aFrame);
|
|
}
|
|
|
|
// Return true if the frame is an atomic inline-level element.
|
|
// It's not supposed to be called for block frames since we never
|
|
// process block descendants for text-overflow.
|
|
static bool
|
|
IsAtomicElement(nsIFrame* aFrame, const nsIAtom* aFrameType)
|
|
{
|
|
NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame) ||
|
|
!aFrame->IsBlockOutside(),
|
|
"unexpected block frame");
|
|
NS_PRECONDITION(aFrameType != nsGkAtoms::placeholderFrame,
|
|
"unexpected placeholder frame");
|
|
return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
|
|
}
|
|
|
|
static bool
|
|
IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
|
|
nscoord* aSnappedLeft, nscoord* aSnappedRight)
|
|
{
|
|
*aSnappedLeft = aLeft;
|
|
*aSnappedRight = aRight;
|
|
if (aLeft <= 0 && aRight <= 0) {
|
|
return false;
|
|
}
|
|
return !aFrame->MeasureCharClippedText(aLeft, aRight,
|
|
aSnappedLeft, aSnappedRight);
|
|
}
|
|
|
|
static bool
|
|
IsInlineAxisOverflowVisible(nsIFrame* aFrame)
|
|
{
|
|
NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr,
|
|
"expected a block frame");
|
|
|
|
nsIFrame* f = aFrame;
|
|
while (f && f->StyleContext()->GetPseudo() &&
|
|
f->GetType() != nsGkAtoms::scrollFrame) {
|
|
f = f->GetParent();
|
|
}
|
|
if (!f) {
|
|
return true;
|
|
}
|
|
auto overflow = aFrame->GetWritingMode().IsVertical() ?
|
|
f->StyleDisplay()->mOverflowY : f->StyleDisplay()->mOverflowX;
|
|
return overflow == NS_STYLE_OVERFLOW_VISIBLE;
|
|
}
|
|
|
|
static void
|
|
ClipMarker(const nsRect& aContentArea,
|
|
const nsRect& aMarkerRect,
|
|
DisplayListClipState::AutoSaveRestore& aClipState)
|
|
{
|
|
nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost();
|
|
nsRect markerRect = aMarkerRect;
|
|
if (rightOverflow > 0) {
|
|
// Marker overflows on the right side (content width < marker width).
|
|
markerRect.width -= rightOverflow;
|
|
aClipState.ClipContentDescendants(markerRect);
|
|
} else {
|
|
nscoord leftOverflow = aContentArea.x - aMarkerRect.x;
|
|
if (leftOverflow > 0) {
|
|
// Marker overflows on the left side
|
|
markerRect.width -= leftOverflow;
|
|
markerRect.x += leftOverflow;
|
|
aClipState.ClipContentDescendants(markerRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
InflateIStart(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
|
|
{
|
|
nscoord iend = aRect->IEnd(aWM);
|
|
aRect->IStart(aWM) -= aDelta;
|
|
aRect->ISize(aWM) = std::max(iend - aRect->IStart(aWM), 0);
|
|
}
|
|
|
|
static void
|
|
InflateIEnd(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
|
|
{
|
|
aRect->ISize(aWM) = std::max(aRect->ISize(aWM) + aDelta, 0);
|
|
}
|
|
|
|
static bool
|
|
IsFrameDescendantOfAny(nsIFrame* aChild,
|
|
const TextOverflow::FrameHashtable& aSetOfFrames,
|
|
nsIFrame* aCommonAncestor)
|
|
{
|
|
for (nsIFrame* f = aChild; f && f != aCommonAncestor;
|
|
f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
|
|
if (aSetOfFrames.GetEntry(f)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class nsDisplayTextOverflowMarker : public nsDisplayItem
|
|
{
|
|
public:
|
|
nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
|
const nsRect& aRect, nscoord aAscent,
|
|
const nsStyleTextOverflowSide* aStyle,
|
|
uint32_t aIndex)
|
|
: nsDisplayItem(aBuilder, aFrame), mRect(aRect),
|
|
mStyle(aStyle), mAscent(aAscent), mIndex(aIndex) {
|
|
MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
|
|
}
|
|
#ifdef NS_BUILD_REFCNT_LOGGING
|
|
virtual ~nsDisplayTextOverflowMarker() {
|
|
MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
|
|
}
|
|
#endif
|
|
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
|
|
bool* aSnap) override {
|
|
*aSnap = false;
|
|
nsRect shadowRect =
|
|
nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
|
|
return mRect.Union(shadowRect);
|
|
}
|
|
virtual void Paint(nsDisplayListBuilder* aBuilder,
|
|
nsRenderingContext* aCtx) override;
|
|
|
|
virtual uint32_t GetPerFrameKey() override {
|
|
return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
|
|
}
|
|
void PaintTextToContext(nsRenderingContext* aCtx,
|
|
nsPoint aOffsetFromRect);
|
|
NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
|
|
private:
|
|
nsRect mRect; // in reference frame coordinates
|
|
const nsStyleTextOverflowSide* mStyle;
|
|
nscoord mAscent; // baseline for the marker text in mRect
|
|
uint32_t mIndex;
|
|
};
|
|
|
|
static void
|
|
PaintTextShadowCallback(nsRenderingContext* aCtx,
|
|
nsPoint aShadowOffset,
|
|
const nscolor& aShadowColor,
|
|
void* aData)
|
|
{
|
|
reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
|
|
PaintTextToContext(aCtx, aShadowOffset);
|
|
}
|
|
|
|
void
|
|
nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
|
|
nsRenderingContext* aCtx)
|
|
{
|
|
nscolor foregroundColor =
|
|
nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
|
|
|
|
// Paint the text-shadows for the overflow marker
|
|
nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
|
|
foregroundColor, PaintTextShadowCallback,
|
|
(void*)this);
|
|
aCtx->ThebesContext()->SetColor(gfx::Color::FromABGR(foregroundColor));
|
|
PaintTextToContext(aCtx, nsPoint(0, 0));
|
|
}
|
|
|
|
void
|
|
nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx,
|
|
nsPoint aOffsetFromRect)
|
|
{
|
|
WritingMode wm = mFrame->GetWritingMode();
|
|
nsPoint pt(mRect.x, mRect.y);
|
|
if (wm.IsVertical()) {
|
|
if (wm.IsVerticalLR()) {
|
|
pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
|
|
mFrame, aCtx->ThebesContext(), pt.x, mAscent));
|
|
} else {
|
|
pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
|
|
mFrame, aCtx->ThebesContext(), pt.x + mRect.width, -mAscent));
|
|
}
|
|
} else {
|
|
pt.y = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineY(
|
|
mFrame, aCtx->ThebesContext(), pt.y, mAscent));
|
|
}
|
|
pt += aOffsetFromRect;
|
|
|
|
if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
|
|
gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
|
|
if (textRun) {
|
|
NS_ASSERTION(!textRun->IsRightToLeft(),
|
|
"Ellipsis textruns should always be LTR!");
|
|
gfxPoint gfxPt(pt.x, pt.y);
|
|
textRun->Draw(aCtx->ThebesContext(), gfxPt, DrawMode::GLYPH_FILL,
|
|
gfxTextRun::Range(textRun), nullptr, nullptr, nullptr);
|
|
}
|
|
} else {
|
|
RefPtr<nsFontMetrics> fm;
|
|
nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm),
|
|
nsLayoutUtils::FontSizeInflationFor(mFrame));
|
|
nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle->mString.get(),
|
|
mStyle->mString.Length(), pt);
|
|
}
|
|
}
|
|
|
|
TextOverflow::TextOverflow(nsDisplayListBuilder* aBuilder,
|
|
nsIFrame* aBlockFrame)
|
|
: mContentArea(aBlockFrame->GetWritingMode(),
|
|
aBlockFrame->GetContentRectRelativeToSelf(),
|
|
aBlockFrame->GetSize())
|
|
, mBuilder(aBuilder)
|
|
, mBlock(aBlockFrame)
|
|
, mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame))
|
|
, mBlockSize(aBlockFrame->GetSize())
|
|
, mBlockWM(aBlockFrame->GetWritingMode())
|
|
, mAdjustForPixelSnapping(false)
|
|
{
|
|
#ifdef MOZ_XUL
|
|
if (!mScrollableFrame) {
|
|
nsIAtom* pseudoType = aBlockFrame->StyleContext()->GetPseudo();
|
|
if (pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
|
|
mScrollableFrame =
|
|
nsLayoutUtils::GetScrollableFrameFor(aBlockFrame->GetParent());
|
|
// nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
|
|
// for RTL blocks (also for overflow:hidden), so we need to move
|
|
// the edges 1px outward in ExamineLineFrames to avoid triggering
|
|
// a text-overflow marker in this case.
|
|
mAdjustForPixelSnapping = !mBlockWM.IsBidiLTR();
|
|
}
|
|
}
|
|
#endif
|
|
mCanHaveInlineAxisScrollbar = false;
|
|
if (mScrollableFrame) {
|
|
auto scrollbarStyle = mBlockWM.IsVertical() ?
|
|
mScrollableFrame->GetScrollbarStyles().mVertical :
|
|
mScrollableFrame->GetScrollbarStyles().mHorizontal;
|
|
mCanHaveInlineAxisScrollbar = scrollbarStyle != NS_STYLE_OVERFLOW_HIDDEN;
|
|
if (!mAdjustForPixelSnapping) {
|
|
// Scrolling to the end position can leave some text still overflowing due
|
|
// to pixel snapping behaviour in our scrolling code.
|
|
mAdjustForPixelSnapping = mCanHaveInlineAxisScrollbar;
|
|
}
|
|
// Use a null containerSize to convert a vector from logical to physical.
|
|
const nsSize nullContainerSize;
|
|
mContentArea.MoveBy(mBlockWM,
|
|
LogicalPoint(mBlockWM,
|
|
mScrollableFrame->GetScrollPosition(),
|
|
nullContainerSize));
|
|
nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
|
|
scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
|
|
}
|
|
uint8_t direction = aBlockFrame->StyleVisibility()->mDirection;
|
|
const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
|
|
if (mBlockWM.IsBidiLTR()) {
|
|
mIStart.Init(style->mTextOverflow.GetLeft(direction));
|
|
mIEnd.Init(style->mTextOverflow.GetRight(direction));
|
|
} else {
|
|
mIStart.Init(style->mTextOverflow.GetRight(direction));
|
|
mIEnd.Init(style->mTextOverflow.GetLeft(direction));
|
|
}
|
|
// The left/right marker string is setup in ExamineLineFrames when a line
|
|
// has overflow on that side.
|
|
}
|
|
|
|
/* static */ TextOverflow*
|
|
TextOverflow::WillProcessLines(nsDisplayListBuilder* aBuilder,
|
|
nsIFrame* aBlockFrame)
|
|
{
|
|
if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) {
|
|
return nullptr;
|
|
}
|
|
nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
|
|
if (scrollableFrame && scrollableFrame->IsTransformingByAPZ()) {
|
|
// If the APZ is actively scrolling this, don't bother with markers.
|
|
return nullptr;
|
|
}
|
|
return new TextOverflow(aBuilder, aBlockFrame);
|
|
}
|
|
|
|
void
|
|
TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
|
|
const LogicalRect& aContentArea,
|
|
const LogicalRect& aInsideMarkersArea,
|
|
FrameHashtable* aFramesToHide,
|
|
AlignmentEdges* aAlignmentEdges,
|
|
bool* aFoundVisibleTextOrAtomic,
|
|
InnerClipEdges* aClippedMarkerEdges)
|
|
{
|
|
const nsIAtom* frameType = aFrame->GetType();
|
|
if (frameType == nsGkAtoms::brFrame ||
|
|
frameType == nsGkAtoms::placeholderFrame) {
|
|
return;
|
|
}
|
|
const bool isAtomic = IsAtomicElement(aFrame, frameType);
|
|
if (aFrame->StyleVisibility()->IsVisible()) {
|
|
LogicalRect childRect =
|
|
GetLogicalScrollableOverflowRectRelativeToBlock(aFrame);
|
|
bool overflowIStart =
|
|
childRect.IStart(mBlockWM) < aContentArea.IStart(mBlockWM);
|
|
bool overflowIEnd =
|
|
childRect.IEnd(mBlockWM) > aContentArea.IEnd(mBlockWM);
|
|
if (overflowIStart) {
|
|
mIStart.mHasOverflow = true;
|
|
}
|
|
if (overflowIEnd) {
|
|
mIEnd.mHasOverflow = true;
|
|
}
|
|
if (isAtomic && ((mIStart.mActive && overflowIStart) ||
|
|
(mIEnd.mActive && overflowIEnd))) {
|
|
aFramesToHide->PutEntry(aFrame);
|
|
} else if (isAtomic || frameType == nsGkAtoms::textFrame) {
|
|
AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
|
|
aFramesToHide, aAlignmentEdges,
|
|
aFoundVisibleTextOrAtomic,
|
|
aClippedMarkerEdges);
|
|
}
|
|
}
|
|
if (isAtomic) {
|
|
return;
|
|
}
|
|
|
|
for (nsIFrame* child : aFrame->PrincipalChildList()) {
|
|
ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
|
|
aFramesToHide, aAlignmentEdges,
|
|
aFoundVisibleTextOrAtomic,
|
|
aClippedMarkerEdges);
|
|
}
|
|
}
|
|
|
|
void
|
|
TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
|
|
const nsIAtom* aFrameType,
|
|
const LogicalRect& aInsideMarkersArea,
|
|
FrameHashtable* aFramesToHide,
|
|
AlignmentEdges* aAlignmentEdges,
|
|
bool* aFoundVisibleTextOrAtomic,
|
|
InnerClipEdges* aClippedMarkerEdges)
|
|
{
|
|
LogicalRect borderRect(mBlockWM,
|
|
nsRect(aFrame->GetOffsetTo(mBlock),
|
|
aFrame->GetSize()),
|
|
mBlockSize);
|
|
nscoord istartOverlap = std::max(
|
|
aInsideMarkersArea.IStart(mBlockWM) - borderRect.IStart(mBlockWM), 0);
|
|
nscoord iendOverlap = std::max(
|
|
borderRect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM), 0);
|
|
bool insideIStartEdge =
|
|
aInsideMarkersArea.IStart(mBlockWM) <= borderRect.IEnd(mBlockWM);
|
|
bool insideIEndEdge =
|
|
borderRect.IStart(mBlockWM) <= aInsideMarkersArea.IEnd(mBlockWM);
|
|
|
|
if (istartOverlap > 0) {
|
|
aClippedMarkerEdges->AccumulateIStart(mBlockWM, borderRect);
|
|
if (!mIStart.mActive) {
|
|
istartOverlap = 0;
|
|
}
|
|
}
|
|
if (iendOverlap > 0) {
|
|
aClippedMarkerEdges->AccumulateIEnd(mBlockWM, borderRect);
|
|
if (!mIEnd.mActive) {
|
|
iendOverlap = 0;
|
|
}
|
|
}
|
|
|
|
if ((istartOverlap > 0 && insideIStartEdge) ||
|
|
(iendOverlap > 0 && insideIEndEdge)) {
|
|
if (aFrameType == nsGkAtoms::textFrame) {
|
|
if (aInsideMarkersArea.IStart(mBlockWM) <
|
|
aInsideMarkersArea.IEnd(mBlockWM)) {
|
|
// a clipped text frame and there is some room between the markers
|
|
nscoord snappedIStart, snappedIEnd;
|
|
auto textFrame = static_cast<nsTextFrame*>(aFrame);
|
|
bool isFullyClipped = mBlockWM.IsBidiLTR() ?
|
|
IsFullyClipped(textFrame, istartOverlap, iendOverlap,
|
|
&snappedIStart, &snappedIEnd) :
|
|
IsFullyClipped(textFrame, iendOverlap, istartOverlap,
|
|
&snappedIEnd, &snappedIStart);
|
|
if (!isFullyClipped) {
|
|
LogicalRect snappedRect = borderRect;
|
|
if (istartOverlap > 0) {
|
|
snappedRect.IStart(mBlockWM) += snappedIStart;
|
|
snappedRect.ISize(mBlockWM) -= snappedIStart;
|
|
}
|
|
if (iendOverlap > 0) {
|
|
snappedRect.ISize(mBlockWM) -= snappedIEnd;
|
|
}
|
|
aAlignmentEdges->Accumulate(mBlockWM, snappedRect);
|
|
*aFoundVisibleTextOrAtomic = true;
|
|
}
|
|
}
|
|
} else {
|
|
aFramesToHide->PutEntry(aFrame);
|
|
}
|
|
} else if (!insideIStartEdge || !insideIEndEdge) {
|
|
// frame is outside
|
|
if (IsAtomicElement(aFrame, aFrameType)) {
|
|
aFramesToHide->PutEntry(aFrame);
|
|
}
|
|
} else {
|
|
// frame is inside
|
|
aAlignmentEdges->Accumulate(mBlockWM, borderRect);
|
|
*aFoundVisibleTextOrAtomic = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
TextOverflow::ExamineLineFrames(nsLineBox* aLine,
|
|
FrameHashtable* aFramesToHide,
|
|
AlignmentEdges* aAlignmentEdges)
|
|
{
|
|
// No ellipsing for 'clip' style.
|
|
bool suppressIStart = mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
|
|
bool suppressIEnd = mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
|
|
if (mCanHaveInlineAxisScrollbar) {
|
|
LogicalPoint pos(mBlockWM, mScrollableFrame->GetScrollPosition(),
|
|
mBlockSize);
|
|
LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
|
|
mBlockSize);
|
|
// No ellipsing when nothing to scroll to on that side (this includes
|
|
// overflow:auto that doesn't trigger a horizontal scrollbar).
|
|
if (pos.I(mBlockWM) <= scrollRange.IStart(mBlockWM)) {
|
|
suppressIStart = true;
|
|
}
|
|
if (pos.I(mBlockWM) >= scrollRange.IEnd(mBlockWM)) {
|
|
suppressIEnd = true;
|
|
}
|
|
}
|
|
|
|
LogicalRect contentArea = mContentArea;
|
|
const nscoord scrollAdjust = mAdjustForPixelSnapping ?
|
|
mBlock->PresContext()->AppUnitsPerDevPixel() : 0;
|
|
InflateIStart(mBlockWM, &contentArea, scrollAdjust);
|
|
InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
|
|
LogicalRect lineRect(mBlockWM, aLine->GetScrollableOverflowArea(),
|
|
mBlockSize);
|
|
const bool istartOverflow =
|
|
!suppressIStart && lineRect.IStart(mBlockWM) < contentArea.IStart(mBlockWM);
|
|
const bool iendOverflow =
|
|
!suppressIEnd && lineRect.IEnd(mBlockWM) > contentArea.IEnd(mBlockWM);
|
|
if (!istartOverflow && !iendOverflow) {
|
|
// The line does not overflow on a side we should ellipsize.
|
|
return;
|
|
}
|
|
|
|
int pass = 0;
|
|
bool retryEmptyLine = true;
|
|
bool guessIStart = istartOverflow;
|
|
bool guessIEnd = iendOverflow;
|
|
mIStart.mActive = istartOverflow;
|
|
mIEnd.mActive = iendOverflow;
|
|
bool clippedIStartMarker = false;
|
|
bool clippedIEndMarker = false;
|
|
do {
|
|
// Setup marker strings as needed.
|
|
if (guessIStart) {
|
|
mIStart.SetupString(mBlock);
|
|
}
|
|
if (guessIEnd) {
|
|
mIEnd.SetupString(mBlock);
|
|
}
|
|
|
|
// If there is insufficient space for both markers then keep the one on the
|
|
// end side per the block's 'direction'.
|
|
nscoord istartMarkerISize = mIStart.mActive ? mIStart.mISize : 0;
|
|
nscoord iendMarkerISize = mIEnd.mActive ? mIEnd.mISize : 0;
|
|
if (istartMarkerISize && iendMarkerISize &&
|
|
istartMarkerISize + iendMarkerISize > contentArea.ISize(mBlockWM)) {
|
|
istartMarkerISize = 0;
|
|
}
|
|
|
|
// Calculate the area between the potential markers aligned at the
|
|
// block's edge.
|
|
LogicalRect insideMarkersArea = mContentArea;
|
|
if (guessIStart) {
|
|
InflateIStart(mBlockWM, &insideMarkersArea, -istartMarkerISize);
|
|
}
|
|
if (guessIEnd) {
|
|
InflateIEnd(mBlockWM, &insideMarkersArea, -iendMarkerISize);
|
|
}
|
|
|
|
// Analyze the frames on aLine for the overflow situation at the content
|
|
// edges and at the edges of the area between the markers.
|
|
bool foundVisibleTextOrAtomic = false;
|
|
int32_t n = aLine->GetChildCount();
|
|
nsIFrame* child = aLine->mFirstChild;
|
|
InnerClipEdges clippedMarkerEdges;
|
|
for (; n-- > 0; child = child->GetNextSibling()) {
|
|
ExamineFrameSubtree(child, contentArea, insideMarkersArea,
|
|
aFramesToHide, aAlignmentEdges,
|
|
&foundVisibleTextOrAtomic,
|
|
&clippedMarkerEdges);
|
|
}
|
|
if (!foundVisibleTextOrAtomic && retryEmptyLine) {
|
|
aAlignmentEdges->mAssigned = false;
|
|
aFramesToHide->Clear();
|
|
pass = -1;
|
|
if (mIStart.IsNeeded() && mIStart.mActive && !clippedIStartMarker) {
|
|
if (clippedMarkerEdges.mAssignedIStart &&
|
|
clippedMarkerEdges.mIStart > mContentArea.IStart(mBlockWM)) {
|
|
mIStart.mISize =
|
|
clippedMarkerEdges.mIStart - mContentArea.IStart(mBlockWM);
|
|
NS_ASSERTION(mIStart.mISize < mIStart.mIntrinsicISize,
|
|
"clipping a marker should make it strictly smaller");
|
|
clippedIStartMarker = true;
|
|
} else {
|
|
mIStart.mActive = guessIStart = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (mIEnd.IsNeeded() && mIEnd.mActive && !clippedIEndMarker) {
|
|
if (clippedMarkerEdges.mAssignedIEnd &&
|
|
mContentArea.IEnd(mBlockWM) > clippedMarkerEdges.mIEnd) {
|
|
mIEnd.mISize = mContentArea.IEnd(mBlockWM) - clippedMarkerEdges.mIEnd;
|
|
NS_ASSERTION(mIEnd.mISize < mIEnd.mIntrinsicISize,
|
|
"clipping a marker should make it strictly smaller");
|
|
clippedIEndMarker = true;
|
|
} else {
|
|
mIEnd.mActive = guessIEnd = false;
|
|
}
|
|
continue;
|
|
}
|
|
// The line simply has no visible content even without markers,
|
|
// so examine the line again without suppressing markers.
|
|
retryEmptyLine = false;
|
|
mIStart.mISize = mIStart.mIntrinsicISize;
|
|
mIStart.mActive = guessIStart = istartOverflow;
|
|
mIEnd.mISize = mIEnd.mIntrinsicISize;
|
|
mIEnd.mActive = guessIEnd = iendOverflow;
|
|
continue;
|
|
}
|
|
if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
|
|
guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
|
|
break;
|
|
} else {
|
|
guessIStart = mIStart.mActive && mIStart.IsNeeded();
|
|
guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
|
|
mIStart.Reset();
|
|
mIEnd.Reset();
|
|
aFramesToHide->Clear();
|
|
}
|
|
NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
|
|
} while (++pass != 2);
|
|
if (!istartOverflow || !mIStart.mActive) {
|
|
mIStart.Reset();
|
|
}
|
|
if (!iendOverflow || !mIEnd.mActive) {
|
|
mIEnd.Reset();
|
|
}
|
|
}
|
|
|
|
void
|
|
TextOverflow::ProcessLine(const nsDisplayListSet& aLists,
|
|
nsLineBox* aLine)
|
|
{
|
|
NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
|
|
mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
|
|
"TextOverflow with 'clip' for both sides");
|
|
mIStart.Reset();
|
|
mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
|
|
mIEnd.Reset();
|
|
mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
|
|
|
|
FrameHashtable framesToHide(64);
|
|
AlignmentEdges alignmentEdges;
|
|
ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
|
|
bool needIStart = mIStart.IsNeeded();
|
|
bool needIEnd = mIEnd.IsNeeded();
|
|
if (!needIStart && !needIEnd) {
|
|
return;
|
|
}
|
|
NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
|
|
!needIStart, "left marker for 'clip'");
|
|
NS_ASSERTION(mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
|
|
!needIEnd, "right marker for 'clip'");
|
|
|
|
// If there is insufficient space for both markers then keep the one on the
|
|
// end side per the block's 'direction'.
|
|
if (needIStart && needIEnd &&
|
|
mIStart.mISize + mIEnd.mISize > mContentArea.ISize(mBlockWM)) {
|
|
needIStart = false;
|
|
}
|
|
LogicalRect insideMarkersArea = mContentArea;
|
|
if (needIStart) {
|
|
InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
|
|
}
|
|
if (needIEnd) {
|
|
InflateIEnd(mBlockWM, &insideMarkersArea, -mIEnd.mISize);
|
|
}
|
|
if (!mCanHaveInlineAxisScrollbar && alignmentEdges.mAssigned) {
|
|
LogicalRect alignmentRect(mBlockWM, alignmentEdges.mIStart,
|
|
insideMarkersArea.BStart(mBlockWM),
|
|
alignmentEdges.ISize(), 1);
|
|
insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
|
|
}
|
|
|
|
// Clip and remove display items as needed at the final marker edges.
|
|
nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() };
|
|
for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
|
|
PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
|
|
}
|
|
CreateMarkers(aLine, needIStart, needIEnd, insideMarkersArea);
|
|
}
|
|
|
|
void
|
|
TextOverflow::PruneDisplayListContents(nsDisplayList* aList,
|
|
const FrameHashtable& aFramesToHide,
|
|
const LogicalRect& aInsideMarkersArea)
|
|
{
|
|
nsDisplayList saved;
|
|
nsDisplayItem* item;
|
|
while ((item = aList->RemoveBottom())) {
|
|
nsIFrame* itemFrame = item->Frame();
|
|
if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
|
|
item->~nsDisplayItem();
|
|
continue;
|
|
}
|
|
|
|
nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
|
|
if (wrapper) {
|
|
if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
|
|
PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
|
|
}
|
|
}
|
|
|
|
nsCharClipDisplayItem* charClip = itemFrame ?
|
|
nsCharClipDisplayItem::CheckCast(item) : nullptr;
|
|
if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
|
|
LogicalRect rect =
|
|
GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
|
|
if (mIStart.IsNeeded()) {
|
|
nscoord istart =
|
|
aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
|
|
if (istart > 0) {
|
|
(mBlockWM.IsBidiLTR() ?
|
|
charClip->mVisIStartEdge : charClip->mVisIEndEdge) = istart;
|
|
}
|
|
}
|
|
if (mIEnd.IsNeeded()) {
|
|
nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
|
|
if (iend > 0) {
|
|
(mBlockWM.IsBidiLTR() ?
|
|
charClip->mVisIEndEdge : charClip->mVisIStartEdge) = iend;
|
|
}
|
|
}
|
|
}
|
|
|
|
saved.AppendToTop(item);
|
|
}
|
|
aList->AppendToTop(&saved);
|
|
}
|
|
|
|
/* static */ bool
|
|
TextOverflow::HasClippedOverflow(nsIFrame* aBlockFrame)
|
|
{
|
|
const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
|
|
return style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
|
|
style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
|
|
}
|
|
|
|
/* static */ bool
|
|
TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
|
|
nsIFrame* aBlockFrame)
|
|
{
|
|
// Nothing to do for text-overflow:clip or if 'overflow-x/y:visible' or if
|
|
// we're just building items for event processing or image visibility.
|
|
if (HasClippedOverflow(aBlockFrame) ||
|
|
IsInlineAxisOverflowVisible(aBlockFrame) ||
|
|
aBuilder->IsForEventDelivery() || aBuilder->IsForImageVisibility()) {
|
|
return false;
|
|
}
|
|
|
|
// Skip ComboboxControlFrame because it would clip the drop-down arrow.
|
|
// Its anon block inherits 'text-overflow' and does what is expected.
|
|
if (aBlockFrame->GetType() == nsGkAtoms::comboboxControlFrame) {
|
|
return false;
|
|
}
|
|
|
|
// Inhibit the markers if a descendant content owns the caret.
|
|
RefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret();
|
|
if (caret && caret->IsVisible()) {
|
|
nsCOMPtr<nsISelection> domSelection = caret->GetSelection();
|
|
if (domSelection) {
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
domSelection->GetFocusNode(getter_AddRefs(node));
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
|
|
if (content && nsContentUtils::ContentIsDescendantOf(content,
|
|
aBlockFrame->GetContent())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TextOverflow::CreateMarkers(const nsLineBox* aLine,
|
|
bool aCreateIStart, bool aCreateIEnd,
|
|
const mozilla::LogicalRect& aInsideMarkersArea)
|
|
{
|
|
if (aCreateIStart) {
|
|
DisplayListClipState::AutoSaveRestore clipState(mBuilder);
|
|
|
|
LogicalRect markerLogicalRect(
|
|
mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
|
|
aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
|
|
nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
|
|
nsRect markerRect =
|
|
markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
|
|
ClipMarker(mContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
|
|
markerRect, clipState);
|
|
nsDisplayItem* marker = new (mBuilder)
|
|
nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
|
|
aLine->GetLogicalAscent(), mIStart.mStyle, 0);
|
|
mMarkerList.AppendNewToTop(marker);
|
|
}
|
|
|
|
if (aCreateIEnd) {
|
|
DisplayListClipState::AutoSaveRestore clipState(mBuilder);
|
|
|
|
LogicalRect markerLogicalRect(
|
|
mBlockWM, aInsideMarkersArea.IEnd(mBlockWM), aLine->BStart(),
|
|
mIEnd.mIntrinsicISize, aLine->BSize());
|
|
nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
|
|
nsRect markerRect =
|
|
markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
|
|
ClipMarker(mContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
|
|
markerRect, clipState);
|
|
nsDisplayItem* marker = new (mBuilder)
|
|
nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
|
|
aLine->GetLogicalAscent(), mIEnd.mStyle, 1);
|
|
mMarkerList.AppendNewToTop(marker);
|
|
}
|
|
}
|
|
|
|
void
|
|
TextOverflow::Marker::SetupString(nsIFrame* aFrame)
|
|
{
|
|
if (mInitialized) {
|
|
return;
|
|
}
|
|
|
|
if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
|
|
gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
|
|
if (textRun) {
|
|
mISize = textRun->GetAdvanceWidth();
|
|
} else {
|
|
mISize = 0;
|
|
}
|
|
} else {
|
|
nsRenderingContext rc(
|
|
aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext());
|
|
RefPtr<nsFontMetrics> fm;
|
|
nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
|
|
nsLayoutUtils::FontSizeInflationFor(aFrame));
|
|
mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
|
|
*fm, rc);
|
|
}
|
|
mIntrinsicISize = mISize;
|
|
mInitialized = true;
|
|
}
|
|
|
|
} // namespace css
|
|
} // namespace mozilla
|