Coverage Report

Created: 2018-09-25 14:53

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