1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsListBoxBodyFrame.h"
8 #include "nsListBoxLayout.h"
10 #include "mozilla/MathAlgorithms.h"
12 #include "nsGridRowGroupLayout.h"
13 #include "nsIServiceManager.h"
14 #include "nsGkAtoms.h"
15 #include "nsIContent.h"
16 #include "nsNameSpaceManager.h"
17 #include "nsIDocument.h"
18 #include "nsIDOMMouseEvent.h"
19 #include "nsIDOMElement.h"
20 #include "nsIDOMNodeList.h"
21 #include "nsCSSFrameConstructor.h"
22 #include "nsIScrollableFrame.h"
23 #include "nsScrollbarFrame.h"
25 #include "nsViewManager.h"
26 #include "nsStyleContext.h"
27 #include "nsFontMetrics.h"
29 #include "nsAutoPtr.h"
30 #include "nsStyleSet.h"
31 #include "nsPIBoxObject.h"
32 #include "nsLayoutUtils.h"
33 #include "nsPIListBoxObject.h"
34 #include "nsContentUtils.h"
35 #include "ChildIterator.h"
36 #include "nsRenderingContext.h"
41 #include "nsAccessibilityService.h"
44 using namespace mozilla
;
45 using namespace mozilla::dom
;
47 /////////////// nsListScrollSmoother //////////////////
49 /* A mediator used to smooth out scrolling. It works by seeing if
50 * we have time to scroll the amount of rows requested. This is determined
51 * by measuring how long it takes to scroll a row. If we can scroll the
52 * rows in time we do so. If not we start a timer and skip the request. We
53 * do this until the timer finally first because the user has stopped moving
54 * the mouse. Then do all the queued requests in on shot.
57 // the longest amount of time that can go by before the use
58 // notices it as a delay.
59 #define USER_TIME_THRESHOLD 150000
61 // how long it takes to layout a single row initial value.
62 // we will time this after we scroll a few rows.
63 #define TIME_PER_ROW_INITAL 50000
65 // if we decide we can't layout the rows in the amount of time. How long
66 // do we wait before checking again?
67 #define SMOOTH_INTERVAL 100
69 class nsListScrollSmoother MOZ_FINAL
: public nsITimerCallback
72 virtual ~nsListScrollSmoother();
77 explicit nsListScrollSmoother(nsListBoxBodyFrame
* aOuter
);
80 NS_DECL_NSITIMERCALLBACK
86 nsCOMPtr
<nsITimer
> mRepeatTimer
;
88 nsListBoxBodyFrame
* mOuter
;
91 nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame
* aOuter
)
97 nsListScrollSmoother::~nsListScrollSmoother()
103 nsListScrollSmoother::Notify(nsITimer
*timer
)
107 NS_ASSERTION(mOuter
, "mOuter is null, see bug #68365");
108 if (!mOuter
) return NS_OK
;
110 // actually do some work.
111 mOuter
->InternalPositionChangedCallback();
116 nsListScrollSmoother::IsRunning()
118 return mRepeatTimer
? true : false;
122 nsListScrollSmoother::Start()
125 mRepeatTimer
= do_CreateInstance("@mozilla.org/timer;1");
126 mRepeatTimer
->InitWithCallback(this, SMOOTH_INTERVAL
, nsITimer::TYPE_ONE_SHOT
);
130 nsListScrollSmoother::Stop()
132 if ( mRepeatTimer
) {
133 mRepeatTimer
->Cancel();
134 mRepeatTimer
= nullptr;
138 NS_IMPL_ISUPPORTS(nsListScrollSmoother
, nsITimerCallback
)
140 /////////////// nsListBoxBodyFrame //////////////////
142 nsListBoxBodyFrame::nsListBoxBodyFrame(nsIPresShell
* aPresShell
,
143 nsStyleContext
* aContext
,
144 nsBoxLayout
* aLayoutManager
)
145 : nsBoxFrame(aPresShell
, aContext
, false, aLayoutManager
),
147 mBottomFrame(nullptr),
148 mLinkupFrame(nullptr),
149 mScrollSmoother(nullptr),
158 mTimePerRow(TIME_PER_ROW_INITAL
),
159 mRowHeightWasSet(false),
161 mAdjustScroll(false),
162 mReflowCallbackPosted(false)
166 nsListBoxBodyFrame::~nsListBoxBodyFrame()
168 NS_IF_RELEASE(mScrollSmoother
);
170 #if USE_TIMER_TO_DELAY_SCROLLING
171 StopScrollTracking();
172 mAutoScrollTimer
= nullptr;
177 NS_QUERYFRAME_HEAD(nsListBoxBodyFrame
)
178 NS_QUERYFRAME_ENTRY(nsIScrollbarMediator
)
179 NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame
)
180 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame
)
182 ////////// nsIFrame /////////////////
185 nsListBoxBodyFrame::Init(nsIContent
* aContent
,
186 nsContainerFrame
* aParent
,
187 nsIFrame
* aPrevInFlow
)
189 nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
190 // Don't call nsLayoutUtils::GetScrollableFrameFor since we are not its
191 // scrollframe child yet.
192 nsIScrollableFrame
* scrollFrame
= do_QueryFrame(aParent
);
194 nsIFrame
* verticalScrollbar
= scrollFrame
->GetScrollbarBox(true);
195 nsScrollbarFrame
* scrollbarFrame
= do_QueryFrame(verticalScrollbar
);
196 if (scrollbarFrame
) {
197 scrollbarFrame
->SetScrollbarMediatorContent(GetContent());
200 nsRefPtr
<nsFontMetrics
> fm
;
201 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm
));
202 mRowHeight
= fm
->MaxHeight();
206 nsListBoxBodyFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
208 // make sure we cancel any posted callbacks.
209 if (mReflowCallbackPosted
)
210 PresContext()->PresShell()->CancelReflowCallback(this);
212 // Revoke any pending position changed events
213 for (uint32_t i
= 0; i
< mPendingPositionChangeEvents
.Length(); ++i
) {
214 mPendingPositionChangeEvents
[i
]->Revoke();
217 // Make sure we tell our listbox's box object we're being destroyed.
219 mBoxObject
->ClearCachedValues();
222 nsBoxFrame::DestroyFrom(aDestructRoot
);
226 nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID
,
232 if (aAttribute
== nsGkAtoms::rows
) {
233 PresContext()->PresShell()->
234 FrameNeedsReflow(this, nsIPresShell::eStyleChange
, NS_FRAME_IS_DIRTY
);
237 rv
= nsBoxFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
244 nsListBoxBodyFrame::MarkIntrinsicISizesDirty()
247 nsBoxFrame::MarkIntrinsicISizesDirty();
250 /////////// nsBox ///////////////
253 nsListBoxBodyFrame::DoLayout(nsBoxLayoutState
& aBoxLayoutState
)
256 aBoxLayoutState
.SetPaintingDisabled(true);
258 nsresult rv
= nsBoxFrame::DoLayout(aBoxLayoutState
);
260 // determine the real height for the scrollable area from the total number
261 // of rows, since non-visible rows don't yet have frames
262 nsRect
rect(nsPoint(0, 0), GetSize());
263 nsOverflowAreas
overflow(rect
, rect
);
264 if (mLayoutManager
) {
265 nsIFrame
* childFrame
= mFrames
.FirstChild();
267 ConsiderChildOverflow(overflow
, childFrame
);
268 childFrame
= childFrame
->GetNextSibling();
271 nsSize prefSize
= mLayoutManager
->GetPrefSize(this, aBoxLayoutState
);
272 NS_FOR_FRAME_OVERFLOW_TYPES(otype
) {
273 nsRect
& o
= overflow
.Overflow(otype
);
274 o
.height
= std::max(o
.height
, prefSize
.height
);
277 FinishAndStoreOverflow(overflow
, GetSize());
280 aBoxLayoutState
.SetPaintingDisabled(false);
282 // if we are scrolled and the row height changed
283 // make sure we are scrolled to a correct index.
285 PostReflowCallback();
291 nsListBoxBodyFrame::GetMinSizeForScrollArea(nsBoxLayoutState
& aBoxLayoutState
)
294 if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None
,
295 nsGkAtoms::sizemode
)) {
296 result
= GetPrefSize(aBoxLayoutState
);
298 nsIScrollableFrame
* scrollFrame
= nsLayoutUtils::GetScrollableFrameFor(this);
300 scrollFrame
->GetScrollbarStyles().mVertical
== NS_STYLE_OVERFLOW_AUTO
) {
301 nsMargin scrollbars
=
302 scrollFrame
->GetDesiredScrollbarSizes(&aBoxLayoutState
);
303 result
.width
+= scrollbars
.left
+ scrollbars
.right
;
310 nsListBoxBodyFrame::GetPrefSize(nsBoxLayoutState
& aBoxLayoutState
)
312 nsSize pref
= nsBoxFrame::GetPrefSize(aBoxLayoutState
);
314 int32_t size
= GetFixedRowSize();
316 pref
.height
= size
*GetRowHeightAppUnits();
318 nsIScrollableFrame
* scrollFrame
= nsLayoutUtils::GetScrollableFrameFor(this);
320 scrollFrame
->GetScrollbarStyles().mVertical
== NS_STYLE_OVERFLOW_AUTO
) {
321 nsMargin scrollbars
= scrollFrame
->GetDesiredScrollbarSizes(&aBoxLayoutState
);
322 pref
.width
+= scrollbars
.left
+ scrollbars
.right
;
327 ///////////// nsIScrollbarMediator ///////////////
330 nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame
* aScrollbar
, int32_t aDirection
)
332 MOZ_ASSERT(aScrollbar
!= nullptr);
333 aScrollbar
->SetIncrementToPage(aDirection
);
334 nsWeakFrame
weakFrame(this);
335 int32_t newPos
= aScrollbar
->MoveToNewPosition();
336 if (!weakFrame
.IsAlive()) {
343 nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame
* aScrollbar
, int32_t aDirection
)
345 MOZ_ASSERT(aScrollbar
!= nullptr);
346 aScrollbar
->SetIncrementToWhole(aDirection
);
347 nsWeakFrame
weakFrame(this);
348 int32_t newPos
= aScrollbar
->MoveToNewPosition();
349 if (!weakFrame
.IsAlive()) {
356 nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame
* aScrollbar
, int32_t aDirection
)
358 MOZ_ASSERT(aScrollbar
!= nullptr);
359 aScrollbar
->SetIncrementToLine(aDirection
);
360 nsWeakFrame
weakFrame(this);
361 int32_t newPos
= aScrollbar
->MoveToNewPosition();
362 if (!weakFrame
.IsAlive()) {
369 nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame
* aScrollbar
)
371 nsWeakFrame
weakFrame(this);
372 int32_t newPos
= aScrollbar
->MoveToNewPosition();
373 if (!weakFrame
.IsAlive()) {
380 nsListBoxBodyFrame::ToRowIndex(nscoord aPos
) const
382 return NS_roundf(float(std::max(aPos
, 0)) / mRowHeight
);
386 nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame
* aScrollbar
,
390 if (mScrolling
|| mRowHeight
== 0)
393 int32_t newIndex
= ToRowIndex(aNewPos
);
394 if (newIndex
== mCurrentIndex
) {
397 int32_t rowDelta
= newIndex
- mCurrentIndex
;
399 nsListScrollSmoother
* smoother
= GetSmoother();
401 // if we can't scroll the rows in time then start a timer. We will eat
402 // events until the user stops moving and the timer stops.
403 if (smoother
->IsRunning() || Abs(rowDelta
)*mTimePerRow
> USER_TIME_THRESHOLD
) {
407 smoother
->mDelta
= rowDelta
;
416 mCurrentIndex
= newIndex
;
417 smoother
->mDelta
= 0;
419 if (mCurrentIndex
< 0) {
423 InternalPositionChanged(rowDelta
< 0, Abs(rowDelta
));
427 nsListBoxBodyFrame::VisibilityChanged(bool aVisible
)
432 int32_t lastPageTopRow
= GetRowCount() - (GetAvailableHeight() / mRowHeight
);
433 if (lastPageTopRow
< 0)
435 int32_t delta
= mCurrentIndex
- lastPageTopRow
;
437 mCurrentIndex
= lastPageTopRow
;
438 InternalPositionChanged(true, delta
);
443 nsListBoxBodyFrame::GetScrollbarBox(bool aVertical
)
445 nsIScrollableFrame
* scrollFrame
= nsLayoutUtils::GetScrollableFrameFor(this);
446 return scrollFrame
? scrollFrame
->GetScrollbarBox(true) : nullptr;
450 nsListBoxBodyFrame::UpdateIndex(int32_t aNewPos
)
452 int32_t newIndex
= ToRowIndex(nsPresContext::CSSPixelsToAppUnits(aNewPos
));
453 if (newIndex
== mCurrentIndex
) {
456 bool up
= newIndex
< mCurrentIndex
;
457 int32_t indexDelta
= Abs(newIndex
- mCurrentIndex
);
458 mCurrentIndex
= newIndex
;
459 InternalPositionChanged(up
, indexDelta
);
462 ///////////// nsIReflowCallback ///////////////
465 nsListBoxBodyFrame::ReflowFinished()
467 nsAutoScriptBlocker scriptBlocker
;
468 // now create or destroy any rows as needed
471 // keep scrollbar in sync
473 VerticalScroll(mYPosition
);
474 mAdjustScroll
= false;
477 // if the row height changed then mark everything as a style change.
478 // That will dirty the entire listbox
479 if (mRowHeightWasSet
) {
480 PresContext()->PresShell()->
481 FrameNeedsReflow(this, nsIPresShell::eStyleChange
, NS_FRAME_IS_DIRTY
);
482 int32_t pos
= mCurrentIndex
* mRowHeight
;
483 if (mYPosition
!= pos
)
484 mAdjustScroll
= true;
485 mRowHeightWasSet
= false;
488 mReflowCallbackPosted
= false;
493 nsListBoxBodyFrame::ReflowCallbackCanceled()
495 mReflowCallbackPosted
= false;
498 ///////// nsIListBoxObject ///////////////
501 nsListBoxBodyFrame::GetRowCount(int32_t* aResult
)
503 *aResult
= GetRowCount();
508 nsListBoxBodyFrame::GetNumberOfVisibleRows(int32_t *aResult
)
510 *aResult
= mRowHeight
? GetAvailableHeight() / mRowHeight
: 0;
515 nsListBoxBodyFrame::GetIndexOfFirstVisibleRow(int32_t *aResult
)
517 *aResult
= mCurrentIndex
;
522 nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex
)
525 return NS_ERROR_ILLEGAL_VALUE
;
529 rows
= GetAvailableHeight()/mRowHeight
;
532 int32_t bottomIndex
= mCurrentIndex
+ rows
;
534 // if row is visible, ignore
535 if (mCurrentIndex
<= aRowIndex
&& aRowIndex
< bottomIndex
)
540 bool up
= aRowIndex
< mCurrentIndex
;
542 delta
= mCurrentIndex
- aRowIndex
;
543 mCurrentIndex
= aRowIndex
;
546 // Check to be sure we're not scrolling off the bottom of the tree
547 if (aRowIndex
>= GetRowCount())
548 return NS_ERROR_ILLEGAL_VALUE
;
550 // Bring it just into view.
551 delta
= 1 + (aRowIndex
-bottomIndex
);
552 mCurrentIndex
+= delta
;
555 // Safe to not go off an event here, since this is coming from the
557 DoInternalPositionChangedSync(up
, delta
);
562 nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines
)
564 int32_t scrollIndex
, visibleRows
;
565 GetIndexOfFirstVisibleRow(&scrollIndex
);
566 GetNumberOfVisibleRows(&visibleRows
);
568 scrollIndex
+= aNumLines
;
573 int32_t numRows
= GetRowCount();
574 int32_t lastPageTopRow
= numRows
- visibleRows
;
575 if (scrollIndex
> lastPageTopRow
)
576 scrollIndex
= lastPageTopRow
;
579 ScrollToIndex(scrollIndex
);
584 // walks the DOM to get the zero-based row index of the content
586 nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement
* aItem
, int32_t* _retval
)
590 nsCOMPtr
<nsIContent
> itemContent(do_QueryInterface(aItem
));
592 FlattenedChildIterator
iter(mContent
);
593 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
594 // we hit a list row, count it
595 if (child
->Tag() == nsGkAtoms::listitem
) {
597 if (child
== itemContent
)
611 nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex
, nsIDOMElement
** aItem
)
617 int32_t itemCount
= 0;
618 FlattenedChildIterator
iter(mContent
);
619 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
620 // we hit a list row, check if it is the one we are looking for
621 if (child
->Tag() == nsGkAtoms::listitem
) {
623 if (itemCount
== aIndex
) {
624 return CallQueryInterface(child
, aItem
);
634 /////////// nsListBoxBodyFrame ///////////////
637 nsListBoxBodyFrame::GetRowCount()
640 ComputeTotalRowCount();
645 nsListBoxBodyFrame::GetFixedRowSize()
650 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::rows
, rows
);
652 return rows
.ToInteger(&dummy
);
654 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::size
, rows
);
657 return rows
.ToInteger(&dummy
);
663 nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight
)
665 if (aRowHeight
> mRowHeight
) {
666 mRowHeight
= aRowHeight
;
668 // signal we need to dirty everything
669 // and we want to be notified after reflow
670 // so we can create or destory rows as needed
671 mRowHeightWasSet
= true;
672 PostReflowCallback();
677 nsListBoxBodyFrame::GetAvailableHeight()
679 nsIScrollableFrame
* scrollFrame
=
680 nsLayoutUtils::GetScrollableFrameFor(this);
682 return scrollFrame
->GetScrollPortRect().height
;
688 nsListBoxBodyFrame::GetYPosition()
694 nsListBoxBodyFrame::ComputeIntrinsicISize(nsBoxLayoutState
& aBoxLayoutState
)
696 if (mStringWidth
!= -1)
699 nscoord largestWidth
= 0;
702 nsCOMPtr
<nsIDOMElement
> firstRowEl
;
703 GetItemAtIndex(index
, getter_AddRefs(firstRowEl
));
704 nsCOMPtr
<nsIContent
> firstRowContent(do_QueryInterface(firstRowEl
));
706 if (firstRowContent
) {
707 nsRefPtr
<nsStyleContext
> styleContext
;
708 nsPresContext
*presContext
= aBoxLayoutState
.PresContext();
709 styleContext
= presContext
->StyleSet()->
710 ResolveStyleFor(firstRowContent
->AsElement(), nullptr);
713 nsMargin
margin(0,0,0,0);
715 if (styleContext
->StylePadding()->GetPadding(margin
))
716 width
+= margin
.LeftRight();
717 width
+= styleContext
->StyleBorder()->GetComputedBorder().LeftRight();
718 if (styleContext
->StyleMargin()->GetMargin(margin
))
719 width
+= margin
.LeftRight();
721 FlattenedChildIterator
iter(mContent
);
722 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
723 if (child
->Tag() == nsGkAtoms::listitem
) {
724 nsRenderingContext
* rendContext
= aBoxLayoutState
.GetRenderingContext();
727 uint32_t textCount
= child
->GetChildCount();
728 for (uint32_t j
= 0; j
< textCount
; ++j
) {
729 nsIContent
* text
= child
->GetChildAt(j
);
730 if (text
&& text
->IsNodeOfType(nsINode::eTEXT
)) {
731 text
->AppendTextTo(value
);
735 nsRefPtr
<nsFontMetrics
> fm
;
736 nsLayoutUtils::GetFontMetricsForStyleContext(styleContext
,
738 rendContext
->SetFont(fm
);
741 nsLayoutUtils::GetStringWidth(this, rendContext
, value
.get(), value
.Length());
744 if (textWidth
> largestWidth
)
745 largestWidth
= textWidth
;
751 mStringWidth
= largestWidth
;
756 nsListBoxBodyFrame::ComputeTotalRowCount()
759 FlattenedChildIterator
iter(mContent
);
760 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
761 if (child
->Tag() == nsGkAtoms::listitem
) {
768 nsListBoxBodyFrame::PostReflowCallback()
770 if (!mReflowCallbackPosted
) {
771 mReflowCallbackPosted
= true;
772 PresContext()->PresShell()->PostReflowCallback(this);
779 nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex
)
781 if (( aRowIndex
< 0 ) || (mRowHeight
== 0))
784 int32_t newIndex
= aRowIndex
;
785 int32_t delta
= mCurrentIndex
> newIndex
? mCurrentIndex
- newIndex
: newIndex
- mCurrentIndex
;
786 bool up
= newIndex
< mCurrentIndex
;
788 // Check to be sure we're not scrolling off the bottom of the tree
789 int32_t lastPageTopRow
= GetRowCount() - (GetAvailableHeight() / mRowHeight
);
790 if (lastPageTopRow
< 0)
793 if (aRowIndex
> lastPageTopRow
)
796 mCurrentIndex
= newIndex
;
798 nsWeakFrame
weak(this);
800 // Since we're going to flush anyway, we need to not do this off an event
801 DoInternalPositionChangedSync(up
, delta
);
803 if (!weak
.IsAlive()) {
807 // This change has to happen immediately.
808 // Flush any pending reflow commands.
809 // XXXbz why, exactly?
810 mContent
->GetComposedDoc()->FlushPendingNotifications(Flush_Layout
);
816 nsListBoxBodyFrame::InternalPositionChangedCallback()
818 nsListScrollSmoother
* smoother
= GetSmoother();
820 if (smoother
->mDelta
== 0)
823 mCurrentIndex
+= smoother
->mDelta
;
825 if (mCurrentIndex
< 0)
828 return DoInternalPositionChangedSync(smoother
->mDelta
< 0,
829 smoother
->mDelta
< 0 ?
830 -smoother
->mDelta
: smoother
->mDelta
);
834 nsListBoxBodyFrame::InternalPositionChanged(bool aUp
, int32_t aDelta
)
836 nsRefPtr
<nsPositionChangedEvent
> ev
=
837 new nsPositionChangedEvent(this, aUp
, aDelta
);
838 nsresult rv
= NS_DispatchToCurrentThread(ev
);
839 if (NS_SUCCEEDED(rv
)) {
840 if (!mPendingPositionChangeEvents
.AppendElement(ev
)) {
841 rv
= NS_ERROR_OUT_OF_MEMORY
;
849 nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp
, int32_t aDelta
)
851 nsWeakFrame
weak(this);
853 // Process all the pending position changes first
854 nsTArray
< nsRefPtr
<nsPositionChangedEvent
> > temp
;
855 temp
.SwapElements(mPendingPositionChangeEvents
);
856 for (uint32_t i
= 0; i
< temp
.Length(); ++i
) {
857 if (weak
.IsAlive()) {
863 if (!weak
.IsAlive()) {
867 return DoInternalPositionChanged(aUp
, aDelta
);
871 nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp
, int32_t aDelta
)
876 nsRefPtr
<nsPresContext
> presContext(PresContext());
877 nsBoxLayoutState
state(presContext
);
879 // begin timing how long it takes to scroll a row
880 PRTime start
= PR_Now();
882 nsWeakFrame
weakThis(this);
883 mContent
->GetComposedDoc()->FlushPendingNotifications(Flush_Layout
);
884 if (!weakThis
.IsAlive()) {
889 nsAutoScriptBlocker scriptBlocker
;
891 int32_t visibleRows
= 0;
893 visibleRows
= GetAvailableHeight()/mRowHeight
;
895 if (aDelta
< visibleRows
) {
896 int32_t loseRows
= aDelta
;
898 // scrolling up, destroy rows from the bottom downwards
899 ReverseDestroyRows(loseRows
);
900 mRowsToPrepend
+= aDelta
;
901 mLinkupFrame
= nullptr;
904 // scrolling down, destroy rows from the top upwards
905 DestroyRows(loseRows
);
910 // We have scrolled so much that all of our current frames will
911 // go off screen, so blow them all away. Weeee!
912 nsIFrame
*currBox
= mFrames
.FirstChild();
913 nsCSSFrameConstructor
* fc
= presContext
->PresShell()->FrameConstructor();
916 nsIFrame
*nextBox
= currBox
->GetNextSibling();
917 RemoveChildFrame(state
, currBox
);
923 // clear frame markers so that CreateRows will re-create
924 mTopFrame
= mBottomFrame
= nullptr;
926 mYPosition
= mCurrentIndex
*mRowHeight
;
928 presContext
->PresShell()->
929 FrameNeedsReflow(this, nsIPresShell::eResize
, NS_FRAME_HAS_DIRTY_CHILDREN
);
931 if (!weakThis
.IsAlive()) {
934 // Flush calls CreateRows
935 // XXXbz there has to be a better way to do this than flushing!
936 presContext
->PresShell()->FlushPendingNotifications(Flush_Layout
);
937 if (!weakThis
.IsAlive()) {
943 VerticalScroll(mYPosition
);
945 PRTime end
= PR_Now();
947 int32_t newTime
= int32_t(end
- start
) / aDelta
;
949 // average old and new
950 mTimePerRow
= (newTime
+ mTimePerRow
)/2;
955 nsListScrollSmoother
*
956 nsListBoxBodyFrame::GetSmoother()
958 if (!mScrollSmoother
) {
959 mScrollSmoother
= new nsListScrollSmoother(this);
960 NS_ASSERTION(mScrollSmoother
, "out of memory");
961 NS_IF_ADDREF(mScrollSmoother
);
964 return mScrollSmoother
;
968 nsListBoxBodyFrame::VerticalScroll(int32_t aPosition
)
970 nsIScrollableFrame
* scrollFrame
971 = nsLayoutUtils::GetScrollableFrameFor(this);
976 nsPoint scrollPosition
= scrollFrame
->GetScrollPosition();
978 nsWeakFrame
weakFrame(this);
979 scrollFrame
->ScrollTo(nsPoint(scrollPosition
.x
, aPosition
),
980 nsIScrollableFrame::INSTANT
);
981 if (!weakFrame
.IsAlive()) {
985 mYPosition
= aPosition
;
988 ////////// frame and box retrieval
991 nsListBoxBodyFrame::GetFirstFrame()
993 mTopFrame
= mFrames
.FirstChild();
998 nsListBoxBodyFrame::GetLastFrame()
1000 return mFrames
.LastChild();
1004 nsListBoxBodyFrame::SupportsOrdinalsInChildren()
1009 ////////// lazy row creation and destruction
1012 nsListBoxBodyFrame::CreateRows()
1014 // Get our client rect.
1016 GetClientRect(clientRect
);
1018 // Get the starting y position and the remaining available
1020 nscoord availableHeight
= GetAvailableHeight();
1022 if (availableHeight
<= 0) {
1023 bool fixed
= (GetFixedRowSize() != -1);
1025 availableHeight
= 10;
1030 // get the first tree box. If there isn't one create one.
1031 bool created
= false;
1032 nsIFrame
* box
= GetFirstItemBox(0, &created
);
1033 nscoord rowHeight
= GetRowHeightAppUnits();
1035 if (created
&& mRowsToPrepend
> 0)
1038 // if the row height is 0 then fail. Wait until someone
1039 // laid out and sets the row height.
1043 availableHeight
-= rowHeight
;
1045 // should we continue? Is the enought height?
1046 if (!ContinueReflow(availableHeight
))
1049 // get the next tree box. Create one if needed.
1050 box
= GetNextItemBox(box
, 0, &created
);
1054 mLinkupFrame
= nullptr;
1058 nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose
)
1060 // We need to destroy frames until our row count has been properly
1061 // reduced. A reflow will then pick up and create the new frames.
1062 nsIFrame
* childFrame
= GetFirstFrame();
1063 nsBoxLayoutState
state(PresContext());
1065 nsCSSFrameConstructor
* fc
= PresContext()->PresShell()->FrameConstructor();
1067 while (childFrame
&& aRowsToLose
> 0) {
1070 nsIFrame
* nextFrame
= childFrame
->GetNextSibling();
1071 RemoveChildFrame(state
, childFrame
);
1073 mTopFrame
= childFrame
= nextFrame
;
1077 PresContext()->PresShell()->
1078 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1079 NS_FRAME_HAS_DIRTY_CHILDREN
);
1083 nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose
)
1085 // We need to destroy frames until our row count has been properly
1086 // reduced. A reflow will then pick up and create the new frames.
1087 nsIFrame
* childFrame
= GetLastFrame();
1088 nsBoxLayoutState
state(PresContext());
1090 nsCSSFrameConstructor
* fc
= PresContext()->PresShell()->FrameConstructor();
1092 while (childFrame
&& aRowsToLose
> 0) {
1095 nsIFrame
* prevFrame
;
1096 prevFrame
= childFrame
->GetPrevSibling();
1097 RemoveChildFrame(state
, childFrame
);
1099 mBottomFrame
= childFrame
= prevFrame
;
1103 PresContext()->PresShell()->
1104 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1105 NS_FRAME_HAS_DIRTY_CHILDREN
);
1109 IsListItemChild(nsListBoxBodyFrame
* aParent
, nsIContent
* aChild
,
1110 nsIFrame
** aChildFrame
)
1112 *aChildFrame
= nullptr;
1113 if (!aChild
->IsXUL() || aChild
->Tag() != nsGkAtoms::listitem
) {
1116 nsIFrame
* existingFrame
= aChild
->GetPrimaryFrame();
1117 if (existingFrame
&& existingFrame
->GetParent() != aParent
) {
1120 *aChildFrame
= existingFrame
;
1125 // Get the nsIFrame for the first visible listitem, and if none exists,
1129 nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset
, bool* aCreated
)
1134 // Clear ourselves out.
1135 mBottomFrame
= mTopFrame
;
1138 return mTopFrame
->IsBoxFrame() ? mTopFrame
: nullptr;
1141 // top frame was cleared out
1142 mTopFrame
= GetFirstFrame();
1143 mBottomFrame
= mTopFrame
;
1145 if (mTopFrame
&& mRowsToPrepend
<= 0) {
1146 return mTopFrame
->IsBoxFrame() ? mTopFrame
: nullptr;
1149 // At this point, we either have no frames at all,
1150 // or the user has scrolled upwards, leaving frames
1151 // to be created at the top. Let's determine which
1152 // content needs a new frame first.
1154 nsCOMPtr
<nsIContent
> startContent
;
1155 if (mTopFrame
&& mRowsToPrepend
> 0) {
1156 // We need to insert rows before the top frame
1157 nsIContent
* topContent
= mTopFrame
->GetContent();
1158 nsIContent
* topParent
= topContent
->GetParent();
1159 int32_t contentIndex
= topParent
->IndexOf(topContent
);
1160 contentIndex
-= aOffset
;
1161 if (contentIndex
< 0)
1163 startContent
= topParent
->GetChildAt(contentIndex
- mRowsToPrepend
);
1165 // This will be the first item frame we create. Use the content
1166 // at the current index, which is the first index scrolled into view
1167 GetListItemContentAt(mCurrentIndex
+aOffset
, getter_AddRefs(startContent
));
1171 nsIFrame
* existingFrame
;
1172 if (!IsListItemChild(this, startContent
, &existingFrame
)) {
1173 return GetFirstItemBox(++aOffset
, aCreated
);
1175 if (existingFrame
) {
1176 return existingFrame
->IsBoxFrame() ? existingFrame
: nullptr;
1179 // Either append the new frame, or prepend it (at index 0)
1180 // XXX check here if frame was even created, it may not have been if
1181 // display: none was on listitem content
1182 bool isAppend
= mRowsToPrepend
<= 0;
1184 nsPresContext
* presContext
= PresContext();
1185 nsCSSFrameConstructor
* fc
= presContext
->PresShell()->FrameConstructor();
1186 nsIFrame
* topFrame
= nullptr;
1187 fc
->CreateListBoxContent(presContext
, this, nullptr, startContent
,
1188 &topFrame
, isAppend
, false, nullptr);
1189 mTopFrame
= topFrame
;
1194 mBottomFrame
= mTopFrame
;
1196 return mTopFrame
->IsBoxFrame() ? mTopFrame
: nullptr;
1198 return GetFirstItemBox(++aOffset
, 0);
1205 // Get the nsIFrame for the next visible listitem after aBox, and if none
1206 // exists, create one.
1209 nsListBoxBodyFrame::GetNextItemBox(nsIFrame
* aBox
, int32_t aOffset
,
1215 nsIFrame
* result
= aBox
->GetNextSibling();
1217 if (!result
|| result
== mLinkupFrame
|| mRowsToPrepend
> 0) {
1218 // No result found. See if there's a content node that wants a frame.
1219 nsIContent
* prevContent
= aBox
->GetContent();
1220 nsIContent
* parentContent
= prevContent
->GetParent();
1222 int32_t i
= parentContent
->IndexOf(prevContent
);
1224 uint32_t childCount
= parentContent
->GetChildCount();
1225 if (((uint32_t)i
+ aOffset
+ 1) < childCount
) {
1226 // There is a content node that wants a frame.
1227 nsIContent
*nextContent
= parentContent
->GetChildAt(i
+ aOffset
+ 1);
1229 nsIFrame
* existingFrame
;
1230 if (!IsListItemChild(this, nextContent
, &existingFrame
)) {
1231 return GetNextItemBox(aBox
, ++aOffset
, aCreated
);
1233 if (!existingFrame
) {
1234 // Either append the new frame, or insert it after the current frame
1235 bool isAppend
= result
!= mLinkupFrame
&& mRowsToPrepend
<= 0;
1236 nsIFrame
* prevFrame
= isAppend
? nullptr : aBox
;
1238 nsPresContext
* presContext
= PresContext();
1239 nsCSSFrameConstructor
* fc
= presContext
->PresShell()->FrameConstructor();
1240 fc
->CreateListBoxContent(presContext
, this, prevFrame
, nextContent
,
1241 &result
, isAppend
, false, nullptr);
1247 return GetNextItemBox(aBox
, ++aOffset
, aCreated
);
1249 result
= existingFrame
;
1252 mLinkupFrame
= nullptr;
1259 mBottomFrame
= result
;
1261 NS_ASSERTION(!result
->IsBoxFrame() || result
->GetParent() == this,
1262 "returning frame that is not in childlist");
1264 return result
->IsBoxFrame() ? result
: nullptr;
1268 nsListBoxBodyFrame::ContinueReflow(nscoord height
)
1270 #ifdef ACCESSIBILITY
1271 if (nsIPresShell::IsAccessibilityActive()) {
1272 // Create all the frames at once so screen readers and
1273 // onscreen keyboards can see the full list right away
1279 nsIFrame
* lastChild
= GetLastFrame();
1280 nsIFrame
* startingPoint
= mBottomFrame
;
1281 if (startingPoint
== nullptr) {
1282 // We just want to delete everything but the first item.
1283 startingPoint
= GetFirstFrame();
1286 if (lastChild
!= startingPoint
) {
1287 // We have some hangers on (probably caused by shrinking the size of the window).
1289 nsIFrame
* currFrame
= startingPoint
->GetNextSibling();
1290 nsBoxLayoutState
state(PresContext());
1292 nsCSSFrameConstructor
* fc
=
1293 PresContext()->PresShell()->FrameConstructor();
1296 nsIFrame
* nextFrame
= currFrame
->GetNextSibling();
1297 RemoveChildFrame(state
, currFrame
);
1298 currFrame
= nextFrame
;
1302 PresContext()->PresShell()->
1303 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1304 NS_FRAME_HAS_DIRTY_CHILDREN
);
1313 nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList
& aFrameList
)
1315 // append them after
1316 nsBoxLayoutState
state(PresContext());
1317 const nsFrameList::Slice
& newFrames
= mFrames
.AppendFrames(nullptr, aFrameList
);
1319 mLayoutManager
->ChildrenAppended(this, state
, newFrames
);
1320 PresContext()->PresShell()->
1321 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1322 NS_FRAME_HAS_DIRTY_CHILDREN
);
1328 nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame
* aPrevFrame
,
1329 nsFrameList
& aFrameList
)
1331 // insert the frames to our info list
1332 nsBoxLayoutState
state(PresContext());
1333 const nsFrameList::Slice
& newFrames
=
1334 mFrames
.InsertFrames(nullptr, aPrevFrame
, aFrameList
);
1336 mLayoutManager
->ChildrenInserted(this, state
, aPrevFrame
, newFrames
);
1337 PresContext()->PresShell()->
1338 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1339 NS_FRAME_HAS_DIRTY_CHILDREN
);
1345 // Called by nsCSSFrameConstructor when a new listitem content is inserted.
1348 nsListBoxBodyFrame::OnContentInserted(nsPresContext
* aPresContext
, nsIContent
* aChildContent
)
1353 // The RDF content builder will build content nodes such that they are all
1354 // ready when OnContentInserted is first called, meaning the first call
1355 // to CreateRows will create all the frames, but OnContentInserted will
1356 // still be called again for each content node - so we need to make sure
1357 // that the frame for each content node hasn't already been created.
1358 nsIFrame
* childFrame
= aChildContent
->GetPrimaryFrame();
1362 int32_t siblingIndex
;
1363 nsCOMPtr
<nsIContent
> nextSiblingContent
;
1364 GetListItemNextSibling(aChildContent
, getter_AddRefs(nextSiblingContent
), siblingIndex
);
1366 // if we're inserting our item before the first visible content,
1367 // then we need to shift all rows down by one
1368 if (siblingIndex
>= 0 && siblingIndex
-1 <= mCurrentIndex
) {
1369 mTopFrame
= nullptr;
1371 } else if (nextSiblingContent
) {
1372 // we may be inserting before a frame that is on screen
1373 nsIFrame
* nextSiblingFrame
= nextSiblingContent
->GetPrimaryFrame();
1374 mLinkupFrame
= nextSiblingFrame
;
1378 PresContext()->PresShell()->
1379 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1380 NS_FRAME_HAS_DIRTY_CHILDREN
);
1384 // Called by nsCSSFrameConstructor when listitem content is removed.
1387 nsListBoxBodyFrame::OnContentRemoved(nsPresContext
* aPresContext
,
1388 nsIContent
* aContainer
,
1389 nsIFrame
* aChildFrame
,
1390 nsIContent
* aOldNextSibling
)
1392 NS_ASSERTION(!aChildFrame
|| aChildFrame
->GetParent() == this,
1393 "Removing frame that's not our child... Not good");
1400 // The row we are removing is out of view, so we need to try to
1401 // determine the index of its next sibling.
1402 int32_t siblingIndex
= -1;
1403 if (aOldNextSibling
) {
1404 nsCOMPtr
<nsIContent
> nextSiblingContent
;
1405 GetListItemNextSibling(aOldNextSibling
,
1406 getter_AddRefs(nextSiblingContent
),
1410 // if the row being removed is off-screen and above the top frame, we need to
1411 // adjust our top index and tell the scrollbar to shift up one row.
1412 if (siblingIndex
>= 0 && siblingIndex
-1 < mCurrentIndex
) {
1413 NS_PRECONDITION(mCurrentIndex
> 0, "mCurrentIndex > 0");
1415 mYPosition
= mCurrentIndex
*mRowHeight
;
1416 nsWeakFrame
weakChildFrame(aChildFrame
);
1417 VerticalScroll(mYPosition
);
1418 if (!weakChildFrame
.IsAlive()) {
1422 } else if (mCurrentIndex
> 0) {
1423 // At this point, we know we have a scrollbar, and we need to know
1424 // if we are scrolled to the last row. In this case, the behavior
1425 // of the scrollbar is to stay locked to the bottom. Since we are
1426 // removing visible content, the first visible row will have to move
1427 // down by one, and we will have to insert a new frame at the top.
1429 // if the last content node has a frame, we are scrolled to the bottom
1430 nsIContent
* lastChild
= nullptr;
1431 FlattenedChildIterator
iter(mContent
);
1432 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
1437 nsIFrame
* lastChildFrame
= lastChild
->GetPrimaryFrame();
1439 if (lastChildFrame
) {
1440 mTopFrame
= nullptr;
1443 mYPosition
= mCurrentIndex
*mRowHeight
;
1444 nsWeakFrame
weakChildFrame(aChildFrame
);
1445 VerticalScroll(mYPosition
);
1446 if (!weakChildFrame
.IsAlive()) {
1454 // if we're removing the top row, the new top row is the next row
1455 if (mTopFrame
&& mTopFrame
== aChildFrame
)
1456 mTopFrame
= mTopFrame
->GetNextSibling();
1458 // Go ahead and delete the frame.
1459 nsBoxLayoutState
state(aPresContext
);
1461 RemoveChildFrame(state
, aChildFrame
);
1464 PresContext()->PresShell()->
1465 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
1466 NS_FRAME_HAS_DIRTY_CHILDREN
);
1470 nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex
, nsIContent
** aContent
)
1472 *aContent
= nullptr;
1474 int32_t itemsFound
= 0;
1475 FlattenedChildIterator
iter(mContent
);
1476 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
1477 if (child
->Tag() == nsGkAtoms::listitem
) {
1479 if (itemsFound
-1 == aIndex
) {
1481 NS_IF_ADDREF(*aContent
);
1489 nsListBoxBodyFrame::GetListItemNextSibling(nsIContent
* aListItem
, nsIContent
** aContent
, int32_t& aSiblingIndex
)
1491 *aContent
= nullptr;
1493 nsIContent
*prevKid
= nullptr;
1494 FlattenedChildIterator
iter(mContent
);
1495 for (nsIContent
* child
= iter
.GetNextChild(); child
; child
= iter
.GetNextChild()) {
1496 if (child
->Tag() == nsGkAtoms::listitem
) {
1498 if (prevKid
== aListItem
) {
1500 NS_IF_ADDREF(*aContent
);
1507 aSiblingIndex
= -1; // no match, so there is no next sibling
1511 nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState
&aState
,
1514 MOZ_ASSERT(mFrames
.ContainsFrame(aFrame
));
1515 MOZ_ASSERT(aFrame
!= GetContentInsertionFrame());
1517 #ifdef ACCESSIBILITY
1518 nsAccessibilityService
* accService
= nsIPresShell::AccService();
1520 nsIContent
* content
= aFrame
->GetContent();
1521 accService
->ContentRemoved(PresContext()->PresShell(), content
->GetParent(),
1526 mFrames
.RemoveFrame(aFrame
);
1528 mLayoutManager
->ChildrenRemoved(this, aState
, aFrame
);
1532 // Creation Routines ///////////////////////////////////////////////////////////////////////
1534 already_AddRefed
<nsBoxLayout
> NS_NewListBoxLayout();
1537 NS_NewListBoxBodyFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
1539 nsCOMPtr
<nsBoxLayout
> layout
= NS_NewListBoxLayout();
1540 return new (aPresShell
) nsListBoxBodyFrame(aPresShell
, aContext
, layout
);
1543 NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame
)