diff options
Diffstat (limited to 'layout/generic/nsFirstLetterFrame.cpp')
-rw-r--r-- | layout/generic/nsFirstLetterFrame.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/layout/generic/nsFirstLetterFrame.cpp b/layout/generic/nsFirstLetterFrame.cpp new file mode 100644 index 0000000000..21ef03196f --- /dev/null +++ b/layout/generic/nsFirstLetterFrame.cpp @@ -0,0 +1,448 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* rendering object for CSS :first-letter pseudo-element */ + +#include "nsFirstLetterFrame.h" +#include "nsPresContext.h" +#include "nsPresContextInlines.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsIContent.h" +#include "nsLayoutUtils.h" +#include "nsLineLayout.h" +#include "nsGkAtoms.h" +#include "nsFrameManager.h" +#include "nsPlaceholderFrame.h" +#include "nsTextFrame.h" +#include "nsCSSFrameConstructor.h" + +using namespace mozilla; +using namespace mozilla::layout; + +nsFirstLetterFrame* NS_NewFirstLetterFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsFirstLetterFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame) + +NS_QUERYFRAME_HEAD(nsFirstLetterFrame) + NS_QUERYFRAME_ENTRY(nsFirstLetterFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult nsFirstLetterFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"Letter"_ns, aResult); +} +#endif + +void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + BuildDisplayListForInline(aBuilder, aLists); +} + +void nsFirstLetterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + RefPtr<ComputedStyle> newSC; + if (aPrevInFlow) { + // Get proper ComputedStyle for ourselves. We're creating the frame + // that represents everything *except* the first letter, so just create + // a ComputedStyle that inherits from our style parent, with no extra rules. + nsIFrame* styleParent = + CorrectStyleParentFrame(aParent, PseudoStyleType::firstLetter); + ComputedStyle* parentComputedStyle = styleParent->Style(); + newSC = PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation( + parentComputedStyle); + SetComputedStyleWithoutNotification(newSC); + } + + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); +} + +void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) { + MOZ_ASSERT(aListID == FrameChildListID::Principal, + "Principal child list is the only " + "list that nsFirstLetterFrame should set via this function"); + for (nsIFrame* f : aChildList) { + MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); + MOZ_ASSERT(f->IsTextFrame(), + "We should not have kids that are containers!"); + nsLayoutUtils::MarkDescendantsDirty(f); // Drops cached textruns + } + + mFrames = std::move(aChildList); +} + +nsresult nsFirstLetterFrame::GetChildFrameContainingOffset( + int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset, + nsIFrame** outChildFrame) { + nsIFrame* kid = mFrames.FirstChild(); + if (kid) { + return kid->GetChildFrameContainingOffset( + inContentOffset, inHint, outFrameContentOffset, outChildFrame); + } + return nsIFrame::GetChildFrameContainingOffset( + inContentOffset, inHint, outFrameContentOffset, outChildFrame); +} + +// Needed for non-floating first-letter frames and for the continuations +// following the first-letter that we also use nsFirstLetterFrame for. +/* virtual */ +void nsFirstLetterFrame::AddInlineMinISize( + gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) { + DoInlineMinISize(aRenderingContext, aData); +} + +// Needed for non-floating first-letter frames and for the continuations +// following the first-letter that we also use nsFirstLetterFrame for. +/* virtual */ +void nsFirstLetterFrame::AddInlinePrefISize( + gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) { + DoInlinePrefISize(aRenderingContext, aData); +} + +// Needed for floating first-letter frames. +/* virtual */ +nscoord nsFirstLetterFrame::GetMinISize(gfxContext* aRenderingContext) { + return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext); +} + +// Needed for floating first-letter frames. +/* virtual */ +nscoord nsFirstLetterFrame::GetPrefISize(gfxContext* aRenderingContext) { + return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext); +} + +/* virtual */ +nsIFrame::SizeComputationResult nsFirstLetterFrame::ComputeSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) { + if (GetPrevInFlow()) { + // We're wrapping the text *after* the first letter, so behave like an + // inline frame. + return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), + AspectRatioUsage::None}; + } + return nsContainerFrame::ComputeSize(aRenderingContext, aWM, aCBSize, + aAvailableISize, aMargin, aBorderPadding, + aSizeOverrides, aFlags); +} + +bool nsFirstLetterFrame::UseTightBounds() const { + int v = StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds(); + + // Check for the simple cases: + // pref value > 0: use legacy gecko behavior + // pref value = 0: use webkit/blink-like behavior + if (v > 0) { + return true; + } + if (v == 0) { + return false; + } + + // Pref value < 0: use heuristics to determine whether the page is assuming + // webkit/blink-style behavior: + // If line-height is less than font-size, or there is a negative block-start + // or -end margin, use webkit/blink behavior. + if (nsTextFrame* textFrame = do_QueryFrame(mFrames.FirstChild())) { + RefPtr<nsFontMetrics> fm = textFrame->InflatedFontMetrics(); + if (textFrame->ComputeLineHeight() < fm->EmHeight()) { + return false; + } + } + + const auto wm = GetWritingMode(); + const auto& margin = StyleMargin()->mMargin; + const auto& bStart = margin.GetBStart(wm); + // Currently, we only check for margins with negative *length* values; + // negative percentages seem unlikely to be used/useful in this context. + if (bStart.ConvertsToLength() && bStart.ToLength() < 0) { + return false; + } + const auto& bEnd = margin.GetBEnd(wm); + if (bEnd.ConvertsToLength() && bEnd.ToLength() < 0) { + return false; + } + + return true; +} + +void nsFirstLetterFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aMetrics, + const ReflowInput& aReflowInput, + nsReflowStatus& aReflowStatus) { + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus); + MOZ_ASSERT(aReflowStatus.IsEmpty(), + "Caller should pass a fresh reflow status!"); + + // Grab overflow list + DrainOverflowFrames(aPresContext); + + nsIFrame* kid = mFrames.FirstChild(); + + // Setup reflow input for our child + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize availSize = aReflowInput.AvailableSize(); + const auto bp = aReflowInput.ComputedLogicalBorderPadding(wm); + NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE, + "should no longer use unconstrained inline size"); + availSize.ISize(wm) -= bp.IStartEnd(wm); + if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { + availSize.BSize(wm) -= bp.BStartEnd(wm); + } + + WritingMode lineWM = aMetrics.GetWritingMode(); + ReflowOutput kidMetrics(lineWM); + + // Reflow the child + if (!aReflowInput.mLineLayout) { + // When there is no lineLayout provided, we provide our own. The + // only time that the first-letter-frame is not reflowing in a + // line context is when its floating. + WritingMode kidWritingMode = WritingModeForLine(wm, kid); + LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm); + ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize); + nsLineLayout ll(aPresContext, nullptr, aReflowInput, nullptr, nullptr); + + ll.BeginLineReflow( + bp.IStart(wm), bp.BStart(wm), availSize.ISize(wm), NS_UNCONSTRAINEDSIZE, + false, true, kidWritingMode, + nsSize(aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); + rs.mLineLayout = ≪ + ll.SetInFirstLetter(true); + ll.SetFirstLetterStyleOK(true); + + kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus); + + ll.EndLineReflow(); + ll.SetInFirstLetter(false); + + // In the floating first-letter case, we need to set this ourselves; + // nsLineLayout::BeginSpan will set it in the other case + mBaseline = kidMetrics.BlockStartAscent(); + + // Place and size the child and update the output metrics + LogicalSize convertedSize = kidMetrics.Size(wm); + + const bool tightBounds = UseTightBounds(); + const nscoord shift = + tightBounds ? 0 + // Shift by half of the difference between the line-height + // we're going to use and current height of the kid frame. + : (rs.GetLineHeight() - convertedSize.BSize(wm)) / 2; + + kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm) + shift, + convertedSize.ISize(wm), convertedSize.BSize(wm))); + kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay); + kid->DidReflow(aPresContext, nullptr); + + if (!tightBounds) { + // Adjust size to account for line-height. + convertedSize.BSize(wm) = rs.GetLineHeight(); + } + + convertedSize.ISize(wm) += bp.IStartEnd(wm); + convertedSize.BSize(wm) += bp.BStartEnd(wm); + aMetrics.SetSize(wm, convertedSize); + aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + bp.BStart(wm)); + + // Ensure that the overflow rect contains the child textframe's + // overflow rect. + // Note that if this is floating, the overline/underline drawable + // area is in the overflow rect of the child textframe. + aMetrics.UnionOverflowAreasWithDesiredBounds(); + ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); + + FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); + } else { + // Pretend we are a span and reflow the child frame + nsLineLayout* ll = aReflowInput.mLineLayout; + bool pushedFrame; + + ll->SetInFirstLetter(Style()->GetPseudoType() == + PseudoStyleType::firstLetter); + ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), availSize.ISize(wm), + &mBaseline); + ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame); + NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(), + "we're assuming we can mix sizes between lineWM and wm " + "since we shouldn't have orthogonal writing modes within " + "a line."); + aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm); + ll->SetInFirstLetter(false); + + if (mComputedStyle->StyleTextReset()->mInitialLetterSize != 0.0f) { + aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + + bp.BStart(wm)); + aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm); + } else { + nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm); + } + } + + if (!aReflowStatus.IsInlineBreakBefore()) { + // Create a continuation or remove existing continuations based on + // the reflow completion status. + if (aReflowStatus.IsComplete()) { + if (aReflowInput.mLineLayout) { + aReflowInput.mLineLayout->SetFirstLetterStyleOK(false); + } + if (nsIFrame* kidNextInFlow = kid->GetNextInFlow()) { + DestroyContext context(PresShell()); + // Remove all of the childs next-in-flows + kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, + kidNextInFlow, true); + } + } else { + // Create a continuation for the child frame if it doesn't already + // have one. + if (!IsFloating()) { + CreateNextInFlow(kid); + // And then push it to our overflow list + nsFrameList overflow = mFrames.TakeFramesAfter(kid); + if (overflow.NotEmpty()) { + SetOverflowFrames(std::move(overflow)); + } + } else if (!kid->GetNextInFlow()) { + // For floating first letter frames (if a continuation wasn't already + // created for us) we need to put the continuation with the rest of the + // text that the first letter frame was made out of. + nsIFrame* continuation; + CreateContinuationForFloatingParent(kid, &continuation, true); + } + } + } +} + +/* virtual */ +bool nsFirstLetterFrame::CanContinueTextRun() const { + // We can continue a text run through a first-letter frame. + return true; +} + +void nsFirstLetterFrame::CreateContinuationForFloatingParent( + nsIFrame* aChild, nsIFrame** aContinuation, bool aIsFluid) { + NS_ASSERTION(IsFloating(), + "can only call this on floating first letter frames"); + MOZ_ASSERT(aContinuation, "bad args"); + + *aContinuation = nullptr; + + mozilla::PresShell* presShell = PresShell(); + nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame(); + nsContainerFrame* parent = placeholderFrame->GetParent(); + + nsIFrame* continuation = presShell->FrameConstructor()->CreateContinuingFrame( + aChild, parent, aIsFluid); + + // The continuation will have gotten the first letter style from its + // prev continuation, so we need to repair the ComputedStyle so it + // doesn't have the first letter styling. + // + // Note that getting parent frame's ComputedStyle is different from getting + // this frame's ComputedStyle's parent in the presence of ::first-line, + // which we do want the continuation to inherit from. + ComputedStyle* parentSC = parent->Style(); + if (parentSC) { + RefPtr<ComputedStyle> newSC; + newSC = + presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC); + continuation->SetComputedStyle(newSC); + nsLayoutUtils::MarkDescendantsDirty(continuation); + } + + // XXX Bidi may not be involved but we have to use the list name + // FrameChildListID::NoReflowPrincipal because this is just like creating a + // continuation except we have to insert it in a different place and we don't + // want a reflow command to try to be issued. + parent->InsertFrames(FrameChildListID::NoReflowPrincipal, placeholderFrame, + nullptr, nsFrameList(continuation, continuation)); + + *aContinuation = continuation; +} + +void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) { + // Check for an overflow list with our prev-in-flow + nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow(); + if (prevInFlow) { + AutoFrameListPtr overflowFrames(aPresContext, + prevInFlow->StealOverflowFrames()); + if (overflowFrames) { + NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list"); + + // When pushing and pulling frames we need to check for whether any + // views need to be reparented. + nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow, + this); + mFrames.InsertFrames(this, nullptr, std::move(*overflowFrames)); + } + } + + // It's also possible that we have an overflow list for ourselves + AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames()); + if (overflowFrames) { + NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames"); + mFrames.AppendFrames(nullptr, std::move(*overflowFrames)); + } + + // Now repair our first frames ComputedStyle (since we only reflow + // one frame there is no point in doing any other ones until they + // are reflowed) + nsIFrame* kid = mFrames.FirstChild(); + if (kid) { + nsIContent* kidContent = kid->GetContent(); + if (kidContent) { + NS_ASSERTION(kidContent->IsText(), "should contain only text nodes"); + ComputedStyle* parentSC; + if (prevInFlow) { + // This is for the rest of the content not in the first-letter. + nsIFrame* styleParent = + CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter); + parentSC = styleParent->Style(); + } else { + // And this for the first-letter style. + parentSC = mComputedStyle; + } + RefPtr<ComputedStyle> sc = + aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC); + kid->SetComputedStyle(sc); + nsLayoutUtils::MarkDescendantsDirty(kid); + } + } +} + +Maybe<nscoord> nsFirstLetterFrame::GetNaturalBaselineBOffset( + WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext) const { + if (aBaselineGroup == BaselineSharingGroup::Last) { + return Nothing{}; + } + return Some(mBaseline); +} + +LogicalSides nsFirstLetterFrame::GetLogicalSkipSides() const { + if (GetPrevContinuation()) { + // We shouldn't get calls to GetSkipSides for later continuations since + // they have separate ComputedStyles with initial values for all the + // properties that could trigger a call to GetSkipSides. Then again, + // it's not really an error to call GetSkipSides on any frame, so + // that's why we handle it properly. + return LogicalSides(mWritingMode, eLogicalSideBitsAll); + } + return LogicalSides(mWritingMode); // first continuation displays all sides +} |