Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsLineBox.cpp
blob7afbd16305650e89e7ffc7d05d4c20cfcec3a5bf
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 /* representation of one line within a block frame, a CSS line box */
9 #include "nsLineBox.h"
11 #include "mozilla/ArenaObjectID.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/Sprintf.h"
16 #include "mozilla/WritingModes.h"
17 #include "mozilla/ToString.h"
18 #include "nsBidiPresUtils.h"
19 #include "nsIFrame.h"
20 #include "nsIFrameInlines.h"
21 #include "nsPresArena.h"
22 #include "nsPrintfCString.h"
23 #include "nsWindowSizes.h"
25 #ifdef DEBUG
26 static int32_t ctorCount;
27 int32_t nsLineBox::GetCtorCount() { return ctorCount; }
28 #endif
30 #ifndef _MSC_VER
31 // static nsLineBox constant; initialized in the header file.
32 const uint32_t nsLineBox::kMinChildCountForHashtable;
33 #endif
35 using namespace mozilla;
37 nsLineBox::nsLineBox(nsIFrame* aFrame, int32_t aCount, bool aIsBlock)
38 : mFirstChild(aFrame),
39 mWritingMode(),
40 mContainerSize(-1, -1),
41 mBounds(WritingMode()), // mBounds will be initialized with the correct
42 // writing mode when it is set
43 mFrames(),
44 mAscent(),
45 mAllFlags(0),
46 mData(nullptr) {
47 // Assert that the union elements chosen for initialisation are at
48 // least as large as all other elements in their respective unions, so
49 // as to ensure that no parts are missed.
50 static_assert(sizeof(mFrames) >= sizeof(mChildCount), "nsLineBox init #1");
51 static_assert(sizeof(mAllFlags) >= sizeof(mFlags), "nsLineBox init #2");
52 static_assert(sizeof(mData) >= sizeof(mBlockData), "nsLineBox init #3");
53 static_assert(sizeof(mData) >= sizeof(mInlineData), "nsLineBox init #4");
55 MOZ_COUNT_CTOR(nsLineBox);
56 #ifdef DEBUG
57 ++ctorCount;
58 NS_ASSERTION(!aIsBlock || aCount == 1, "Blocks must have exactly one child");
59 nsIFrame* f = aFrame;
60 for (int32_t n = aCount; n > 0; f = f->GetNextSibling(), --n) {
61 NS_ASSERTION(aIsBlock == f->IsBlockOutside(), "wrong kind of child frame");
63 #endif
64 mChildCount = aCount;
65 MarkDirty();
66 mFlags.mBlock = aIsBlock;
69 nsLineBox::~nsLineBox() {
70 MOZ_COUNT_DTOR(nsLineBox);
71 if (MOZ_UNLIKELY(mFlags.mHasHashedFrames)) {
72 delete mFrames;
74 Cleanup();
77 nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsIFrame* aFrame,
78 bool aIsBlock) {
79 return new (aPresShell) nsLineBox(aFrame, 1, aIsBlock);
82 nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsLineBox* aFromLine,
83 nsIFrame* aFrame, int32_t aCount) {
84 nsLineBox* newLine = new (aPresShell) nsLineBox(aFrame, aCount, false);
85 newLine->NoteFramesMovedFrom(aFromLine);
86 newLine->mContainerSize = aFromLine->mContainerSize;
87 return newLine;
90 void nsLineBox::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
91 if (mFlags.mHasHashedFrames) {
92 aSizes.mLayoutFramePropertiesSize +=
93 mFrames->ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
97 void nsLineBox::StealHashTableFrom(nsLineBox* aFromLine,
98 uint32_t aFromLineNewCount) {
99 MOZ_ASSERT(!mFlags.mHasHashedFrames);
100 MOZ_ASSERT(GetChildCount() >= int32_t(aFromLineNewCount));
101 mFrames = aFromLine->mFrames;
102 mFlags.mHasHashedFrames = 1;
103 aFromLine->mFlags.mHasHashedFrames = 0;
104 aFromLine->mChildCount = aFromLineNewCount;
105 // remove aFromLine's frames that aren't on this line
106 nsIFrame* f = aFromLine->mFirstChild;
107 for (uint32_t i = 0; i < aFromLineNewCount; f = f->GetNextSibling(), ++i) {
108 mFrames->Remove(f);
112 void nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine) {
113 uint32_t fromCount = aFromLine->GetChildCount();
114 uint32_t toCount = GetChildCount();
115 MOZ_ASSERT(toCount <= fromCount, "moved more frames than aFromLine has");
116 uint32_t fromNewCount = fromCount - toCount;
117 if (MOZ_LIKELY(!aFromLine->mFlags.mHasHashedFrames)) {
118 aFromLine->mChildCount = fromNewCount;
119 MOZ_ASSERT(toCount < kMinChildCountForHashtable);
120 } else if (fromNewCount < kMinChildCountForHashtable) {
121 // aFromLine has a hash table but will not have it after moving the frames
122 // so this line can steal the hash table if it needs it.
123 if (toCount >= kMinChildCountForHashtable) {
124 StealHashTableFrom(aFromLine, fromNewCount);
125 } else {
126 delete aFromLine->mFrames;
127 aFromLine->mFlags.mHasHashedFrames = 0;
128 aFromLine->mChildCount = fromNewCount;
130 } else {
131 // aFromLine still needs a hash table.
132 if (toCount < kMinChildCountForHashtable) {
133 // remove the moved frames from it
134 nsIFrame* f = mFirstChild;
135 for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
136 aFromLine->mFrames->Remove(f);
138 } else if (toCount <= fromNewCount) {
139 // This line needs a hash table, allocate a hash table for it since that
140 // means fewer hash ops.
141 nsIFrame* f = mFirstChild;
142 for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
143 aFromLine->mFrames->Remove(f); // toCount RemoveEntry
145 SwitchToHashtable(); // toCount PutEntry
146 } else {
147 // This line needs a hash table, but it's fewer hash ops to steal
148 // aFromLine's hash table and allocate a new hash table for that line.
149 StealHashTableFrom(aFromLine, fromNewCount); // fromNewCount RemoveEntry
150 aFromLine->SwitchToHashtable(); // fromNewCount PutEntry
155 void* nsLineBox::operator new(size_t sz, PresShell* aPresShell) {
156 return aPresShell->AllocateByObjectID(eArenaObjectID_nsLineBox, sz);
159 void nsLineBox::Destroy(PresShell* aPresShell) {
160 this->nsLineBox::~nsLineBox();
161 aPresShell->FreeByObjectID(eArenaObjectID_nsLineBox, this);
164 void nsLineBox::Cleanup() {
165 if (mData) {
166 if (IsBlock()) {
167 delete mBlockData;
168 } else {
169 delete mInlineData;
171 mData = nullptr;
175 #ifdef DEBUG_FRAME_DUMP
176 static void ListFloats(FILE* out, const char* aPrefix,
177 const nsTArray<nsIFrame*>& aFloats) {
178 for (nsIFrame* f : aFloats) {
179 nsCString str(aPrefix);
180 str += nsPrintfCString("floatframe@%p ", static_cast<void*>(f));
181 nsAutoString frameName;
182 f->GetFrameName(frameName);
183 str += NS_ConvertUTF16toUTF8(frameName).get();
184 fprintf_stderr(out, "%s\n", str.get());
188 /* static */ const char* nsLineBox::StyleClearToString(StyleClear aClearType) {
189 switch (aClearType) {
190 case StyleClear::None:
191 return "none";
192 case StyleClear::Left:
193 return "left";
194 case StyleClear::Right:
195 return "right";
196 case StyleClear::Both:
197 return "both";
199 return "unknown";
202 void nsLineBox::List(FILE* out, int32_t aIndent,
203 nsIFrame::ListFlags aFlags) const {
204 nsCString str;
205 while (aIndent-- > 0) {
206 str += " ";
208 List(out, str.get(), aFlags);
211 void nsLineBox::List(FILE* out, const char* aPrefix,
212 nsIFrame::ListFlags aFlags) const {
213 nsCString str(aPrefix);
214 str += nsPrintfCString(
215 "line@%p count=%d state=%s,%s,%s,%s,%s,%s,clear-before:%s,clear-after:%s",
216 this, GetChildCount(), IsBlock() ? "block" : "inline",
217 IsDirty() ? "dirty" : "clean",
218 IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean",
219 IsImpactedByFloat() ? "impacted" : "not-impacted",
220 IsLineWrapped() ? "wrapped" : "not-wrapped",
221 HasForcedLineBreak() ? "forced-break" : "no-break",
222 StyleClearToString(FloatClearTypeBefore()),
223 StyleClearToString(FloatClearTypeAfter()));
225 if (IsBlock() && !GetCarriedOutBEndMargin().IsZero()) {
226 const nscoord bm = GetCarriedOutBEndMargin().get();
227 str += nsPrintfCString("bm=%s ",
228 nsIFrame::ConvertToString(bm, aFlags).c_str());
230 nsRect bounds = GetPhysicalBounds();
231 str +=
232 nsPrintfCString("%s ", nsIFrame::ConvertToString(bounds, aFlags).c_str());
233 if (mWritingMode.IsVertical() || mWritingMode.IsBidiRTL()) {
234 str += nsPrintfCString(
235 "wm=%s cs=(%s) logical-rect=%s ", ToString(mWritingMode).c_str(),
236 nsIFrame::ConvertToString(mContainerSize, aFlags).c_str(),
237 nsIFrame::ConvertToString(mBounds, mWritingMode, aFlags).c_str());
239 if (mData) {
240 const nsRect vo = mData->mOverflowAreas.InkOverflow();
241 const nsRect so = mData->mOverflowAreas.ScrollableOverflow();
242 if (!vo.IsEqualEdges(bounds) || !so.IsEqualEdges(bounds)) {
243 str += nsPrintfCString("ink-overflow=%s scr-overflow=%s ",
244 nsIFrame::ConvertToString(vo, aFlags).c_str(),
245 nsIFrame::ConvertToString(so, aFlags).c_str());
248 fprintf_stderr(out, "%s<\n", str.get());
250 nsIFrame* frame = mFirstChild;
251 int32_t n = GetChildCount();
252 nsCString pfx(aPrefix);
253 pfx += " ";
254 while (--n >= 0) {
255 frame->List(out, pfx.get(), aFlags);
256 frame = frame->GetNextSibling();
259 if (HasFloats()) {
260 fprintf_stderr(out, "%s> floats <\n", aPrefix);
261 ListFloats(out, pfx.get(), mInlineData->mFloats);
263 fprintf_stderr(out, "%s>\n", aPrefix);
266 nsIFrame* nsLineBox::LastChild() const {
267 nsIFrame* frame = mFirstChild;
268 int32_t n = GetChildCount() - 1;
269 while (--n >= 0) {
270 frame = frame->GetNextSibling();
272 return frame;
274 #endif
276 int32_t nsLineBox::IndexOf(nsIFrame* aFrame) const {
277 int32_t i, n = GetChildCount();
278 nsIFrame* frame = mFirstChild;
279 for (i = 0; i < n; i++) {
280 if (frame == aFrame) {
281 return i;
283 frame = frame->GetNextSibling();
285 return -1;
288 int32_t nsLineBox::RIndexOf(nsIFrame* aFrame,
289 nsIFrame* aLastFrameInLine) const {
290 nsIFrame* frame = aLastFrameInLine;
291 for (int32_t i = GetChildCount() - 1; i >= 0; --i) {
292 MOZ_ASSERT(i != 0 || frame == mFirstChild,
293 "caller provided incorrect last frame");
294 if (frame == aFrame) {
295 return i;
297 frame = frame->GetPrevSibling();
299 return -1;
302 bool nsLineBox::IsEmpty() const {
303 if (IsBlock()) return mFirstChild->IsEmpty();
305 int32_t n;
306 nsIFrame* kid;
307 for (n = GetChildCount(), kid = mFirstChild; n > 0;
308 --n, kid = kid->GetNextSibling()) {
309 if (!kid->IsEmpty()) return false;
311 if (HasMarker()) {
312 return false;
314 return true;
317 bool nsLineBox::CachedIsEmpty() {
318 if (mFlags.mDirty) {
319 return IsEmpty();
322 if (mFlags.mEmptyCacheValid) {
323 return mFlags.mEmptyCacheState;
326 bool result;
327 if (IsBlock()) {
328 result = mFirstChild->CachedIsEmpty();
329 } else {
330 int32_t n;
331 nsIFrame* kid;
332 result = true;
333 for (n = GetChildCount(), kid = mFirstChild; n > 0;
334 --n, kid = kid->GetNextSibling()) {
335 if (!kid->CachedIsEmpty()) {
336 result = false;
337 break;
340 if (HasMarker()) {
341 result = false;
345 mFlags.mEmptyCacheValid = true;
346 mFlags.mEmptyCacheState = result;
347 return result;
350 void nsLineBox::DeleteLineList(nsPresContext* aPresContext, nsLineList& aLines,
351 nsFrameList* aFrames, DestroyContext& aContext) {
352 PresShell* presShell = aPresContext->PresShell();
354 // Keep our line list and frame list up to date as we
355 // remove frames, in case something wants to traverse the
356 // frame tree while we're destroying.
357 while (!aLines.empty()) {
358 nsLineBox* line = aLines.front();
359 if (MOZ_UNLIKELY(line->mFlags.mHasHashedFrames)) {
360 line->SwitchToCounter(); // Avoid expensive has table removals.
362 while (line->GetChildCount() > 0) {
363 nsIFrame* child = aFrames->RemoveFirstChild();
364 MOZ_DIAGNOSTIC_ASSERT(child->PresContext() == aPresContext);
365 MOZ_DIAGNOSTIC_ASSERT(child == line->mFirstChild, "Lines out of sync");
366 line->mFirstChild = aFrames->FirstChild();
367 line->NoteFrameRemoved(child);
368 child->Destroy(aContext);
370 MOZ_DIAGNOSTIC_ASSERT(line == aLines.front(),
371 "destroying child frames messed up our lines!");
372 aLines.pop_front();
373 line->Destroy(presShell);
377 bool nsLineBox::RFindLineContaining(nsIFrame* aFrame,
378 const nsLineList::iterator& aBegin,
379 nsLineList::iterator& aEnd,
380 nsIFrame* aLastFrameBeforeEnd,
381 int32_t* aFrameIndexInLine) {
382 MOZ_ASSERT(aFrame, "null ptr");
384 nsIFrame* curFrame = aLastFrameBeforeEnd;
385 while (aBegin != aEnd) {
386 --aEnd;
387 NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame");
388 if (MOZ_UNLIKELY(aEnd->mFlags.mHasHashedFrames) &&
389 !aEnd->Contains(aFrame)) {
390 if (aEnd->mFirstChild) {
391 curFrame = aEnd->mFirstChild->GetPrevSibling();
393 continue;
395 // i is the index of curFrame in aEnd
396 int32_t i = aEnd->GetChildCount() - 1;
397 while (i >= 0) {
398 if (curFrame == aFrame) {
399 *aFrameIndexInLine = i;
400 return true;
402 --i;
403 curFrame = curFrame->GetPrevSibling();
405 MOZ_ASSERT(!aEnd->mFlags.mHasHashedFrames, "Contains lied to us!");
407 *aFrameIndexInLine = -1;
408 return false;
411 nsCollapsingMargin nsLineBox::GetCarriedOutBEndMargin() const {
412 NS_ASSERTION(IsBlock(), "GetCarriedOutBEndMargin called on non-block line.");
413 return (IsBlock() && mBlockData) ? mBlockData->mCarriedOutBEndMargin
414 : nsCollapsingMargin();
417 bool nsLineBox::SetCarriedOutBEndMargin(nsCollapsingMargin aValue) {
418 bool changed = false;
419 if (IsBlock()) {
420 if (!aValue.IsZero()) {
421 if (!mBlockData) {
422 mBlockData = new ExtraBlockData(GetPhysicalBounds());
424 changed = aValue != mBlockData->mCarriedOutBEndMargin;
425 mBlockData->mCarriedOutBEndMargin = aValue;
426 } else if (mBlockData) {
427 changed = aValue != mBlockData->mCarriedOutBEndMargin;
428 mBlockData->mCarriedOutBEndMargin = aValue;
429 MaybeFreeData();
432 return changed;
435 void nsLineBox::MaybeFreeData() {
436 nsRect bounds = GetPhysicalBounds();
437 if (mData && mData->mOverflowAreas == OverflowAreas(bounds, bounds)) {
438 if (IsInline()) {
439 if (mInlineData->mFloats.IsEmpty()) {
440 delete mInlineData;
441 mInlineData = nullptr;
443 } else if (mBlockData->mCarriedOutBEndMargin.IsZero()) {
444 delete mBlockData;
445 mBlockData = nullptr;
450 void nsLineBox::ClearFloats() {
451 MOZ_ASSERT(IsInline(), "block line can't have floats");
452 if (IsInline() && mInlineData) {
453 mInlineData->mFloats.Clear();
454 MaybeFreeData();
458 void nsLineBox::AppendFloats(nsTArray<nsIFrame*>&& aFloats) {
459 MOZ_ASSERT(IsInline(), "block line can't have floats");
460 if (MOZ_UNLIKELY(!IsInline())) {
461 return;
463 if (!aFloats.IsEmpty()) {
464 if (mInlineData) {
465 mInlineData->mFloats.AppendElements(std::move(aFloats));
466 } else {
467 mInlineData = new ExtraInlineData(GetPhysicalBounds());
468 mInlineData->mFloats = std::move(aFloats);
473 bool nsLineBox::RemoveFloat(nsIFrame* aFrame) {
474 MOZ_ASSERT(IsInline(), "block line can't have floats");
475 MOZ_ASSERT(aFrame);
476 if (IsInline() && mInlineData) {
477 if (mInlineData->mFloats.RemoveElement(aFrame)) {
478 // Note: the placeholder is part of the line's child list
479 // and will be removed later.
480 MaybeFreeData();
481 return true;
484 return false;
487 void nsLineBox::SetFloatEdges(nscoord aStart, nscoord aEnd) {
488 MOZ_ASSERT(IsInline(), "block line can't have float edges");
489 if (!mInlineData) {
490 mInlineData = new ExtraInlineData(GetPhysicalBounds());
492 mInlineData->mFloatEdgeIStart = aStart;
493 mInlineData->mFloatEdgeIEnd = aEnd;
496 void nsLineBox::ClearFloatEdges() {
497 MOZ_ASSERT(IsInline(), "block line can't have float edges");
498 if (mInlineData) {
499 mInlineData->mFloatEdgeIStart = nscoord_MIN;
500 mInlineData->mFloatEdgeIEnd = nscoord_MIN;
504 void nsLineBox::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
505 #ifdef DEBUG
506 for (const auto otype : mozilla::AllOverflowTypes()) {
507 NS_ASSERTION(aOverflowAreas.Overflow(otype).width >= 0,
508 "Illegal width for an overflow area!");
509 NS_ASSERTION(aOverflowAreas.Overflow(otype).height >= 0,
510 "Illegal height for an overflow area!");
512 #endif
514 nsRect bounds = GetPhysicalBounds();
515 if (!aOverflowAreas.InkOverflow().IsEqualInterior(bounds) ||
516 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
517 if (!mData) {
518 if (IsInline()) {
519 mInlineData = new ExtraInlineData(bounds);
520 } else {
521 mBlockData = new ExtraBlockData(bounds);
524 mData->mOverflowAreas = aOverflowAreas;
525 } else if (mData) {
526 // Store away new value so that MaybeFreeData compares against
527 // the right value.
528 mData->mOverflowAreas = aOverflowAreas;
529 MaybeFreeData();
533 //----------------------------------------------------------------------
535 Result<nsILineIterator::LineInfo, nsresult> nsLineIterator::GetLine(
536 int32_t aLineNumber) {
537 const nsLineBox* line = GetLineAt(aLineNumber);
538 if (!line) {
539 return Err(NS_ERROR_FAILURE);
541 LineInfo structure;
542 structure.mFirstFrameOnLine = line->mFirstChild;
543 structure.mNumFramesOnLine = line->GetChildCount();
544 structure.mLineBounds = line->GetPhysicalBounds();
545 structure.mIsWrapped = line->IsLineWrapped();
546 return structure;
549 int32_t nsLineIterator::FindLineContaining(nsIFrame* aFrame,
550 int32_t aStartLine) {
551 const nsLineBox* line = GetLineAt(aStartLine);
552 MOZ_ASSERT(line, "aStartLine out of range");
553 while (line) {
554 if (line->Contains(aFrame)) {
555 return mIndex;
557 line = GetNextLine();
559 return -1;
562 NS_IMETHODIMP
563 nsLineIterator::CheckLineOrder(int32_t aLine, bool* aIsReordered,
564 nsIFrame** aFirstVisual,
565 nsIFrame** aLastVisual) {
566 const nsLineBox* line = GetLineAt(aLine);
567 MOZ_ASSERT(line, "aLine out of range!");
569 if (!line || !line->mFirstChild) { // empty line
570 *aIsReordered = false;
571 *aFirstVisual = nullptr;
572 *aLastVisual = nullptr;
573 return NS_OK;
576 nsIFrame* leftmostFrame;
577 nsIFrame* rightmostFrame;
578 *aIsReordered =
579 nsBidiPresUtils::CheckLineOrder(line->mFirstChild, line->GetChildCount(),
580 &leftmostFrame, &rightmostFrame);
582 // map leftmost/rightmost to first/last according to paragraph direction
583 *aFirstVisual = mRightToLeft ? rightmostFrame : leftmostFrame;
584 *aLastVisual = mRightToLeft ? leftmostFrame : rightmostFrame;
586 return NS_OK;
589 NS_IMETHODIMP
590 nsLineIterator::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
591 nsIFrame** aFrameFound,
592 bool* aPosIsBeforeFirstFrame,
593 bool* aPosIsAfterLastFrame) {
594 MOZ_ASSERT(aFrameFound && aPosIsBeforeFirstFrame && aPosIsAfterLastFrame,
595 "null OUT ptr");
597 if (!aFrameFound || !aPosIsBeforeFirstFrame || !aPosIsAfterLastFrame) {
598 return NS_ERROR_NULL_POINTER;
601 const nsLineBox* line = GetLineAt(aLineNumber);
602 if (!line) {
603 *aFrameFound = nullptr;
604 *aPosIsBeforeFirstFrame = true;
605 *aPosIsAfterLastFrame = false;
606 return NS_OK;
609 if (line->ISize() == 0 && line->BSize() == 0) {
610 return NS_ERROR_FAILURE;
613 LineFrameFinder finder(aPos, line->mContainerSize, line->mWritingMode,
614 mRightToLeft);
615 int32_t n = line->GetChildCount();
616 nsIFrame* frame = line->mFirstChild;
617 while (n--) {
618 finder.Scan(frame);
619 if (finder.IsDone()) {
620 break;
622 frame = frame->GetNextSibling();
624 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
625 return NS_OK;