1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/. */
7 /* rendering object for CSS :first-letter pseudo-element */
9 #include "nsFirstLetterFrame.h"
10 #include "nsPresContext.h"
11 #include "nsPresContextInlines.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/PresShellInlines.h"
15 #include "mozilla/RestyleManager.h"
16 #include "mozilla/ServoStyleSet.h"
17 #include "mozilla/StaticPrefs_layout.h"
18 #include "nsIContent.h"
19 #include "nsLayoutUtils.h"
20 #include "nsLineLayout.h"
21 #include "nsGkAtoms.h"
22 #include "nsFrameManager.h"
23 #include "nsPlaceholderFrame.h"
24 #include "nsTextFrame.h"
25 #include "nsCSSFrameConstructor.h"
27 using namespace mozilla
;
28 using namespace mozilla::layout
;
30 nsFirstLetterFrame
* NS_NewFirstLetterFrame(PresShell
* aPresShell
,
31 ComputedStyle
* aStyle
) {
32 return new (aPresShell
)
33 nsFirstLetterFrame(aStyle
, aPresShell
->GetPresContext());
36 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame
)
38 NS_QUERYFRAME_HEAD(nsFirstLetterFrame
)
39 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame
)
40 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
42 #ifdef DEBUG_FRAME_DUMP
43 nsresult
nsFirstLetterFrame::GetFrameName(nsAString
& aResult
) const {
44 return MakeFrameName(u
"Letter"_ns
, aResult
);
48 void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
49 const nsDisplayListSet
& aLists
) {
50 BuildDisplayListForInline(aBuilder
, aLists
);
53 void nsFirstLetterFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
54 nsIFrame
* aPrevInFlow
) {
55 RefPtr
<ComputedStyle
> newSC
;
57 // Get proper ComputedStyle for ourselves. We're creating the frame
58 // that represents everything *except* the first letter, so just create
59 // a ComputedStyle that inherits from our style parent, with no extra rules.
60 nsIFrame
* styleParent
=
61 CorrectStyleParentFrame(aParent
, PseudoStyleType::firstLetter
);
62 ComputedStyle
* parentComputedStyle
= styleParent
->Style();
63 newSC
= PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation(
65 SetComputedStyleWithoutNotification(newSC
);
68 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
71 void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID
,
72 nsFrameList
&& aChildList
) {
73 MOZ_ASSERT(aListID
== FrameChildListID::Principal
,
74 "Principal child list is the only "
75 "list that nsFirstLetterFrame should set via this function");
76 for (nsIFrame
* f
: aChildList
) {
77 MOZ_ASSERT(f
->GetParent() == this, "Unexpected parent");
78 MOZ_ASSERT(f
->IsTextFrame(),
79 "We should not have kids that are containers!");
80 nsLayoutUtils::MarkDescendantsDirty(f
); // Drops cached textruns
83 mFrames
= std::move(aChildList
);
86 nsresult
nsFirstLetterFrame::GetChildFrameContainingOffset(
87 int32_t inContentOffset
, bool inHint
, int32_t* outFrameContentOffset
,
88 nsIFrame
** outChildFrame
) {
89 nsIFrame
* kid
= mFrames
.FirstChild();
91 return kid
->GetChildFrameContainingOffset(
92 inContentOffset
, inHint
, outFrameContentOffset
, outChildFrame
);
94 return nsIFrame::GetChildFrameContainingOffset(
95 inContentOffset
, inHint
, outFrameContentOffset
, outChildFrame
);
98 // Needed for non-floating first-letter frames and for the continuations
99 // following the first-letter that we also use nsFirstLetterFrame for.
101 void nsFirstLetterFrame::AddInlineMinISize(
102 gfxContext
* aRenderingContext
, nsIFrame::InlineMinISizeData
* aData
) {
103 DoInlineMinISize(aRenderingContext
, aData
);
106 // Needed for non-floating first-letter frames and for the continuations
107 // following the first-letter that we also use nsFirstLetterFrame for.
109 void nsFirstLetterFrame::AddInlinePrefISize(
110 gfxContext
* aRenderingContext
, nsIFrame::InlinePrefISizeData
* aData
) {
111 DoInlinePrefISize(aRenderingContext
, aData
);
114 // Needed for floating first-letter frames.
116 nscoord
nsFirstLetterFrame::GetMinISize(gfxContext
* aRenderingContext
) {
117 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext
);
120 // Needed for floating first-letter frames.
122 nscoord
nsFirstLetterFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
123 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext
);
127 nsIFrame::SizeComputationResult
nsFirstLetterFrame::ComputeSize(
128 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
129 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
130 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
131 ComputeSizeFlags aFlags
) {
132 if (GetPrevInFlow()) {
133 // We're wrapping the text *after* the first letter, so behave like an
135 return {LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
),
136 AspectRatioUsage::None
};
138 return nsContainerFrame::ComputeSize(aRenderingContext
, aWM
, aCBSize
,
139 aAvailableISize
, aMargin
, aBorderPadding
,
140 aSizeOverrides
, aFlags
);
143 bool nsFirstLetterFrame::UseTightBounds() const {
144 int v
= StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds();
146 // Check for the simple cases:
147 // pref value > 0: use legacy gecko behavior
148 // pref value = 0: use webkit/blink-like behavior
156 // Pref value < 0: use heuristics to determine whether the page is assuming
157 // webkit/blink-style behavior:
158 // If line-height is less than font-size, or there is a negative block-start
159 // or -end margin, use webkit/blink behavior.
160 if (nsTextFrame
* textFrame
= do_QueryFrame(mFrames
.FirstChild())) {
161 RefPtr
<nsFontMetrics
> fm
= textFrame
->InflatedFontMetrics();
162 if (textFrame
->ComputeLineHeight() < fm
->EmHeight()) {
167 const auto wm
= GetWritingMode();
168 const auto& margin
= StyleMargin()->mMargin
;
169 const auto& bStart
= margin
.GetBStart(wm
);
170 // Currently, we only check for margins with negative *length* values;
171 // negative percentages seem unlikely to be used/useful in this context.
172 if (bStart
.ConvertsToLength() && bStart
.ToLength() < 0) {
175 const auto& bEnd
= margin
.GetBEnd(wm
);
176 if (bEnd
.ConvertsToLength() && bEnd
.ToLength() < 0) {
183 void nsFirstLetterFrame::Reflow(nsPresContext
* aPresContext
,
184 ReflowOutput
& aMetrics
,
185 const ReflowInput
& aReflowInput
,
186 nsReflowStatus
& aReflowStatus
) {
188 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
189 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aMetrics
, aReflowStatus
);
190 MOZ_ASSERT(aReflowStatus
.IsEmpty(),
191 "Caller should pass a fresh reflow status!");
193 // Grab overflow list
194 DrainOverflowFrames(aPresContext
);
196 nsIFrame
* kid
= mFrames
.FirstChild();
198 // Setup reflow input for our child
199 WritingMode wm
= aReflowInput
.GetWritingMode();
200 LogicalSize availSize
= aReflowInput
.AvailableSize();
201 const auto bp
= aReflowInput
.ComputedLogicalBorderPadding(wm
);
202 NS_ASSERTION(availSize
.ISize(wm
) != NS_UNCONSTRAINEDSIZE
,
203 "should no longer use unconstrained inline size");
204 availSize
.ISize(wm
) -= bp
.IStartEnd(wm
);
205 if (NS_UNCONSTRAINEDSIZE
!= availSize
.BSize(wm
)) {
206 availSize
.BSize(wm
) -= bp
.BStartEnd(wm
);
209 WritingMode lineWM
= aMetrics
.GetWritingMode();
210 ReflowOutput
kidMetrics(lineWM
);
213 if (!aReflowInput
.mLineLayout
) {
214 // When there is no lineLayout provided, we provide our own. The
215 // only time that the first-letter-frame is not reflowing in a
216 // line context is when its floating.
217 WritingMode kidWritingMode
= WritingModeForLine(wm
, kid
);
218 LogicalSize kidAvailSize
= availSize
.ConvertTo(kidWritingMode
, wm
);
219 ReflowInput
rs(aPresContext
, aReflowInput
, kid
, kidAvailSize
);
220 nsLineLayout
ll(aPresContext
, nullptr, aReflowInput
, nullptr, nullptr);
223 bp
.IStart(wm
), bp
.BStart(wm
), availSize
.ISize(wm
), NS_UNCONSTRAINEDSIZE
,
224 false, true, kidWritingMode
,
225 nsSize(aReflowInput
.AvailableWidth(), aReflowInput
.AvailableHeight()));
226 rs
.mLineLayout
= &ll
;
227 ll
.SetInFirstLetter(true);
228 ll
.SetFirstLetterStyleOK(true);
230 kid
->Reflow(aPresContext
, kidMetrics
, rs
, aReflowStatus
);
233 ll
.SetInFirstLetter(false);
235 // In the floating first-letter case, we need to set this ourselves;
236 // nsLineLayout::BeginSpan will set it in the other case
237 mBaseline
= kidMetrics
.BlockStartAscent();
239 // Place and size the child and update the output metrics
240 LogicalSize convertedSize
= kidMetrics
.Size(wm
);
242 const bool tightBounds
= UseTightBounds();
243 const nscoord shift
=
245 // Shift by half of the difference between the line-height
246 // we're going to use and current height of the kid frame.
247 : (rs
.GetLineHeight() - convertedSize
.BSize(wm
)) / 2;
249 kid
->SetRect(nsRect(bp
.IStart(wm
), bp
.BStart(wm
) + shift
,
250 convertedSize
.ISize(wm
), convertedSize
.BSize(wm
)));
251 kid
->FinishAndStoreOverflow(&kidMetrics
, rs
.mStyleDisplay
);
252 kid
->DidReflow(aPresContext
, nullptr);
255 // Adjust size to account for line-height.
256 convertedSize
.BSize(wm
) = rs
.GetLineHeight();
259 convertedSize
.ISize(wm
) += bp
.IStartEnd(wm
);
260 convertedSize
.BSize(wm
) += bp
.BStartEnd(wm
);
261 aMetrics
.SetSize(wm
, convertedSize
);
262 aMetrics
.SetBlockStartAscent(kidMetrics
.BlockStartAscent() + bp
.BStart(wm
));
264 // Ensure that the overflow rect contains the child textframe's
266 // Note that if this is floating, the overline/underline drawable
267 // area is in the overflow rect of the child textframe.
268 aMetrics
.UnionOverflowAreasWithDesiredBounds();
269 ConsiderChildOverflow(aMetrics
.mOverflowAreas
, kid
);
271 FinishAndStoreOverflow(&aMetrics
, aReflowInput
.mStyleDisplay
);
273 // Pretend we are a span and reflow the child frame
274 nsLineLayout
* ll
= aReflowInput
.mLineLayout
;
277 ll
->SetInFirstLetter(Style()->GetPseudoType() ==
278 PseudoStyleType::firstLetter
);
279 ll
->BeginSpan(this, &aReflowInput
, bp
.IStart(wm
), availSize
.ISize(wm
),
281 ll
->ReflowFrame(kid
, aReflowStatus
, &kidMetrics
, pushedFrame
);
282 NS_ASSERTION(lineWM
.IsVertical() == wm
.IsVertical(),
283 "we're assuming we can mix sizes between lineWM and wm "
284 "since we shouldn't have orthogonal writing modes within "
286 aMetrics
.ISize(lineWM
) = ll
->EndSpan(this) + bp
.IStartEnd(wm
);
287 ll
->SetInFirstLetter(false);
289 if (mComputedStyle
->StyleTextReset()->mInitialLetterSize
!= 0.0f
) {
290 aMetrics
.SetBlockStartAscent(kidMetrics
.BlockStartAscent() +
292 aMetrics
.BSize(lineWM
) = kidMetrics
.BSize(lineWM
) + bp
.BStartEnd(wm
);
294 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics
, bp
, lineWM
, wm
);
298 if (!aReflowStatus
.IsInlineBreakBefore()) {
299 // Create a continuation or remove existing continuations based on
300 // the reflow completion status.
301 if (aReflowStatus
.IsComplete()) {
302 if (aReflowInput
.mLineLayout
) {
303 aReflowInput
.mLineLayout
->SetFirstLetterStyleOK(false);
305 if (nsIFrame
* kidNextInFlow
= kid
->GetNextInFlow()) {
306 DestroyContext
context(PresShell());
307 // Remove all of the childs next-in-flows
308 kidNextInFlow
->GetParent()->DeleteNextInFlowChild(context
,
309 kidNextInFlow
, true);
312 // Create a continuation for the child frame if it doesn't already
315 CreateNextInFlow(kid
);
316 // And then push it to our overflow list
317 nsFrameList overflow
= mFrames
.TakeFramesAfter(kid
);
318 if (overflow
.NotEmpty()) {
319 SetOverflowFrames(std::move(overflow
));
321 } else if (!kid
->GetNextInFlow()) {
322 // For floating first letter frames (if a continuation wasn't already
323 // created for us) we need to put the continuation with the rest of the
324 // text that the first letter frame was made out of.
325 nsIFrame
* continuation
;
326 CreateContinuationForFloatingParent(kid
, &continuation
, true);
333 bool nsFirstLetterFrame::CanContinueTextRun() const {
334 // We can continue a text run through a first-letter frame.
338 void nsFirstLetterFrame::CreateContinuationForFloatingParent(
339 nsIFrame
* aChild
, nsIFrame
** aContinuation
, bool aIsFluid
) {
340 NS_ASSERTION(IsFloating(),
341 "can only call this on floating first letter frames");
342 MOZ_ASSERT(aContinuation
, "bad args");
344 *aContinuation
= nullptr;
346 mozilla::PresShell
* presShell
= PresShell();
347 nsPlaceholderFrame
* placeholderFrame
= GetPlaceholderFrame();
348 nsContainerFrame
* parent
= placeholderFrame
->GetParent();
350 nsIFrame
* continuation
= presShell
->FrameConstructor()->CreateContinuingFrame(
351 aChild
, parent
, aIsFluid
);
353 // The continuation will have gotten the first letter style from its
354 // prev continuation, so we need to repair the ComputedStyle so it
355 // doesn't have the first letter styling.
357 // Note that getting parent frame's ComputedStyle is different from getting
358 // this frame's ComputedStyle's parent in the presence of ::first-line,
359 // which we do want the continuation to inherit from.
360 ComputedStyle
* parentSC
= parent
->Style();
362 RefPtr
<ComputedStyle
> newSC
;
364 presShell
->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC
);
365 continuation
->SetComputedStyle(newSC
);
366 nsLayoutUtils::MarkDescendantsDirty(continuation
);
369 // XXX Bidi may not be involved but we have to use the list name
370 // FrameChildListID::NoReflowPrincipal because this is just like creating a
371 // continuation except we have to insert it in a different place and we don't
372 // want a reflow command to try to be issued.
373 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, placeholderFrame
,
374 nullptr, nsFrameList(continuation
, continuation
));
376 *aContinuation
= continuation
;
379 void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext
* aPresContext
) {
380 // Check for an overflow list with our prev-in-flow
381 nsFirstLetterFrame
* prevInFlow
= (nsFirstLetterFrame
*)GetPrevInFlow();
383 AutoFrameListPtr
overflowFrames(aPresContext
,
384 prevInFlow
->StealOverflowFrames());
385 if (overflowFrames
) {
386 NS_ASSERTION(mFrames
.IsEmpty(), "bad overflow list");
388 // When pushing and pulling frames we need to check for whether any
389 // views need to be reparented.
390 nsContainerFrame::ReparentFrameViewList(*overflowFrames
, prevInFlow
,
392 mFrames
.InsertFrames(this, nullptr, std::move(*overflowFrames
));
396 // It's also possible that we have an overflow list for ourselves
397 AutoFrameListPtr
overflowFrames(aPresContext
, StealOverflowFrames());
398 if (overflowFrames
) {
399 NS_ASSERTION(mFrames
.NotEmpty(), "overflow list w/o frames");
400 mFrames
.AppendFrames(nullptr, std::move(*overflowFrames
));
403 // Now repair our first frames ComputedStyle (since we only reflow
404 // one frame there is no point in doing any other ones until they
406 nsIFrame
* kid
= mFrames
.FirstChild();
408 nsIContent
* kidContent
= kid
->GetContent();
410 NS_ASSERTION(kidContent
->IsText(), "should contain only text nodes");
411 ComputedStyle
* parentSC
;
413 // This is for the rest of the content not in the first-letter.
414 nsIFrame
* styleParent
=
415 CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter
);
416 parentSC
= styleParent
->Style();
418 // And this for the first-letter style.
419 parentSC
= mComputedStyle
;
421 RefPtr
<ComputedStyle
> sc
=
422 aPresContext
->StyleSet()->ResolveStyleForText(kidContent
, parentSC
);
423 kid
->SetComputedStyle(sc
);
424 nsLayoutUtils::MarkDescendantsDirty(kid
);
429 Maybe
<nscoord
> nsFirstLetterFrame::GetNaturalBaselineBOffset(
430 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
431 BaselineExportContext
) const {
432 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
435 return Some(mBaseline
);
438 LogicalSides
nsFirstLetterFrame::GetLogicalSkipSides() const {
439 if (GetPrevContinuation()) {
440 // We shouldn't get calls to GetSkipSides for later continuations since
441 // they have separate ComputedStyles with initial values for all the
442 // properties that could trigger a call to GetSkipSides. Then again,
443 // it's not really an error to call GetSkipSides on any frame, so
444 // that's why we handle it properly.
445 return LogicalSides(mWritingMode
, eLogicalSideBitsAll
);
447 return LogicalSides(mWritingMode
); // first continuation displays all sides