Bug 1879774 [wpt PR 44524] - WebKit export: Implement field-sizing support for input...
[gecko.git] / layout / generic / nsFirstLetterFrame.cpp
blobfe294652c76a30279d3fc2ebf1cfdb5b365b7e91
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 :first-letter pseudo-element */
9 #include "nsFirstLetterFrame.h"
10 #include "nsPresContext.h"
11 #include "nsPresContextInlines.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/PresShellInlines.h"
15 #include "mozilla/RestyleManager.h"
16 #include "mozilla/ServoStyleSet.h"
17 #include "mozilla/StaticPrefs_layout.h"
18 #include "nsIContent.h"
19 #include "nsLayoutUtils.h"
20 #include "nsLineLayout.h"
21 #include "nsGkAtoms.h"
22 #include "nsFrameManager.h"
23 #include "nsPlaceholderFrame.h"
24 #include "nsTextFrame.h"
25 #include "nsCSSFrameConstructor.h"
27 using namespace mozilla;
28 using namespace mozilla::layout;
30 nsFirstLetterFrame* NS_NewFirstLetterFrame(PresShell* aPresShell,
31 ComputedStyle* aStyle) {
32 return new (aPresShell)
33 nsFirstLetterFrame(aStyle, aPresShell->GetPresContext());
36 nsFirstLetterFrame* NS_NewFloatingFirstLetterFrame(PresShell* aPresShell,
37 ComputedStyle* aStyle) {
38 return new (aPresShell)
39 nsFloatingFirstLetterFrame(aStyle, aPresShell->GetPresContext());
42 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame)
44 NS_QUERYFRAME_HEAD(nsFirstLetterFrame)
45 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame)
46 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
48 NS_IMPL_FRAMEARENA_HELPERS(nsFloatingFirstLetterFrame)
49 NS_QUERYFRAME_HEAD(nsFloatingFirstLetterFrame)
50 NS_QUERYFRAME_ENTRY(nsFloatingFirstLetterFrame)
51 NS_QUERYFRAME_TAIL_INHERITING(nsFirstLetterFrame)
53 #ifdef DEBUG_FRAME_DUMP
54 nsresult nsFirstLetterFrame::GetFrameName(nsAString& aResult) const {
55 return MakeFrameName(u"Letter"_ns, aResult);
57 #endif
59 void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
60 const nsDisplayListSet& aLists) {
61 BuildDisplayListForInline(aBuilder, aLists);
64 void nsFirstLetterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
65 nsIFrame* aPrevInFlow) {
66 RefPtr<ComputedStyle> newSC;
67 if (aPrevInFlow) {
68 // Get proper ComputedStyle for ourselves. We're creating the frame
69 // that represents everything *except* the first letter, so just create
70 // a ComputedStyle that inherits from our style parent, with no extra rules.
71 nsIFrame* styleParent =
72 CorrectStyleParentFrame(aParent, PseudoStyleType::firstLetter);
73 ComputedStyle* parentComputedStyle = styleParent->Style();
74 newSC = PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation(
75 parentComputedStyle);
76 SetComputedStyleWithoutNotification(newSC);
79 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
82 void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID,
83 nsFrameList&& aChildList) {
84 MOZ_ASSERT(aListID == FrameChildListID::Principal,
85 "Principal child list is the only "
86 "list that nsFirstLetterFrame should set via this function");
87 for (nsIFrame* f : aChildList) {
88 MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
89 MOZ_ASSERT(f->IsTextFrame(),
90 "We should not have kids that are containers!");
91 nsLayoutUtils::MarkDescendantsDirty(f); // Drops cached textruns
94 mFrames = std::move(aChildList);
97 nsresult nsFirstLetterFrame::GetChildFrameContainingOffset(
98 int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset,
99 nsIFrame** outChildFrame) {
100 nsIFrame* kid = mFrames.FirstChild();
101 if (kid) {
102 return kid->GetChildFrameContainingOffset(
103 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
105 return nsIFrame::GetChildFrameContainingOffset(
106 inContentOffset, inHint, outFrameContentOffset, outChildFrame);
109 // Needed for non-floating first-letter frames and for the continuations
110 // following the first-letter that we also use nsFirstLetterFrame for.
111 /* virtual */
112 void nsFirstLetterFrame::AddInlineMinISize(
113 gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
114 DoInlineMinISize(aRenderingContext, aData);
117 // Needed for non-floating first-letter frames and for the continuations
118 // following the first-letter that we also use nsFirstLetterFrame for.
119 /* virtual */
120 void nsFirstLetterFrame::AddInlinePrefISize(
121 gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData) {
122 DoInlinePrefISize(aRenderingContext, aData);
125 // Needed for floating first-letter frames.
126 /* virtual */
127 nscoord nsFirstLetterFrame::GetMinISize(gfxContext* aRenderingContext) {
128 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
131 // Needed for floating first-letter frames.
132 /* virtual */
133 nscoord nsFirstLetterFrame::GetPrefISize(gfxContext* aRenderingContext) {
134 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
137 /* virtual */
138 nsIFrame::SizeComputationResult nsFirstLetterFrame::ComputeSize(
139 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
140 nscoord aAvailableISize, const LogicalSize& aMargin,
141 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
142 ComputeSizeFlags aFlags) {
143 if (GetPrevInFlow()) {
144 // We're wrapping the text *after* the first letter, so behave like an
145 // inline frame.
146 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
147 AspectRatioUsage::None};
149 return nsContainerFrame::ComputeSize(aRenderingContext, aWM, aCBSize,
150 aAvailableISize, aMargin, aBorderPadding,
151 aSizeOverrides, aFlags);
154 bool nsFirstLetterFrame::UseTightBounds() const {
155 int v = StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds();
157 // Check for the simple cases:
158 // pref value > 0: use legacy gecko behavior
159 // pref value = 0: use webkit/blink-like behavior
160 if (v > 0) {
161 return true;
163 if (v == 0) {
164 return false;
167 // Pref value < 0: use heuristics to determine whether the page is assuming
168 // webkit/blink-style behavior:
169 // If line-height is less than font-size, or there is a negative block-start
170 // or -end margin, use webkit/blink behavior.
171 if (nsTextFrame* textFrame = do_QueryFrame(mFrames.FirstChild())) {
172 RefPtr<nsFontMetrics> fm = textFrame->InflatedFontMetrics();
173 if (textFrame->ComputeLineHeight() < fm->EmHeight()) {
174 return false;
178 const auto wm = GetWritingMode();
179 const auto& margin = StyleMargin()->mMargin;
180 const auto& bStart = margin.GetBStart(wm);
181 // Currently, we only check for margins with negative *length* values;
182 // negative percentages seem unlikely to be used/useful in this context.
183 if (bStart.ConvertsToLength() && bStart.ToLength() < 0) {
184 return false;
186 const auto& bEnd = margin.GetBEnd(wm);
187 if (bEnd.ConvertsToLength() && bEnd.ToLength() < 0) {
188 return false;
191 return true;
194 void nsFirstLetterFrame::Reflow(nsPresContext* aPresContext,
195 ReflowOutput& aMetrics,
196 const ReflowInput& aReflowInput,
197 nsReflowStatus& aReflowStatus) {
198 MarkInReflow();
199 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame");
200 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aReflowStatus);
201 MOZ_ASSERT(aReflowStatus.IsEmpty(),
202 "Caller should pass a fresh reflow status!");
204 // Grab overflow list
205 DrainOverflowFrames(aPresContext);
207 nsIFrame* kid = mFrames.FirstChild();
209 // Setup reflow input for our child
210 WritingMode wm = aReflowInput.GetWritingMode();
211 LogicalSize availSize = aReflowInput.AvailableSize();
212 const auto bp = aReflowInput.ComputedLogicalBorderPadding(wm);
213 NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE,
214 "should no longer use unconstrained inline size");
215 availSize.ISize(wm) -= bp.IStartEnd(wm);
216 if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
217 availSize.BSize(wm) -= bp.BStartEnd(wm);
220 WritingMode lineWM = aMetrics.GetWritingMode();
221 ReflowOutput kidMetrics(lineWM);
223 // Reflow the child
224 if (!aReflowInput.mLineLayout) {
225 // When there is no lineLayout provided, we provide our own. The
226 // only time that the first-letter-frame is not reflowing in a
227 // line context is when its floating.
228 WritingMode kidWritingMode = WritingModeForLine(wm, kid);
229 LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm);
230 ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize);
231 nsLineLayout ll(aPresContext, nullptr, aReflowInput, nullptr, nullptr);
233 ll.BeginLineReflow(
234 bp.IStart(wm), bp.BStart(wm), availSize.ISize(wm), NS_UNCONSTRAINEDSIZE,
235 false, true, kidWritingMode,
236 nsSize(aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
237 rs.mLineLayout = &ll;
238 ll.SetInFirstLetter(true);
239 ll.SetFirstLetterStyleOK(true);
241 kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus);
243 ll.EndLineReflow();
244 ll.SetInFirstLetter(false);
246 // In the floating first-letter case, we need to set this ourselves;
247 // nsLineLayout::BeginSpan will set it in the other case
248 mBaseline = kidMetrics.BlockStartAscent();
250 // Place and size the child and update the output metrics
251 LogicalSize convertedSize = kidMetrics.Size(wm);
253 const bool tightBounds = UseTightBounds();
254 const nscoord shift =
255 tightBounds ? 0
256 // Shift by half of the difference between the line-height
257 // we're going to use and current height of the kid frame.
258 : (rs.GetLineHeight() - convertedSize.BSize(wm)) / 2;
260 kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm) + shift,
261 convertedSize.ISize(wm), convertedSize.BSize(wm)));
262 kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay);
263 kid->DidReflow(aPresContext, nullptr);
265 if (!tightBounds) {
266 // Adjust size to account for line-height.
267 convertedSize.BSize(wm) = rs.GetLineHeight();
270 convertedSize.ISize(wm) += bp.IStartEnd(wm);
271 convertedSize.BSize(wm) += bp.BStartEnd(wm);
272 aMetrics.SetSize(wm, convertedSize);
273 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + bp.BStart(wm));
275 // Ensure that the overflow rect contains the child textframe's
276 // overflow rect.
277 // Note that if this is floating, the overline/underline drawable
278 // area is in the overflow rect of the child textframe.
279 aMetrics.UnionOverflowAreasWithDesiredBounds();
280 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
282 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
283 } else {
284 // Pretend we are a span and reflow the child frame
285 nsLineLayout* ll = aReflowInput.mLineLayout;
286 bool pushedFrame;
288 ll->SetInFirstLetter(Style()->GetPseudoType() ==
289 PseudoStyleType::firstLetter);
290 ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), availSize.ISize(wm),
291 &mBaseline);
292 ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame);
293 NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(),
294 "we're assuming we can mix sizes between lineWM and wm "
295 "since we shouldn't have orthogonal writing modes within "
296 "a line.");
297 aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm);
298 ll->SetInFirstLetter(false);
300 if (mComputedStyle->StyleTextReset()->mInitialLetterSize != 0.0f) {
301 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() +
302 bp.BStart(wm));
303 aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm);
304 } else {
305 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm);
309 if (!aReflowStatus.IsInlineBreakBefore()) {
310 // Create a continuation or remove existing continuations based on
311 // the reflow completion status.
312 if (aReflowStatus.IsComplete()) {
313 if (aReflowInput.mLineLayout) {
314 aReflowInput.mLineLayout->SetFirstLetterStyleOK(false);
316 if (nsIFrame* kidNextInFlow = kid->GetNextInFlow()) {
317 DestroyContext context(PresShell());
318 // Remove all of the childs next-in-flows
319 kidNextInFlow->GetParent()->DeleteNextInFlowChild(context,
320 kidNextInFlow, true);
322 } else {
323 // Create a continuation for the child frame if it doesn't already
324 // have one.
325 if (!IsFloating()) {
326 CreateNextInFlow(kid);
327 // And then push it to our overflow list
328 nsFrameList overflow = mFrames.TakeFramesAfter(kid);
329 if (overflow.NotEmpty()) {
330 SetOverflowFrames(std::move(overflow));
332 } else if (!kid->GetNextInFlow()) {
333 // For floating first letter frames (if a continuation wasn't already
334 // created for us) we need to put the continuation with the rest of the
335 // text that the first letter frame was made out of.
336 nsIFrame* continuation;
337 CreateContinuationForFloatingParent(kid, &continuation, true);
343 /* virtual */
344 bool nsFirstLetterFrame::CanContinueTextRun() const {
345 // We can continue a text run through a first-letter frame.
346 return true;
349 void nsFirstLetterFrame::CreateContinuationForFloatingParent(
350 nsIFrame* aChild, nsIFrame** aContinuation, bool aIsFluid) {
351 NS_ASSERTION(IsFloating(),
352 "can only call this on floating first letter frames");
353 MOZ_ASSERT(aContinuation, "bad args");
355 *aContinuation = nullptr;
357 mozilla::PresShell* presShell = PresShell();
358 nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame();
359 nsContainerFrame* parent = placeholderFrame->GetParent();
361 nsIFrame* continuation = presShell->FrameConstructor()->CreateContinuingFrame(
362 aChild, parent, aIsFluid);
364 // The continuation will have gotten the first letter style from its
365 // prev continuation, so we need to repair the ComputedStyle so it
366 // doesn't have the first letter styling.
368 // Note that getting parent frame's ComputedStyle is different from getting
369 // this frame's ComputedStyle's parent in the presence of ::first-line,
370 // which we do want the continuation to inherit from.
371 ComputedStyle* parentSC = parent->Style();
372 if (parentSC) {
373 RefPtr<ComputedStyle> newSC;
374 newSC =
375 presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC);
376 continuation->SetComputedStyle(newSC);
377 nsLayoutUtils::MarkDescendantsDirty(continuation);
380 // XXX Bidi may not be involved but we have to use the list name
381 // FrameChildListID::NoReflowPrincipal because this is just like creating a
382 // continuation except we have to insert it in a different place and we don't
383 // want a reflow command to try to be issued.
384 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, placeholderFrame,
385 nullptr, nsFrameList(continuation, continuation));
387 *aContinuation = continuation;
390 void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) {
391 // Check for an overflow list with our prev-in-flow
392 nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow();
393 if (prevInFlow) {
394 AutoFrameListPtr overflowFrames(aPresContext,
395 prevInFlow->StealOverflowFrames());
396 if (overflowFrames) {
397 NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list");
399 // When pushing and pulling frames we need to check for whether any
400 // views need to be reparented.
401 nsContainerFrame::ReparentFrameViewList(*overflowFrames, prevInFlow,
402 this);
403 mFrames.InsertFrames(this, nullptr, std::move(*overflowFrames));
407 // It's also possible that we have an overflow list for ourselves
408 AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames());
409 if (overflowFrames) {
410 NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
411 mFrames.AppendFrames(nullptr, std::move(*overflowFrames));
414 // Now repair our first frames ComputedStyle (since we only reflow
415 // one frame there is no point in doing any other ones until they
416 // are reflowed)
417 nsIFrame* kid = mFrames.FirstChild();
418 if (kid) {
419 nsIContent* kidContent = kid->GetContent();
420 if (kidContent) {
421 NS_ASSERTION(kidContent->IsText(), "should contain only text nodes");
422 ComputedStyle* parentSC;
423 if (prevInFlow) {
424 // This is for the rest of the content not in the first-letter.
425 nsIFrame* styleParent =
426 CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter);
427 parentSC = styleParent->Style();
428 } else {
429 // And this for the first-letter style.
430 parentSC = mComputedStyle;
432 RefPtr<ComputedStyle> sc =
433 aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC);
434 kid->SetComputedStyle(sc);
435 nsLayoutUtils::MarkDescendantsDirty(kid);
440 Maybe<nscoord> nsFirstLetterFrame::GetNaturalBaselineBOffset(
441 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
442 BaselineExportContext) const {
443 if (aBaselineGroup == BaselineSharingGroup::Last) {
444 return Nothing{};
446 return Some(mBaseline);
449 LogicalSides nsFirstLetterFrame::GetLogicalSkipSides() const {
450 if (GetPrevContinuation()) {
451 // We shouldn't get calls to GetSkipSides for later continuations since
452 // they have separate ComputedStyles with initial values for all the
453 // properties that could trigger a call to GetSkipSides. Then again,
454 // it's not really an error to call GetSkipSides on any frame, so
455 // that's why we handle it properly.
456 return LogicalSides(mWritingMode, eLogicalSideBitsAll);
458 return LogicalSides(mWritingMode); // first continuation displays all sides