michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* rendering object for CSS :first-letter pseudo-element */ michael@0: michael@0: #include "nsFirstLetterFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsLineLayout.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsFrameManager.h" michael@0: #include "RestyleManager.h" michael@0: #include "nsPlaceholderFrame.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: nsIFrame* michael@0: NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsFirstLetterFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsFirstLetterFrame) michael@0: NS_QUERYFRAME_ENTRY(nsFirstLetterFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsFirstLetterFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("Letter"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: nsIAtom* michael@0: nsFirstLetterFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::letterFrame; michael@0: } michael@0: michael@0: void michael@0: nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: BuildDisplayListForInline(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: void michael@0: nsFirstLetterFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsRefPtr newSC; michael@0: if (aPrevInFlow) { michael@0: // Get proper style context for ourselves. We're creating the frame michael@0: // that represents everything *except* the first letter, so just create michael@0: // a style context like we would for a text node. michael@0: nsStyleContext* parentStyleContext = mStyleContext->GetParent(); michael@0: if (parentStyleContext) { michael@0: newSC = PresContext()->StyleSet()-> michael@0: ResolveStyleForNonElement(parentStyleContext); michael@0: SetStyleContextWithoutNotification(newSC); michael@0: } michael@0: } michael@0: michael@0: nsContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: nsresult michael@0: nsFirstLetterFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: RestyleManager* restyleManager = PresContext()->RestyleManager(); michael@0: michael@0: for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) { michael@0: NS_ASSERTION(e.get()->GetParent() == this, "Unexpected parent"); michael@0: restyleManager->ReparentStyleContext(e.get()); michael@0: nsLayoutUtils::MarkDescendantsDirty(e.get()); michael@0: } michael@0: michael@0: mFrames.SetFrames(aChildList); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFirstLetterFrame::GetChildFrameContainingOffset(int32_t inContentOffset, michael@0: bool inHint, michael@0: int32_t* outFrameContentOffset, michael@0: nsIFrame **outChildFrame) michael@0: { michael@0: nsIFrame *kid = mFrames.FirstChild(); michael@0: if (kid) michael@0: { michael@0: return kid->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame); michael@0: } michael@0: else michael@0: return nsFrame::GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame); michael@0: } michael@0: michael@0: // Needed for non-floating first-letter frames and for the continuations michael@0: // following the first-letter that we also use nsFirstLetterFrame for. michael@0: /* virtual */ void michael@0: nsFirstLetterFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlineMinWidthData *aData) michael@0: { michael@0: DoInlineIntrinsicWidth(aRenderingContext, aData, nsLayoutUtils::MIN_WIDTH); michael@0: } michael@0: michael@0: // Needed for non-floating first-letter frames and for the continuations michael@0: // following the first-letter that we also use nsFirstLetterFrame for. michael@0: /* virtual */ void michael@0: nsFirstLetterFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlinePrefWidthData *aData) michael@0: { michael@0: DoInlineIntrinsicWidth(aRenderingContext, aData, nsLayoutUtils::PREF_WIDTH); michael@0: } michael@0: michael@0: // Needed for floating first-letter frames. michael@0: /* virtual */ nscoord michael@0: nsFirstLetterFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); michael@0: } michael@0: michael@0: // Needed for floating first-letter frames. michael@0: /* virtual */ nscoord michael@0: nsFirstLetterFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsFirstLetterFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: if (GetPrevInFlow()) { michael@0: // We're wrapping the text *after* the first letter, so behave like an michael@0: // inline frame. michael@0: return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); michael@0: } michael@0: return nsContainerFrame::ComputeSize(aRenderingContext, michael@0: aCBSize, aAvailableWidth, aMargin, aBorder, aPadding, aFlags); michael@0: } michael@0: michael@0: nsresult michael@0: nsFirstLetterFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aReflowStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aReflowStatus); michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Grab overflow list michael@0: DrainOverflowFrames(aPresContext); michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: michael@0: // Setup reflow state for our child michael@0: nsSize availSize(aReflowState.AvailableWidth(), aReflowState.AvailableHeight()); michael@0: const nsMargin& bp = aReflowState.ComputedPhysicalBorderPadding(); michael@0: nscoord lr = bp.left + bp.right; michael@0: nscoord tb = bp.top + bp.bottom; michael@0: NS_ASSERTION(availSize.width != NS_UNCONSTRAINEDSIZE, michael@0: "should no longer use unconstrained widths"); michael@0: availSize.width -= lr; michael@0: if (NS_UNCONSTRAINEDSIZE != availSize.height) { michael@0: availSize.height -= tb; michael@0: } michael@0: michael@0: // Reflow the child michael@0: if (!aReflowState.mLineLayout) { michael@0: // When there is no lineLayout provided, we provide our own. The michael@0: // only time that the first-letter-frame is not reflowing in a michael@0: // line context is when its floating. michael@0: nsHTMLReflowState rs(aPresContext, aReflowState, kid, availSize); michael@0: nsLineLayout ll(aPresContext, nullptr, &aReflowState, nullptr); michael@0: michael@0: ll.BeginLineReflow(bp.left, bp.top, availSize.width, NS_UNCONSTRAINEDSIZE, michael@0: false, true, michael@0: ll.LineContainerFrame()->GetWritingMode(kid), michael@0: aReflowState.AvailableWidth()); michael@0: rs.mLineLayout = ≪ michael@0: ll.SetInFirstLetter(true); michael@0: ll.SetFirstLetterStyleOK(true); michael@0: michael@0: kid->WillReflow(aPresContext); michael@0: kid->Reflow(aPresContext, aMetrics, rs, aReflowStatus); michael@0: michael@0: ll.EndLineReflow(); michael@0: ll.SetInFirstLetter(false); michael@0: michael@0: // In the floating first-letter case, we need to set this ourselves; michael@0: // nsLineLayout::BeginSpan will set it in the other case michael@0: mBaseline = aMetrics.TopAscent(); michael@0: } michael@0: else { michael@0: // Pretend we are a span and reflow the child frame michael@0: nsLineLayout* ll = aReflowState.mLineLayout; michael@0: bool pushedFrame; michael@0: michael@0: ll->SetInFirstLetter( michael@0: mStyleContext->GetPseudo() == nsCSSPseudoElements::firstLetter); michael@0: ll->BeginSpan(this, &aReflowState, bp.left, availSize.width, &mBaseline); michael@0: ll->ReflowFrame(kid, aReflowStatus, &aMetrics, pushedFrame); michael@0: ll->EndSpan(this); michael@0: ll->SetInFirstLetter(false); michael@0: } michael@0: michael@0: // Place and size the child and update the output metrics michael@0: kid->SetRect(nsRect(bp.left, bp.top, aMetrics.Width(), aMetrics.Height())); michael@0: kid->FinishAndStoreOverflow(&aMetrics); michael@0: kid->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); michael@0: michael@0: aMetrics.Width() += lr; michael@0: aMetrics.Height() += tb; michael@0: aMetrics.SetTopAscent(aMetrics.TopAscent() + bp.top); michael@0: michael@0: // Ensure that the overflow rect contains the child textframe's overflow rect. michael@0: // Note that if this is floating, the overline/underline drawable area is in michael@0: // the overflow rect of the child textframe. michael@0: aMetrics.UnionOverflowAreasWithDesiredBounds(); michael@0: ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); michael@0: michael@0: if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) { michael@0: // Create a continuation or remove existing continuations based on michael@0: // the reflow completion status. michael@0: if (NS_FRAME_IS_COMPLETE(aReflowStatus)) { michael@0: if (aReflowState.mLineLayout) { michael@0: aReflowState.mLineLayout->SetFirstLetterStyleOK(false); michael@0: } michael@0: nsIFrame* kidNextInFlow = kid->GetNextInFlow(); michael@0: if (kidNextInFlow) { michael@0: // Remove all of the childs next-in-flows michael@0: static_cast(kidNextInFlow->GetParent()) michael@0: ->DeleteNextInFlowChild(kidNextInFlow, true); michael@0: } michael@0: } michael@0: else { michael@0: // Create a continuation for the child frame if it doesn't already michael@0: // have one. michael@0: if (!IsFloating()) { michael@0: nsIFrame* nextInFlow; michael@0: rv = CreateNextInFlow(kid, nextInFlow); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // And then push it to our overflow list michael@0: const nsFrameList& overflow = mFrames.RemoveFramesAfter(kid); michael@0: if (overflow.NotEmpty()) { michael@0: SetOverflowFrames(overflow); michael@0: } michael@0: } else if (!kid->GetNextInFlow()) { michael@0: // For floating first letter frames (if a continuation wasn't already michael@0: // created for us) we need to put the continuation with the rest of the michael@0: // text that the first letter frame was made out of. michael@0: nsIFrame* continuation; michael@0: rv = CreateContinuationForFloatingParent(aPresContext, kid, michael@0: &continuation, true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: FinishAndStoreOverflow(&aMetrics); michael@0: michael@0: NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowState, aMetrics); michael@0: return rv; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsFirstLetterFrame::CanContinueTextRun() const michael@0: { michael@0: // We can continue a text run through a first-letter frame. michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext* aPresContext, michael@0: nsIFrame* aChild, michael@0: nsIFrame** aContinuation, michael@0: bool aIsFluid) michael@0: { michael@0: NS_ASSERTION(IsFloating(), michael@0: "can only call this on floating first letter frames"); michael@0: NS_PRECONDITION(aContinuation, "bad args"); michael@0: michael@0: *aContinuation = nullptr; michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsIPresShell* presShell = aPresContext->PresShell(); michael@0: nsPlaceholderFrame* placeholderFrame = michael@0: presShell->FrameManager()->GetPlaceholderFrameFor(this); michael@0: nsIFrame* parent = placeholderFrame->GetParent(); michael@0: michael@0: nsIFrame* continuation = presShell->FrameConstructor()-> michael@0: CreateContinuingFrame(aPresContext, aChild, parent, aIsFluid); michael@0: michael@0: // The continuation will have gotten the first letter style from its michael@0: // prev continuation, so we need to repair the style context so it michael@0: // doesn't have the first letter styling. michael@0: nsStyleContext* parentSC = this->StyleContext()->GetParent(); michael@0: if (parentSC) { michael@0: nsRefPtr newSC; michael@0: newSC = presShell->StyleSet()->ResolveStyleForNonElement(parentSC); michael@0: continuation->SetStyleContext(newSC); michael@0: nsLayoutUtils::MarkDescendantsDirty(continuation); michael@0: } michael@0: michael@0: //XXX Bidi may not be involved but we have to use the list name michael@0: // kNoReflowPrincipalList because this is just like creating a continuation michael@0: // except we have to insert it in a different place and we don't want a michael@0: // reflow command to try to be issued. michael@0: nsFrameList temp(continuation, continuation); michael@0: rv = parent->InsertFrames(kNoReflowPrincipalList, placeholderFrame, temp); michael@0: michael@0: *aContinuation = continuation; michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) michael@0: { michael@0: // Check for an overflow list with our prev-in-flow michael@0: nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow(); michael@0: if (prevInFlow) { michael@0: AutoFrameListPtr overflowFrames(aPresContext, michael@0: prevInFlow->StealOverflowFrames()); michael@0: if (overflowFrames) { michael@0: NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list"); michael@0: michael@0: // When pushing and pulling frames we need to check for whether any michael@0: // views need to be reparented. michael@0: nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow, michael@0: this); michael@0: mFrames.InsertFrames(this, nullptr, *overflowFrames); michael@0: } michael@0: } michael@0: michael@0: // It's also possible that we have an overflow list for ourselves michael@0: AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames()); michael@0: if (overflowFrames) { michael@0: NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames"); michael@0: mFrames.AppendFrames(nullptr, *overflowFrames); michael@0: } michael@0: michael@0: // Now repair our first frames style context (since we only reflow michael@0: // one frame there is no point in doing any other ones until they michael@0: // are reflowed) michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: if (kid) { michael@0: nsRefPtr sc; michael@0: nsIContent* kidContent = kid->GetContent(); michael@0: if (kidContent) { michael@0: NS_ASSERTION(kidContent->IsNodeOfType(nsINode::eTEXT), michael@0: "should contain only text nodes"); michael@0: nsStyleContext* parentSC = prevInFlow ? mStyleContext->GetParent() : michael@0: mStyleContext; michael@0: sc = aPresContext->StyleSet()->ResolveStyleForNonElement(parentSC); michael@0: kid->SetStyleContext(sc); michael@0: nsLayoutUtils::MarkDescendantsDirty(kid); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: nsFirstLetterFrame::GetBaseline() const michael@0: { michael@0: return mBaseline; michael@0: } michael@0: michael@0: int michael@0: nsFirstLetterFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const michael@0: { michael@0: if (GetPrevContinuation()) { michael@0: // We shouldn't get calls to GetSkipSides for later continuations since michael@0: // they have separate style contexts with initial values for all the michael@0: // properties that could trigger a call to GetSkipSides. Then again, michael@0: // it's not really an error to call GetSkipSides on any frame, so michael@0: // that's why we handle it properly. michael@0: return LOGICAL_SIDES_ALL; michael@0: } michael@0: return 0; // first continuation displays all sides michael@0: }