Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
6 /* rendering object for CSS :first-letter pseudo-element */
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"
21 using namespace mozilla;
22 using namespace mozilla::layout;
24 nsIFrame*
25 NS_NewFirstLetterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
26 {
27 return new (aPresShell) nsFirstLetterFrame(aContext);
28 }
30 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
32 NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
33 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
34 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
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
44 nsIAtom*
45 nsFirstLetterFrame::GetType() const
46 {
47 return nsGkAtoms::letterFrame;
48 }
50 void
51 nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
52 const nsRect& aDirtyRect,
53 const nsDisplayListSet& aLists)
54 {
55 BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
56 }
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 }
76 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
77 }
79 nsresult
80 nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
81 nsFrameList& aChildList)
82 {
83 RestyleManager* restyleManager = PresContext()->RestyleManager();
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 }
91 mFrames.SetFrames(aChildList);
92 return NS_OK;
93 }
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 }
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 }
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 }
128 // Needed for floating first-letter frames.
129 /* virtual */ nscoord
130 nsFirstLetterFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
131 {
132 return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext);
133 }
135 // Needed for floating first-letter frames.
136 /* virtual */ nscoord
137 nsFirstLetterFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
138 {
139 return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext);
140 }
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 }
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;
167 // Grab overflow list
168 DrainOverflowFrames(aPresContext);
170 nsIFrame* kid = mFrames.FirstChild();
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 }
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);
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);
200 kid->WillReflow(aPresContext);
201 kid->Reflow(aPresContext, aMetrics, rs, aReflowStatus);
203 ll.EndLineReflow();
204 ll.SetInFirstLetter(false);
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;
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 }
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);
228 aMetrics.Width() += lr;
229 aMetrics.Height() += tb;
230 aMetrics.SetTopAscent(aMetrics.TopAscent() + bp.top);
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);
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 }
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 }
278 FinishAndStoreOverflow(&aMetrics);
280 NS_FRAME_SET_TRUNCATION(aReflowStatus, aReflowState, aMetrics);
281 return rv;
282 }
284 /* virtual */ bool
285 nsFirstLetterFrame::CanContinueTextRun() const
286 {
287 // We can continue a text run through a first-letter frame.
288 return true;
289 }
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");
301 *aContinuation = nullptr;
302 nsresult rv = NS_OK;
304 nsIPresShell* presShell = aPresContext->PresShell();
305 nsPlaceholderFrame* placeholderFrame =
306 presShell->FrameManager()->GetPlaceholderFrameFor(this);
307 nsIFrame* parent = placeholderFrame->GetParent();
309 nsIFrame* continuation = presShell->FrameConstructor()->
310 CreateContinuingFrame(aPresContext, aChild, parent, aIsFluid);
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 }
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);
330 *aContinuation = continuation;
331 return rv;
332 }
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");
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 }
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 }
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 }
379 nscoord
380 nsFirstLetterFrame::GetBaseline() const
381 {
382 return mBaseline;
383 }
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 }