Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/generic/nsRubyFrame.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 "display: ruby" */
8
9
#include "nsRubyFrame.h"
10
11
#include "RubyUtils.h"
12
#include "mozilla/ComputedStyle.h"
13
#include "mozilla/Maybe.h"
14
#include "mozilla/WritingModes.h"
15
#include "nsLineLayout.h"
16
#include "nsPresContext.h"
17
#include "nsRubyBaseContainerFrame.h"
18
#include "nsRubyTextContainerFrame.h"
19
20
using namespace mozilla;
21
22
//----------------------------------------------------------------------
23
24
// Frame class boilerplate
25
// =======================
26
27
0
NS_QUERYFRAME_HEAD(nsRubyFrame)
28
0
  NS_QUERYFRAME_ENTRY(nsRubyFrame)
29
0
NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
30
31
NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
32
33
nsContainerFrame*
34
NS_NewRubyFrame(nsIPresShell* aPresShell,
35
                ComputedStyle* aStyle)
36
0
{
37
0
  return new (aPresShell) nsRubyFrame(aStyle);
38
0
}
39
40
//----------------------------------------------------------------------
41
42
// nsRubyFrame Method Implementations
43
// ==================================
44
45
/* virtual */ bool
46
nsRubyFrame::IsFrameOfType(uint32_t aFlags) const
47
0
{
48
0
  if (aFlags & eBidiInlineContainer) {
49
0
    return false;
50
0
  }
51
0
  return nsInlineFrame::IsFrameOfType(aFlags);
52
0
}
53
54
#ifdef DEBUG_FRAME_DUMP
55
nsresult
56
nsRubyFrame::GetFrameName(nsAString& aResult) const
57
{
58
  return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult);
59
}
60
#endif
61
62
/* virtual */ void
63
nsRubyFrame::AddInlineMinISize(gfxContext *aRenderingContext,
64
                               nsIFrame::InlineMinISizeData *aData)
65
0
{
66
0
  for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
67
0
    for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame));
68
0
         !e.AtEnd(); e.Next()) {
69
0
      e.GetBaseContainer()->AddInlineMinISize(aRenderingContext, aData);
70
0
    }
71
0
  }
72
0
}
73
74
/* virtual */ void
75
nsRubyFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
76
                                nsIFrame::InlinePrefISizeData *aData)
77
0
{
78
0
  for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
79
0
    for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame));
80
0
         !e.AtEnd(); e.Next()) {
81
0
      e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, aData);
82
0
    }
83
0
  }
84
0
  aData->mLineIsEmpty = false;
85
0
}
86
87
static nsRubyBaseContainerFrame*
88
FindRubyBaseContainerAncestor(nsIFrame* aFrame)
89
0
{
90
0
  for (nsIFrame* ancestor = aFrame->GetParent();
91
0
       ancestor && ancestor->IsFrameOfType(nsIFrame::eLineParticipant);
92
0
       ancestor = ancestor->GetParent()) {
93
0
    if (ancestor->IsRubyBaseContainerFrame()) {
94
0
      return static_cast<nsRubyBaseContainerFrame*>(ancestor);
95
0
    }
96
0
  }
97
0
  return nullptr;
98
0
}
99
100
/* virtual */ void
101
nsRubyFrame::Reflow(nsPresContext* aPresContext,
102
                    ReflowOutput& aDesiredSize,
103
                    const ReflowInput& aReflowInput,
104
                    nsReflowStatus& aStatus)
