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