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