105
0
{
106
0
  MarkInReflow();
107
0
  DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
108
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
109
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
110
0
111
0
  if (!aReflowInput.mLineLayout) {
112
0
    NS_ASSERTION(aReflowInput.mLineLayout,
113
0
                 "No line layout provided to RubyFrame reflow method.");
114
0
    return;
115
0
  }
116
0
117
0
  // Grab overflow frames from prev-in-flow and its own.
118
0
  MoveInlineOverflowToChildList(
119
0
    aReflowInput.mLineLayout->LineContainerFrame());
120
0
121
0
  // Clear leadings
122
0
  mLeadings.Reset();
123
0
124
0
  // Since the ruby base container is going to reflow not only the ruby
125
0
  // base frames, but also the ruby text frames, and then *afterwards*
126
0
  // we're going to reflow the ruby text containers (which do not reflow
127
0
  // their children), we need to transfer NS_FRAME_IS_DIRTY status from
128
0
  // the ruby text containers to their child ruby texts now, both so
129
0
  // that the ruby texts are marked dirty if needed, and so that the
130
0
  // ruby text container doesn't mark the ruby text frames dirty *after*
131
0
  // they're reflowed and leave dirty bits in a clean tree (suppressing
132
0
  // future reflows, due to lack of a queued reflow to clean them).
133
0
  for (nsIFrame* child : PrincipalChildList()) {
134
0
    if (child->HasAnyStateBits(NS_FRAME_IS_DIRTY) &&
135
0
        child->IsRubyTextContainerFrame()) {
136
0
      for (nsIFrame* grandchild : child->PrincipalChildList()) {
137
0
        grandchild->AddStateBits(NS_FRAME_IS_DIRTY);
138
0
      }
139
0
      // Replace NS_FRAME_IS_DIRTY with NS_FRAME_HAS_DIRTY_CHILDREN so
140
0
      // we still have a dirty marking, but one that we won't transfer
141
0
      // to children again.
142
0
      child->RemoveStateBits(NS_FRAME_IS_DIRTY);
143
0
      child->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
144
0
    }
145
0
  }
146
0
147
0
  // Begin the span for the ruby frame
148
0
  WritingMode frameWM = aReflowInput.GetWritingMode();
149
0
  WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
150
0
  LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding();
151
0
  nscoord startEdge = 0;
152
0
  const bool boxDecorationBreakClone =
153
0
    StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
154
0
  if (boxDecorationBreakClone || !GetPrevContinuation()) {
155
0
    startEdge = borderPadding.IStart(frameWM);
156
0
  }
157
0
  NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
158
0
               "should no longer use available widths");
159
0
  nscoord availableISize = aReflowInput.AvailableISize();
160
0
  availableISize -= startEdge + borderPadding.IEnd(frameWM);
161
0
  aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput,
162
0
                                      startEdge, availableISize, &mBaseline);
163
0
164
0
  for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
165
0
    ReflowSegment(aPresContext, aReflowInput, e.GetBaseContainer(), aStatus);
166
0
167
0
    if (aStatus.IsInlineBreak()) {
168
0
      // A break occurs when reflowing the segment.
169
0
      // Don't continue reflowing more segments.
170
0
      break;
171
0
    }
172
0
  }
173
0
174
0
  ContinuationTraversingState pullState(this);
175
0
  while (aStatus.IsEmpty()) {
176
0
    nsRubyBaseContainerFrame* baseContainer =
177
0
      PullOneSegment(aReflowInput.mLineLayout, pullState);
178
0
    if (!baseContainer) {
179
0
      // No more continuations after, finish now.
180
0
      break;
181
0
    }
182
0
    ReflowSegment(aPresContext, aReflowInput, baseContainer, aStatus);
183
0
  }
184
0
  // We never handle overflow in ruby.
185
0
  MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
186
0
187
0
  aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
188
0
  if (boxDecorationBreakClone || !GetPrevContinuation()) {
189
0
    aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
190
0
  }
191
0
  if (boxDecorationBreakClone || aStatus.IsComplete()) {
192
0
    aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
193
0
  }
194
0
195
0
  // Update descendant leadings of ancestor ruby base container.
196
0
  if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
197
0
    rbc->UpdateDescendantLeadings(mLeadings);
198
0
  }
199
0
200
0
  nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize,
201
0
                                         borderPadding, lineWM, frameWM);
