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 nsFirstLetterFrame
* NS_NewFloatingFirstLetterFrame(PresShell
* aPresShell
,
37 ComputedStyle
* aStyle
) {
38 return new (aPresShell
)
39 nsFloatingFirstLetterFrame(aStyle
, aPresShell
->GetPresContext());
42 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame
)
44 NS_QUERYFRAME_HEAD(nsFirstLetterFrame
)
45 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame
)
46 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
48 NS_IMPL_FRAMEARENA_HELPERS(nsFloatingFirstLetterFrame
)
49 NS_QUERYFRAME_HEAD(nsFloatingFirstLetterFrame
)
50 NS_QUERYFRAME_ENTRY(nsFloatingFirstLetterFrame
)
51 NS_QUERYFRAME_TAIL_INHERITING(nsFirstLetterFrame
)
53 #ifdef DEBUG_FRAME_DUMP
54 nsresult
nsFirstLetterFrame::GetFrameName(nsAString
& aResult
) const {
55 return MakeFrameName(u
"Letter"_ns
, aResult
);
59 void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
60 const nsDisplayListSet
& aLists
) {
61 BuildDisplayListForInline(aBuilder
, aLists
);
64 void nsFirstLetterFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
65 nsIFrame
* aPrevInFlow
) {
66 RefPtr
<ComputedStyle
> newSC
;
68 // Get proper ComputedStyle for ourselves. We're creating the frame
69 // that represents everything *except* the first letter, so just create
70 // a ComputedStyle that inherits from our style parent, with no extra rules.
71 nsIFrame
* styleParent
=
72 CorrectStyleParentFrame(aParent
, PseudoStyleType::firstLetter
);
73 ComputedStyle
* parentComputedStyle
= styleParent
->Style();
74 newSC
= PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation(
76 SetComputedStyleWithoutNotification(newSC
);
79 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
82 void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID
,
83 nsFrameList
&& aChildList
) {
84 MOZ_ASSERT(aListID
== FrameChildListID::Principal
,
85 "Principal child list is the only "
86 "list that nsFirstLetterFrame should set via this function");
87 for (nsIFrame
* f
: aChildList
) {
88 MOZ_ASSERT(f
->GetParent() == this, "Unexpected parent");
89 MOZ_ASSERT(f
->IsTextFrame(),
90 "We should not have kids that are containers!");
91 nsLayoutUtils::MarkDescendantsDirty(f
); // Drops cached textruns
94 mFrames
= std::move(aChildList
);
97 nsresult
nsFirstLetterFrame::GetChildFrameContainingOffset(
98 int32_t inContentOffset
, bool inHint
, int32_t* outFrameContentOffset
,
99 nsIFrame
** outChildFrame
) {
100 nsIFrame
* kid
= mFrames
.FirstChild();
102 return kid
->GetChildFrameContainingOffset(
103 inContentOffset
, inHint
, outFrameContentOffset
, outChildFrame
);
105 return nsIFrame::GetChildFrameContainingOffset(
106 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 void nsFirstLetterFrame::AddInlineMinISize(
113 gfxContext
* aRenderingContext
, nsIFrame::InlineMinISizeData
* aData
) {
114 DoInlineMinISize(aRenderingContext
, aData
);
117 // Needed for non-floating first-letter frames and for the continuations
118 // following the first-letter that we also use nsFirstLetterFrame for.
120 void nsFirstLetterFrame::AddInlinePrefISize(
121 gfxContext
* aRenderingContext
, nsIFrame::InlinePrefISizeData
* aData
) {
122 DoInlinePrefISize(aRenderingContext
, aData
);
125 // Needed for floating first-letter frames.
127 nscoord
nsFirstLetterFrame::GetMinISize(gfxContext
* aRenderingContext
) {
128 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext
);
131 // Needed for floating first-letter frames.
133 nscoord
nsFirstLetterFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
134 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext
);
138 nsIFrame::SizeComputationResult
nsFirstLetterFrame::ComputeSize(
139 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
140 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
141 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
142 ComputeSizeFlags aFlags
) {
143 if (GetPrevInFlow()) {
144 // We're wrapping the text *after* the first letter, so behave like an
146 return {LogicalSize(aWM
, NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
),
147 AspectRatioUsage::None
};
149 return nsContainerFrame::ComputeSize(aRenderingContext
, aWM
, aCBSize
,
150 aAvailableISize
, aMargin
, aBorderPadding
,
151 aSizeOverrides
, aFlags
);
154 bool nsFirstLetterFrame::UseTightBounds() const {
155 int v
= StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds();
157 // Check for the simple cases:
158 // pref value > 0: use legacy gecko behavior
159 // pref value = 0: use webkit/blink-like behavior
167 // Pref value < 0: use heuristics to determine whether the page is assuming
168 // webkit/blink-style behavior:
169 // If line-height is less than font-size, or there is a negative block-start
170 // or -end margin, use webkit/blink behavior.
171 if (nsTextFrame
* textFrame
= do_QueryFrame(mFrames
.FirstChild())) {
172 RefPtr
<nsFontMetrics
> fm
= textFrame
->InflatedFontMetrics();
173 if (textFrame
->ComputeLineHeight() < fm
->EmHeight()) {
178 const auto wm
= GetWritingMode();
179 const auto& margin
= StyleMargin()->mMargin
;
180 const auto& bStart
= margin
.GetBStart(wm
);
181 // Currently, we only check for margins with negative *length* values;
182 // negative percentages seem unlikely to be used/useful in this context.
183 if (bStart
.ConvertsToLength() && bStart
.ToLength() < 0) {
186 const auto& bEnd
= margin
.GetBEnd(wm
);
187 if (bEnd
.ConvertsToLength() && bEnd
.ToLength() < 0) {
194 void nsFirstLetterFrame::Reflow(nsPresContext
* aPresContext
,
195 ReflowOutput
& aMetrics
,
196 const ReflowInput
& aReflowInput
,
197 nsReflowStatus
& aReflowStatus
) {
199 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
200 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aMetrics
, aReflowStatus
);
201 MOZ_ASSERT(aReflowStatus
.IsEmpty(),
202 "Caller should pass a fresh reflow status!");
204 // Grab overflow list
205 DrainOverflowFrames(aPresContext
);
207 nsIFrame
* kid
= mFrames
.FirstChild();
209 // Setup reflow input for our child
210 WritingMode wm
= aReflowInput
.GetWritingMode();
211 LogicalSize availSize
= aReflowInput
.AvailableSize();
212 const auto bp
= aReflowInput
.ComputedLogicalBorderPadding(wm
);
213 NS_ASSERTION(availSize
.ISize(wm
) != NS_UNCONSTRAINEDSIZE
,
214 "should no longer use unconstrained inline size");
215 availSize
.ISize(wm
) -= bp
.IStartEnd(wm
);
216 if (NS_UNCONSTRAINEDSIZE
!= availSize
.BSize(wm
)) {
217 availSize
.BSize(wm
) -= bp
.BStartEnd(wm
);
220 WritingMode lineWM
= aMetrics
.GetWritingMode();
221 ReflowOutput
kidMetrics(lineWM
);
224 if (!aReflowInput
.mLineLayout
) {
225 // When there is no lineLayout provided, we provide our own. The
226 // only time that the first-letter-frame is not reflowing in a
227 // line context is when its floating.
228 WritingMode kidWritingMode
= WritingModeForLine(wm
, kid
);
229 LogicalSize kidAvailSize
= availSize
.ConvertTo(kidWritingMode
, wm
);
230 ReflowInput
rs(aPresContext
, aReflowInput
, kid
, kidAvailSize
);
231 nsLineLayout
ll(aPresContext
, nullptr, aReflowInput
, nullptr, nullptr);
234 bp
.IStart(wm
), bp
.BStart(wm
), availSize
.ISize(wm
), NS_UNCONSTRAINEDSIZE
,
235 false, true, kidWritingMode
,
236 nsSize(aReflowInput
.AvailableWidth(), aReflowInput
.AvailableHeight()));
237 rs
.mLineLayout
= &ll
;
238 ll
.SetInFirstLetter(true);
239 ll
.SetFirstLetterStyleOK(true);
241 kid
->Reflow(aPresContext
, kidMetrics
, rs
, aReflowStatus
);
244 ll
.SetInFirstLetter(false);
246 // In the floating first-letter case, we need to set this ourselves;
247 // nsLineLayout::BeginSpan will set it in the other case
248 mBaseline
= kidMetrics
.BlockStartAscent();
250 // Place and size the child and update the output metrics
251 LogicalSize convertedSize
= kidMetrics
.Size(wm
);
253 const bool tightBounds
= UseTightBounds();
254 const nscoord shift
=
256 // Shift by half of the difference between the line-height
257 // we're going to use and current height of the kid frame.
258 : (rs
.GetLineHeight() - convertedSize
.BSize(wm
)) / 2;
260 kid
->SetRect(nsRect(bp
.IStart(wm
), bp
.BStart(wm
) + shift
,
261 convertedSize
.ISize(wm
), convertedSize
.BSize(wm
)));
262 kid
->FinishAndStoreOverflow(&kidMetrics
, rs
.mStyleDisplay
);
263 kid
->DidReflow(aPresContext
, nullptr);
266 // Adjust size to account for line-height.
267 convertedSize
.BSize(wm
) = rs
.GetLineHeight();
270 convertedSize
.ISize(wm
) += bp
.IStartEnd(wm
);
271 convertedSize
.BSize(wm
) += bp
.BStartEnd(wm
);
272 aMetrics
.SetSize(wm
, convertedSize
);
273 aMetrics
.SetBlockStartAscent(kidMetrics
.BlockStartAscent() + bp
.BStart(wm
));
275 // Ensure that the overflow rect contains the child textframe's
277 // Note that if this is floating, the overline/underline drawable
278 // area is in the overflow rect of the child textframe.
279 aMetrics
.UnionOverflowAreasWithDesiredBounds();
280 ConsiderChildOverflow(aMetrics
.mOverflowAreas
, kid
);
282 FinishAndStoreOverflow(&aMetrics
, aReflowInput
.mStyleDisplay
);
284 // Pretend we are a span and reflow the child frame
285 nsLineLayout
* ll
= aReflowInput
.mLineLayout
;
288 ll
->SetInFirstLetter(Style()->GetPseudoType() ==
289 PseudoStyleType::firstLetter
);
290 ll
->BeginSpan(this, &aReflowInput
, bp
.IStart(wm
), availSize
.ISize(wm
),
292 ll
->ReflowFrame(kid
, aReflowStatus
, &kidMetrics
, pushedFrame
);
293 NS_ASSERTION(lineWM
.IsVertical() == wm
.IsVertical(),
294 "we're assuming we can mix sizes between lineWM and wm "
295 "since we shouldn't have orthogonal writing modes within "
297 aMetrics
.ISize(lineWM
) = ll
->EndSpan(this) + bp
.IStartEnd(wm
);
298 ll
->SetInFirstLetter(false);
300 if (mComputedStyle
->StyleTextReset()->mInitialLetterSize
!= 0.0f
) {
301 aMetrics
.SetBlockStartAscent(kidMetrics
.BlockStartAscent() +
303 aMetrics
.BSize(lineWM
) = kidMetrics
.BSize(lineWM
) + bp
.BStartEnd(wm
);
305 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics
, bp
, lineWM
, wm
);
309 if (!aReflowStatus
.IsInlineBreakBefore()) {
310 // Create a continuation or remove existing continuations based on
311 // the reflow completion status.
312 if (aReflowStatus
.IsComplete()) {
313 if (aReflowInput
.mLineLayout
) {
314 aReflowInput
.mLineLayout
->SetFirstLetterStyleOK(false);
316 if (nsIFrame
* kidNextInFlow
= kid
->GetNextInFlow()) {
317 DestroyContext
context(PresShell());
318 // Remove all of the childs next-in-flows
319 kidNextInFlow
->GetParent()->DeleteNextInFlowChild(context
,
320 kidNextInFlow
, true);
323 // Create a continuation for the child frame if it doesn't already
326 CreateNextInFlow(kid
);
327 // And then push it to our overflow list
328 nsFrameList overflow
= mFrames
.TakeFramesAfter(kid
);
329 if (overflow
.NotEmpty()) {
330 SetOverflowFrames(std::move(overflow
));
332 } else if (!kid
->GetNextInFlow()) {
333 // For floating first letter frames (if a continuation wasn't already
334 // created for us) we need to put the continuation with the rest of the
335 // text that the first letter frame was made out of.
336 nsIFrame
* continuation
;
337 CreateContinuationForFloatingParent(kid
, &continuation
, true);
344 bool nsFirstLetterFrame::CanContinueTextRun() const {
345 // We can continue a text run through a first-letter frame.
349 void nsFirstLetterFrame::CreateContinuationForFloatingParent(
350 nsIFrame
* aChild
, nsIFrame
** aContinuation
, bool aIsFluid
) {
351 NS_ASSERTION(IsFloating(),
352 "can only call this on floating first letter frames");
353 MOZ_ASSERT(aContinuation
, "bad args");
355 *aContinuation
= nullptr;
357 mozilla::PresShell
* presShell
= PresShell();
358 nsPlaceholderFrame
* placeholderFrame
= GetPlaceholderFrame();
359 nsContainerFrame
* parent
= placeholderFrame
->GetParent();
361 nsIFrame
* continuation
= presShell
->FrameConstructor()->CreateContinuingFrame(
362 aChild
, parent
, aIsFluid
);
364 // The continuation will have gotten the first letter style from its
365 // prev continuation, so we need to repair the ComputedStyle so it
366 // doesn't have the first letter styling.
368 // Note that getting parent frame's ComputedStyle is different from getting
369 // this frame's ComputedStyle's parent in the presence of ::first-line,
370 // which we do want the continuation to inherit from.
371 ComputedStyle
* parentSC
= parent
->Style();
373 RefPtr
<ComputedStyle
> newSC
;
375 presShell
->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC
);
376 continuation
->SetComputedStyle(newSC
);
377 nsLayoutUtils::MarkDescendantsDirty(continuation
);
380 // XXX Bidi may not be involved but we have to use the list name
381 // FrameChildListID::NoReflowPrincipal because this is just like creating a
382 // continuation except we have to insert it in a different place and we don't
383 // want a reflow command to try to be issued.
384 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, placeholderFrame
,
385 nullptr, nsFrameList(continuation
, continuation
));
387 *aContinuation
= continuation
;
390 void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext
* aPresContext
) {
391 // Check for an overflow list with our prev-in-flow
392 nsFirstLetterFrame
* prevInFlow
= (nsFirstLetterFrame
*)GetPrevInFlow();
394 AutoFrameListPtr
overflowFrames(aPresContext
,
395 prevInFlow
->StealOverflowFrames());
396 if (overflowFrames
) {
397 NS_ASSERTION(mFrames
.IsEmpty(), "bad overflow list");
399 // When pushing and pulling frames we need to check for whether any
400 // views need to be reparented.
401 nsContainerFrame::ReparentFrameViewList(*overflowFrames
, prevInFlow
,
403 mFrames
.InsertFrames(this, nullptr, std::move(*overflowFrames
));
407 // It's also possible that we have an overflow list for ourselves
408 AutoFrameListPtr
overflowFrames(aPresContext
, StealOverflowFrames());
409 if (overflowFrames
) {
410 NS_ASSERTION(mFrames
.NotEmpty(), "overflow list w/o frames");
411 mFrames
.AppendFrames(nullptr, std::move(*overflowFrames
));
414 // Now repair our first frames ComputedStyle (since we only reflow
415 // one frame there is no point in doing any other ones until they
417 nsIFrame
* kid
= mFrames
.FirstChild();
419 nsIContent
* kidContent
= kid
->GetContent();
421 NS_ASSERTION(kidContent
->IsText(), "should contain only text nodes");
422 ComputedStyle
* parentSC
;
424 // This is for the rest of the content not in the first-letter.
425 nsIFrame
* styleParent
=
426 CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter
);
427 parentSC
= styleParent
->Style();
429 // And this for the first-letter style.
430 parentSC
= mComputedStyle
;
432 RefPtr
<ComputedStyle
> sc
=
433 aPresContext
->StyleSet()->ResolveStyleForText(kidContent
, parentSC
);
434 kid
->SetComputedStyle(sc
);
435 nsLayoutUtils::MarkDescendantsDirty(kid
);
440 Maybe
<nscoord
> nsFirstLetterFrame::GetNaturalBaselineBOffset(
441 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
442 BaselineExportContext
) const {
443 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
446 return Some(mBaseline
);
449 LogicalSides
nsFirstLetterFrame::GetLogicalSkipSides() const {
450 if (GetPrevContinuation()) {
451 // We shouldn't get calls to GetSkipSides for later continuations since
452 // they have separate ComputedStyles with initial values for all the
453 // properties that could trigger a call to GetSkipSides. Then again,
454 // it's not really an error to call GetSkipSides on any frame, so
455 // that's why we handle it properly.
456 return LogicalSides(mWritingMode
, eLogicalSideBitsAll
);
458 return LogicalSides(mWritingMode
); // first continuation displays all sides