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/. */
6
7 /* rendering object for CSS :first-letter pseudo-element */
8
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 "nsIContent.h"
18 #include "nsLayoutUtils.h"
19 #include "nsLineLayout.h"
20 #include "nsGkAtoms.h"
21 #include "nsFrameManager.h"
22 #include "nsPlaceholderFrame.h"
23 #include "nsCSSFrameConstructor.h"
24
25 using namespace mozilla;
26 using namespace mozilla::layout;
27
NS_NewFirstLetterFrame(PresShell * aPresShell,ComputedStyle * aStyle)28 nsFirstLetterFrame* NS_NewFirstLetterFrame(PresShell* aPresShell,
29 ComputedStyle* aStyle) {
30 return new (aPresShell)
31 nsFirstLetterFrame(aStyle, aPresShell->GetPresContext());
32 }
33
34 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
35
NS_QUERYFRAME_HEAD(nsFirstLetterFrame)36 NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
37 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
38 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
39
40 #ifdef DEBUG_FRAME_DUMP
41 nsresult nsFirstLetterFrame::GetFrameName(nsAString& aResult) const {
42 return MakeFrameName(u"Letter"_ns, aResult);
43 }
44 #endif
45
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)46 void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
47 const nsDisplayListSet& aLists) {
48 BuildDisplayListForInline(aBuilder, aLists);
49 }
50
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)51 void nsFirstLetterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
52 nsIFrame* aPrevInFlow) {
53 RefPtr<ComputedStyle> newSC;
54 if (aPrevInFlow) {
55 // Get proper ComputedStyle for ourselves. We're creating the frame
56 // that represents everything *except* the first letter, so just create
57 // a ComputedStyle that inherits from our style parent, with no extra rules.
58 nsIFrame* styleParent =
59 CorrectStyleParentFrame(aParent, PseudoStyleType::firstLetter);
60 ComputedStyle* parentComputedStyle = styleParent->Style();
61 newSC = PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation(
62 parentComputedStyle);
63 SetComputedStyleWithoutNotification(newSC);
64 }
65
66 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
67 }
68
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)69 void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
70 nsFrameList& aChildList) {
71 MOZ_ASSERT(aListID == kPrincipalList,
72 "Principal child list is the only "
73 "list that nsFirstLetterFrame should set via this function");
74 for (nsIFrame* f : aChildList) {
75 MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
76 MOZ_ASSERT(f->IsTextFrame(),
77 "We should not have kids that are containers!");
78 nsLayoutUtils::MarkDescendantsDirty(f); // Drops cached textruns
79 }
80
81 mFrames.SetFrames(aChildList);
82 }
83
GetChildFrameContainingOffset(int32_t inContentOffset,bool inHint,int32_t * outFrameContentOffset,nsIFrame ** outChildFrame)84 nsresult nsFirstLetterFrame::GetChildFrameContainingOffset(
85 int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset,
86 nsIFrame** outChildFrame) {
87 nsIFrame* kid = mFrames.FirstChild();
88 if (kid) {
89 return kid->GetChildFrameContainingOffset(
90 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
91 }
92 return nsIFrame::GetChildFrameContainingOffset(
93 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
94 }
95
96 // Needed for non-floating first-letter frames and for the continuations
97 // following the first-letter that we also use nsFirstLetterFrame for.
98 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)99 void nsFirstLetterFrame::AddInlineMinISize(
100 gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
101 DoInlineMinISize(aRenderingContext, aData);
102 }
103
104 // Needed for non-floating first-letter frames and for the continuations
105 // following the first-letter that we also use nsFirstLetterFrame for.
106 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData)107 void nsFirstLetterFrame::AddInlinePrefISize(
108 gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
109 DoInlinePrefISize(aRenderingContext, aData);
110 }
111
112 // Needed for floating first-letter frames.
113 /* virtual */
GetMinISize(gfxContext * aRenderingContext)114 nscoord nsFirstLetterFrame::GetMinISize(gfxContext* aRenderingContext) {
115 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
116 }
117
118 // Needed for floating first-letter frames.
119 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)120 nscoord nsFirstLetterFrame::GetPrefISize(gfxContext* aRenderingContext) {
121 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
122 }
123
124 /* virtual */
ComputeSize(gfxContext * aRenderingContext,WritingMode aWM,const LogicalSize & aCBSize,nscoord aAvailableISize,const LogicalSize & aMargin,const LogicalSize & aBorderPadding,const StyleSizeOverrides & aSizeOverrides,ComputeSizeFlags aFlags)125 nsIFrame::SizeComputationResult nsFirstLetterFrame::ComputeSize(
126 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
127 nscoord aAvailableISize, const LogicalSize& aMargin,
128 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
129 ComputeSizeFlags aFlags) {
130 if (GetPrevInFlow()) {
131 // We're wrapping the text *after* the first letter, so behave like an
132 // inline frame.
133 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
134 AspectRatioUsage::None};
135 }
136 return nsContainerFrame::ComputeSize(aRenderingContext, aWM, aCBSize,
137 aAvailableISize, aMargin, aBorderPadding,
138 aSizeOverrides, aFlags);
139 }
140
Reflow(nsPresContext * aPresContext,ReflowOutput & aMetrics,const ReflowInput & aReflowInput,nsReflowStatus & aReflowStatus)141 void nsFirstLetterFrame::Reflow(nsPresContext* aPresContext,
142 ReflowOutput& aMetrics,
143 const ReflowInput& aReflowInput,
144 nsReflowStatus& aReflowStatus) {
145 MarkInReflow();
146 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
147 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus);
148 MOZ_ASSERT(aReflowStatus.IsEmpty(),
149 "Caller should pass a fresh reflow status!");
150
151 // Grab overflow list
152 DrainOverflowFrames(aPresContext);
153
154 nsIFrame* kid = mFrames.FirstChild();
155
156 // Setup reflow input for our child
157 WritingMode wm = aReflowInput.GetWritingMode();
158 LogicalSize availSize = aReflowInput.AvailableSize();
159 const auto bp = aReflowInput.ComputedLogicalBorderPadding(wm);
160 NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE,
161 "should no longer use unconstrained inline size");
162 availSize.ISize(wm) -= bp.IStartEnd(wm);
163 if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
164 availSize.BSize(wm) -= bp.BStartEnd(wm);
165 }
166
167 WritingMode lineWM = aMetrics.GetWritingMode();
168 ReflowOutput kidMetrics(lineWM);
169
170 // Reflow the child
171 if (!aReflowInput.mLineLayout) {
172 // When there is no lineLayout provided, we provide our own. The
173 // only time that the first-letter-frame is not reflowing in a
174 // line context is when its floating.
175 WritingMode kidWritingMode = WritingModeForLine(wm, kid);
176 LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm);
177 ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize);
178 nsLineLayout ll(aPresContext, nullptr, aReflowInput, nullptr, nullptr);
179
180 ll.BeginLineReflow(
181 bp.IStart(wm), bp.BStart(wm), availSize.ISize(wm), NS_UNCONSTRAINEDSIZE,
182 false, true, kidWritingMode,
183 nsSize(aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
184 rs.mLineLayout = ≪
185 ll.SetInFirstLetter(true);
186 ll.SetFirstLetterStyleOK(true);
187
188 kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus);
189
190 ll.EndLineReflow();
191 ll.SetInFirstLetter(false);
192
193 // In the floating first-letter case, we need to set this ourselves;
194 // nsLineLayout::BeginSpan will set it in the other case
195 mBaseline = kidMetrics.BlockStartAscent();
196
197 // Place and size the child and update the output metrics
198 LogicalSize convertedSize = kidMetrics.Size(wm);
199 kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm), convertedSize.ISize(wm),
200 convertedSize.BSize(wm)));
201 kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay);
202 kid->DidReflow(aPresContext, nullptr);
203
204 convertedSize.ISize(wm) += bp.IStartEnd(wm);
205 convertedSize.BSize(wm) += bp.BStartEnd(wm);
206 aMetrics.SetSize(wm, convertedSize);
207 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + bp.BStart(wm));
208
209 // Ensure that the overflow rect contains the child textframe's
210 // overflow rect.
211 // Note that if this is floating, the overline/underline drawable
212 // area is in the overflow rect of the child textframe.
213 aMetrics.UnionOverflowAreasWithDesiredBounds();
214 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
215
216 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
217 } else {
218 // Pretend we are a span and reflow the child frame
219 nsLineLayout* ll = aReflowInput.mLineLayout;
220 bool pushedFrame;
221
222 ll->SetInFirstLetter(Style()->GetPseudoType() ==
223 PseudoStyleType::firstLetter);
224 ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), availSize.ISize(wm),
225 &mBaseline);
226 ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame);
227 NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(),
228 "we're assuming we can mix sizes between lineWM and wm "
229 "since we shouldn't have orthogonal writing modes within "
230 "a line.");
231 aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm);
232 ll->SetInFirstLetter(false);
233
234 if (mComputedStyle->StyleTextReset()->mInitialLetterSize != 0.0f) {
235 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
236 bp.BStart(wm));
237 aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm);
238 } else {
239 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm);
240 }
241 }
242
243 if (!aReflowStatus.IsInlineBreakBefore()) {
244 // Create a continuation or remove existing continuations based on
245 // the reflow completion status.
246 if (aReflowStatus.IsComplete()) {
247 if (aReflowInput.mLineLayout) {
248 aReflowInput.mLineLayout->SetFirstLetterStyleOK(false);
249 }
250 nsIFrame* kidNextInFlow = kid->GetNextInFlow();
251 if (kidNextInFlow) {
252 // Remove all of the childs next-in-flows
253 kidNextInFlow->GetParent()->DeleteNextInFlowChild(kidNextInFlow, true);
254 }
255 } else {
256 // Create a continuation for the child frame if it doesn't already
257 // have one.
258 if (!IsFloating()) {
259 CreateNextInFlow(kid);
260 // And then push it to our overflow list
261 nsFrameList overflow = mFrames.RemoveFramesAfter(kid);
262 if (overflow.NotEmpty()) {
263 SetOverflowFrames(std::move(overflow));
264 }
265 } else if (!kid->GetNextInFlow()) {
266 // For floating first letter frames (if a continuation wasn't already
267 // created for us) we need to put the continuation with the rest of the
268 // text that the first letter frame was made out of.
269 nsIFrame* continuation;
270 CreateContinuationForFloatingParent(kid, &continuation, true);
271 }
272 }
273 }
274
275 NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowInput, aMetrics);
276 }
277
278 /* virtual */
CanContinueTextRun() const279 bool nsFirstLetterFrame::CanContinueTextRun() const {
280 // We can continue a text run through a first-letter frame.
281 return true;
282 }
283
CreateContinuationForFloatingParent(nsIFrame * aChild,nsIFrame ** aContinuation,bool aIsFluid)284 void nsFirstLetterFrame::CreateContinuationForFloatingParent(
285 nsIFrame* aChild, nsIFrame** aContinuation, bool aIsFluid) {
286 NS_ASSERTION(IsFloating(),
287 "can only call this on floating first letter frames");
288 MOZ_ASSERT(aContinuation, "bad args");
289
290 *aContinuation = nullptr;
291
292 mozilla::PresShell* presShell = PresShell();
293 nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame();
294 nsContainerFrame* parent = placeholderFrame->GetParent();
295
296 nsIFrame* continuation = presShell->FrameConstructor()->CreateContinuingFrame(
297 aChild, parent, aIsFluid);
298
299 // The continuation will have gotten the first letter style from its
300 // prev continuation, so we need to repair the ComputedStyle so it
301 // doesn't have the first letter styling.
302 //
303 // Note that getting parent frame's ComputedStyle is different from getting
304 // this frame's ComputedStyle's parent in the presence of ::first-line,
305 // which we do want the continuation to inherit from.
306 ComputedStyle* parentSC = parent->Style();
307 if (parentSC) {
308 RefPtr<ComputedStyle> newSC;
309 newSC =
310 presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC);
311 continuation->SetComputedStyle(newSC);
312 nsLayoutUtils::MarkDescendantsDirty(continuation);
313 }
314
315 // XXX Bidi may not be involved but we have to use the list name
316 // kNoReflowPrincipalList because this is just like creating a continuation
317 // except we have to insert it in a different place and we don't want a
318 // reflow command to try to be issued.
319 nsFrameList temp(continuation, continuation);
320 parent->InsertFrames(kNoReflowPrincipalList, placeholderFrame, nullptr, temp);
321
322 *aContinuation = continuation;
323 }
324
DrainOverflowFrames(nsPresContext * aPresContext)325 void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) {
326 // Check for an overflow list with our prev-in-flow
327 nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow();
328 if (prevInFlow) {
329 AutoFrameListPtr overflowFrames(aPresContext,
330 prevInFlow->StealOverflowFrames());
331 if (overflowFrames) {
332 NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list");
333
334 // When pushing and pulling frames we need to check for whether any
335 // views need to be reparented.
336 nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow,
337 this);
338 mFrames.InsertFrames(this, nullptr, *overflowFrames);
339 }
340 }
341
342 // It's also possible that we have an overflow list for ourselves
343 AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames());
344 if (overflowFrames) {
345 NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
346 mFrames.AppendFrames(nullptr, *overflowFrames);
347 }
348
349 // Now repair our first frames ComputedStyle (since we only reflow
350 // one frame there is no point in doing any other ones until they
351 // are reflowed)
352 nsIFrame* kid = mFrames.FirstChild();
353 if (kid) {
354 nsIContent* kidContent = kid->GetContent();
355 if (kidContent) {
356 NS_ASSERTION(kidContent->IsText(), "should contain only text nodes");
357 ComputedStyle* parentSC;
358 if (prevInFlow) {
359 // This is for the rest of the content not in the first-letter.
360 nsIFrame* styleParent =
361 CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter);
362 parentSC = styleParent->Style();
363 } else {
364 // And this for the first-letter style.
365 parentSC = mComputedStyle;
366 }
367 RefPtr<ComputedStyle> sc =
368 aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC);
369 kid->SetComputedStyle(sc);
370 nsLayoutUtils::MarkDescendantsDirty(kid);
371 }
372 }
373 }
374
GetLogicalBaseline(WritingMode aWritingMode) const375 nscoord nsFirstLetterFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
376 return mBaseline;
377 }
378
GetLogicalSkipSides() const379 LogicalSides nsFirstLetterFrame::GetLogicalSkipSides() const {
380 if (GetPrevContinuation()) {
381 // We shouldn't get calls to GetSkipSides for later continuations since
382 // they have separate ComputedStyles with initial values for all the
383 // properties that could trigger a call to GetSkipSides. Then again,
384 // it's not really an error to call GetSkipSides on any frame, so
385 // that's why we handle it properly.
386 return LogicalSides(mWritingMode, eLogicalSideBitsAll);
387 }
388 return LogicalSides(mWritingMode); // first continuation displays all sides
389 }
390