202
0
}
203
204
void
205
nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
206
                           const ReflowInput& aReflowInput,
207
                           nsRubyBaseContainerFrame* aBaseContainer,
208
                           nsReflowStatus& aStatus)
209
0
{
210
0
  WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
211
0
  LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
212
0
                        aReflowInput.AvailableBSize());
213
0
  WritingMode rubyWM = GetWritingMode();
214
0
  NS_ASSERTION(!rubyWM.IsOrthogonalTo(lineWM),
215
0
               "Ruby frame writing-mode shouldn't be orthogonal to its line");
216
0
217
0
  AutoRubyTextContainerArray textContainers(aBaseContainer);
218
0
  const uint32_t rtcCount = textContainers.Length();
219
0
220
0
  ReflowOutput baseMetrics(aReflowInput);
221
0
  bool pushedFrame;
222
0
  aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus,
223
0
                                        &baseMetrics, pushedFrame);
224
0
225
0
  if (aStatus.IsInlineBreakBefore()) {
226
0
    if (aBaseContainer != mFrames.FirstChild()) {
227
0
      // Some segments may have been reflowed before, hence it is not
228
0
      // a break-before for the ruby container.
229
0
      aStatus.Reset();
230
0
      aStatus.SetInlineLineBreakAfter();
231
0
      aStatus.SetIncomplete();
232
0
      PushChildrenToOverflow(aBaseContainer, aBaseContainer->GetPrevSibling());
233
0
      aReflowInput.mLineLayout->SetDirtyNextLine();
234
0
    }
235
0
    // This base container is not placed at all, we can skip all
236
0
    // text containers paired with it.
237
0
    return;
238
0
  }
239
0
  if (aStatus.IsIncomplete()) {
240
0
    // It always promise that if the status is incomplete, there is a
241
0
    // break occurs. Break before has been processed above. However,
242
0
    // it is possible that break after happens with the frame reflow
243
0
    // completed. It happens if there is a force break at the end.
244
0
    MOZ_ASSERT(aStatus.IsInlineBreakAfter());
245
0
    // Find the previous sibling which we will
246
0
    // insert new continuations after.
247
0
    nsIFrame* lastChild;
248
0
    if (rtcCount > 0) {
249
0
      lastChild = textContainers.LastElement();
250
0
    } else {
251
0
      lastChild = aBaseContainer;
252
0
    }
253
0
254
0
    // Create continuations for the base container
255
0
    nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
256
0
    // newBaseContainer is null if there are existing next-in-flows.
257
0
    // We only need to move and push if there were not.
258
0
    if (newBaseContainer) {
259
0
      // Move the new frame after all the text containers
260
0
      mFrames.RemoveFrame(newBaseContainer);
261
0
      mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
262
0
263
0
      // Create continuations for text containers
264
0
      nsIFrame* newLastChild = newBaseContainer;
265
0
      for (uint32_t i = 0; i < rtcCount; i++) {
266
0
        nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
267
0
        MOZ_ASSERT(newTextContainer, "Next-in-flow of rtc should not exist "
268
0
                   "if the corresponding rbc does not");
269
0
        mFrames.RemoveFrame(newTextContainer);
270
0
        mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
271
0
        newLastChild = newTextContainer;
272
0
      }
273
0
    }
274
0
    if (lastChild != mFrames.LastChild()) {
275
0
      // Always push the next frame after the last child in this segment.
276
0
      // It is possible that we pulled it back before our next-in-flow
277
0
      // drain our overflow.
278
0
      PushChildrenToOverflow(lastChild->GetNextSibling(), lastChild);
279
0
      aReflowInput.mLineLayout->SetDirtyNextLine();
280
0
    }
281
0
  } else {
282
0
    // If the ruby base container is reflowed completely, the line
283
0
    // layout will remove the next-in-flows of that frame. But the
284
0
    // line layout is not aware of the ruby text containers, hence
285
0
    // it is necessary to remove them here.
286
0
    for (uint32_t i = 0; i < rtcCount; i++) {
287
0
      nsIFrame* nextRTC = textContainers[i]->GetNextInFlow();
288
0
      if (nextRTC) {
289
0
        nextRTC->GetParent()->DeleteNextInFlowChild(nextRTC, true);
290
0
      }
291
0
    }
292
0
  }
