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 = &ll;
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