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