293
0
294
0
  nscoord segmentISize = baseMetrics.ISize(lineWM);
295
0
  const nsSize dummyContainerSize;
296
0
  LogicalRect baseRect =
297
0
    aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
298
0
  // We need to position our rtc frames on one side or the other of the
299
0
  // base container's rect, using a coordinate space that's relative to
300
0
  // the ruby frame. Right now, the base container's rect's block-axis
301
0
  // position is relative to the block container frame containing the
302
0
  // lines, so we use 0 instead. (i.e. we assume that the base container
303
0
  // is adjacent to the ruby frame's block-start edge.)
304
0
  // XXX We may need to add border/padding here. See bug 1055667.
305
0
  baseRect.BStart(lineWM) = 0;
306
0
  // The rect for offsets of text containers.
307
0
  LogicalRect offsetRect = baseRect;
308
0
  RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
309
0
  offsetRect.BStart(lineWM) -= descLeadings.mStart;
310
0
  offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
311
0
  for (uint32_t i = 0; i < rtcCount; i++) {
312
0
    nsRubyTextContainerFrame* textContainer = textContainers[i];
313
0
    WritingMode rtcWM = textContainer->GetWritingMode();
314
0
    nsReflowStatus textReflowStatus;
315
0
    ReflowOutput textMetrics(aReflowInput);
316
0
    ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
317
0
                                      availSize.ConvertTo(rtcWM, lineWM));
318
0
    textContainer->Reflow(aPresContext, textMetrics,
319
0
                          textReflowInput, textReflowStatus);
320
0
    // Ruby text containers always return complete reflow status even when
321
0
    // they have continuations, because the breaking has already been
322
0
    // handled when reflowing the base containers.
323
0
    NS_ASSERTION(textReflowStatus.IsEmpty(),
324
0
                 "Ruby text container must not break itself inside");
325
0
    // The metrics is initialized with reflow state of this ruby frame,
326
0
    // hence the writing-mode is tied to rubyWM instead of rtcWM.
327
0
    LogicalSize size = textMetrics.Size(rubyWM).ConvertTo(lineWM, rubyWM);
328
0
    textContainer->SetSize(lineWM, size);
329
0
330
0
    nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
331
0
    segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
332
0
333
0
    uint8_t rubyPosition = textContainer->StyleText()->mRubyPosition;
334
0
    MOZ_ASSERT(rubyPosition == NS_STYLE_RUBY_POSITION_OVER ||
335
0
               rubyPosition == NS_STYLE_RUBY_POSITION_UNDER);
336
0
    Maybe<LogicalSide> side;
337
0
    if (rubyPosition == NS_STYLE_RUBY_POSITION_OVER) {
338
0
      side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirOver));
339
0
    } else if (rubyPosition == NS_STYLE_RUBY_POSITION_UNDER) {
340
0
      side.emplace(lineWM.LogicalSideForLineRelativeDir(eLineRelativeDirUnder));
341
0
    } else {
342
0
      // XXX inter-character support in bug 1055672
343
0
      MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
344
0
    }
345
0
346
0
    LogicalPoint position(lineWM);
