1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* rendering object for CSS :first-letter pseudo-element */
8 #include "nsFirstLetterFrame.h"
9 #include "nsPresContext.h"
10 #include "nsStyleContext.h"
11 #include "nsIContent.h"
12 #include "nsLineLayout.h"
13 #include "nsGkAtoms.h"
14 #include "nsAutoPtr.h"
15 #include "nsStyleSet.h"
16 #include "nsFrameManager.h"
17 #include "RestyleManager.h"
18 #include "nsPlaceholderFrame.h"
19 #include "nsCSSFrameConstructor.h"
21 using namespace mozilla
;
22 using namespace mozilla::layout
;
25 NS_NewFirstLetterFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
27 return new (aPresShell
) nsFirstLetterFrame(aContext
);
30 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame
)
32 NS_QUERYFRAME_HEAD(nsFirstLetterFrame
)
33 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame
)
34 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
36 #ifdef DEBUG_FRAME_DUMP
38 nsFirstLetterFrame::GetFrameName(nsAString
& aResult
) const
40 return MakeFrameName(NS_LITERAL_STRING("Letter"), aResult
);
45 nsFirstLetterFrame::GetType() const
47 return nsGkAtoms::letterFrame
;
51 nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
52 const nsRect
& aDirtyRect
,
53 const nsDisplayListSet
& aLists
)
55 BuildDisplayListForInline(aBuilder
, aDirtyRect
, aLists
);
59 nsFirstLetterFrame::Init(nsIContent
* aContent
,
60 nsContainerFrame
* aParent
,
61 nsIFrame
* aPrevInFlow
)
63 nsRefPtr
<nsStyleContext
> newSC
;
65 // Get proper style context for ourselves. We're creating the frame
66 // that represents everything *except* the first letter, so just create
67 // a style context like we would for a text node.
68 nsStyleContext
* parentStyleContext
= mStyleContext
->GetParent();
69 if (parentStyleContext
) {
70 newSC
= PresContext()->StyleSet()->
71 ResolveStyleForNonElement(parentStyleContext
);
72 SetStyleContextWithoutNotification(newSC
);
76 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
80 nsFirstLetterFrame::SetInitialChildList(ChildListID aListID
,
81 nsFrameList
& aChildList
)
83 RestyleManager
* restyleManager
= PresContext()->RestyleManager();
85 for (nsFrameList::Enumerator
e(aChildList
); !e
.AtEnd(); e
.Next()) {
86 NS_ASSERTION(e
.get()->GetParent() == this, "Unexpected parent");
87 restyleManager
->ReparentStyleContext(e
.get());
88 nsLayoutUtils::MarkDescendantsDirty(e
.get());
91 mFrames
.SetFrames(aChildList
);
95 nsFirstLetterFrame::GetChildFrameContainingOffset(int32_t inContentOffset
,
97 int32_t* outFrameContentOffset
,
98 nsIFrame
**outChildFrame
)
100 nsIFrame
*kid
= mFrames
.FirstChild();
103 return kid
->GetChildFrameContainingOffset(inContentOffset
, inHint
, outFrameContentOffset
, outChildFrame
);
106 return nsFrame::GetChildFrameContainingOffset(inContentOffset
, inHint
, outFrameContentOffset
, outChildFrame
);
109 // Needed for non-floating first-letter frames and for the continuations
110 // following the first-letter that we also use nsFirstLetterFrame for.
112 nsFirstLetterFrame::AddInlineMinISize(nsRenderingContext
*aRenderingContext
,
113 nsIFrame::InlineMinISizeData
*aData
)
115 DoInlineIntrinsicISize(aRenderingContext
, aData
, nsLayoutUtils::MIN_ISIZE
);
118 // Needed for non-floating first-letter frames and for the continuations
119 // following the first-letter that we also use nsFirstLetterFrame for.
121 nsFirstLetterFrame::AddInlinePrefISize(nsRenderingContext
*aRenderingContext
,
122 nsIFrame::InlinePrefISizeData
*aData
)
124 DoInlineIntrinsicISize(aRenderingContext
, aData
, nsLayoutUtils::PREF_ISIZE
);
127 // Needed for floating first-letter frames.
128 /* virtual */ nscoord
129 nsFirstLetterFrame::GetMinISize(nsRenderingContext
*aRenderingContext
)
131 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext
);
134 // Needed for floating first-letter frames.
135 /* virtual */ nscoord
136 nsFirstLetterFrame::GetPrefISize(nsRenderingContext
*aRenderingContext
)
138 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext
);
143 nsFirstLetterFrame::ComputeSize(nsRenderingContext
*aRenderingContext
,
145 const LogicalSize
& aCBSize
,
146 nscoord aAvailableISize
,
147 const LogicalSize
& aMargin
,
148 const LogicalSize
& aBorder
,
149 const LogicalSize
& aPadding
,
152 if (GetPrevInFlow()) {
153 // We're wrapping the text *after* the first letter, so behave like an
155 return LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
157 return nsContainerFrame::ComputeSize(aRenderingContext
, aWM
,
158 aCBSize
, aAvailableISize
, aMargin
, aBorder
, aPadding
, aFlags
);
162 nsFirstLetterFrame::Reflow(nsPresContext
* aPresContext
,
163 nsHTMLReflowMetrics
& aMetrics
,
164 const nsHTMLReflowState
& aReflowState
,
165 nsReflowStatus
& aReflowStatus
)
167 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
168 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aMetrics
, aReflowStatus
);
171 // Grab overflow list
172 DrainOverflowFrames(aPresContext
);
174 nsIFrame
* kid
= mFrames
.FirstChild();
176 // Setup reflow state for our child
177 WritingMode wm
= aReflowState
.GetWritingMode();
178 LogicalSize availSize
= aReflowState
.AvailableSize();
179 const LogicalMargin
& bp
= aReflowState
.ComputedLogicalBorderPadding();
180 NS_ASSERTION(availSize
.ISize(wm
) != NS_UNCONSTRAINEDSIZE
,
181 "should no longer use unconstrained inline size");
182 availSize
.ISize(wm
) -= bp
.IStartEnd(wm
);
183 if (NS_UNCONSTRAINEDSIZE
!= availSize
.BSize(wm
)) {
184 availSize
.BSize(wm
) -= bp
.BStartEnd(wm
);
187 WritingMode lineWM
= aMetrics
.GetWritingMode();
188 nsHTMLReflowMetrics
kidMetrics(lineWM
);
191 if (!aReflowState
.mLineLayout
) {
192 // When there is no lineLayout provided, we provide our own. The
193 // only time that the first-letter-frame is not reflowing in a
194 // line context is when its floating.
195 WritingMode kidWritingMode
= GetWritingMode(kid
);
196 LogicalSize kidAvailSize
= availSize
.ConvertTo(kidWritingMode
, wm
);
197 nsHTMLReflowState
rs(aPresContext
, aReflowState
, kid
, kidAvailSize
);
198 nsLineLayout
ll(aPresContext
, nullptr, &aReflowState
, nullptr);
200 ll
.BeginLineReflow(bp
.IStart(wm
), bp
.BStart(wm
),
201 availSize
.ISize(wm
), NS_UNCONSTRAINEDSIZE
,
202 false, true, kidWritingMode
,
203 aReflowState
.AvailableWidth());
204 rs
.mLineLayout
= &ll
;
205 ll
.SetInFirstLetter(true);
206 ll
.SetFirstLetterStyleOK(true);
208 kid
->WillReflow(aPresContext
);
209 kid
->Reflow(aPresContext
, kidMetrics
, rs
, aReflowStatus
);
212 ll
.SetInFirstLetter(false);
214 // In the floating first-letter case, we need to set this ourselves;
215 // nsLineLayout::BeginSpan will set it in the other case
216 mBaseline
= kidMetrics
.BlockStartAscent();
218 // Place and size the child and update the output metrics
219 LogicalSize convertedSize
= kidMetrics
.Size(lineWM
).ConvertTo(wm
, lineWM
);
220 kid
->SetRect(nsRect(bp
.IStart(wm
), bp
.BStart(wm
),
221 convertedSize
.ISize(wm
), convertedSize
.BSize(wm
)));
222 kid
->FinishAndStoreOverflow(&kidMetrics
);
223 kid
->DidReflow(aPresContext
, nullptr, nsDidReflowStatus::FINISHED
);
225 convertedSize
.ISize(wm
) += bp
.IStartEnd(wm
);
226 convertedSize
.BSize(wm
) += bp
.BStartEnd(wm
);
227 aMetrics
.SetSize(wm
, convertedSize
);
228 aMetrics
.SetBlockStartAscent(kidMetrics
.BlockStartAscent() +
232 // Pretend we are a span and reflow the child frame
233 nsLineLayout
* ll
= aReflowState
.mLineLayout
;
236 ll
->SetInFirstLetter(
237 mStyleContext
->GetPseudo() == nsCSSPseudoElements::firstLetter
);
238 ll
->BeginSpan(this, &aReflowState
, bp
.IStart(wm
),
239 availSize
.ISize(wm
), &mBaseline
);
240 ll
->ReflowFrame(kid
, aReflowStatus
, &kidMetrics
, pushedFrame
);
241 NS_ASSERTION(lineWM
.IsVertical() == wm
.IsVertical(),
242 "we're assuming we can mix sizes between lineWM and wm "
243 "since we shouldn't have orthogonal writing modes within "
245 aMetrics
.ISize(lineWM
) = ll
->EndSpan(this) + bp
.IStartEnd(wm
);
246 ll
->SetInFirstLetter(false);
248 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics
, aReflowState
,
252 // Ensure that the overflow rect contains the child textframe's overflow rect.
253 // Note that if this is floating, the overline/underline drawable area is in
254 // the overflow rect of the child textframe.
255 aMetrics
.UnionOverflowAreasWithDesiredBounds();
256 ConsiderChildOverflow(aMetrics
.mOverflowAreas
, kid
);
258 if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus
)) {
259 // Create a continuation or remove existing continuations based on
260 // the reflow completion status.
261 if (NS_FRAME_IS_COMPLETE(aReflowStatus
)) {
262 if (aReflowState
.mLineLayout
) {
263 aReflowState
.mLineLayout
->SetFirstLetterStyleOK(false);
265 nsIFrame
* kidNextInFlow
= kid
->GetNextInFlow();
267 // Remove all of the childs next-in-flows
268 kidNextInFlow
->GetParent()->DeleteNextInFlowChild(kidNextInFlow
, true);
272 // Create a continuation for the child frame if it doesn't already
275 nsIFrame
* nextInFlow
;
276 rv
= CreateNextInFlow(kid
, nextInFlow
);
281 // And then push it to our overflow list
282 const nsFrameList
& overflow
= mFrames
.RemoveFramesAfter(kid
);
283 if (overflow
.NotEmpty()) {
284 SetOverflowFrames(overflow
);
286 } else if (!kid
->GetNextInFlow()) {
287 // For floating first letter frames (if a continuation wasn't already
288 // created for us) we need to put the continuation with the rest of the
289 // text that the first letter frame was made out of.
290 nsIFrame
* continuation
;
291 CreateContinuationForFloatingParent(aPresContext
, kid
,
292 &continuation
, true);
297 FinishAndStoreOverflow(&aMetrics
);
299 NS_FRAME_SET_TRUNCATION(aReflowStatus
, aReflowState
, aMetrics
);
303 nsFirstLetterFrame::CanContinueTextRun() const
305 // We can continue a text run through a first-letter frame.
310 nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext
* aPresContext
,
312 nsIFrame
** aContinuation
,
315 NS_ASSERTION(IsFloating(),
316 "can only call this on floating first letter frames");
317 NS_PRECONDITION(aContinuation
, "bad args");
319 *aContinuation
= nullptr;
321 nsIPresShell
* presShell
= aPresContext
->PresShell();
322 nsPlaceholderFrame
* placeholderFrame
=
323 presShell
->FrameManager()->GetPlaceholderFrameFor(this);
324 nsContainerFrame
* parent
= placeholderFrame
->GetParent();
326 nsIFrame
* continuation
= presShell
->FrameConstructor()->
327 CreateContinuingFrame(aPresContext
, aChild
, parent
, aIsFluid
);
329 // The continuation will have gotten the first letter style from its
330 // prev continuation, so we need to repair the style context so it
331 // doesn't have the first letter styling.
332 nsStyleContext
* parentSC
= this->StyleContext()->GetParent();
334 nsRefPtr
<nsStyleContext
> newSC
;
335 newSC
= presShell
->StyleSet()->ResolveStyleForNonElement(parentSC
);
336 continuation
->SetStyleContext(newSC
);
337 nsLayoutUtils::MarkDescendantsDirty(continuation
);
340 //XXX Bidi may not be involved but we have to use the list name
341 // kNoReflowPrincipalList because this is just like creating a continuation
342 // except we have to insert it in a different place and we don't want a
343 // reflow command to try to be issued.
344 nsFrameList
temp(continuation
, continuation
);
345 parent
->InsertFrames(kNoReflowPrincipalList
, placeholderFrame
, temp
);
347 *aContinuation
= continuation
;
352 nsFirstLetterFrame::DrainOverflowFrames(nsPresContext
* aPresContext
)
354 // Check for an overflow list with our prev-in-flow
355 nsFirstLetterFrame
* prevInFlow
= (nsFirstLetterFrame
*)GetPrevInFlow();
357 AutoFrameListPtr
overflowFrames(aPresContext
,
358 prevInFlow
->StealOverflowFrames());
359 if (overflowFrames
) {
360 NS_ASSERTION(mFrames
.IsEmpty(), "bad overflow list");
362 // When pushing and pulling frames we need to check for whether any
363 // views need to be reparented.
364 nsContainerFrame::ReparentFrameViewList(*overflowFrames
, prevInFlow
,
366 mFrames
.InsertFrames(this, nullptr, *overflowFrames
);
370 // It's also possible that we have an overflow list for ourselves
371 AutoFrameListPtr
overflowFrames(aPresContext
, StealOverflowFrames());
372 if (overflowFrames
) {
373 NS_ASSERTION(mFrames
.NotEmpty(), "overflow list w/o frames");
374 mFrames
.AppendFrames(nullptr, *overflowFrames
);
377 // Now repair our first frames style context (since we only reflow
378 // one frame there is no point in doing any other ones until they
380 nsIFrame
* kid
= mFrames
.FirstChild();
382 nsRefPtr
<nsStyleContext
> sc
;
383 nsIContent
* kidContent
= kid
->GetContent();
385 NS_ASSERTION(kidContent
->IsNodeOfType(nsINode::eTEXT
),
386 "should contain only text nodes");
387 nsStyleContext
* parentSC
= prevInFlow
? mStyleContext
->GetParent() :
389 sc
= aPresContext
->StyleSet()->ResolveStyleForNonElement(parentSC
);
390 kid
->SetStyleContext(sc
);
391 nsLayoutUtils::MarkDescendantsDirty(kid
);
397 nsFirstLetterFrame::GetLogicalBaseline(WritingMode aWritingMode
) const
402 nsIFrame::LogicalSides
403 nsFirstLetterFrame::GetLogicalSkipSides(const nsHTMLReflowState
* aReflowState
) const
405 if (GetPrevContinuation()) {
406 // We shouldn't get calls to GetSkipSides for later continuations since
407 // they have separate style contexts with initial values for all the
408 // properties that could trigger a call to GetSkipSides. Then again,
409 // it's not really an error to call GetSkipSides on any frame, so
410 // that's why we handle it properly.
411 return LogicalSides(eLogicalSideBitsAll
);
413 return LogicalSides(); // first continuation displays all sides