Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / generic / nsRubyFrame.cpp
blobd7da5bc32142a13c3a96e20421467ef2335e5d63
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" */
9 #include "nsRubyFrame.h"
11 #include "RubyUtils.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/StaticPrefs_layout.h"
16 #include "mozilla/WritingModes.h"
17 #include "nsLayoutUtils.h"
18 #include "nsLineLayout.h"
19 #include "nsPresContext.h"
20 #include "nsContainerFrameInlines.h"
21 #include "nsRubyBaseContainerFrame.h"
22 #include "nsRubyTextContainerFrame.h"
24 using namespace mozilla;
26 //----------------------------------------------------------------------
28 // Frame class boilerplate
29 // =======================
31 NS_QUERYFRAME_HEAD(nsRubyFrame)
32 NS_QUERYFRAME_ENTRY(nsRubyFrame)
33 NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
35 NS_IMPL_FRAMEARENA_HELPERS(nsRubyFrame)
37 nsContainerFrame* NS_NewRubyFrame(PresShell* aPresShell,
38 ComputedStyle* aStyle) {
39 return new (aPresShell) nsRubyFrame(aStyle, aPresShell->GetPresContext());
42 //----------------------------------------------------------------------
44 // nsRubyFrame Method Implementations
45 // ==================================
47 #ifdef DEBUG_FRAME_DUMP
48 nsresult nsRubyFrame::GetFrameName(nsAString& aResult) const {
49 return MakeFrameName(u"Ruby"_ns, aResult);
51 #endif
53 /* virtual */
54 void nsRubyFrame::AddInlineMinISize(gfxContext* aRenderingContext,
55 nsIFrame::InlineMinISizeData* aData) {
56 auto handleChildren = [aRenderingContext](auto frame, auto data) {
57 for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
58 e.Next()) {
59 e.GetBaseContainer()->AddInlineMinISize(aRenderingContext, data);
62 DoInlineIntrinsicISize(aData, handleChildren);
65 /* virtual */
66 void nsRubyFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
67 nsIFrame::InlinePrefISizeData* aData) {
68 auto handleChildren = [aRenderingContext](auto frame, auto data) {
69 for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(frame)); !e.AtEnd();
70 e.Next()) {
71 e.GetBaseContainer()->AddInlinePrefISize(aRenderingContext, data);
74 DoInlineIntrinsicISize(aData, handleChildren);
75 aData->mLineIsEmpty = false;
78 static nsRubyBaseContainerFrame* FindRubyBaseContainerAncestor(
79 nsIFrame* aFrame) {
80 for (nsIFrame* ancestor = aFrame->GetParent();
81 ancestor && ancestor->IsLineParticipant();
82 ancestor = ancestor->GetParent()) {
83 if (ancestor->IsRubyBaseContainerFrame()) {
84 return static_cast<nsRubyBaseContainerFrame*>(ancestor);
87 return nullptr;
90 /* virtual */
91 void nsRubyFrame::Reflow(nsPresContext* aPresContext,
92 ReflowOutput& aDesiredSize,
93 const ReflowInput& aReflowInput,
94 nsReflowStatus& aStatus) {
95 MarkInReflow();
96 DO_GLOBAL_REFLOW_COUNT("nsRubyFrame");
97 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
98 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
100 if (!aReflowInput.mLineLayout) {
101 NS_ASSERTION(aReflowInput.mLineLayout,
102 "No line layout provided to RubyFrame reflow method.");
103 return;
106 // Grab overflow frames from prev-in-flow and its own.
107 MoveInlineOverflowToChildList(aReflowInput.mLineLayout->LineContainerFrame());
109 // Clear leadings
110 mLeadings.Reset();
112 // Begin the span for the ruby frame
113 WritingMode frameWM = aReflowInput.GetWritingMode();
114 WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
115 LogicalMargin borderPadding =
116 aReflowInput.ComputedLogicalBorderPadding(frameWM);
117 nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, borderPadding,
118 lineWM, frameWM);
120 nscoord startEdge = 0;
121 const bool boxDecorationBreakClone =
122 StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone;
123 if (boxDecorationBreakClone || !GetPrevContinuation()) {
124 startEdge = borderPadding.IStart(frameWM);
126 NS_ASSERTION(aReflowInput.AvailableISize() != NS_UNCONSTRAINEDSIZE,
127 "should no longer use available widths");
128 nscoord endEdge = aReflowInput.AvailableISize() - borderPadding.IEnd(frameWM);
129 aReflowInput.mLineLayout->BeginSpan(this, &aReflowInput, startEdge, endEdge,
130 &mBaseline);
132 for (RubySegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
133 ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
134 aDesiredSize.BSize(lineWM), e.GetBaseContainer(), aStatus);
136 if (aStatus.IsInlineBreak()) {
137 // A break occurs when reflowing the segment.
138 // Don't continue reflowing more segments.
139 break;
143 ContinuationTraversingState pullState(this);
144 while (aStatus.IsEmpty()) {
145 nsRubyBaseContainerFrame* baseContainer =
146 PullOneSegment(aReflowInput.mLineLayout, pullState);
147 if (!baseContainer) {
148 // No more continuations after, finish now.
149 break;
151 ReflowSegment(aPresContext, aReflowInput, aDesiredSize.BlockStartAscent(),
152 aDesiredSize.BSize(lineWM), baseContainer, aStatus);
154 // We never handle overflow in ruby.
155 MOZ_ASSERT(!aStatus.IsOverflowIncomplete());
157 aDesiredSize.ISize(lineWM) = aReflowInput.mLineLayout->EndSpan(this);
158 if (boxDecorationBreakClone || !GetPrevContinuation()) {
159 aDesiredSize.ISize(lineWM) += borderPadding.IStart(frameWM);
161 if (boxDecorationBreakClone || aStatus.IsComplete()) {
162 aDesiredSize.ISize(lineWM) += borderPadding.IEnd(frameWM);
165 // Update descendant leadings of ancestor ruby base container.
166 if (nsRubyBaseContainerFrame* rbc = FindRubyBaseContainerAncestor(this)) {
167 rbc->UpdateDescendantLeadings(mLeadings);
170 ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
173 void nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
174 const ReflowInput& aReflowInput,
175 nscoord aBlockStartAscent, nscoord aBlockSize,
176 nsRubyBaseContainerFrame* aBaseContainer,
177 nsReflowStatus& aStatus) {
178 WritingMode lineWM = aReflowInput.mLineLayout->GetWritingMode();
179 LogicalSize availSize(lineWM, aReflowInput.AvailableISize(),
180 aReflowInput.AvailableBSize());
181 NS_ASSERTION(!GetWritingMode().IsOrthogonalTo(lineWM),
182 "Ruby frame writing-mode shouldn't be orthogonal to its line");
184 AutoRubyTextContainerArray textContainers(aBaseContainer);
185 const uint32_t rtcCount = textContainers.Length();
187 ReflowOutput baseMetrics(aReflowInput);
188 bool pushedFrame;
189 aReflowInput.mLineLayout->ReflowFrame(aBaseContainer, aStatus, &baseMetrics,
190 pushedFrame);
192 if (aStatus.IsInlineBreakBefore()) {
193 if (aBaseContainer != mFrames.FirstChild()) {
194 // Some segments may have been reflowed before, hence it is not
195 // a break-before for the ruby container.
196 aStatus.Reset();
197 aStatus.SetInlineLineBreakAfter();
198 aStatus.SetIncomplete();
199 PushChildrenToOverflow(aBaseContainer, aBaseContainer->GetPrevSibling());
200 aReflowInput.mLineLayout->SetDirtyNextLine();
202 // This base container is not placed at all, we can skip all
203 // text containers paired with it.
204 return;
206 if (aStatus.IsIncomplete()) {
207 // It always promise that if the status is incomplete, there is a
208 // break occurs. Break before has been processed above. However,
209 // it is possible that break after happens with the frame reflow
210 // completed. It happens if there is a force break at the end.
211 MOZ_ASSERT(aStatus.IsInlineBreakAfter());
212 // Find the previous sibling which we will
213 // insert new continuations after.
214 nsIFrame* lastChild;
215 if (rtcCount > 0) {
216 lastChild = textContainers.LastElement();
217 } else {
218 lastChild = aBaseContainer;
221 // Create continuations for the base container
222 nsIFrame* newBaseContainer = CreateNextInFlow(aBaseContainer);
223 // newBaseContainer is null if there are existing next-in-flows.
224 // We only need to move and push if there were not.
225 if (newBaseContainer) {
226 // Move the new frame after all the text containers
227 mFrames.RemoveFrame(newBaseContainer);
228 mFrames.InsertFrame(nullptr, lastChild, newBaseContainer);
230 // Create continuations for text containers
231 nsIFrame* newLastChild = newBaseContainer;
232 for (uint32_t i = 0; i < rtcCount; i++) {
233 nsIFrame* newTextContainer = CreateNextInFlow(textContainers[i]);
234 MOZ_ASSERT(newTextContainer,
235 "Next-in-flow of rtc should not exist "
236 "if the corresponding rbc does not");
237 mFrames.RemoveFrame(newTextContainer);
238 mFrames.InsertFrame(nullptr, newLastChild, newTextContainer);
239 newLastChild = newTextContainer;
242 if (lastChild != mFrames.LastChild()) {
243 // Always push the next frame after the last child in this segment.
244 // It is possible that we pulled it back before our next-in-flow
245 // drain our overflow.
246 PushChildrenToOverflow(lastChild->GetNextSibling(), lastChild);
247 aReflowInput.mLineLayout->SetDirtyNextLine();
249 } else if (rtcCount) {
250 DestroyContext context(PresShell());
251 // If the ruby base container is reflowed completely, the line
252 // layout will remove the next-in-flows of that frame. But the
253 // line layout is not aware of the ruby text containers, hence
254 // it is necessary to remove them here.
255 for (uint32_t i = 0; i < rtcCount; i++) {
256 if (nsIFrame* nextRTC = textContainers[i]->GetNextInFlow()) {
257 nextRTC->GetParent()->DeleteNextInFlowChild(context, nextRTC, true);
262 nscoord segmentISize = baseMetrics.ISize(lineWM);
263 const nsSize dummyContainerSize;
264 LogicalRect baseRect =
265 aBaseContainer->GetLogicalRect(lineWM, dummyContainerSize);
266 // We need to position our rtc frames on one side or the other of the
267 // base container's rect, using a coordinate space that's relative to
268 // the ruby frame. Right now, the base container's rect's block-axis
269 // position is relative to the block container frame containing the
270 // lines, so here we reset it to the different between the ascents of
271 // the ruby container and the ruby base container, assuming they are
272 // aligned with the baseline.
273 // XXX We may need to add border/padding here. See bug 1055667.
274 baseRect.BStart(lineWM) = aBlockStartAscent - baseMetrics.BlockStartAscent();
275 // The rect for offsets of text containers.
276 LogicalRect offsetRect = baseRect;
277 RubyBlockLeadings descLeadings = aBaseContainer->GetDescendantLeadings();
278 offsetRect.BStart(lineWM) -= descLeadings.mStart;
279 offsetRect.BSize(lineWM) += descLeadings.mStart + descLeadings.mEnd;
280 Maybe<LineRelativeDir> lastLineSide;
281 for (uint32_t i = 0; i < rtcCount; i++) {
282 nsRubyTextContainerFrame* textContainer = textContainers[i];
283 WritingMode rtcWM = textContainer->GetWritingMode();
284 nsReflowStatus textReflowStatus;
285 ReflowOutput textMetrics(aReflowInput);
286 ReflowInput textReflowInput(aPresContext, aReflowInput, textContainer,
287 availSize.ConvertTo(rtcWM, lineWM));
288 textContainer->Reflow(aPresContext, textMetrics, textReflowInput,
289 textReflowStatus);
290 // Ruby text containers always return complete reflow status even when
291 // they have continuations, because the breaking has already been
292 // handled when reflowing the base containers.
293 NS_ASSERTION(textReflowStatus.IsEmpty(),
294 "Ruby text container must not break itself inside");
295 const LogicalSize size = textMetrics.Size(lineWM);
296 textContainer->SetSize(lineWM, size);
298 nscoord reservedISize = RubyUtils::GetReservedISize(textContainer);
299 segmentISize = std::max(segmentISize, size.ISize(lineWM) + reservedISize);
301 Maybe<LineRelativeDir> lineSide;
302 switch (textContainer->StyleText()->mRubyPosition) {
303 case StyleRubyPosition::Over:
304 lineSide.emplace(eLineRelativeDirOver);
305 break;
306 case StyleRubyPosition::Under:
307 lineSide.emplace(eLineRelativeDirUnder);
308 break;
309 case StyleRubyPosition::AlternateOver:
310 if (lastLineSide.isSome() &&
311 lastLineSide.value() == eLineRelativeDirOver) {
312 lineSide.emplace(eLineRelativeDirUnder);
313 } else {
314 lineSide.emplace(eLineRelativeDirOver);
316 break;
317 case StyleRubyPosition::AlternateUnder:
318 if (lastLineSide.isSome() &&
319 lastLineSide.value() == eLineRelativeDirUnder) {
320 lineSide.emplace(eLineRelativeDirOver);
321 } else {
322 lineSide.emplace(eLineRelativeDirUnder);
324 break;
325 default:
326 // XXX inter-character support in bug 1055672
327 MOZ_ASSERT_UNREACHABLE("Unsupported ruby-position");
329 lastLineSide = lineSide;
331 LogicalPoint position(lineWM);
332 if (lineSide.isSome()) {
333 LogicalSide logicalSide =
334 lineWM.LogicalSideForLineRelativeDir(lineSide.value());
335 if (StaticPrefs::layout_css_ruby_intercharacter_enabled() &&
336 rtcWM.IsVerticalRL() &&
337 lineWM.GetInlineDir() == WritingMode::eInlineLTR) {
338 // Inter-character ruby annotations are only supported for vertical-rl
339 // in ltr horizontal writing. Fall back to non-inter-character behavior
340 // otherwise.
341 LogicalPoint offset(
342 lineWM, offsetRect.ISize(lineWM),
343 offsetRect.BSize(lineWM) > size.BSize(lineWM)
344 ? (offsetRect.BSize(lineWM) - size.BSize(lineWM)) / 2
345 : 0);
346 position = offsetRect.Origin(lineWM) + offset;
347 aReflowInput.mLineLayout->AdvanceICoord(size.ISize(lineWM));
348 } else if (logicalSide == eLogicalSideBStart) {
349 offsetRect.BStart(lineWM) -= size.BSize(lineWM);
350 offsetRect.BSize(lineWM) += size.BSize(lineWM);
351 position = offsetRect.Origin(lineWM);
352 } else if (logicalSide == eLogicalSideBEnd) {
353 position = offsetRect.Origin(lineWM) +
354 LogicalPoint(lineWM, 0, offsetRect.BSize(lineWM));
355 offsetRect.BSize(lineWM) += size.BSize(lineWM);
356 } else {
357 MOZ_ASSERT_UNREACHABLE("???");
360 // Using a dummy container-size here, so child positioning may not be
361 // correct. We will fix it in nsLineLayout after the whole line is
362 // reflowed.
363 FinishReflowChild(textContainer, aPresContext, textMetrics,
364 &textReflowInput, lineWM, position, dummyContainerSize,
365 ReflowChildFlags::Default);
367 MOZ_ASSERT(baseRect.ISize(lineWM) == offsetRect.ISize(lineWM),
368 "Annotations should only be placed on the block directions");
370 nscoord deltaISize = segmentISize - baseMetrics.ISize(lineWM);
371 if (deltaISize <= 0) {
372 RubyUtils::ClearReservedISize(aBaseContainer);
373 } else {
374 RubyUtils::SetReservedISize(aBaseContainer, deltaISize);
375 aReflowInput.mLineLayout->AdvanceICoord(deltaISize);
378 // Set block leadings of the base container.
379 // The leadings are the difference between the offsetRect and the rect
380 // of this ruby container, which has block start zero and block size
381 // aBlockSize.
382 nscoord startLeading = -offsetRect.BStart(lineWM);
383 nscoord endLeading = offsetRect.BEnd(lineWM) - aBlockSize;
384 // XXX When bug 765861 gets fixed, this warning should be upgraded.
385 NS_WARNING_ASSERTION(startLeading >= 0 && endLeading >= 0,
386 "Leadings should be non-negative (because adding "
387 "ruby annotation can only increase the size)");
388 mLeadings.Update(startLeading, endLeading);
391 nsRubyBaseContainerFrame* nsRubyFrame::PullOneSegment(
392 const nsLineLayout* aLineLayout, ContinuationTraversingState& aState) {
393 // Pull a ruby base container
394 nsIFrame* baseFrame = GetNextInFlowChild(aState);
395 if (!baseFrame) {
396 return nullptr;
398 MOZ_ASSERT(baseFrame->IsRubyBaseContainerFrame());
400 // Get the float containing block of the base frame before we pull it.
401 nsBlockFrame* oldFloatCB = nsLayoutUtils::GetFloatContainingBlock(baseFrame);
402 PullNextInFlowChild(aState);
404 // Pull all ruby text containers following the base container
405 nsIFrame* nextFrame;
406 while ((nextFrame = GetNextInFlowChild(aState)) != nullptr &&
407 nextFrame->IsRubyTextContainerFrame()) {
408 PullNextInFlowChild(aState);
411 if (nsBlockFrame* newFloatCB =
412 do_QueryFrame(aLineLayout->LineContainerFrame())) {
413 if (oldFloatCB && oldFloatCB != newFloatCB) {
414 newFloatCB->ReparentFloats(baseFrame, oldFloatCB, true);
418 return static_cast<nsRubyBaseContainerFrame*>(baseFrame);