347
0
    if (side.isSome()) {
348
0
      if (nsLayoutUtils::IsInterCharacterRubyEnabled() &&
349
0
          rtcWM.IsVerticalRL() &&
350
0
          lineWM.GetInlineDir() == WritingMode::eInlineLTR) {
351
0
        // Inter-character ruby annotations are only supported for vertical-rl
352
0
        // in ltr horizontal writing. Fall back to non-inter-character behavior
353
0
        // otherwise.
354
0
        LogicalPoint offset(lineWM, offsetRect.ISize(lineWM),
355
0
          offsetRect.BSize(lineWM) > size.BSize(lineWM) ?
356
0
          (offsetRect.BSize(lineWM) - size.BSize(lineWM)) / 2 : 0);
357
0
        position = offsetRect.Origin(lineWM) + offset;
358
0
        aReflowInput.mLineLayout->AdvanceICoord(size.ISize(lineWM));
359
0
      } else if (side.value() == eLogicalSideBStart) {
360
0
        offsetRect.BStart(lineWM) -= size.BSize(lineWM);
361
0
        offsetRect.BSize(lineWM) += size.BSize(lineWM);
362
0
        position = offsetRect.Origin(lineWM);
363
0
      } else if (side.value() == eLogicalSideBEnd) {
364
0
        position = offsetRect.Origin(lineWM) +
365
0
          LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
366
0
        offsetRect.BSize(lineWM) += size.BSize(lineWM);
367
0
      } else {
368
0
        MOZ_ASSERT_UNREACHABLE("???");
369
0
      }
370
0
    }
371
0
    // Using a dummy container-size here, so child positioning may not be
372
0
    // correct. We will fix it in nsLineLayout after the whole line is
373
0
    // reflowed.
374
0
    FinishReflowChild(textContainer, aPresContext, textMetrics,
375
0
                      &textReflowInput, lineWM, position, dummyContainerSize, 0);
376
0
  }
377
0
  MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
378
0
             "Annotations should only be placed on the block directions");
379
0
380
0
  nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
381
0
  if (deltaISize <= 0) {
382
0
    RubyUtils::ClearReservedISize(aBaseContainer);
383
0
  } else {
384
0
    RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
385
0
    aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
386
0
  }
387
0
388
0
  // Set block leadings of the base container
389
0
  nscoord startLeading = baseRect.BStart(lineWM) - offsetRect.BStart(lineWM);
390
0
  nscoord endLeading = offsetRect.BEnd(lineWM) - baseRect.BEnd(lineWM);
391
0
  // XXX When bug 765861 gets fixed, this warning should be upgraded.
392
0
  NS_WARNING_ASSERTION(startLeading >= 0 && endLeading >= 0,
393
0
                       "Leadings should be non-negative (because adding "
394
0
                       "ruby annotation can only increase the size)");
395
0
  mLeadings.Update(startLeading, endLeading);
396
0
}
397
398
nsRubyBaseContainerFrame*
399
nsRubyFrame::PullOneSegment(const nsLineLayout* aLineLayout,
400
                            ContinuationTraversingState& aState)
401
0
{
402
0
  // Pull a ruby base container
403
0
  nsIFrame* baseFrame = GetNextInFlowChild(aState);
404
0
  if (!baseFrame) {
405
0
    return nullptr;
406
0
  }
407
0
  MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
408
0
409
0
  // Get the float containing block of the base frame before we pull it.
410
0
  nsBlockFrame* oldFloatCB =
411
0
    nsLayoutUtils::GetFloatContainingBlock(baseFrame);
412
0
  PullNextInFlowChild(aState);
413
0
414
0
  // Pull all ruby text containers following the base container
415
0
  nsIFrame* nextFrame;
416
0
  while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
417
0
         nextFrame->IsRubyTextContainerFrame()) {
418
0
    PullNextInFlowChild(aState);
419
0
  }
420
0
421
0
  if (nsBlockFrame* newFloatCB =
422
0
      nsLayoutUtils::GetAsBlock(aLineLayout->LineContainerFrame())) {
423
0
    if (oldFloatCB && oldFloatCB != newFloatCB) {
424
0
      newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true,
425
0
                                 ReparentingDirection::Backwards);
426
0
    }
427
0
  }
428
0
429
0
  return static_cast<nsRubyBaseContainerFrame*>(baseFrame);
430
0
}