Bug 574454 - Cleanup nsNativeThemeWin's GetMinimumWidgetSize a bit. r=roc.
[mozilla-central.git] / layout / generic / nsFrameList.cpp
blob04ae0b6aca7e2760089927a6bd895f8f9c9fefed
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsFrameList.h"
40 #include "nsIFrame.h"
41 #include "nsLayoutUtils.h"
43 #ifdef IBMBIDI
44 #include "nsCOMPtr.h"
45 #include "nsGkAtoms.h"
46 #include "nsILineIterator.h"
47 #include "nsBidiPresUtils.h"
48 #endif // IBMBIDI
50 const nsFrameList* nsFrameList::sEmptyList;
52 /* static */
53 nsresult
54 nsFrameList::Init()
56 NS_PRECONDITION(!sEmptyList, "Shouldn't be allocated");
58 sEmptyList = new nsFrameList();
59 if (!sEmptyList)
60 return NS_ERROR_OUT_OF_MEMORY;
62 return NS_OK;
65 void
66 nsFrameList::Destroy()
68 NS_PRECONDITION(this != sEmptyList, "Shouldn't Destroy() sEmptyList");
70 DestroyFrames();
71 delete this;
74 void
75 nsFrameList::DestroyFrom(nsIFrame* aDestructRoot)
77 NS_PRECONDITION(this != sEmptyList, "Shouldn't Destroy() sEmptyList");
79 DestroyFramesFrom(aDestructRoot);
80 delete this;
83 void
84 nsFrameList::DestroyFrames()
86 while (nsIFrame* frame = RemoveFirstChild()) {
87 frame->Destroy();
89 mLastChild = nsnull;
92 void
93 nsFrameList::DestroyFramesFrom(nsIFrame* aDestructRoot)
95 NS_PRECONDITION(aDestructRoot, "Missing destruct root");
97 while (nsIFrame* frame = RemoveFirstChild()) {
98 frame->DestroyFrom(aDestructRoot);
100 mLastChild = nsnull;
103 void
104 nsFrameList::SetFrames(nsIFrame* aFrameList)
106 NS_PRECONDITION(!mFirstChild, "Losing frames");
108 mFirstChild = aFrameList;
109 mLastChild = nsLayoutUtils::GetLastSibling(mFirstChild);
112 void
113 nsFrameList::RemoveFrame(nsIFrame* aFrame)
115 NS_PRECONDITION(aFrame, "null ptr");
116 #ifdef DEBUG_FRAME_LIST
117 // ContainsFrame is O(N)
118 NS_PRECONDITION(ContainsFrame(aFrame), "wrong list");
119 #endif
121 nsIFrame* nextFrame = aFrame->GetNextSibling();
122 if (aFrame == mFirstChild) {
123 mFirstChild = nextFrame;
124 aFrame->SetNextSibling(nsnull);
125 if (!nextFrame) {
126 mLastChild = nsnull;
129 else {
130 nsIFrame* prevSibling = aFrame->GetPrevSibling();
131 NS_ASSERTION(prevSibling && prevSibling->GetNextSibling() == aFrame,
132 "Broken frame linkage");
133 prevSibling->SetNextSibling(nextFrame);
134 aFrame->SetNextSibling(nsnull);
135 if (!nextFrame) {
136 mLastChild = prevSibling;
141 PRBool
142 nsFrameList::RemoveFrameIfPresent(nsIFrame* aFrame)
144 NS_PRECONDITION(aFrame, "null ptr");
146 for (Enumerator e(*this); !e.AtEnd(); e.Next()) {
147 if (e.get() == aFrame) {
148 RemoveFrame(aFrame);
149 return PR_TRUE;
152 return PR_FALSE;
155 nsFrameList
156 nsFrameList::RemoveFramesAfter(nsIFrame* aAfterFrame)
158 if (!aAfterFrame) {
159 nsFrameList result;
160 result.InsertFrames(nsnull, nsnull, *this);
161 return result;
164 NS_PRECONDITION(NotEmpty(), "illegal operation on empty list");
165 #ifdef DEBUG_FRAME_LIST
166 NS_PRECONDITION(ContainsFrame(aAfterFrame), "wrong list");
167 #endif
169 nsIFrame* tail = aAfterFrame->GetNextSibling();
170 // if (!tail) return EmptyList(); -- worth optimizing this case?
171 nsIFrame* oldLastChild = mLastChild;
172 mLastChild = aAfterFrame;
173 aAfterFrame->SetNextSibling(nsnull);
174 return nsFrameList(tail, tail ? oldLastChild : nsnull);
177 nsIFrame*
178 nsFrameList::RemoveFirstChild()
180 if (mFirstChild) {
181 nsIFrame* firstChild = mFirstChild;
182 RemoveFrame(firstChild);
183 return firstChild;
185 return nsnull;
188 void
189 nsFrameList::DestroyFrame(nsIFrame* aFrame)
191 NS_PRECONDITION(aFrame, "null ptr");
192 RemoveFrame(aFrame);
193 aFrame->Destroy();
196 PRBool
197 nsFrameList::DestroyFrameIfPresent(nsIFrame* aFrame)
199 NS_PRECONDITION(aFrame, "null ptr");
201 if (RemoveFrameIfPresent(aFrame)) {
202 aFrame->Destroy();
203 return PR_TRUE;
205 return PR_FALSE;
208 nsFrameList::Slice
209 nsFrameList::InsertFrames(nsIFrame* aParent, nsIFrame* aPrevSibling,
210 nsFrameList& aFrameList)
212 NS_PRECONDITION(aFrameList.NotEmpty(), "Unexpected empty list");
214 if (aParent) {
215 aFrameList.ApplySetParent(aParent);
218 NS_ASSERTION(IsEmpty() ||
219 FirstChild()->GetParent() == aFrameList.FirstChild()->GetParent(),
220 "frame to add has different parent");
221 NS_ASSERTION(!aPrevSibling ||
222 aPrevSibling->GetParent() == aFrameList.FirstChild()->GetParent(),
223 "prev sibling has different parent");
224 #ifdef DEBUG_FRAME_LIST
225 // ContainsFrame is O(N)
226 NS_ASSERTION(!aPrevSibling || ContainsFrame(aPrevSibling),
227 "prev sibling is not on this list");
228 #endif
230 nsIFrame* firstNewFrame = aFrameList.FirstChild();
231 nsIFrame* nextSibling;
232 if (aPrevSibling) {
233 nextSibling = aPrevSibling->GetNextSibling();
234 aPrevSibling->SetNextSibling(firstNewFrame);
236 else {
237 nextSibling = mFirstChild;
238 mFirstChild = firstNewFrame;
241 nsIFrame* lastNewFrame = aFrameList.LastChild();
242 lastNewFrame->SetNextSibling(nextSibling);
243 if (!nextSibling) {
244 mLastChild = lastNewFrame;
247 VerifyList();
249 aFrameList.Clear();
250 return Slice(*this, firstNewFrame, nextSibling);
253 nsFrameList
254 nsFrameList::ExtractHead(FrameLinkEnumerator& aLink)
256 NS_PRECONDITION(&aLink.List() == this, "Unexpected list");
257 NS_PRECONDITION(!aLink.PrevFrame() ||
258 aLink.PrevFrame()->GetNextSibling() ==
259 aLink.NextFrame(),
260 "Unexpected PrevFrame()");
261 NS_PRECONDITION(aLink.PrevFrame() ||
262 aLink.NextFrame() == FirstChild(),
263 "Unexpected NextFrame()");
264 NS_PRECONDITION(!aLink.PrevFrame() ||
265 aLink.NextFrame() != FirstChild(),
266 "Unexpected NextFrame()");
267 NS_PRECONDITION(aLink.mEnd == nsnull,
268 "Unexpected mEnd for frame link enumerator");
270 nsIFrame* prev = aLink.PrevFrame();
271 nsIFrame* newFirstFrame = nsnull;
272 if (prev) {
273 // Truncate the list after |prev| and hand the first part to our new list.
274 prev->SetNextSibling(nsnull);
275 newFirstFrame = mFirstChild;
276 mFirstChild = aLink.NextFrame();
277 if (!mFirstChild) { // we handed over the whole list
278 mLastChild = nsnull;
281 // Now make sure aLink doesn't point to a frame we no longer have.
282 aLink.mPrev = nsnull;
284 // else aLink is pointing to before our first frame. Nothing to do.
286 return nsFrameList(newFirstFrame, prev);
289 nsFrameList
290 nsFrameList::ExtractTail(FrameLinkEnumerator& aLink)
292 NS_PRECONDITION(&aLink.List() == this, "Unexpected list");
293 NS_PRECONDITION(!aLink.PrevFrame() ||
294 aLink.PrevFrame()->GetNextSibling() ==
295 aLink.NextFrame(),
296 "Unexpected PrevFrame()");
297 NS_PRECONDITION(aLink.PrevFrame() ||
298 aLink.NextFrame() == FirstChild(),
299 "Unexpected NextFrame()");
300 NS_PRECONDITION(!aLink.PrevFrame() ||
301 aLink.NextFrame() != FirstChild(),
302 "Unexpected NextFrame()");
303 NS_PRECONDITION(aLink.mEnd == nsnull,
304 "Unexpected mEnd for frame link enumerator");
306 nsIFrame* prev = aLink.PrevFrame();
307 nsIFrame* newFirstFrame;
308 nsIFrame* newLastFrame;
309 if (prev) {
310 // Truncate the list after |prev| and hand the second part to our new list
311 prev->SetNextSibling(nsnull);
312 newFirstFrame = aLink.NextFrame();
313 newLastFrame = newFirstFrame ? mLastChild : nsnull;
314 mLastChild = prev;
315 } else {
316 // Hand the whole list over to our new list
317 newFirstFrame = mFirstChild;
318 newLastFrame = mLastChild;
319 Clear();
322 // Now make sure aLink doesn't point to a frame we no longer have.
323 aLink.mFrame = nsnull;
325 NS_POSTCONDITION(aLink.AtEnd(), "What's going on here?");
327 return nsFrameList(newFirstFrame, newLastFrame);
330 nsIFrame*
331 nsFrameList::FrameAt(PRInt32 aIndex) const
333 NS_PRECONDITION(aIndex >= 0, "invalid arg");
334 if (aIndex < 0) return nsnull;
335 nsIFrame* frame = mFirstChild;
336 while ((aIndex-- > 0) && frame) {
337 frame = frame->GetNextSibling();
339 return frame;
342 PRInt32
343 nsFrameList::IndexOf(nsIFrame* aFrame) const
345 PRInt32 count = 0;
346 for (nsIFrame* f = mFirstChild; f; f = f->GetNextSibling()) {
347 if (f == aFrame)
348 return count;
349 ++count;
351 return -1;
354 PRBool
355 nsFrameList::ContainsFrame(const nsIFrame* aFrame) const
357 NS_PRECONDITION(aFrame, "null ptr");
359 nsIFrame* frame = mFirstChild;
360 while (frame) {
361 if (frame == aFrame) {
362 return PR_TRUE;
364 frame = frame->GetNextSibling();
366 return PR_FALSE;
369 PRInt32
370 nsFrameList::GetLength() const
372 PRInt32 count = 0;
373 nsIFrame* frame = mFirstChild;
374 while (frame) {
375 count++;
376 frame = frame->GetNextSibling();
378 return count;
381 static int CompareByContentOrder(const nsIFrame* aF1, const nsIFrame* aF2)
383 if (aF1->GetContent() != aF2->GetContent()) {
384 return nsLayoutUtils::CompareTreePosition(aF1->GetContent(), aF2->GetContent());
387 if (aF1 == aF2) {
388 return 0;
391 const nsIFrame* f;
392 for (f = aF2; f; f = f->GetPrevInFlow()) {
393 if (f == aF1) {
394 // f1 comes before f2 in the flow
395 return -1;
398 for (f = aF1; f; f = f->GetPrevInFlow()) {
399 if (f == aF2) {
400 // f1 comes after f2 in the flow
401 return 1;
405 NS_ASSERTION(PR_FALSE, "Frames for same content but not in relative flow order");
406 return 0;
409 class CompareByContentOrderComparator
411 public:
412 PRBool Equals(const nsIFrame* aA, const nsIFrame* aB) const {
413 return aA == aB;
415 PRBool LessThan(const nsIFrame* aA, const nsIFrame* aB) const {
416 return CompareByContentOrder(aA, aB) < 0;
420 void
421 nsFrameList::ApplySetParent(nsIFrame* aParent) const
423 NS_ASSERTION(aParent, "null ptr");
425 for (nsIFrame* f = FirstChild(); f; f = f->GetNextSibling()) {
426 f->SetParent(aParent);
430 #ifdef DEBUG
431 void
432 nsFrameList::List(FILE* out) const
434 fputs("<\n", out);
435 for (nsIFrame* frame = mFirstChild; frame;
436 frame = frame->GetNextSibling()) {
437 frame->List(out, 1);
439 fputs(">\n", out);
441 #endif
443 #ifdef IBMBIDI
444 nsIFrame*
445 nsFrameList::GetPrevVisualFor(nsIFrame* aFrame) const
447 if (!mFirstChild)
448 return nsnull;
450 nsIFrame* parent = mFirstChild->GetParent();
451 if (!parent)
452 return aFrame ? aFrame->GetPrevSibling() : LastChild();
454 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(mFirstChild);
455 nsBidiPresUtils* bidiUtils = mFirstChild->PresContext()->GetBidiUtils();
457 nsAutoLineIterator iter = parent->GetLineIterator();
458 if (!iter) {
459 // Parent is not a block Frame
460 if (parent->GetType() == nsGkAtoms::lineFrame) {
461 // Line frames are not bidi-splittable, so need to consider bidi reordering
462 if (baseLevel == NSBIDI_LTR) {
463 return bidiUtils->GetFrameToLeftOf(aFrame, mFirstChild, -1);
464 } else { // RTL
465 return bidiUtils->GetFrameToRightOf(aFrame, mFirstChild, -1);
467 } else {
468 // Just get the next or prev sibling, depending on block and frame direction.
469 nsBidiLevel frameEmbeddingLevel = nsBidiPresUtils::GetFrameEmbeddingLevel(mFirstChild);
470 if ((frameEmbeddingLevel & 1) == (baseLevel & 1)) {
471 return aFrame ? aFrame->GetPrevSibling() : LastChild();
472 } else {
473 return aFrame ? aFrame->GetNextSibling() : mFirstChild;
478 // Parent is a block frame, so use the LineIterator to find the previous visual
479 // sibling on this line, or the last one on the previous line.
481 PRInt32 thisLine;
482 if (aFrame) {
483 thisLine = iter->FindLineContaining(aFrame);
484 if (thisLine < 0)
485 return nsnull;
486 } else {
487 thisLine = iter->GetNumLines();
490 nsIFrame* frame = nsnull;
491 nsIFrame* firstFrameOnLine;
492 PRInt32 numFramesOnLine;
493 nsRect lineBounds;
494 PRUint32 lineFlags;
496 if (aFrame) {
497 iter->GetLine(thisLine, &firstFrameOnLine, &numFramesOnLine, lineBounds, &lineFlags);
499 if (baseLevel == NSBIDI_LTR) {
500 frame = bidiUtils->GetFrameToLeftOf(aFrame, firstFrameOnLine, numFramesOnLine);
501 } else { // RTL
502 frame = bidiUtils->GetFrameToRightOf(aFrame, firstFrameOnLine, numFramesOnLine);
506 if (!frame && thisLine > 0) {
507 // Get the last frame of the previous line
508 iter->GetLine(thisLine - 1, &firstFrameOnLine, &numFramesOnLine, lineBounds, &lineFlags);
510 if (baseLevel == NSBIDI_LTR) {
511 frame = bidiUtils->GetFrameToLeftOf(nsnull, firstFrameOnLine, numFramesOnLine);
512 } else { // RTL
513 frame = bidiUtils->GetFrameToRightOf(nsnull, firstFrameOnLine, numFramesOnLine);
516 return frame;
519 nsIFrame*
520 nsFrameList::GetNextVisualFor(nsIFrame* aFrame) const
522 if (!mFirstChild)
523 return nsnull;
525 nsIFrame* parent = mFirstChild->GetParent();
526 if (!parent)
527 return aFrame ? aFrame->GetPrevSibling() : mFirstChild;
529 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(mFirstChild);
530 nsBidiPresUtils* bidiUtils = mFirstChild->PresContext()->GetBidiUtils();
532 nsAutoLineIterator iter = parent->GetLineIterator();
533 if (!iter) {
534 // Parent is not a block Frame
535 if (parent->GetType() == nsGkAtoms::lineFrame) {
536 // Line frames are not bidi-splittable, so need to consider bidi reordering
537 if (baseLevel == NSBIDI_LTR) {
538 return bidiUtils->GetFrameToRightOf(aFrame, mFirstChild, -1);
539 } else { // RTL
540 return bidiUtils->GetFrameToLeftOf(aFrame, mFirstChild, -1);
542 } else {
543 // Just get the next or prev sibling, depending on block and frame direction.
544 nsBidiLevel frameEmbeddingLevel = nsBidiPresUtils::GetFrameEmbeddingLevel(mFirstChild);
545 if ((frameEmbeddingLevel & 1) == (baseLevel & 1)) {
546 return aFrame ? aFrame->GetNextSibling() : mFirstChild;
547 } else {
548 return aFrame ? aFrame->GetPrevSibling() : LastChild();
553 // Parent is a block frame, so use the LineIterator to find the next visual
554 // sibling on this line, or the first one on the next line.
556 PRInt32 thisLine;
557 if (aFrame) {
558 thisLine = iter->FindLineContaining(aFrame);
559 if (thisLine < 0)
560 return nsnull;
561 } else {
562 thisLine = -1;
565 nsIFrame* frame = nsnull;
566 nsIFrame* firstFrameOnLine;
567 PRInt32 numFramesOnLine;
568 nsRect lineBounds;
569 PRUint32 lineFlags;
571 if (aFrame) {
572 iter->GetLine(thisLine, &firstFrameOnLine, &numFramesOnLine, lineBounds, &lineFlags);
574 if (baseLevel == NSBIDI_LTR) {
575 frame = bidiUtils->GetFrameToRightOf(aFrame, firstFrameOnLine, numFramesOnLine);
576 } else { // RTL
577 frame = bidiUtils->GetFrameToLeftOf(aFrame, firstFrameOnLine, numFramesOnLine);
581 PRInt32 numLines = iter->GetNumLines();
582 if (!frame && thisLine < numLines - 1) {
583 // Get the first frame of the next line
584 iter->GetLine(thisLine + 1, &firstFrameOnLine, &numFramesOnLine, lineBounds, &lineFlags);
586 if (baseLevel == NSBIDI_LTR) {
587 frame = bidiUtils->GetFrameToRightOf(nsnull, firstFrameOnLine, numFramesOnLine);
588 } else { // RTL
589 frame = bidiUtils->GetFrameToLeftOf(nsnull, firstFrameOnLine, numFramesOnLine);
592 return frame;
594 #endif
596 #ifdef DEBUG_FRAME_LIST
597 void
598 nsFrameList::VerifyList() const
600 NS_ASSERTION((mFirstChild == nsnull) == (mLastChild == nsnull),
601 "bad list state");
603 if (IsEmpty()) {
604 return;
607 // Simple algorithm to find a loop in a linked list -- advance pointers
608 // through it at speeds of 1 and 2, and if they ever get to be equal bail
609 NS_ASSERTION(!mFirstChild->GetPrevSibling(), "bad prev sibling pointer");
610 nsIFrame *first = mFirstChild, *second = mFirstChild;
611 for (;;) {
612 first = first->GetNextSibling();
613 second = second->GetNextSibling();
614 if (!second) {
615 break;
617 NS_ASSERTION(second->GetPrevSibling()->GetNextSibling() == second,
618 "bad prev sibling pointer");
619 second = second->GetNextSibling();
620 if (first == second) {
621 // Loop detected! Since second advances faster, they can't both be null;
622 // we would have broken out of the loop long ago.
623 NS_ERROR("loop in frame list. This will probably hang soon.");
624 return;
626 if (!second) {
627 break;
629 NS_ASSERTION(second->GetPrevSibling()->GetNextSibling() == second,
630 "bad prev sibling pointer");
633 NS_ASSERTION(mLastChild == nsLayoutUtils::GetLastSibling(mFirstChild),
634 "bogus mLastChild");
635 // XXX we should also assert that all GetParent() are either null or
636 // the same non-null value, but nsCSSFrameConstructor::nsFrameItems
637 // prevents that, e.g. table captions.
639 #endif