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 */
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"
20 #include "nsIFrameInlines.h"
21 #include "nsPresArena.h"
22 #include "nsPrintfCString.h"
23 #include "nsWindowSizes.h"
26 static int32_t ctorCount
;
27 int32_t nsLineBox::GetCtorCount() { return ctorCount
; }
31 // static nsLineBox constant; initialized in the header file.
32 const uint32_t nsLineBox::kMinChildCountForHashtable
;
35 using namespace mozilla
;
37 nsLineBox::nsLineBox(nsIFrame
* aFrame
, int32_t aCount
, bool aIsBlock
)
38 : mFirstChild(aFrame
),
40 mContainerSize(-1, -1),
41 mBounds(WritingMode()), // mBounds will be initialized with the correct
42 // writing mode when it is set
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
);
58 NS_ASSERTION(!aIsBlock
|| aCount
== 1, "Blocks must have exactly one child");
60 for (int32_t n
= aCount
; n
> 0; f
= f
->GetNextSibling(), --n
) {
61 NS_ASSERTION(aIsBlock
== f
->IsBlockOutside(), "wrong kind of child frame");
66 mFlags
.mBlock
= aIsBlock
;
69 nsLineBox::~nsLineBox() {
70 MOZ_COUNT_DTOR(nsLineBox
);
71 if (MOZ_UNLIKELY(mFlags
.mHasHashedFrames
)) {
77 nsLineBox
* NS_NewLineBox(PresShell
* aPresShell
, nsIFrame
* aFrame
,
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
;
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
) {
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
);
126 delete aFromLine
->mFrames
;
127 aFromLine
->mFlags
.mHasHashedFrames
= 0;
128 aFromLine
->mChildCount
= fromNewCount
;
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
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() {
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
:
192 case StyleClear::Left
:
194 case StyleClear::Right
:
196 case StyleClear::Both
:
202 void nsLineBox::List(FILE* out
, int32_t aIndent
,
203 nsIFrame::ListFlags aFlags
) const {
205 while (aIndent
-- > 0) {
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();
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());
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
);
255 frame
->List(out
, pfx
.get(), aFlags
);
256 frame
= frame
->GetNextSibling();
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;
270 frame
= frame
->GetNextSibling();
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
) {
283 frame
= frame
->GetNextSibling();
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
) {
297 frame
= frame
->GetPrevSibling();
302 bool nsLineBox::IsEmpty() const {
303 if (IsBlock()) return mFirstChild
->IsEmpty();
307 for (n
= GetChildCount(), kid
= mFirstChild
; n
> 0;
308 --n
, kid
= kid
->GetNextSibling()) {
309 if (!kid
->IsEmpty()) return false;
317 bool nsLineBox::CachedIsEmpty() {
322 if (mFlags
.mEmptyCacheValid
) {
323 return mFlags
.mEmptyCacheState
;
328 result
= mFirstChild
->CachedIsEmpty();
333 for (n
= GetChildCount(), kid
= mFirstChild
; n
> 0;
334 --n
, kid
= kid
->GetNextSibling()) {
335 if (!kid
->CachedIsEmpty()) {
345 mFlags
.mEmptyCacheValid
= true;
346 mFlags
.mEmptyCacheState
= 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!");
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
) {
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();
395 // i is the index of curFrame in aEnd
396 int32_t i
= aEnd
->GetChildCount() - 1;
398 if (curFrame
== aFrame
) {
399 *aFrameIndexInLine
= i
;
403 curFrame
= curFrame
->GetPrevSibling();
405 MOZ_ASSERT(!aEnd
->mFlags
.mHasHashedFrames
, "Contains lied to us!");
407 *aFrameIndexInLine
= -1;
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;
420 if (!aValue
.IsZero()) {
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
;
435 void nsLineBox::MaybeFreeData() {
436 nsRect bounds
= GetPhysicalBounds();
437 if (mData
&& mData
->mOverflowAreas
== OverflowAreas(bounds
, bounds
)) {
439 if (mInlineData
->mFloats
.IsEmpty()) {
441 mInlineData
= nullptr;
443 } else if (mBlockData
->mCarriedOutBEndMargin
.IsZero()) {
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();
458 void nsLineBox::AppendFloats(nsTArray
<nsIFrame
*>&& aFloats
) {
459 MOZ_ASSERT(IsInline(), "block line can't have floats");
460 if (MOZ_UNLIKELY(!IsInline())) {
463 if (!aFloats
.IsEmpty()) {
465 mInlineData
->mFloats
.AppendElements(std::move(aFloats
));
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");
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.
487 void nsLineBox::SetFloatEdges(nscoord aStart
, nscoord aEnd
) {
488 MOZ_ASSERT(IsInline(), "block line can't have float edges");
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");
499 mInlineData
->mFloatEdgeIStart
= nscoord_MIN
;
500 mInlineData
->mFloatEdgeIEnd
= nscoord_MIN
;
504 void nsLineBox::SetOverflowAreas(const OverflowAreas
& aOverflowAreas
) {
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!");
514 nsRect bounds
= GetPhysicalBounds();
515 if (!aOverflowAreas
.InkOverflow().IsEqualInterior(bounds
) ||
516 !aOverflowAreas
.ScrollableOverflow().IsEqualEdges(bounds
)) {
519 mInlineData
= new ExtraInlineData(bounds
);
521 mBlockData
= new ExtraBlockData(bounds
);
524 mData
->mOverflowAreas
= aOverflowAreas
;
526 // Store away new value so that MaybeFreeData compares against
528 mData
->mOverflowAreas
= aOverflowAreas
;
533 //----------------------------------------------------------------------
535 Result
<nsILineIterator::LineInfo
, nsresult
> nsLineIterator::GetLine(
536 int32_t aLineNumber
) {
537 const nsLineBox
* line
= GetLineAt(aLineNumber
);
539 return Err(NS_ERROR_FAILURE
);
542 structure
.mFirstFrameOnLine
= line
->mFirstChild
;
543 structure
.mNumFramesOnLine
= line
->GetChildCount();
544 structure
.mLineBounds
= line
->GetPhysicalBounds();
545 structure
.mIsWrapped
= line
->IsLineWrapped();
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");
554 if (line
->Contains(aFrame
)) {
557 line
= GetNextLine();
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;
576 nsIFrame
* leftmostFrame
;
577 nsIFrame
* rightmostFrame
;
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
;
590 nsLineIterator::FindFrameAt(int32_t aLineNumber
, nsPoint aPos
,
591 nsIFrame
** aFrameFound
,
592 bool* aPosIsBeforeFirstFrame
,
593 bool* aPosIsAfterLastFrame
) {
594 MOZ_ASSERT(aFrameFound
&& aPosIsBeforeFirstFrame
&& aPosIsAfterLastFrame
,
597 if (!aFrameFound
|| !aPosIsBeforeFirstFrame
|| !aPosIsAfterLastFrame
) {
598 return NS_ERROR_NULL_POINTER
;
601 const nsLineBox
* line
= GetLineAt(aLineNumber
);
603 *aFrameFound
= nullptr;
604 *aPosIsBeforeFirstFrame
= true;
605 *aPosIsAfterLastFrame
= false;
609 if (line
->ISize() == 0 && line
->BSize() == 0) {
610 return NS_ERROR_FAILURE
;
613 LineFrameFinder
finder(aPos
, line
->mContainerSize
, line
->mWritingMode
,
615 int32_t n
= line
->GetChildCount();
616 nsIFrame
* frame
= line
->mFirstChild
;
619 if (finder
.IsDone()) {
622 frame
= frame
->GetNextSibling();
624 finder
.Finish(aFrameFound
, aPosIsBeforeFirstFrame
, aPosIsAfterLastFrame
);