nsFirstLetterFrame.cpp (20163B)
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 11 #include "mozilla/ComputedStyle.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/PresShellInlines.h" 14 #include "mozilla/RestyleManager.h" 15 #include "mozilla/ServoStyleSet.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "nsCSSFrameConstructor.h" 18 #include "nsFrameManager.h" 19 #include "nsGkAtoms.h" 20 #include "nsIContent.h" 21 #include "nsLayoutUtils.h" 22 #include "nsLineLayout.h" 23 #include "nsPlaceholderFrame.h" 24 #include "nsPresContext.h" 25 #include "nsPresContextInlines.h" 26 #include "nsTextFrame.h" 27 28 using namespace mozilla; 29 using namespace mozilla::layout; 30 31 nsFirstLetterFrame* NS_NewFirstLetterFrame(PresShell* aPresShell, 32 ComputedStyle* aStyle) { 33 return new (aPresShell) 34 nsFirstLetterFrame(aStyle, aPresShell->GetPresContext()); 35 } 36 37 nsFirstLetterFrame* NS_NewFloatingFirstLetterFrame(PresShell* aPresShell, 38 ComputedStyle* aStyle) { 39 return new (aPresShell) 40 nsFloatingFirstLetterFrame(aStyle, aPresShell->GetPresContext()); 41 } 42 43 NS_IMPL_FRAMEARENA_HELPERS(nsFirstLetterFrame) 44 45 NS_QUERYFRAME_HEAD(nsFirstLetterFrame) 46 NS_QUERYFRAME_ENTRY(nsFirstLetterFrame) 47 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 48 49 NS_IMPL_FRAMEARENA_HELPERS(nsFloatingFirstLetterFrame) 50 NS_QUERYFRAME_HEAD(nsFloatingFirstLetterFrame) 51 NS_QUERYFRAME_ENTRY(nsFloatingFirstLetterFrame) 52 NS_QUERYFRAME_TAIL_INHERITING(nsFirstLetterFrame) 53 54 #ifdef DEBUG_FRAME_DUMP 55 nsresult nsFirstLetterFrame::GetFrameName(nsAString& aResult) const { 56 return MakeFrameName(u"Letter"_ns, aResult); 57 } 58 #endif 59 60 void nsFirstLetterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 61 const nsDisplayListSet& aLists) { 62 BuildDisplayListForInline(aBuilder, aLists); 63 } 64 65 void nsFirstLetterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 66 nsIFrame* aPrevInFlow) { 67 RefPtr<ComputedStyle> newSC; 68 if (aPrevInFlow) { 69 // Get proper ComputedStyle for ourselves. We're creating the frame 70 // that represents everything *except* the first letter, so just create 71 // a ComputedStyle that inherits from our style parent, with no extra rules. 72 nsIFrame* styleParent = 73 CorrectStyleParentFrame(aParent, PseudoStyleType::firstLetter); 74 ComputedStyle* parentComputedStyle = styleParent->Style(); 75 newSC = PresContext()->StyleSet()->ResolveStyleForFirstLetterContinuation( 76 parentComputedStyle); 77 SetComputedStyleWithoutNotification(newSC); 78 } 79 80 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 81 } 82 83 void nsFirstLetterFrame::SetInitialChildList(ChildListID aListID, 84 nsFrameList&& aChildList) { 85 MOZ_ASSERT(aListID == FrameChildListID::Principal, 86 "Principal child list is the only " 87 "list that nsFirstLetterFrame should set via this function"); 88 for (nsIFrame* f : aChildList) { 89 MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); 90 MOZ_ASSERT(f->IsTextFrame(), 91 "We should not have kids that are containers!"); 92 nsLayoutUtils::MarkDescendantsDirty(f); // Drops cached textruns 93 } 94 95 mFrames = std::move(aChildList); 96 } 97 98 nsresult nsFirstLetterFrame::GetChildFrameContainingOffset( 99 int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset, 100 nsIFrame** outChildFrame) { 101 nsIFrame* kid = mFrames.FirstChild(); 102 if (kid) { 103 return kid->GetChildFrameContainingOffset( 104 inContentOffset, inHint, outFrameContentOffset, outChildFrame); 105 } 106 return nsIFrame::GetChildFrameContainingOffset( 107 inContentOffset, inHint, outFrameContentOffset, outChildFrame); 108 } 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 */ 113 void nsFirstLetterFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, 114 InlineMinISizeData* aData) { 115 DoInlineMinISize(aInput, aData); 116 } 117 118 // Needed for non-floating first-letter frames and for the continuations 119 // following the first-letter that we also use nsFirstLetterFrame for. 120 /* virtual */ 121 void nsFirstLetterFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput, 122 InlinePrefISizeData* aData) { 123 DoInlinePrefISize(aInput, aData); 124 } 125 126 // Needed for floating first-letter frames. 127 nscoord nsFirstLetterFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 128 IntrinsicISizeType aType) { 129 return IntrinsicISizeFromInline(aInput, aType); 130 } 131 132 /* virtual */ 133 nsIFrame::SizeComputationResult nsFirstLetterFrame::ComputeSize( 134 const SizeComputationInput& aSizingInput, WritingMode aWM, 135 const LogicalSize& aCBSize, nscoord aAvailableISize, 136 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 137 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 138 if (GetPrevInFlow()) { 139 // We're wrapping the text *after* the first letter, so behave like an 140 // inline frame. 141 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), 142 AspectRatioUsage::None}; 143 } 144 return nsContainerFrame::ComputeSize(aSizingInput, aWM, aCBSize, 145 aAvailableISize, aMargin, aBorderPadding, 146 aSizeOverrides, aFlags); 147 } 148 149 bool nsFirstLetterFrame::UseTightBounds() const { 150 int v = StaticPrefs::layout_css_floating_first_letter_tight_glyph_bounds(); 151 152 // Check for the simple cases: 153 // pref value > 0: use legacy gecko behavior 154 // pref value = 0: use webkit/blink-like behavior 155 if (v > 0) { 156 return true; 157 } 158 if (v == 0) { 159 return false; 160 } 161 162 // Pref value < 0: use heuristics to determine whether the page is assuming 163 // webkit/blink-style behavior: 164 // If line-height is less than font-size, or there is a negative block-start 165 // or -end margin, use webkit/blink behavior. 166 if (nsTextFrame* textFrame = do_QueryFrame(mFrames.FirstChild())) { 167 RefPtr<nsFontMetrics> fm = textFrame->InflatedFontMetrics(); 168 if (textFrame->ComputeLineHeight() < fm->EmHeight()) { 169 return false; 170 } 171 } 172 173 const auto wm = GetWritingMode(); 174 const auto* styleMargin = StyleMargin(); 175 const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); 176 const auto bStart = 177 styleMargin->GetMargin(LogicalSide::BStart, wm, anchorResolutionParams); 178 // Currently, we only check for margins with negative *length* values; 179 // negative percentages seem unlikely to be used/useful in this context. 180 if (bStart->ConvertsToLength() && bStart->ToLength() < 0) { 181 return false; 182 } 183 const auto bEnd = 184 styleMargin->GetMargin(LogicalSide::BEnd, wm, anchorResolutionParams); 185 return !(bEnd->ConvertsToLength() && bEnd->ToLength() < 0); 186 } 187 188 void nsFirstLetterFrame::Reflow(nsPresContext* aPresContext, 189 ReflowOutput& aMetrics, 190 const ReflowInput& aReflowInput, 191 nsReflowStatus& aReflowStatus) { 192 MarkInReflow(); 193 DO_GLOBAL_REFLOW_COUNT("nsFirstLetterFrame"); 194 MOZ_ASSERT(aReflowStatus.IsEmpty(), 195 "Caller should pass a fresh reflow status!"); 196 197 // Grab overflow list 198 DrainOverflowFrames(aPresContext); 199 200 nsIFrame* kid = mFrames.FirstChild(); 201 202 // Setup reflow input for our child 203 WritingMode wm = aReflowInput.GetWritingMode(); 204 LogicalSize availSize = aReflowInput.AvailableSize(); 205 const auto bp = aReflowInput.ComputedLogicalBorderPadding(wm); 206 NS_ASSERTION(availSize.ISize(wm) != NS_UNCONSTRAINEDSIZE, 207 "should no longer use unconstrained inline size"); 208 availSize.ISize(wm) -= bp.IStartEnd(wm); 209 if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { 210 availSize.BSize(wm) -= bp.BStartEnd(wm); 211 } 212 213 WritingMode lineWM = aMetrics.GetWritingMode(); 214 ReflowOutput kidMetrics(lineWM); 215 216 // Reflow the child 217 if (!aReflowInput.mLineLayout) { 218 // When there is no lineLayout provided, we provide our own. The 219 // only time that the first-letter-frame is not reflowing in a 220 // line context is when its floating. 221 WritingMode kidWritingMode = WritingModeForLine(wm, kid); 222 LogicalSize kidAvailSize = availSize.ConvertTo(kidWritingMode, wm); 223 ReflowInput rs(aPresContext, aReflowInput, kid, kidAvailSize); 224 nsLineLayout ll(aPresContext, nullptr, aReflowInput, nullptr, nullptr); 225 226 // This frame does not get constructed for an empty inline frame, so 227 // `CollapseEmptyInlineFramesInLine` should not matter. 228 ll.BeginLineReflow( 229 bp.IStart(wm), bp.BStart(wm), availSize.ISize(wm), NS_UNCONSTRAINEDSIZE, 230 false, true, kidWritingMode, 231 nsSize(aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); 232 rs.mLineLayout = ≪ 233 ll.SetInFirstLetter(true); 234 ll.SetFirstLetterStyleOK(true); 235 236 kid->Reflow(aPresContext, kidMetrics, rs, aReflowStatus); 237 238 ll.EndLineReflow(); 239 ll.SetInFirstLetter(false); 240 241 // In the floating first-letter case, we need to set this ourselves; 242 // nsLineLayout::BeginSpan will set it in the other case 243 mBaseline = kidMetrics.BlockStartAscent(); 244 245 // Place and size the child and update the output metrics 246 LogicalSize convertedSize = kidMetrics.Size(wm); 247 248 const bool tightBounds = UseTightBounds(); 249 const nscoord shift = 250 tightBounds ? 0 251 // Shift by half of the difference between the line-height 252 // we're going to use and current height of the kid frame. 253 : (rs.GetLineHeight() - convertedSize.BSize(wm)) / 2; 254 255 kid->SetRect(nsRect(bp.IStart(wm), bp.BStart(wm) + shift, 256 convertedSize.ISize(wm), convertedSize.BSize(wm))); 257 kid->FinishAndStoreOverflow(&kidMetrics, rs.mStyleDisplay); 258 kid->DidReflow(aPresContext, nullptr); 259 260 if (!tightBounds) { 261 // Adjust size to account for line-height. 262 convertedSize.BSize(wm) = rs.GetLineHeight(); 263 } 264 265 convertedSize.ISize(wm) += bp.IStartEnd(wm); 266 convertedSize.BSize(wm) += bp.BStartEnd(wm); 267 aMetrics.SetSize(wm, convertedSize); 268 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + bp.BStart(wm)); 269 270 // Ensure that the overflow rect contains the child textframe's 271 // overflow rect. 272 // Note that if this is floating, the overline/underline drawable 273 // area is in the overflow rect of the child textframe. 274 aMetrics.UnionOverflowAreasWithDesiredBounds(); 275 ConsiderChildOverflow(aMetrics.mOverflowAreas, kid); 276 277 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); 278 } else { 279 // Pretend we are a span and reflow the child frame 280 nsLineLayout* ll = aReflowInput.mLineLayout; 281 bool pushedFrame; 282 283 ll->SetInFirstLetter(Style()->GetPseudoType() == 284 PseudoStyleType::firstLetter); 285 ll->BeginSpan(this, &aReflowInput, bp.IStart(wm), availSize.ISize(wm), 286 &mBaseline); 287 ll->ReflowFrame(kid, aReflowStatus, &kidMetrics, pushedFrame); 288 NS_ASSERTION(lineWM.IsVertical() == wm.IsVertical(), 289 "we're assuming we can mix sizes between lineWM and wm " 290 "since we shouldn't have orthogonal writing modes within " 291 "a line."); 292 aMetrics.ISize(lineWM) = ll->EndSpan(this) + bp.IStartEnd(wm); 293 ll->SetInFirstLetter(false); 294 295 if (mComputedStyle->StyleTextReset()->mInitialLetter.size != 0.0f) { 296 aMetrics.SetBlockStartAscent(kidMetrics.BlockStartAscent() + 297 bp.BStart(wm)); 298 aMetrics.BSize(lineWM) = kidMetrics.BSize(lineWM) + bp.BStartEnd(wm); 299 } else { 300 nsLayoutUtils::SetBSizeFromFontMetrics(this, aMetrics, bp, lineWM, wm); 301 } 302 } 303 304 if (!aReflowStatus.IsInlineBreakBefore()) { 305 // Create a continuation or remove existing continuations based on 306 // the reflow completion status. 307 if (aReflowStatus.IsComplete()) { 308 if (aReflowInput.mLineLayout) { 309 aReflowInput.mLineLayout->SetFirstLetterStyleOK(false); 310 } 311 if (nsIFrame* kidNextInFlow = kid->GetNextInFlow()) { 312 DestroyContext context(PresShell()); 313 // Remove all of the childs next-in-flows 314 kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, 315 kidNextInFlow, true); 316 } 317 } else { 318 // Create a continuation for the child frame if it doesn't already 319 // have one. 320 if (!IsFloating()) { 321 CreateNextInFlow(kid); 322 // And then push it to our overflow list 323 nsFrameList overflow = mFrames.TakeFramesAfter(kid); 324 if (overflow.NotEmpty()) { 325 SetOverflowFrames(std::move(overflow)); 326 } 327 } else if (!kid->GetNextInFlow()) { 328 // For floating first letter frames (if a continuation wasn't already 329 // created for us) we need to put the continuation with the rest of the 330 // text that the first letter frame was made out of. 331 nsIFrame* continuation; 332 CreateContinuationForFloatingParent(kid, &continuation, true); 333 } 334 } 335 } 336 } 337 338 /* virtual */ 339 bool nsFirstLetterFrame::CanContinueTextRun() const { 340 // We can continue a text run through a first-letter frame. 341 return true; 342 } 343 344 void nsFirstLetterFrame::CreateContinuationForFloatingParent( 345 nsIFrame* aChild, nsIFrame** aContinuation, bool aIsFluid) { 346 NS_ASSERTION(IsFloating(), 347 "can only call this on floating first letter frames"); 348 MOZ_ASSERT(aContinuation, "bad args"); 349 350 *aContinuation = nullptr; 351 352 mozilla::PresShell* presShell = PresShell(); 353 nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrame(); 354 nsContainerFrame* parent = placeholderFrame->GetParent(); 355 356 nsIFrame* continuation = presShell->FrameConstructor()->CreateContinuingFrame( 357 aChild, parent, aIsFluid); 358 359 // The continuation will have gotten the first letter style from its 360 // prev continuation, so we need to repair the ComputedStyle so it 361 // doesn't have the first letter styling. 362 // 363 // Note that getting parent frame's ComputedStyle is different from getting 364 // this frame's ComputedStyle's parent in the presence of ::first-line, 365 // which we do want the continuation to inherit from. 366 ComputedStyle* parentSC = parent->Style(); 367 RefPtr<ComputedStyle> newSC = 368 presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC); 369 continuation->SetComputedStyle(newSC); 370 nsLayoutUtils::MarkDescendantsDirty(continuation); 371 372 // XXX Bidi may not be involved but we have to use the list name 373 // FrameChildListID::NoReflowPrincipal because this is just like creating a 374 // continuation except we have to insert it in a different place and we don't 375 // want a reflow command to try to be issued. 376 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, placeholderFrame, 377 nullptr, nsFrameList(continuation, continuation)); 378 379 *aContinuation = continuation; 380 } 381 382 nsTextFrame* nsFirstLetterFrame::CreateContinuationForFramesAfter( 383 nsTextFrame* aFrame) { 384 auto* presShell = PresShell(); 385 auto* parent = GetParent(); 386 auto* letterContinuation = static_cast<nsFirstLetterFrame*>( 387 presShell->FrameConstructor()->CreateContinuingFrame(this, parent, true)); 388 389 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, this, nullptr, 390 nsFrameList(letterContinuation, letterContinuation)); 391 392 nsTextFrame* next; 393 auto list = mFrames.TakeFramesAfter(aFrame); 394 if (list.NotEmpty()) { 395 // If we already have additional frames, just move them to the continuation. 396 next = static_cast<nsTextFrame*>(list.FirstChild()); 397 for (auto* frame : list) { 398 frame->SetParent(letterContinuation); 399 } 400 // If the first frame of the list was not a fluid continuation, we need to 401 // insert one there to accept the overflowing text without disrupting the 402 // existing fixed continuation. 403 if (!next->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) { 404 next = static_cast<nsTextFrame*>( 405 presShell->FrameConstructor()->CreateContinuingFrame( 406 aFrame, letterContinuation)); 407 list.InsertFrame(letterContinuation, nullptr, next); 408 } 409 letterContinuation->SetInitialChildList(FrameChildListID::Principal, 410 std::move(list)); 411 } else { 412 // We don't have extra frames already, so create a new text continuation. 413 next = static_cast<nsTextFrame*>( 414 presShell->FrameConstructor()->CreateContinuingFrame( 415 aFrame, letterContinuation)); 416 letterContinuation->SetInitialChildList(FrameChildListID::Principal, 417 nsFrameList(next, next)); 418 } 419 420 // Update the computed style of the continuation text frame(s) that are 421 // no longer supposed to be first-letter style. 422 ComputedStyle* parentSC = letterContinuation->Style(); 423 RefPtr<ComputedStyle> newSC = 424 presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC); 425 for (auto* frame : letterContinuation->PrincipalChildList()) { 426 frame->SetComputedStyle(newSC); 427 } 428 429 return next; 430 } 431 432 void nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) { 433 // Check for an overflow list with our prev-in-flow 434 nsFirstLetterFrame* prevInFlow = (nsFirstLetterFrame*)GetPrevInFlow(); 435 if (prevInFlow) { 436 AutoFrameListPtr overflowFrames(aPresContext, 437 prevInFlow->StealOverflowFrames()); 438 if (overflowFrames) { 439 NS_ASSERTION(mFrames.IsEmpty(), "bad overflow list"); 440 mFrames.InsertFrames(this, nullptr, std::move(*overflowFrames)); 441 } 442 } 443 444 // It's also possible that we have an overflow list for ourselves 445 AutoFrameListPtr overflowFrames(aPresContext, StealOverflowFrames()); 446 if (overflowFrames) { 447 NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames"); 448 mFrames.AppendFrames(nullptr, std::move(*overflowFrames)); 449 } 450 451 // Now repair our first frames ComputedStyle (since we only reflow 452 // one frame there is no point in doing any other ones until they 453 // are reflowed) 454 nsIFrame* kid = mFrames.FirstChild(); 455 if (kid) { 456 nsIContent* kidContent = kid->GetContent(); 457 if (kidContent) { 458 NS_ASSERTION(kidContent->IsText(), "should contain only text nodes"); 459 ComputedStyle* parentSC; 460 if (prevInFlow) { 461 // This is for the rest of the content not in the first-letter. 462 nsIFrame* styleParent = 463 CorrectStyleParentFrame(GetParent(), PseudoStyleType::firstLetter); 464 parentSC = styleParent->Style(); 465 } else { 466 // And this for the first-letter style. 467 parentSC = mComputedStyle; 468 } 469 RefPtr<ComputedStyle> sc = 470 aPresContext->StyleSet()->ResolveStyleForText(kidContent, parentSC); 471 kid->SetComputedStyle(sc); 472 nsLayoutUtils::MarkDescendantsDirty(kid); 473 } 474 } 475 } 476 477 Maybe<nscoord> nsFirstLetterFrame::GetNaturalBaselineBOffset( 478 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 479 BaselineExportContext) const { 480 if (aBaselineGroup == BaselineSharingGroup::Last) { 481 return Nothing{}; 482 } 483 return Some(mBaseline); 484 } 485 486 LogicalSides nsFirstLetterFrame::GetLogicalSkipSides() const { 487 if (GetPrevContinuation()) { 488 // We shouldn't get calls to GetLogicalSkipSides for later continuations 489 // since they have separate ComputedStyles with initial values for all the 490 // properties that could trigger a call to GetLogicalSkipSides. Then again, 491 // it's not really an error to call GetLogicalSkipSides on any frame, so 492 // that's why we handle it properly. 493 return LogicalSides(mWritingMode, LogicalSides::All); 494 } 495 return LogicalSides(mWritingMode); // first continuation displays all sides 496 }