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
),
39 mContainerSize(-1, -1),
40 mBounds(WritingMode()), // mBounds will be initialized with the correct
41 // writing mode when it is set
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
);
57 NS_ASSERTION(!aIsBlock
|| aCount
== 1, "Blocks must have exactly one child");
59 for (int32_t n
= aCount
; n
> 0; f
= f
->GetNextSibling(), --n
) {
60 NS_ASSERTION(aIsBlock
== f
->IsBlockOutside(), "wrong kind of child frame");
65 mFlags
.mBlock
= aIsBlock
;
68 nsLineBox::~nsLineBox() {
69 MOZ_COUNT_DTOR(nsLineBox
);
70 if (MOZ_UNLIKELY(mFlags
.mHasHashedFrames
)) {
76 nsLineBox
* NS_NewLineBox(PresShell
* aPresShell
, nsIFrame
* aFrame
,
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
;
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
) {
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
);
125 delete aFromLine
->mFrames
;
126 aFromLine
->mFlags
.mHasHashedFrames
= 0;
127 aFromLine
->mChildCount
= fromNewCount
;
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
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() {
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
:
191 case StyleClear::Left
:
193 case StyleClear::Right
:
195 case StyleClear::Both
:
201 void nsLineBox::List(FILE* out
, int32_t aIndent
,
202 nsIFrame::ListFlags aFlags
) const {
204 while (aIndent
-- > 0) {
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();
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());
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
);
254 frame
->List(out
, pfx
.get(), aFlags
);
255 frame
= frame
->GetNextSibling();
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;
269 frame
= frame
->GetNextSibling();
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
) {
282 frame
= frame
->GetNextSibling();
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
) {
296 frame
= frame
->GetPrevSibling();
301 bool nsLineBox::IsEmpty() const {
302 if (IsBlock()) return mFirstChild
->IsEmpty();
306 for (n
= GetChildCount(), kid
= mFirstChild
; n
> 0;
307 --n
, kid
= kid
->GetNextSibling()) {
308 if (!kid
->IsEmpty()) return false;
316 bool nsLineBox::CachedIsEmpty() {
321 if (mFlags
.mEmptyCacheValid
) {
322 return mFlags
.mEmptyCacheState
;
327 result
= mFirstChild
->CachedIsEmpty();
332 for (n
= GetChildCount(), kid
= mFirstChild
; n
> 0;
333 --n
, kid
= kid
->GetNextSibling()) {
334 if (!kid
->CachedIsEmpty()) {
344 mFlags
.mEmptyCacheValid
= true;
345 mFlags
.mEmptyCacheState
= 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!");
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
) {
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();
394 // i is the index of curFrame in aEnd
395 int32_t i
= aEnd
->GetChildCount() - 1;
397 if (curFrame
== aFrame
) {
398 *aFrameIndexInLine
= i
;
402 curFrame
= curFrame
->GetPrevSibling();
404 MOZ_ASSERT(!aEnd
->mFlags
.mHasHashedFrames
, "Contains lied to us!");
406 *aFrameIndexInLine
= -1;
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;
419 if (!aValue
.IsZero()) {
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
;
434 void nsLineBox::MaybeFreeData() {
435 nsRect bounds
= GetPhysicalBounds();
436 if (mData
&& mData
->mOverflowAreas
== OverflowAreas(bounds
, bounds
)) {
438 if (mInlineData
->mFloats
.IsEmpty()) {
440 mInlineData
= nullptr;
442 } else if (mBlockData
->mCarriedOutBEndMargin
.IsZero()) {
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();
457 void nsLineBox::AppendFloats(nsTArray
<nsIFrame
*>&& aFloats
) {
458 MOZ_ASSERT(IsInline(), "block line can't have floats");
459 if (MOZ_UNLIKELY(!IsInline())) {
462 if (!aFloats
.IsEmpty()) {
464 mInlineData
->mFloats
.AppendElements(std::move(aFloats
));
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");
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.
486 void nsLineBox::SetFloatEdges(nscoord aStart
, nscoord aEnd
) {
487 MOZ_ASSERT(IsInline(), "block line can't have float edges");
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");
498 mInlineData
->mFloatEdgeIStart
= nscoord_MIN
;
499 mInlineData
->mFloatEdgeIEnd
= nscoord_MIN
;
503 void nsLineBox::SetOverflowAreas(const OverflowAreas
& aOverflowAreas
) {
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!");
513 nsRect bounds
= GetPhysicalBounds();
514 if (!aOverflowAreas
.InkOverflow().IsEqualInterior(bounds
) ||
515 !aOverflowAreas
.ScrollableOverflow().IsEqualEdges(bounds
)) {
518 mInlineData
= new ExtraInlineData(bounds
);
520 mBlockData
= new ExtraBlockData(bounds
);
523 mData
->mOverflowAreas
= aOverflowAreas
;
525 // Store away new value so that MaybeFreeData compares against
527 mData
->mOverflowAreas
= aOverflowAreas
;
532 //----------------------------------------------------------------------
534 Result
<nsILineIterator::LineInfo
, nsresult
> nsLineIterator::GetLine(
535 int32_t aLineNumber
) {
536 const nsLineBox
* line
= GetLineAt(aLineNumber
);
538 return Err(NS_ERROR_FAILURE
);
541 structure
.mFirstFrameOnLine
= line
->mFirstChild
;
542 structure
.mNumFramesOnLine
= line
->GetChildCount();
543 structure
.mLineBounds
= line
->GetPhysicalBounds();
544 structure
.mIsWrapped
= line
->IsLineWrapped();
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");
553 if (line
->Contains(aFrame
)) {
556 line
= GetNextLine();
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;
575 nsIFrame
* leftmostFrame
;
576 nsIFrame
* rightmostFrame
;
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
;
589 nsLineIterator::FindFrameAt(int32_t aLineNumber
, nsPoint aPos
,
590 nsIFrame
** aFrameFound
,
591 bool* aPosIsBeforeFirstFrame
,
592 bool* aPosIsAfterLastFrame
) {
593 MOZ_ASSERT(aFrameFound
&& aPosIsBeforeFirstFrame
&& aPosIsAfterLastFrame
,
596 if (!aFrameFound
|| !aPosIsBeforeFirstFrame
|| !aPosIsAfterLastFrame
) {
597 return NS_ERROR_NULL_POINTER
;
600 const nsLineBox
* line
= GetLineAt(aLineNumber
);
602 *aFrameFound
= nullptr;
603 *aPosIsBeforeFirstFrame
= true;
604 *aPosIsAfterLastFrame
= false;
608 if (line
->ISize() == 0 && line
->BSize() == 0) {
609 return NS_ERROR_FAILURE
;
612 LineFrameFinder
finder(aPos
, line
->mContainerSize
, line
->mWritingMode
,
614 int32_t n
= line
->GetChildCount();
615 nsIFrame
* frame
= line
->mFirstChild
;
618 if (finder
.IsDone()) {
621 frame
= frame
->GetNextSibling();
623 finder
.Finish(aFrameFound
, aPosIsBeforeFirstFrame
, aPosIsAfterLastFrame
);