Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / generic / nsRubyBaseContainerFrame.cpp
blob6e29152bc90296a9c4719f14c3ef74b7b0b864d8
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/. */
7 /* rendering object for CSS "display: ruby-base-container" */
9 #include "nsRubyBaseContainerFrame.h"
10 #include "nsRubyTextContainerFrame.h"
11 #include "nsRubyBaseFrame.h"
12 #include "nsRubyTextFrame.h"
13 #include "mozilla/ComputedStyle.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/Maybe.h"
16 #include "mozilla/PresShell.h"
17 #include "mozilla/WritingModes.h"
18 #include "nsLayoutUtils.h"
19 #include "nsLineLayout.h"
20 #include "nsPresContext.h"
21 #include "nsStyleStructInlines.h"
22 #include "nsTextFrame.h"
23 #include "gfxContext.h"
24 #include "RubyUtils.h"
26 using namespace mozilla;
27 using namespace mozilla::gfx;
29 //----------------------------------------------------------------------
31 // Frame class boilerplate
32 // =======================
34 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame)
35 NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame)
36 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
38 NS_IMPL_FRAMEARENA_HELPERS(nsRubyBaseContainerFrame)
40 nsContainerFrame* NS_NewRubyBaseContainerFrame(PresShell* aPresShell,
41 ComputedStyle* aStyle) {
42 return new (aPresShell)
43 nsRubyBaseContainerFrame(aStyle, aPresShell->GetPresContext());
46 //----------------------------------------------------------------------
48 // nsRubyBaseContainerFrame Method Implementations
49 // ===============================================
51 #ifdef DEBUG_FRAME_DUMP
52 nsresult nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const {
53 return MakeFrameName(u"RubyBaseContainer"_ns, aResult);
55 #endif
57 static gfxBreakPriority LineBreakBefore(nsIFrame* aFrame,
58 DrawTarget* aDrawTarget,
59 nsIFrame* aLineContainerFrame,
60 const nsLineList::iterator* aLine) {
61 for (nsIFrame* child = aFrame; child;
62 child = child->PrincipalChildList().FirstChild()) {
63 if (!child->CanContinueTextRun()) {
64 // It is not an inline element. We can break before it.
65 return gfxBreakPriority::eNormalBreak;
67 if (!child->IsTextFrame()) {
68 continue;
71 auto textFrame = static_cast<nsTextFrame*>(child);
72 gfxSkipCharsIterator iter = textFrame->EnsureTextRun(
73 nsTextFrame::eInflated, aDrawTarget, aLineContainerFrame, aLine);
74 iter.SetOriginalOffset(textFrame->GetContentOffset());
75 uint32_t pos = iter.GetSkippedOffset();
76 gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated);
77 MOZ_ASSERT(textRun, "fail to build textrun?");
78 if (!textRun || pos >= textRun->GetLength()) {
79 // The text frame contains no character at all.
80 return gfxBreakPriority::eNoBreak;
82 // Return whether we can break before the first character.
83 if (textRun->CanBreakLineBefore(pos)) {
84 return gfxBreakPriority::eNormalBreak;
86 // Check whether we can wrap word here.
87 const nsStyleText* textStyle = textFrame->StyleText();
88 if (textStyle->WordCanWrap(textFrame) && textRun->IsClusterStart(pos)) {
89 return gfxBreakPriority::eWordWrapBreak;
91 // We cannot break before.
92 return gfxBreakPriority::eNoBreak;
94 // Neither block, nor text frame is found as a leaf. We won't break
95 // before this base frame. It is the behavior of empty spans.
96 return gfxBreakPriority::eNoBreak;
99 static void GetIsLineBreakAllowed(nsIFrame* aFrame, bool aIsLineBreakable,
100 bool* aAllowInitialLineBreak,
101 bool* aAllowLineBreak) {
102 nsIFrame* parent = aFrame->GetParent();
103 bool lineBreakSuppressed = parent->Style()->ShouldSuppressLineBreak();
104 // Allow line break between ruby bases when white-space allows,
105 // we are not inside a nested ruby, and there is no span.
106 bool allowLineBreak =
107 !lineBreakSuppressed && aFrame->StyleText()->WhiteSpaceCanWrap(aFrame);
108 bool allowInitialLineBreak = allowLineBreak;
109 if (!aFrame->GetPrevInFlow()) {
110 allowInitialLineBreak =
111 !lineBreakSuppressed && parent->StyleText()->WhiteSpaceCanWrap(parent);
113 if (!aIsLineBreakable) {
114 allowInitialLineBreak = false;
116 *aAllowInitialLineBreak = allowInitialLineBreak;
117 *aAllowLineBreak = allowLineBreak;
121 * @param aBaseISizeData is an in/out param. This method updates the
122 * `skipWhitespace` and `trailingWhitespace` fields of the struct with
123 * the base level frame. Note that we don't need to do the same thing
124 * for ruby text frames, because they are text run container themselves
125 * (see nsTextFrame.cpp:BuildTextRuns), and thus no whitespace collapse
126 * happens across the boundary of those frames.
128 static nscoord CalculateColumnPrefISize(
129 gfxContext* aRenderingContext, const RubyColumnEnumerator& aEnumerator,
130 nsIFrame::InlineIntrinsicISizeData* aBaseISizeData) {
131 nscoord max = 0;
132 uint32_t levelCount = aEnumerator.GetLevelCount();
133 for (uint32_t i = 0; i < levelCount; i++) {
134 nsIFrame* frame = aEnumerator.GetFrameAtLevel(i);
135 if (frame) {
136 nsIFrame::InlinePrefISizeData data;
137 if (i == 0) {
138 data.SetLineContainer(aBaseISizeData->LineContainer());
139 data.mSkipWhitespace = aBaseISizeData->mSkipWhitespace;
140 data.mTrailingWhitespace = aBaseISizeData->mTrailingWhitespace;
141 } else {
142 // The line container of ruby text frames is their parent,
143 // ruby text container frame.
144 data.SetLineContainer(frame->GetParent());
146 frame->AddInlinePrefISize(aRenderingContext, &data);
147 MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
148 max = std::max(max, data.mCurrentLine);
149 if (i == 0) {
150 aBaseISizeData->mSkipWhitespace = data.mSkipWhitespace;
151 aBaseISizeData->mTrailingWhitespace = data.mTrailingWhitespace;
155 return max;
158 // FIXME Currently we use pref isize of ruby content frames for
159 // computing min isize of ruby frame, which may cause problem.
160 // See bug 1134945.
161 /* virtual */
162 void nsRubyBaseContainerFrame::AddInlineMinISize(
163 gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
164 AutoRubyTextContainerArray textContainers(this);
166 for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
167 if (textContainers[i]->IsSpanContainer()) {
168 // Since spans are not breakable internally, use our pref isize
169 // directly if there is any span.
170 nsIFrame::InlinePrefISizeData data;
171 data.SetLineContainer(aData->LineContainer());
172 data.mSkipWhitespace = aData->mSkipWhitespace;
173 data.mTrailingWhitespace = aData->mTrailingWhitespace;
174 AddInlinePrefISize(aRenderingContext, &data);
175 aData->mCurrentLine += data.mCurrentLine;
176 if (data.mCurrentLine > 0) {
177 aData->mAtStartOfLine = false;
179 aData->mSkipWhitespace = data.mSkipWhitespace;
180 aData->mTrailingWhitespace = data.mTrailingWhitespace;
181 return;
185 bool firstFrame = true;
186 bool allowInitialLineBreak, allowLineBreak;
187 GetIsLineBreakAllowed(this, !aData->mAtStartOfLine, &allowInitialLineBreak,
188 &allowLineBreak);
189 for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
190 RubyColumnEnumerator enumerator(
191 static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
192 for (; !enumerator.AtEnd(); enumerator.Next()) {
193 if (firstFrame ? allowInitialLineBreak : allowLineBreak) {
194 nsIFrame* baseFrame = enumerator.GetFrameAtLevel(0);
195 if (baseFrame) {
196 gfxBreakPriority breakPriority = LineBreakBefore(
197 baseFrame, aRenderingContext->GetDrawTarget(), nullptr, nullptr);
198 if (breakPriority != gfxBreakPriority::eNoBreak) {
199 aData->OptionallyBreak();
203 firstFrame = false;
204 nscoord isize =
205 CalculateColumnPrefISize(aRenderingContext, enumerator, aData);
206 aData->mCurrentLine += isize;
207 if (isize > 0) {
208 aData->mAtStartOfLine = false;
214 /* virtual */
215 void nsRubyBaseContainerFrame::AddInlinePrefISize(
216 gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
217 AutoRubyTextContainerArray textContainers(this);
219 nscoord sum = 0;
220 for (nsIFrame* frame = this; frame; frame = frame->GetNextInFlow()) {
221 RubyColumnEnumerator enumerator(
222 static_cast<nsRubyBaseContainerFrame*>(frame), textContainers);
223 for (; !enumerator.AtEnd(); enumerator.Next()) {
224 sum += CalculateColumnPrefISize(aRenderingContext, enumerator, aData);
227 for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
228 if (textContainers[i]->IsSpanContainer()) {
229 nsIFrame* frame = textContainers[i]->PrincipalChildList().FirstChild();
230 nsIFrame::InlinePrefISizeData data;
231 frame->AddInlinePrefISize(aRenderingContext, &data);
232 MOZ_ASSERT(data.mPrevLines == 0, "Shouldn't have prev lines");
233 sum = std::max(sum, data.mCurrentLine);
236 aData->mCurrentLine += sum;
239 /* virtual */
240 bool nsRubyBaseContainerFrame::CanContinueTextRun() const { return true; }
242 /* virtual */
243 nsIFrame::SizeComputationResult nsRubyBaseContainerFrame::ComputeSize(
244 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
245 nscoord aAvailableISize, const LogicalSize& aMargin,
246 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
247 ComputeSizeFlags aFlags) {
248 // Ruby base container frame is inline,
249 // hence don't compute size before reflow.
250 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
251 AspectRatioUsage::None};
254 Maybe<nscoord> nsRubyBaseContainerFrame::GetNaturalBaselineBOffset(
255 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
256 BaselineExportContext) const {
257 if (aBaselineGroup == BaselineSharingGroup::Last) {
258 return Nothing{};
260 return Some(mBaseline);
263 struct nsRubyBaseContainerFrame::RubyReflowInput {
264 bool mAllowInitialLineBreak;
265 bool mAllowLineBreak;
266 const AutoRubyTextContainerArray& mTextContainers;
267 const ReflowInput& mBaseReflowInput;
268 const nsTArray<UniquePtr<ReflowInput>>& mTextReflowInputs;
271 /* virtual */
272 void nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
273 ReflowOutput& aDesiredSize,
274 const ReflowInput& aReflowInput,
275 nsReflowStatus& aStatus) {
276 MarkInReflow();
277 DO_GLOBAL_REFLOW_COUNT("nsRubyBaseContainerFrame");
278 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
279 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
281 if (!aReflowInput.mLineLayout) {
282 NS_ASSERTION(
283 aReflowInput.mLineLayout,
284 "No line layout provided to RubyBaseContainerFrame reflow method.");
285 return;
288 mDescendantLeadings.Reset();
290 nsIFrame* lineContainer = aReflowInput.mLineLayout->LineContainerFrame();
291 MoveInlineOverflowToChildList(lineContainer);
292 // Ask text containers to drain overflows
293 AutoRubyTextContainerArray textContainers(this);
294 const uint32_t rtcCount = textContainers.Length();
295 for (uint32_t i = 0; i < rtcCount; i++) {
296 textContainers[i]->MoveInlineOverflowToChildList(lineContainer);
299 WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
300 LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
301 aReflowInput.AvailableBSize());
303 // We have a reflow input and a line layout for each RTC.
304 // They are conceptually the state of the RTCs, but we don't actually
305 // reflow those RTCs in this code. These two arrays are holders of
306 // the reflow inputs and line layouts.
307 // Since there are pointers refer to reflow inputs and line layouts,
308 // it is necessary to guarantee that they won't be moved. For this
309 // reason, they are wrapped in UniquePtr here.
310 AutoTArray<UniquePtr<ReflowInput>, RTC_ARRAY_SIZE> reflowInputs;
311 AutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
312 reflowInputs.SetCapacity(rtcCount);
313 lineLayouts.SetCapacity(rtcCount);
315 // Begin the line layout for each ruby text container in advance.
316 bool hasSpan = false;
317 for (uint32_t i = 0; i < rtcCount; i++) {
318 nsRubyTextContainerFrame* textContainer = textContainers[i];
319 WritingMode rtcWM = textContainer->GetWritingMode();
320 WritingMode reflowWM = lineWM.IsOrthogonalTo(rtcWM) ? rtcWM : lineWM;
321 if (textContainer->IsSpanContainer()) {
322 hasSpan = true;
325 ReflowInput* reflowInput = new ReflowInput(
326 aPresContext, *aReflowInput.mParentReflowInput, textContainer,
327 availSize.ConvertTo(textContainer->GetWritingMode(), lineWM));
328 reflowInputs.AppendElement(reflowInput);
329 nsLineLayout* lineLayout =
330 new nsLineLayout(aPresContext, reflowInput->mFloatManager, *reflowInput,
331 nullptr, aReflowInput.mLineLayout);
332 lineLayout->SetSuppressLineWrap(true);
333 lineLayouts.AppendElement(lineLayout);
335 // Line number is useless for ruby text
336 // XXX nullptr here may cause problem, see comments for
337 // nsLineLayout::mBlockRI and nsLineLayout::AddFloat
338 lineLayout->Init(nullptr, reflowInput->GetLineHeight(), -1);
339 reflowInput->mLineLayout = lineLayout;
341 // Border and padding are suppressed on ruby text containers.
342 // If the writing mode is vertical-rl, the horizontal position of
343 // rt frames will be updated when reflowing this text container,
344 // hence leave container size 0 here for now.
345 lineLayout->BeginLineReflow(0, 0, reflowInput->ComputedISize(),
346 NS_UNCONSTRAINEDSIZE, false, false, reflowWM,
347 nsSize(0, 0));
348 lineLayout->AttachRootFrameToBaseLineLayout();
351 aReflowInput.mLineLayout->BeginSpan(
352 this, &aReflowInput, 0, aReflowInput.AvailableISize(), &mBaseline);
354 bool allowInitialLineBreak, allowLineBreak;
355 GetIsLineBreakAllowed(this, aReflowInput.mLineLayout->LineIsBreakable(),
356 &allowInitialLineBreak, &allowLineBreak);
358 // Reflow columns excluding any span
359 RubyReflowInput reflowInput = {allowInitialLineBreak,
360 allowLineBreak && !hasSpan, textContainers,
361 aReflowInput, reflowInputs};
362 aDesiredSize.BSize(lineWM) = 0;
363 aDesiredSize.SetBlockStartAscent(0);
364 nscoord isize = ReflowColumns(reflowInput, aDesiredSize, aStatus);
365 DebugOnly<nscoord> lineSpanSize = aReflowInput.mLineLayout->EndSpan(this);
366 aDesiredSize.ISize(lineWM) = isize;
367 // When there are no frames inside the ruby base container, EndSpan
368 // will return 0. However, in this case, the actual width of the
369 // container could be non-zero because of non-empty ruby annotations.
370 // XXX When bug 765861 gets fixed, this warning should be upgraded.
371 NS_WARNING_ASSERTION(
372 aStatus.IsInlineBreak() || isize == lineSpanSize || mFrames.IsEmpty(),
373 "bad isize");
375 // If there exists any span, the columns must either be completely
376 // reflowed, or be not reflowed at all.
377 MOZ_ASSERT(aStatus.IsInlineBreakBefore() || aStatus.IsComplete() || !hasSpan);
378 if (!aStatus.IsInlineBreakBefore() && aStatus.IsComplete() && hasSpan) {
379 // Reflow spans
380 RubyReflowInput reflowInput = {false, false, textContainers, aReflowInput,
381 reflowInputs};
382 nscoord spanISize = ReflowSpans(reflowInput);
383 isize = std::max(isize, spanISize);
386 for (uint32_t i = 0; i < rtcCount; i++) {
387 // It happens before the ruby text container is reflowed, and that
388 // when it is reflowed, it will just use this size.
389 nsRubyTextContainerFrame* textContainer = textContainers[i];
390 nsLineLayout* lineLayout = lineLayouts[i].get();
392 RubyUtils::ClearReservedISize(textContainer);
393 nscoord rtcISize = lineLayout->GetCurrentICoord();
394 // Only span containers and containers with collapsed annotations
395 // need reserving isize. For normal ruby text containers, their
396 // children will be expanded properly. We only need to expand their
397 // own size.
398 if (!textContainer->IsSpanContainer()) {
399 rtcISize = isize;
400 } else if (isize > rtcISize) {
401 RubyUtils::SetReservedISize(textContainer, isize - rtcISize);
404 lineLayout->VerticalAlignLine();
405 textContainer->SetISize(rtcISize);
406 lineLayout->EndLineReflow();
409 // If this ruby base container is empty, size it as if there were
410 // an empty inline child inside.
411 if (mFrames.IsEmpty()) {
412 // Border and padding are suppressed on ruby base container, so we
413 // create a dummy zero-sized borderPadding for setting BSize.
414 WritingMode frameWM = aReflowInput.GetWritingMode();
415 LogicalMargin borderPadding(frameWM);
416 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
417 lineWM, frameWM);
420 ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
424 * This struct stores the continuations after this frame and
425 * corresponding text containers. It is used to speed up looking
426 * ahead for nonempty continuations.
428 struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState {
429 ContinuationTraversingState mBase;
430 AutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
431 const AutoRubyTextContainerArray& mTextContainers;
433 PullFrameState(nsRubyBaseContainerFrame* aBaseContainer,
434 const AutoRubyTextContainerArray& aTextContainers);
437 nscoord nsRubyBaseContainerFrame::ReflowColumns(
438 const RubyReflowInput& aReflowInput, ReflowOutput& aDesiredSize,
439 nsReflowStatus& aStatus) {
440 nsLineLayout* lineLayout = aReflowInput.mBaseReflowInput.mLineLayout;
441 const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
442 nscoord icoord = lineLayout->GetCurrentICoord();
443 MOZ_ASSERT(icoord == 0, "border/padding of rbc should have been suppressed");
444 nsReflowStatus reflowStatus;
445 aStatus.Reset();
447 uint32_t columnIndex = 0;
448 RubyColumn column;
449 column.mTextFrames.SetCapacity(rtcCount);
450 RubyColumnEnumerator e(this, aReflowInput.mTextContainers);
451 for (; !e.AtEnd(); e.Next()) {
452 e.GetColumn(column);
453 icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize,
454 reflowStatus);
455 if (!reflowStatus.IsInlineBreakBefore()) {
456 columnIndex++;
458 if (reflowStatus.IsInlineBreak()) {
459 break;
461 // We are not handling overflow here.
462 MOZ_ASSERT(reflowStatus.IsEmpty());
465 bool isComplete = false;
466 PullFrameState pullFrameState(this, aReflowInput.mTextContainers);
467 while (!reflowStatus.IsInlineBreak()) {
468 // We are not handling overflow here.
469 MOZ_ASSERT(reflowStatus.IsEmpty());
471 // Try pull some frames from next continuations. This call replaces
472 // frames in |column| with the frame pulled in each level.
473 PullOneColumn(lineLayout, pullFrameState, column, isComplete);
474 if (isComplete) {
475 // No more frames can be pulled.
476 break;
478 icoord += ReflowOneColumn(aReflowInput, columnIndex, column, aDesiredSize,
479 reflowStatus);
480 if (!reflowStatus.IsInlineBreakBefore()) {
481 columnIndex++;
485 if (!e.AtEnd() && reflowStatus.IsInlineBreakAfter()) {
486 // The current column has been successfully placed.
487 // Skip to the next column and mark break before.
488 e.Next();
489 e.GetColumn(column);
490 reflowStatus.SetInlineLineBreakBeforeAndReset();
492 if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
493 aStatus.SetIncomplete();
496 if (reflowStatus.IsInlineBreakBefore()) {
497 if (!columnIndex || !aReflowInput.mAllowLineBreak) {
498 // If no column has been placed yet, or we have any span,
499 // the whole container should be in the next line.
500 aStatus.SetInlineLineBreakBeforeAndReset();
501 return 0;
503 aStatus.SetInlineLineBreakAfter();
504 MOZ_ASSERT(aStatus.IsComplete() || aReflowInput.mAllowLineBreak);
506 // If we are on an intra-level whitespace column, null values in
507 // column.mBaseFrame and column.mTextFrames don't represent the
508 // end of the frame-sibling-chain at that level -- instead, they
509 // represent an anonymous empty intra-level whitespace box. It is
510 // likely that there are frames in the next column (which can't be
511 // intra-level whitespace). Those frames should be pushed as well.
512 Maybe<RubyColumn> nextColumn;
513 if (column.mIsIntraLevelWhitespace && !e.AtEnd()) {
514 e.Next();
515 nextColumn.emplace();
516 e.GetColumn(nextColumn.ref());
518 nsIFrame* baseFrame = column.mBaseFrame;
519 if (!baseFrame & nextColumn.isSome()) {
520 baseFrame = nextColumn->mBaseFrame;
522 if (baseFrame) {
523 PushChildrenToOverflow(baseFrame, baseFrame->GetPrevSibling());
525 for (uint32_t i = 0; i < rtcCount; i++) {
526 nsRubyTextFrame* textFrame = column.mTextFrames[i];
527 if (!textFrame && nextColumn.isSome()) {
528 textFrame = nextColumn->mTextFrames[i];
530 if (textFrame) {
531 aReflowInput.mTextContainers[i]->PushChildrenToOverflow(
532 textFrame, textFrame->GetPrevSibling());
535 } else if (reflowStatus.IsInlineBreakAfter()) {
536 // |reflowStatus| being break after here may only happen when
537 // there is a break after the column just pulled, or the whole
538 // segment has been completely reflowed. In those cases, we do
539 // not need to push anything.
540 MOZ_ASSERT(e.AtEnd());
541 aStatus.SetInlineLineBreakAfter();
544 return icoord;
547 nscoord nsRubyBaseContainerFrame::ReflowOneColumn(
548 const RubyReflowInput& aReflowInput, uint32_t aColumnIndex,
549 const RubyColumn& aColumn, ReflowOutput& aDesiredSize,
550 nsReflowStatus& aStatus) {
551 const ReflowInput& baseReflowInput = aReflowInput.mBaseReflowInput;
552 const auto& textReflowInputs = aReflowInput.mTextReflowInputs;
553 nscoord istart = baseReflowInput.mLineLayout->GetCurrentICoord();
555 if (aColumn.mBaseFrame) {
556 bool allowBreakBefore = aColumnIndex ? aReflowInput.mAllowLineBreak
557 : aReflowInput.mAllowInitialLineBreak;
558 if (allowBreakBefore) {
559 gfxBreakPriority breakPriority =
560 LineBreakBefore(aColumn.mBaseFrame,
561 baseReflowInput.mRenderingContext->GetDrawTarget(),
562 baseReflowInput.mLineLayout->LineContainerFrame(),
563 baseReflowInput.mLineLayout->GetLine());
564 if (breakPriority != gfxBreakPriority::eNoBreak) {
565 gfxBreakPriority lastBreakPriority =
566 baseReflowInput.mLineLayout->LastOptionalBreakPriority();
567 if (breakPriority >= lastBreakPriority) {
568 // Either we have been overflow, or we are forced
569 // to break here, do break before.
570 if (istart > baseReflowInput.AvailableISize() ||
571 baseReflowInput.mLineLayout->NotifyOptionalBreakPosition(
572 aColumn.mBaseFrame, 0, true, breakPriority)) {
573 aStatus.SetInlineLineBreakBeforeAndReset();
574 return 0;
581 const uint32_t rtcCount = aReflowInput.mTextContainers.Length();
582 MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
583 MOZ_ASSERT(textReflowInputs.Length() == rtcCount);
584 nscoord columnISize = 0;
586 nsAutoString baseText;
587 if (aColumn.mBaseFrame) {
588 nsLayoutUtils::GetFrameTextContent(aColumn.mBaseFrame, baseText);
591 // Reflow text frames
592 for (uint32_t i = 0; i < rtcCount; i++) {
593 nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
594 if (textFrame) {
595 bool isCollapsed = false;
596 if (textFrame->StyleVisibility()->mVisible == StyleVisibility::Collapse) {
597 isCollapsed = true;
598 } else {
599 // Per CSS Ruby spec, the content comparison for auto-hiding
600 // takes place prior to white spaces collapsing (white-space)
601 // and text transformation (text-transform), and ignores elements
602 // (considers only the textContent of the boxes). Which means
603 // using the content tree text comparison is correct.
604 nsAutoString annotationText;
605 nsLayoutUtils::GetFrameTextContent(textFrame, annotationText);
606 isCollapsed = annotationText.Equals(baseText);
608 if (isCollapsed) {
609 textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED);
610 } else {
611 textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_COLLAPSED);
613 RubyUtils::ClearReservedISize(textFrame);
615 bool pushedFrame;
616 nsReflowStatus reflowStatus;
617 nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
618 nscoord textIStart = lineLayout->GetCurrentICoord();
619 lineLayout->ReflowFrame(textFrame, reflowStatus, nullptr, pushedFrame);
620 if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
621 MOZ_ASSERT_UNREACHABLE(
622 "Any line break inside ruby box should have been suppressed");
623 // For safety, always drain the overflow list, so that
624 // no frames are left there after reflow.
625 textFrame->DrainSelfOverflowList();
627 nscoord textISize = lineLayout->GetCurrentICoord() - textIStart;
628 columnISize = std::max(columnISize, textISize);
632 // Reflow the base frame
633 if (aColumn.mBaseFrame) {
634 RubyUtils::ClearReservedISize(aColumn.mBaseFrame);
636 bool pushedFrame;
637 nsReflowStatus reflowStatus;
638 nsLineLayout* lineLayout = baseReflowInput.mLineLayout;
639 WritingMode lineWM = lineLayout->GetWritingMode();
640 nscoord baseIStart = lineLayout->GetCurrentICoord();
641 ReflowOutput metrics(lineWM);
642 lineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus, &metrics,
643 pushedFrame);
644 if (MOZ_UNLIKELY(reflowStatus.IsInlineBreak() || pushedFrame)) {
645 MOZ_ASSERT_UNREACHABLE(
646 "Any line break inside ruby box should have been suppressed");
647 // For safety, always drain the overflow list, so that
648 // no frames are left there after reflow.
649 aColumn.mBaseFrame->DrainSelfOverflowList();
651 nscoord baseISize = lineLayout->GetCurrentICoord() - baseIStart;
652 columnISize = std::max(columnISize, baseISize);
653 // Calculate ascent & descent of the base frame and update desired
654 // size of this base container accordingly.
655 nscoord oldAscent = aDesiredSize.BlockStartAscent();
656 nscoord oldDescent = aDesiredSize.BSize(lineWM) - oldAscent;
657 nscoord baseAscent = metrics.BlockStartAscent();
658 nscoord baseDesent = metrics.BSize(lineWM) - baseAscent;
659 LogicalMargin margin = aColumn.mBaseFrame->GetLogicalUsedMargin(lineWM);
660 nscoord newAscent = std::max(baseAscent + margin.BStart(lineWM), oldAscent);
661 nscoord newDescent = std::max(baseDesent + margin.BEnd(lineWM), oldDescent);
662 aDesiredSize.SetBlockStartAscent(newAscent);
663 aDesiredSize.BSize(lineWM) = newAscent + newDescent;
666 // Align all the line layout to the new coordinate.
667 nscoord icoord = istart + columnISize;
668 nscoord deltaISize = icoord - baseReflowInput.mLineLayout->GetCurrentICoord();
669 if (deltaISize > 0) {
670 baseReflowInput.mLineLayout->AdvanceICoord(deltaISize);
671 if (aColumn.mBaseFrame) {
672 RubyUtils::SetReservedISize(aColumn.mBaseFrame, deltaISize);
675 for (uint32_t i = 0; i < rtcCount; i++) {
676 if (aReflowInput.mTextContainers[i]->IsSpanContainer()) {
677 continue;
679 nsLineLayout* lineLayout = textReflowInputs[i]->mLineLayout;
680 nsRubyTextFrame* textFrame = aColumn.mTextFrames[i];
681 nscoord deltaISize = icoord - lineLayout->GetCurrentICoord();
682 if (deltaISize > 0) {
683 lineLayout->AdvanceICoord(deltaISize);
684 if (textFrame && !textFrame->IsCollapsed()) {
685 RubyUtils::SetReservedISize(textFrame, deltaISize);
688 if (aColumn.mBaseFrame && textFrame) {
689 lineLayout->AttachLastFrameToBaseLineLayout();
693 return columnISize;
696 nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
697 nsRubyBaseContainerFrame* aBaseContainer,
698 const AutoRubyTextContainerArray& aTextContainers)
699 : mBase(aBaseContainer), mTextContainers(aTextContainers) {
700 const uint32_t rtcCount = aTextContainers.Length();
701 for (uint32_t i = 0; i < rtcCount; i++) {
702 mTexts.AppendElement(aTextContainers[i]);
706 void nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout,
707 PullFrameState& aPullFrameState,
708 RubyColumn& aColumn,
709 bool& aIsComplete) {
710 const AutoRubyTextContainerArray& textContainers =
711 aPullFrameState.mTextContainers;
712 const uint32_t rtcCount = textContainers.Length();
714 nsIFrame* nextBase = GetNextInFlowChild(aPullFrameState.mBase);
715 MOZ_ASSERT(!nextBase || nextBase->IsRubyBaseFrame());
716 aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(nextBase);
717 bool foundFrame = !!aColumn.mBaseFrame;
718 bool pullingIntraLevelWhitespace =
719 aColumn.mBaseFrame && aColumn.mBaseFrame->IsIntraLevelWhitespace();
721 aColumn.mTextFrames.ClearAndRetainStorage();
722 for (uint32_t i = 0; i < rtcCount; i++) {
723 nsIFrame* nextText =
724 textContainers[i]->GetNextInFlowChild(aPullFrameState.mTexts[i]);
725 MOZ_ASSERT(!nextText || nextText->IsRubyTextFrame());
726 nsRubyTextFrame* textFrame = static_cast<nsRubyTextFrame*>(nextText);
727 aColumn.mTextFrames.AppendElement(textFrame);
728 foundFrame = foundFrame || nextText;
729 if (nextText && !pullingIntraLevelWhitespace) {
730 pullingIntraLevelWhitespace = textFrame->IsIntraLevelWhitespace();
733 // If there exists any frame in continations, we haven't
734 // completed the reflow process.
735 aIsComplete = !foundFrame;
736 if (!foundFrame) {
737 return;
740 aColumn.mIsIntraLevelWhitespace = pullingIntraLevelWhitespace;
741 if (pullingIntraLevelWhitespace) {
742 // We are pulling an intra-level whitespace. Drop all frames which
743 // are not part of this intra-level whitespace column. (Those frames
744 // are really part of the *next* column, after the pulled one.)
745 if (aColumn.mBaseFrame && !aColumn.mBaseFrame->IsIntraLevelWhitespace()) {
746 aColumn.mBaseFrame = nullptr;
748 for (uint32_t i = 0; i < rtcCount; i++) {
749 nsRubyTextFrame*& textFrame = aColumn.mTextFrames[i];
750 if (textFrame && !textFrame->IsIntraLevelWhitespace()) {
751 textFrame = nullptr;
754 } else {
755 // We are not pulling an intra-level whitespace, which means all
756 // elements we are going to pull can have non-whitespace content,
757 // which may contain float which we need to reparent.
758 MOZ_ASSERT(aColumn.begin() != aColumn.end(),
759 "Ruby column shouldn't be empty");
760 nsBlockFrame* oldFloatCB =
761 nsLayoutUtils::GetFloatContainingBlock(*aColumn.begin());
762 #ifdef DEBUG
763 MOZ_ASSERT(oldFloatCB, "Must have found a float containing block");
764 for (nsIFrame* frame : aColumn) {
765 MOZ_ASSERT(nsLayoutUtils::GetFloatContainingBlock(frame) == oldFloatCB,
766 "All frames in the same ruby column should share "
767 "the same old float containing block");
769 #endif
770 nsBlockFrame* newFloatCB = do_QueryFrame(aLineLayout->LineContainerFrame());
771 MOZ_ASSERT(newFloatCB, "Must have a float containing block");
772 if (oldFloatCB != newFloatCB) {
773 for (nsIFrame* frame : aColumn) {
774 newFloatCB->ReparentFloats(frame, oldFloatCB, false);
779 // Pull the frames of this column.
780 if (aColumn.mBaseFrame) {
781 DebugOnly<nsIFrame*> pulled = PullNextInFlowChild(aPullFrameState.mBase);
782 MOZ_ASSERT(pulled == aColumn.mBaseFrame, "pulled a wrong frame?");
784 for (uint32_t i = 0; i < rtcCount; i++) {
785 if (aColumn.mTextFrames[i]) {
786 DebugOnly<nsIFrame*> pulled =
787 textContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
788 MOZ_ASSERT(pulled == aColumn.mTextFrames[i], "pulled a wrong frame?");
792 if (!aIsComplete) {
793 // We pulled frames from the next line, hence mark it dirty.
794 aLineLayout->SetDirtyNextLine();
798 nscoord nsRubyBaseContainerFrame::ReflowSpans(
799 const RubyReflowInput& aReflowInput) {
800 nscoord spanISize = 0;
801 for (uint32_t i = 0, iend = aReflowInput.mTextContainers.Length(); i < iend;
802 i++) {
803 nsRubyTextContainerFrame* container = aReflowInput.mTextContainers[i];
804 if (!container->IsSpanContainer()) {
805 continue;
808 nsIFrame* rtFrame = container->PrincipalChildList().FirstChild();
809 nsReflowStatus reflowStatus;
810 bool pushedFrame;
811 nsLineLayout* lineLayout = aReflowInput.mTextReflowInputs[i]->mLineLayout;
812 MOZ_ASSERT(lineLayout->GetCurrentICoord() == 0,
813 "border/padding of rtc should have been suppressed");
814 lineLayout->ReflowFrame(rtFrame, reflowStatus, nullptr, pushedFrame);
815 MOZ_ASSERT(!reflowStatus.IsInlineBreak() && !pushedFrame,
816 "Any line break inside ruby box should has been suppressed");
817 spanISize = std::max(spanISize, lineLayout->GetCurrentICoord());
819 return spanISize;