Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / layout / generic / nsLineBox.cpp
blob53a69140300102c10055ebe909e970466306e24e
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 mContainerSize(-1, -1),
40 mBounds(WritingMode()), // mBounds will be initialized with the correct
41 // writing mode when it is set
42 mFrames(),
43 mAscent(),
44 mAllFlags(0),
45 mData(nullptr) {
46 // Assert that the union elements chosen for initialisation are at
47 // least as large as all other elements in their respective unions, so
48 // as to ensure that no parts are missed.
49 static_assert(sizeof(mFrames) >= sizeof(mChildCount), "nsLineBox init #1");
50 static_assert(sizeof(mAllFlags) >= sizeof(mFlags), "nsLineBox init #2");
51 static_assert(sizeof(mData) >= sizeof(mBlockData), "nsLineBox init #3");
52 static_assert(sizeof(mData) >= sizeof(mInlineData), "nsLineBox init #4");
54 MOZ_COUNT_CTOR(nsLineBox);
55 #ifdef DEBUG
56 ++ctorCount;
57 NS_ASSERTION(!aIsBlock || aCount == 1, "Blocks must have exactly one child");
58 nsIFrame* f = aFrame;
59 for (int32_t n = aCount; n > 0; f = f->GetNextSibling(), --n) {
60 NS_ASSERTION(aIsBlock == f->IsBlockOutside(), "wrong kind of child frame");
62 #endif
63 mChildCount = aCount;
64 MarkDirty();
65 mFlags.mBlock = aIsBlock;
68 nsLineBox::~nsLineBox() {
69 MOZ_COUNT_DTOR(nsLineBox);
70 if (MOZ_UNLIKELY(mFlags.mHasHashedFrames)) {
71 delete mFrames;
73 Cleanup();
76 nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsIFrame* aFrame,
77 bool aIsBlock) {
78 return new (aPresShell) nsLineBox(aFrame, 1, aIsBlock);
81 nsLineBox* NS_NewLineBox(PresShell* aPresShell, nsLineBox* aFromLine,
82 nsIFrame* aFrame, int32_t aCount) {
83 nsLineBox* newLine = new (aPresShell) nsLineBox(aFrame, aCount, false);
84 newLine->NoteFramesMovedFrom(aFromLine);
85 newLine->mContainerSize = aFromLine->mContainerSize;
86 return newLine;
89 void nsLineBox::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const {
90 if (mFlags.mHasHashedFrames) {
91 aSizes.mLayoutFramePropertiesSize +=
92 mFrames->ShallowSizeOfIncludingThis(aSizes.mState.mMallocSizeOf);
96 void nsLineBox::StealHashTableFrom(nsLineBox* aFromLine,
97 uint32_t aFromLineNewCount) {
98 MOZ_ASSERT(!mFlags.mHasHashedFrames);
99 MOZ_ASSERT(GetChildCount() >= int32_t(aFromLineNewCount));
100 mFrames = aFromLine->mFrames;
101 mFlags.mHasHashedFrames = 1;
102 aFromLine->mFlags.mHasHashedFrames = 0;
103 aFromLine->mChildCount = aFromLineNewCount;
104 // remove aFromLine's frames that aren't on this line
105 nsIFrame* f = aFromLine->mFirstChild;
106 for (uint32_t i = 0; i < aFromLineNewCount; f = f->GetNextSibling(), ++i) {
107 mFrames->Remove(f);
111 void nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine) {
112 uint32_t fromCount = aFromLine->GetChildCount();
113 uint32_t toCount = GetChildCount();
114 MOZ_ASSERT(toCount <= fromCount, "moved more frames than aFromLine has");
115 uint32_t fromNewCount = fromCount - toCount;
116 if (MOZ_LIKELY(!aFromLine->mFlags.mHasHashedFrames)) {
117 aFromLine->mChildCount = fromNewCount;
118 MOZ_ASSERT(toCount < kMinChildCountForHashtable);
119 } else if (fromNewCount < kMinChildCountForHashtable) {
120 // aFromLine has a hash table but will not have it after moving the frames
121 // so this line can steal the hash table if it needs it.
122 if (toCount >= kMinChildCountForHashtable) {
123 StealHashTableFrom(aFromLine, fromNewCount);
124 } else {
125 delete aFromLine->mFrames;
126 aFromLine->mFlags.mHasHashedFrames = 0;
127 aFromLine->mChildCount = fromNewCount;
129 } else {
130 // aFromLine still needs a hash table.
131 if (toCount < kMinChildCountForHashtable) {
132 // remove the moved frames from it
133 nsIFrame* f = mFirstChild;
134 for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
135 aFromLine->mFrames->Remove(f);
137 } else if (toCount <= fromNewCount) {
138 // This line needs a hash table, allocate a hash table for it since that
139 // means fewer hash ops.
140 nsIFrame* f = mFirstChild;
141 for (uint32_t i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
142 aFromLine->mFrames->Remove(f); // toCount RemoveEntry
144 SwitchToHashtable(); // toCount PutEntry
145 } else {
146 // This line needs a hash table, but it's fewer hash ops to steal
147 // aFromLine's hash table and allocate a new hash table for that line.
148 StealHashTableFrom(aFromLine, fromNewCount); // fromNewCount RemoveEntry
149 aFromLine->SwitchToHashtable(); // fromNewCount PutEntry
154 void* nsLineBox::operator new(size_t sz, PresShell* aPresShell) {
155 return aPresShell->AllocateByObjectID(eArenaObjectID_nsLineBox, sz);
158 void nsLineBox::Destroy(PresShell* aPresShell) {
159 this->nsLineBox::~nsLineBox();
160 aPresShell->FreeByObjectID(eArenaObjectID_nsLineBox, this);
163 void nsLineBox::Cleanup() {
164 if (mData) {
165 if (IsBlock()) {
166 delete mBlockData;
167 } else {
168 delete mInlineData;
170 mData = nullptr;
174 #ifdef DEBUG_FRAME_DUMP
175 static void ListFloats(FILE* out, const char* aPrefix,
176 const nsTArray<nsIFrame*>& aFloats) {
177 for (nsIFrame* f : aFloats) {
178 nsCString str(aPrefix);
179 str += nsPrintfCString("floatframe@%p ", static_cast<void*>(f));
180 nsAutoString frameName;
181 f->GetFrameName(frameName);
182 str += NS_ConvertUTF16toUTF8(frameName).get();
183 fprintf_stderr(out, "%s\n", str.get());
187 /* static */ const char* nsLineBox::StyleClearToString(StyleClear aClearType) {
188 switch (aClearType) {
189 case StyleClear::None:
190 return "none";
191 case StyleClear::Left:
192 return "left";
193 case StyleClear::Right:
194 return "right";
195 case StyleClear::Both:
196 return "both";
198 return "unknown";
201 void nsLineBox::List(FILE* out, int32_t aIndent,
202 nsIFrame::ListFlags aFlags) const {
203 nsCString str;
204 while (aIndent-- > 0) {
205 str += " ";
207 List(out, str.get(), aFlags);
210 void nsLineBox::List(FILE* out, const char* aPrefix,
211 nsIFrame::ListFlags aFlags) const {
212 nsCString str(aPrefix);
213 str += nsPrintfCString(
214 "line@%p count=%d state=%s,%s,%s,%s,%s,%s,clear-before:%s,clear-after:%s",
215 this, GetChildCount(), IsBlock() ? "block" : "inline",
216 IsDirty() ? "dirty" : "clean",
217 IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean",
218 IsImpactedByFloat() ? "impacted" : "not-impacted",
219 IsLineWrapped() ? "wrapped" : "not-wrapped",
220 HasForcedLineBreakAfter() ? "forced-break-after" : "no-break",
221 StyleClearToString(FloatClearTypeBefore()),
222 StyleClearToString(FloatClearTypeAfter()));
224 if (IsBlock() && !GetCarriedOutBEndMargin().IsZero()) {
225 const nscoord bm = GetCarriedOutBEndMargin().get();
226 str += nsPrintfCString("bm=%s ",
227 nsIFrame::ConvertToString(bm, aFlags).c_str());
229 nsRect bounds = GetPhysicalBounds();
230 str +=
231 nsPrintfCString("%s ", nsIFrame::ConvertToString(bounds, aFlags).c_str());
232 if (mWritingMode.IsVertical() || mWritingMode.IsBidiRTL()) {
233 str += nsPrintfCString(
234 "wm=%s cs=(%s) logical-rect=%s ", ToString(mWritingMode).c_str(),
235 nsIFrame::ConvertToString(mContainerSize, aFlags).c_str(),
236 nsIFrame::ConvertToString(mBounds, mWritingMode, aFlags).c_str());
238 if (mData) {
239 const nsRect vo = mData->mOverflowAreas.InkOverflow();
240 const nsRect so = mData->mOverflowAreas.ScrollableOverflow();
241 if (!vo.IsEqualEdges(bounds) || !so.IsEqualEdges(bounds)) {
242 str += nsPrintfCString("ink-overflow=%s scr-overflow=%s ",
243 nsIFrame::ConvertToString(vo, aFlags).c_str(),
244 nsIFrame::ConvertToString(so, aFlags).c_str());
247 fprintf_stderr(out, "%s<\n", str.get());
249 nsIFrame* frame = mFirstChild;
250 int32_t n = GetChildCount();
251 nsCString pfx(aPrefix);
252 pfx += " ";
253 while (--n >= 0) {
254 frame->List(out, pfx.get(), aFlags);
255 frame = frame->GetNextSibling();
258 if (HasFloats()) {
259 fprintf_stderr(out, "%s> floats <\n", aPrefix);
260 ListFloats(out, pfx.get(), mInlineData->mFloats);
262 fprintf_stderr(out, "%s>\n", aPrefix);
265 nsIFrame* nsLineBox::LastChild() const {
266 nsIFrame* frame = mFirstChild;
267 int32_t n = GetChildCount() - 1;
268 while (--n >= 0) {
269 frame = frame->GetNextSibling();
271 return frame;
273 #endif
275 int32_t nsLineBox::IndexOf(nsIFrame* aFrame) const {
276 int32_t i, n = GetChildCount();
277 nsIFrame* frame = mFirstChild;
278 for (i = 0; i < n; i++) {
279 if (frame == aFrame) {
280 return i;
282 frame = frame->GetNextSibling();
284 return -1;
287 int32_t nsLineBox::RIndexOf(nsIFrame* aFrame,
288 nsIFrame* aLastFrameInLine) const {
289 nsIFrame* frame = aLastFrameInLine;
290 for (int32_t i = GetChildCount() - 1; i >= 0; --i) {
291 MOZ_ASSERT(i != 0 || frame == mFirstChild,
292 "caller provided incorrect last frame");
293 if (frame == aFrame) {
294 return i;
296 frame = frame->GetPrevSibling();
298 return -1;
301 bool nsLineBox::IsEmpty() const {
302 if (IsBlock()) return mFirstChild->IsEmpty();
304 int32_t n;
305 nsIFrame* kid;
306 for (n = GetChildCount(), kid = mFirstChild; n > 0;
307 --n, kid = kid->GetNextSibling()) {
308 if (!kid->IsEmpty()) return false;
310 if (HasMarker()) {
311 return false;
313 return true;
316 bool nsLineBox::CachedIsEmpty() {
317 if (mFlags.mDirty) {
318 return IsEmpty();
321 if (mFlags.mEmptyCacheValid) {
322 return mFlags.mEmptyCacheState;
325 bool result;
326 if (IsBlock()) {
327 result = mFirstChild->CachedIsEmpty();
328 } else {
329 int32_t n;
330 nsIFrame* kid;
331 result = true;
332 for (n = GetChildCount(), kid = mFirstChild; n > 0;
333 --n, kid = kid->GetNextSibling()) {
334 if (!kid->CachedIsEmpty()) {
335 result = false;
336 break;
339 if (HasMarker()) {
340 result = false;
344 mFlags.mEmptyCacheValid = true;
345 mFlags.mEmptyCacheState = result;
346 return result;
349 void nsLineBox::DeleteLineList(nsPresContext* aPresContext, nsLineList& aLines,
350 nsFrameList* aFrames, DestroyContext& aContext) {
351 PresShell* presShell = aPresContext->PresShell();
353 // Keep our line list and frame list up to date as we
354 // remove frames, in case something wants to traverse the
355 // frame tree while we're destroying.
356 while (!aLines.empty()) {
357 nsLineBox* line = aLines.front();
358 if (MOZ_UNLIKELY(line->mFlags.mHasHashedFrames)) {
359 line->SwitchToCounter(); // Avoid expensive has table removals.
361 while (line->GetChildCount() > 0) {
362 nsIFrame* child = aFrames->RemoveFirstChild();
363 MOZ_DIAGNOSTIC_ASSERT(child->PresContext() == aPresContext);
364 MOZ_DIAGNOSTIC_ASSERT(child == line->mFirstChild, "Lines out of sync");
365 line->mFirstChild = aFrames->FirstChild();
366 line->NoteFrameRemoved(child);
367 child->Destroy(aContext);
369 MOZ_DIAGNOSTIC_ASSERT(line == aLines.front(),
370 "destroying child frames messed up our lines!");
371 aLines.pop_front();
372 line->Destroy(presShell);
376 bool nsLineBox::RFindLineContaining(nsIFrame* aFrame,
377 const nsLineList::iterator& aBegin,
378 nsLineList::iterator& aEnd,
379 nsIFrame* aLastFrameBeforeEnd,
380 int32_t* aFrameIndexInLine) {
381 MOZ_ASSERT(aFrame, "null ptr");
383 nsIFrame* curFrame = aLastFrameBeforeEnd;
384 while (aBegin != aEnd) {
385 --aEnd;
386 NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame");
387 if (MOZ_UNLIKELY(aEnd->mFlags.mHasHashedFrames) &&
388 !aEnd->Contains(aFrame)) {
389 if (aEnd->mFirstChild) {
390 curFrame = aEnd->mFirstChild->GetPrevSibling();
392 continue;
394 // i is the index of curFrame in aEnd
395 int32_t i = aEnd->GetChildCount() - 1;
396 while (i >= 0) {
397 if (curFrame == aFrame) {
398 *aFrameIndexInLine = i;
399 return true;
401 --i;
402 curFrame = curFrame->GetPrevSibling();
404 MOZ_ASSERT(!aEnd->mFlags.mHasHashedFrames, "Contains lied to us!");
406 *aFrameIndexInLine = -1;
407 return false;
410 nsCollapsingMargin nsLineBox::GetCarriedOutBEndMargin() const {
411 NS_ASSERTION(IsBlock(), "GetCarriedOutBEndMargin called on non-block line.");
412 return (IsBlock() && mBlockData) ? mBlockData->mCarriedOutBEndMargin
413 : nsCollapsingMargin();
416 bool nsLineBox::SetCarriedOutBEndMargin(nsCollapsingMargin aValue) {
417 bool changed = false;
418 if (IsBlock()) {
419 if (!aValue.IsZero()) {
420 if (!mBlockData) {
421 mBlockData = new ExtraBlockData(GetPhysicalBounds());
423 changed = aValue != mBlockData->mCarriedOutBEndMargin;
424 mBlockData->mCarriedOutBEndMargin = aValue;
425 } else if (mBlockData) {
426 changed = aValue != mBlockData->mCarriedOutBEndMargin;
427 mBlockData->mCarriedOutBEndMargin = aValue;
428 MaybeFreeData();
431 return changed;
434 void nsLineBox::MaybeFreeData() {
435 nsRect bounds = GetPhysicalBounds();
436 if (mData && mData->mOverflowAreas == OverflowAreas(bounds, bounds)) {
437 if (IsInline()) {
438 if (mInlineData->mFloats.IsEmpty()) {
439 delete mInlineData;
440 mInlineData = nullptr;
442 } else if (mBlockData->mCarriedOutBEndMargin.IsZero()) {
443 delete mBlockData;
444 mBlockData = nullptr;
449 void nsLineBox::ClearFloats() {
450 MOZ_ASSERT(IsInline(), "block line can't have floats");
451 if (IsInline() && mInlineData) {
452 mInlineData->mFloats.Clear();
453 MaybeFreeData();
457 void nsLineBox::AppendFloats(nsTArray<nsIFrame*>&& aFloats) {
458 MOZ_ASSERT(IsInline(), "block line can't have floats");
459 if (MOZ_UNLIKELY(!IsInline())) {
460 return;
462 if (!aFloats.IsEmpty()) {
463 if (mInlineData) {
464 mInlineData->mFloats.AppendElements(std::move(aFloats));
465 } else {
466 mInlineData = new ExtraInlineData(GetPhysicalBounds());
467 mInlineData->mFloats = std::move(aFloats);
472 bool nsLineBox::RemoveFloat(nsIFrame* aFrame) {
473 MOZ_ASSERT(IsInline(), "block line can't have floats");
474 MOZ_ASSERT(aFrame);
475 if (IsInline() && mInlineData) {
476 if (mInlineData->mFloats.RemoveElement(aFrame)) {
477 // Note: the placeholder is part of the line's child list
478 // and will be removed later.
479 MaybeFreeData();
480 return true;
483 return false;
486 void nsLineBox::SetFloatEdges(nscoord aStart, nscoord aEnd) {
487 MOZ_ASSERT(IsInline(), "block line can't have float edges");
488 if (!mInlineData) {
489 mInlineData = new ExtraInlineData(GetPhysicalBounds());
491 mInlineData->mFloatEdgeIStart = aStart;
492 mInlineData->mFloatEdgeIEnd = aEnd;
495 void nsLineBox::ClearFloatEdges() {
496 MOZ_ASSERT(IsInline(), "block line can't have float edges");
497 if (mInlineData) {
498 mInlineData->mFloatEdgeIStart = nscoord_MIN;
499 mInlineData->mFloatEdgeIEnd = nscoord_MIN;
503 void nsLineBox::SetOverflowAreas(const OverflowAreas& aOverflowAreas) {
504 #ifdef DEBUG
505 for (const auto otype : mozilla::AllOverflowTypes()) {
506 NS_ASSERTION(aOverflowAreas.Overflow(otype).width >= 0,
507 "Illegal width for an overflow area!");
508 NS_ASSERTION(aOverflowAreas.Overflow(otype).height >= 0,
509 "Illegal height for an overflow area!");
511 #endif
513 nsRect bounds = GetPhysicalBounds();
514 if (!aOverflowAreas.InkOverflow().IsEqualInterior(bounds) ||
515 !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
516 if (!mData) {
517 if (IsInline()) {
518 mInlineData = new ExtraInlineData(bounds);
519 } else {
520 mBlockData = new ExtraBlockData(bounds);
523 mData->mOverflowAreas = aOverflowAreas;
524 } else if (mData) {
525 // Store away new value so that MaybeFreeData compares against
526 // the right value.
527 mData->mOverflowAreas = aOverflowAreas;
528 MaybeFreeData();
532 //----------------------------------------------------------------------
534 Result<nsILineIterator::LineInfo, nsresult> nsLineIterator::GetLine(
535 int32_t aLineNumber) {
536 const nsLineBox* line = GetLineAt(aLineNumber);
537 if (!line) {
538 return Err(NS_ERROR_FAILURE);
540 LineInfo structure;
541 structure.mFirstFrameOnLine = line->mFirstChild;
542 structure.mNumFramesOnLine = line->GetChildCount();
543 structure.mLineBounds = line->GetPhysicalBounds();
544 structure.mIsWrapped = line->IsLineWrapped();
545 return structure;
548 int32_t nsLineIterator::FindLineContaining(nsIFrame* aFrame,
549 int32_t aStartLine) {
550 const nsLineBox* line = GetLineAt(aStartLine);
551 MOZ_ASSERT(line, "aStartLine out of range");
552 while (line) {
553 if (line->Contains(aFrame)) {
554 return mIndex;
556 line = GetNextLine();
558 return -1;
561 NS_IMETHODIMP
562 nsLineIterator::CheckLineOrder(int32_t aLine, bool* aIsReordered,
563 nsIFrame** aFirstVisual,
564 nsIFrame** aLastVisual) {
565 const nsLineBox* line = GetLineAt(aLine);
566 MOZ_ASSERT(line, "aLine out of range!");
568 if (!line || !line->mFirstChild) { // empty line
569 *aIsReordered = false;
570 *aFirstVisual = nullptr;
571 *aLastVisual = nullptr;
572 return NS_OK;
575 nsIFrame* leftmostFrame;
576 nsIFrame* rightmostFrame;
577 *aIsReordered =
578 nsBidiPresUtils::CheckLineOrder(line->mFirstChild, line->GetChildCount(),
579 &leftmostFrame, &rightmostFrame);
581 // map leftmost/rightmost to first/last according to paragraph direction
582 *aFirstVisual = mRightToLeft ? rightmostFrame : leftmostFrame;
583 *aLastVisual = mRightToLeft ? leftmostFrame : rightmostFrame;
585 return NS_OK;
588 NS_IMETHODIMP
589 nsLineIterator::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
590 nsIFrame** aFrameFound,
591 bool* aPosIsBeforeFirstFrame,
592 bool* aPosIsAfterLastFrame) {
593 MOZ_ASSERT(aFrameFound && aPosIsBeforeFirstFrame && aPosIsAfterLastFrame,
594 "null OUT ptr");
596 if (!aFrameFound || !aPosIsBeforeFirstFrame || !aPosIsAfterLastFrame) {
597 return NS_ERROR_NULL_POINTER;
600 const nsLineBox* line = GetLineAt(aLineNumber);
601 if (!line) {
602 *aFrameFound = nullptr;
603 *aPosIsBeforeFirstFrame = true;
604 *aPosIsAfterLastFrame = false;
605 return NS_OK;
608 if (line->ISize() == 0 && line->BSize() == 0) {
609 return NS_ERROR_FAILURE;
612 LineFrameFinder finder(aPos, line->mContainerSize, line->mWritingMode,
613 mRightToLeft);
614 int32_t n = line->GetChildCount();
615 nsIFrame* frame = line->mFirstChild;
616 while (n--) {
617 finder.Scan(frame);
618 if (finder.IsDone()) {
619 break;
621 frame = frame->GetNextSibling();
623 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
624 return NS_OK;