summaryrefslogtreecommitdiff
path: root/layout/xul/nsListBoxBodyFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul/nsListBoxBodyFrame.cpp')
-rw-r--r--layout/xul/nsListBoxBodyFrame.cpp1535
1 files changed, 1535 insertions, 0 deletions
diff --git a/layout/xul/nsListBoxBodyFrame.cpp b/layout/xul/nsListBoxBodyFrame.cpp
new file mode 100644
index 0000000000..8c4a5e2fdd
--- /dev/null
+++ b/layout/xul/nsListBoxBodyFrame.cpp
@@ -0,0 +1,1535 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsListBoxBodyFrame.h"
+
+#include "nsListBoxLayout.h"
+
+#include "mozilla/MathAlgorithms.h"
+#include "nsCOMPtr.h"
+#include "nsGridRowGroupLayout.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsIScrollableFrame.h"
+#include "nsScrollbarFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsStyleContext.h"
+#include "nsFontMetrics.h"
+#include "nsITimer.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsPIBoxObject.h"
+#include "nsLayoutUtils.h"
+#include "nsPIListBoxObject.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+#include "nsRenderingContext.h"
+#include "prtime.h"
+#include <algorithm>
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/////////////// nsListScrollSmoother //////////////////
+
+/* A mediator used to smooth out scrolling. It works by seeing if
+ * we have time to scroll the amount of rows requested. This is determined
+ * by measuring how long it takes to scroll a row. If we can scroll the
+ * rows in time we do so. If not we start a timer and skip the request. We
+ * do this until the timer finally first because the user has stopped moving
+ * the mouse. Then do all the queued requests in on shot.
+ */
+
+// the longest amount of time that can go by before the use
+// notices it as a delay.
+#define USER_TIME_THRESHOLD 150000
+
+// how long it takes to layout a single row initial value.
+// we will time this after we scroll a few rows.
+#define TIME_PER_ROW_INITAL 50000
+
+// if we decide we can't layout the rows in the amount of time. How long
+// do we wait before checking again?
+#define SMOOTH_INTERVAL 100
+
+class nsListScrollSmoother final : public nsITimerCallback
+{
+private:
+ virtual ~nsListScrollSmoother();
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit nsListScrollSmoother(nsListBoxBodyFrame* aOuter);
+
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ void Start();
+ void Stop();
+ bool IsRunning();
+
+ nsCOMPtr<nsITimer> mRepeatTimer;
+ int32_t mDelta;
+ nsListBoxBodyFrame* mOuter;
+};
+
+nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter)
+{
+ mDelta = 0;
+ mOuter = aOuter;
+}
+
+nsListScrollSmoother::~nsListScrollSmoother()
+{
+ Stop();
+}
+
+NS_IMETHODIMP
+nsListScrollSmoother::Notify(nsITimer *timer)
+{
+ Stop();
+
+ NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
+ if (!mOuter) return NS_OK;
+
+ // actually do some work.
+ mOuter->InternalPositionChangedCallback();
+ return NS_OK;
+}
+
+bool
+nsListScrollSmoother::IsRunning()
+{
+ return mRepeatTimer ? true : false;
+}
+
+void
+nsListScrollSmoother::Start()
+{
+ Stop();
+ mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+nsListScrollSmoother::Stop()
+{
+ if ( mRepeatTimer ) {
+ mRepeatTimer->Cancel();
+ mRepeatTimer = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsListScrollSmoother, nsITimerCallback)
+
+/////////////// nsListBoxBodyFrame //////////////////
+
+nsListBoxBodyFrame::nsListBoxBodyFrame(nsStyleContext* aContext,
+ nsBoxLayout* aLayoutManager)
+ : nsBoxFrame(aContext, false, aLayoutManager),
+ mTopFrame(nullptr),
+ mBottomFrame(nullptr),
+ mLinkupFrame(nullptr),
+ mScrollSmoother(nullptr),
+ mRowsToPrepend(0),
+ mRowCount(-1),
+ mRowHeight(0),
+ mAvailableHeight(0),
+ mStringWidth(-1),
+ mCurrentIndex(0),
+ mOldIndex(0),
+ mYPosition(0),
+ mTimePerRow(TIME_PER_ROW_INITAL),
+ mRowHeightWasSet(false),
+ mScrolling(false),
+ mAdjustScroll(false),
+ mReflowCallbackPosted(false)
+{
+}
+
+nsListBoxBodyFrame::~nsListBoxBodyFrame()
+{
+ NS_IF_RELEASE(mScrollSmoother);
+
+#if USE_TIMER_TO_DELAY_SCROLLING
+ StopScrollTracking();
+ mAutoScrollTimer = nullptr;
+#endif
+
+}
+
+NS_QUERYFRAME_HEAD(nsListBoxBodyFrame)
+ NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+ NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
+
+////////// nsIFrame /////////////////
+
+void
+nsListBoxBodyFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+ // Don't call nsLayoutUtils::GetScrollableFrameFor since we are not its
+ // scrollframe child yet.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(aParent);
+ if (scrollFrame) {
+ nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true);
+ nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar);
+ if (scrollbarFrame) {
+ scrollbarFrame->SetScrollbarMediatorContent(GetContent());
+ }
+ }
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+ mRowHeight = fm->MaxHeight();
+}
+
+void
+nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // make sure we cancel any posted callbacks.
+ if (mReflowCallbackPosted)
+ PresContext()->PresShell()->CancelReflowCallback(this);
+
+ // Revoke any pending position changed events
+ for (uint32_t i = 0; i < mPendingPositionChangeEvents.Length(); ++i) {
+ mPendingPositionChangeEvents[i]->Revoke();
+ }
+
+ // Make sure we tell our listbox's box object we're being destroyed.
+ if (mBoxObject) {
+ mBoxObject->ClearCachedValues();
+ }
+
+ nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = NS_OK;
+
+ if (aAttribute == nsGkAtoms::rows) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ else
+ rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+
+ return rv;
+
+}
+
+/* virtual */ void
+nsListBoxBodyFrame::MarkIntrinsicISizesDirty()
+{
+ mStringWidth = -1;
+ nsBoxFrame::MarkIntrinsicISizesDirty();
+}
+
+/////////// nsBox ///////////////
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mScrolling)
+ aBoxLayoutState.SetPaintingDisabled(true);
+
+ nsresult rv = nsBoxFrame::DoXULLayout(aBoxLayoutState);
+
+ // determine the real height for the scrollable area from the total number
+ // of rows, since non-visible rows don't yet have frames
+ nsRect rect(nsPoint(0, 0), GetSize());
+ nsOverflowAreas overflow(rect, rect);
+ if (mLayoutManager) {
+ nsIFrame* childFrame = mFrames.FirstChild();
+ while (childFrame) {
+ ConsiderChildOverflow(overflow, childFrame);
+ childFrame = childFrame->GetNextSibling();
+ }
+
+ nsSize prefSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState);
+ NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
+ nsRect& o = overflow.Overflow(otype);
+ o.height = std::max(o.height, prefSize.height);
+ }
+ }
+ FinishAndStoreOverflow(overflow, GetSize());
+
+ if (mScrolling)
+ aBoxLayoutState.SetPaintingDisabled(false);
+
+ // if we are scrolled and the row height changed
+ // make sure we are scrolled to a correct index.
+ if (mAdjustScroll)
+ PostReflowCallback();
+
+ return rv;
+}
+
+nsSize
+nsListBoxBodyFrame::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize result(0, 0);
+ if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None,
+ nsGkAtoms::sizemode)) {
+ result = GetXULPrefSize(aBoxLayoutState);
+ result.height = 0;
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame &&
+ scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
+ nsMargin scrollbars =
+ scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
+ result.width += scrollbars.left + scrollbars.right;
+ }
+ }
+ return result;
+}
+
+nsSize
+nsListBoxBodyFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState)
+{
+ nsSize pref = nsBoxFrame::GetXULPrefSize(aBoxLayoutState);
+
+ int32_t size = GetFixedRowSize();
+ if (size > -1)
+ pref.height = size*GetRowHeightAppUnits();
+
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame &&
+ scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
+ nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
+ pref.width += scrollbars.left + scrollbars.right;
+ }
+ return pref;
+}
+
+///////////// nsIScrollbarMediator ///////////////
+
+void
+nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToPage(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToWhole(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
+ nsIScrollbarMediator::ScrollSnapMode aSnap)
+{
+ // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
+ MOZ_ASSERT(aScrollbar != nullptr);
+ aScrollbar->SetIncrementToLine(aDirection);
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+void
+nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
+{
+ nsWeakFrame weakFrame(this);
+ int32_t newPos = aScrollbar->MoveToNewPosition();
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ UpdateIndex(newPos);
+}
+
+int32_t
+nsListBoxBodyFrame::ToRowIndex(nscoord aPos) const
+{
+ return NS_roundf(float(std::max(aPos, 0)) / mRowHeight);
+}
+
+void
+nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
+ nscoord aOldPos,
+ nscoord aNewPos)
+{
+ if (mScrolling || mRowHeight == 0)
+ return;
+
+ int32_t newIndex = ToRowIndex(aNewPos);
+ if (newIndex == mCurrentIndex) {
+ return;
+ }
+ int32_t rowDelta = newIndex - mCurrentIndex;
+
+ nsListScrollSmoother* smoother = GetSmoother();
+
+ // if we can't scroll the rows in time then start a timer. We will eat
+ // events until the user stops moving and the timer stops.
+ if (smoother->IsRunning() || Abs(rowDelta)*mTimePerRow > USER_TIME_THRESHOLD) {
+
+ smoother->Stop();
+
+ smoother->mDelta = rowDelta;
+
+ smoother->Start();
+
+ return;
+ }
+
+ smoother->Stop();
+
+ mCurrentIndex = newIndex;
+ smoother->mDelta = 0;
+
+ if (mCurrentIndex < 0) {
+ mCurrentIndex = 0;
+ return;
+ }
+ InternalPositionChanged(rowDelta < 0, Abs(rowDelta));
+}
+
+void
+nsListBoxBodyFrame::VisibilityChanged(bool aVisible)
+{
+ if (mRowHeight == 0)
+ return;
+
+ int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
+ if (lastPageTopRow < 0)
+ lastPageTopRow = 0;
+ int32_t delta = mCurrentIndex - lastPageTopRow;
+ if (delta > 0) {
+ mCurrentIndex = lastPageTopRow;
+ InternalPositionChanged(true, delta);
+ }
+}
+
+nsIFrame*
+nsListBoxBodyFrame::GetScrollbarBox(bool aVertical)
+{
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
+ return scrollFrame ? scrollFrame->GetScrollbarBox(true) : nullptr;
+}
+
+void
+nsListBoxBodyFrame::UpdateIndex(int32_t aNewPos)
+{
+ int32_t newIndex = ToRowIndex(nsPresContext::CSSPixelsToAppUnits(aNewPos));
+ if (newIndex == mCurrentIndex) {
+ return;
+ }
+ bool up = newIndex < mCurrentIndex;
+ int32_t indexDelta = Abs(newIndex - mCurrentIndex);
+ mCurrentIndex = newIndex;
+ InternalPositionChanged(up, indexDelta);
+}
+
+///////////// nsIReflowCallback ///////////////
+
+bool
+nsListBoxBodyFrame::ReflowFinished()
+{
+ nsAutoScriptBlocker scriptBlocker;
+ // now create or destroy any rows as needed
+ CreateRows();
+
+ // keep scrollbar in sync
+ if (mAdjustScroll) {
+ VerticalScroll(mYPosition);
+ mAdjustScroll = false;
+ }
+
+ // if the row height changed then mark everything as a style change.
+ // That will dirty the entire listbox
+ if (mRowHeightWasSet) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ int32_t pos = mCurrentIndex * mRowHeight;
+ if (mYPosition != pos)
+ mAdjustScroll = true;
+ mRowHeightWasSet = false;
+ }
+
+ mReflowCallbackPosted = false;
+ return true;
+}
+
+void
+nsListBoxBodyFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackPosted = false;
+}
+
+///////// ListBoxObject ///////////////
+
+int32_t
+nsListBoxBodyFrame::GetNumberOfVisibleRows()
+{
+ return mRowHeight ? GetAvailableHeight() / mRowHeight : 0;
+}
+
+int32_t
+nsListBoxBodyFrame::GetIndexOfFirstVisibleRow()
+{
+ return mCurrentIndex;
+}
+
+nsresult
+nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex)
+{
+ if (aRowIndex < 0)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ int32_t rows = 0;
+ if (mRowHeight)
+ rows = GetAvailableHeight()/mRowHeight;
+ if (rows <= 0)
+ rows = 1;
+ int32_t bottomIndex = mCurrentIndex + rows;
+
+ // if row is visible, ignore
+ if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
+ return NS_OK;
+
+ int32_t delta;
+
+ bool up = aRowIndex < mCurrentIndex;
+ if (up) {
+ delta = mCurrentIndex - aRowIndex;
+ mCurrentIndex = aRowIndex;
+ }
+ else {
+ // Check to be sure we're not scrolling off the bottom of the tree
+ if (aRowIndex >= GetRowCount())
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ // Bring it just into view.
+ delta = 1 + (aRowIndex-bottomIndex);
+ mCurrentIndex += delta;
+ }
+
+ // Safe to not go off an event here, since this is coming from the
+ // box object.
+ DoInternalPositionChangedSync(up, delta);
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines)
+{
+ int32_t scrollIndex = GetIndexOfFirstVisibleRow(),
+ visibleRows = GetNumberOfVisibleRows();
+
+ scrollIndex += aNumLines;
+
+ if (scrollIndex < 0)
+ scrollIndex = 0;
+ else {
+ int32_t numRows = GetRowCount();
+ int32_t lastPageTopRow = numRows - visibleRows;
+ if (scrollIndex > lastPageTopRow)
+ scrollIndex = lastPageTopRow;
+ }
+
+ ScrollToIndex(scrollIndex);
+
+ return NS_OK;
+}
+
+// walks the DOM to get the zero-based row index of the content
+nsresult
+nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval)
+{
+ if (aItem) {
+ *_retval = 0;
+ nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem));
+
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ // we hit a list row, count it
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ // is this it?
+ if (child == itemContent)
+ return NS_OK;
+
+ ++(*_retval);
+ }
+ }
+ }
+
+ // not found
+ *_retval = -1;
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex, nsIDOMElement** aItem)
+{
+ *aItem = nullptr;
+ if (aIndex < 0)
+ return NS_OK;
+
+ int32_t itemCount = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ // we hit a list row, check if it is the one we are looking for
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ // is this it?
+ if (itemCount == aIndex) {
+ return CallQueryInterface(child, aItem);
+ }
+ ++itemCount;
+ }
+ }
+
+ // not found
+ return NS_OK;
+}
+
+/////////// nsListBoxBodyFrame ///////////////
+
+int32_t
+nsListBoxBodyFrame::GetRowCount()
+{
+ if (mRowCount < 0)
+ ComputeTotalRowCount();
+ return mRowCount;
+}
+
+int32_t
+nsListBoxBodyFrame::GetFixedRowSize()
+{
+ nsresult dummy;
+
+ nsAutoString rows;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
+ if (!rows.IsEmpty())
+ return rows.ToInteger(&dummy);
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows);
+
+ if (!rows.IsEmpty())
+ return rows.ToInteger(&dummy);
+
+ return -1;
+}
+
+void
+nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight)
+{
+ if (aRowHeight > mRowHeight) {
+ mRowHeight = aRowHeight;
+
+ // signal we need to dirty everything
+ // and we want to be notified after reflow
+ // so we can create or destory rows as needed
+ mRowHeightWasSet = true;
+ PostReflowCallback();
+ }
+}
+
+nscoord
+nsListBoxBodyFrame::GetAvailableHeight()
+{
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetScrollableFrameFor(this);
+ if (scrollFrame) {
+ return scrollFrame->GetScrollPortRect().height;
+ }
+ return 0;
+}
+
+nscoord
+nsListBoxBodyFrame::GetYPosition()
+{
+ return mYPosition;
+}
+
+nscoord
+nsListBoxBodyFrame::ComputeIntrinsicISize(nsBoxLayoutState& aBoxLayoutState)
+{
+ if (mStringWidth != -1)
+ return mStringWidth;
+
+ nscoord largestWidth = 0;
+
+ int32_t index = 0;
+ nsCOMPtr<nsIDOMElement> firstRowEl;
+ GetItemAtIndex(index, getter_AddRefs(firstRowEl));
+ nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
+
+ if (firstRowContent) {
+ RefPtr<nsStyleContext> styleContext;
+ nsPresContext *presContext = aBoxLayoutState.PresContext();
+ styleContext = presContext->StyleSet()->
+ ResolveStyleFor(firstRowContent->AsElement(), nullptr);
+
+ nscoord width = 0;
+ nsMargin margin(0,0,0,0);
+
+ if (styleContext->StylePadding()->GetPadding(margin))
+ width += margin.LeftRight();
+ width += styleContext->StyleBorder()->GetComputedBorder().LeftRight();
+ if (styleContext->StyleMargin()->GetMargin(margin))
+ width += margin.LeftRight();
+
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
+ if (rendContext) {
+ nsAutoString value;
+ uint32_t textCount = child->GetChildCount();
+ for (uint32_t j = 0; j < textCount; ++j) {
+ nsIContent* text = child->GetChildAt(j);
+ if (text && text->IsNodeOfType(nsINode::eTEXT)) {
+ text->AppendTextTo(value);
+ }
+ }
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForStyleContext(styleContext);
+
+ nscoord textWidth =
+ nsLayoutUtils::AppUnitWidthOfStringBidi(value, this, *fm,
+ *rendContext);
+ textWidth += width;
+
+ if (textWidth > largestWidth)
+ largestWidth = textWidth;
+ }
+ }
+ }
+ }
+
+ mStringWidth = largestWidth;
+ return mStringWidth;
+}
+
+void
+nsListBoxBodyFrame::ComputeTotalRowCount()
+{
+ mRowCount = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++mRowCount;
+ }
+ }
+}
+
+void
+nsListBoxBodyFrame::PostReflowCallback()
+{
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresContext()->PresShell()->PostReflowCallback(this);
+ }
+}
+
+////////// scrolling
+
+nsresult
+nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex)
+{
+ if (( aRowIndex < 0 ) || (mRowHeight == 0))
+ return NS_OK;
+
+ int32_t newIndex = aRowIndex;
+ int32_t delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
+ bool up = newIndex < mCurrentIndex;
+
+ // Check to be sure we're not scrolling off the bottom of the tree
+ int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
+ if (lastPageTopRow < 0)
+ lastPageTopRow = 0;
+
+ if (aRowIndex > lastPageTopRow)
+ return NS_OK;
+
+ mCurrentIndex = newIndex;
+
+ nsWeakFrame weak(this);
+
+ // Since we're going to flush anyway, we need to not do this off an event
+ DoInternalPositionChangedSync(up, delta);
+
+ if (!weak.IsAlive()) {
+ return NS_OK;
+ }
+
+ // This change has to happen immediately.
+ // Flush any pending reflow commands.
+ // XXXbz why, exactly?
+ mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
+
+ return NS_OK;
+}
+
+nsresult
+nsListBoxBodyFrame::InternalPositionChangedCallback()
+{
+ nsListScrollSmoother* smoother = GetSmoother();
+
+ if (smoother->mDelta == 0)
+ return NS_OK;
+
+ mCurrentIndex += smoother->mDelta;
+
+ if (mCurrentIndex < 0)
+ mCurrentIndex = 0;
+
+ return DoInternalPositionChangedSync(smoother->mDelta < 0,
+ smoother->mDelta < 0 ?
+ -smoother->mDelta : smoother->mDelta);
+}
+
+nsresult
+nsListBoxBodyFrame::InternalPositionChanged(bool aUp, int32_t aDelta)
+{
+ RefPtr<nsPositionChangedEvent> ev =
+ new nsPositionChangedEvent(this, aUp, aDelta);
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mPendingPositionChangeEvents.AppendElement(ev)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ ev->Revoke();
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, int32_t aDelta)
+{
+ nsWeakFrame weak(this);
+
+ // Process all the pending position changes first
+ nsTArray< RefPtr<nsPositionChangedEvent> > temp;
+ temp.SwapElements(mPendingPositionChangeEvents);
+ for (uint32_t i = 0; i < temp.Length(); ++i) {
+ if (weak.IsAlive()) {
+ temp[i]->Run();
+ }
+ temp[i]->Revoke();
+ }
+
+ if (!weak.IsAlive()) {
+ return NS_OK;
+ }
+
+ return DoInternalPositionChanged(aUp, aDelta);
+}
+
+nsresult
+nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, int32_t aDelta)
+{
+ if (aDelta == 0)
+ return NS_OK;
+
+ RefPtr<nsPresContext> presContext(PresContext());
+ nsBoxLayoutState state(presContext);
+
+ // begin timing how long it takes to scroll a row
+ PRTime start = PR_Now();
+
+ nsWeakFrame weakThis(this);
+ mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ int32_t visibleRows = 0;
+ if (mRowHeight)
+ visibleRows = GetAvailableHeight()/mRowHeight;
+
+ if (aDelta < visibleRows) {
+ int32_t loseRows = aDelta;
+ if (aUp) {
+ // scrolling up, destroy rows from the bottom downwards
+ ReverseDestroyRows(loseRows);
+ mRowsToPrepend += aDelta;
+ mLinkupFrame = nullptr;
+ }
+ else {
+ // scrolling down, destroy rows from the top upwards
+ DestroyRows(loseRows);
+ mRowsToPrepend = 0;
+ }
+ }
+ else {
+ // We have scrolled so much that all of our current frames will
+ // go off screen, so blow them all away. Weeee!
+ nsIFrame *currBox = mFrames.FirstChild();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (currBox) {
+ nsIFrame *nextBox = currBox->GetNextSibling();
+ RemoveChildFrame(state, currBox);
+ currBox = nextBox;
+ }
+ fc->EndUpdate();
+ }
+
+ // clear frame markers so that CreateRows will re-create
+ mTopFrame = mBottomFrame = nullptr;
+
+ mYPosition = mCurrentIndex*mRowHeight;
+ mScrolling = true;
+ presContext->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+ // Flush calls CreateRows
+ // XXXbz there has to be a better way to do this than flushing!
+ presContext->PresShell()->FlushPendingNotifications(Flush_Layout);
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+
+ mScrolling = false;
+
+ VerticalScroll(mYPosition);
+
+ PRTime end = PR_Now();
+
+ int32_t newTime = int32_t(end - start) / aDelta;
+
+ // average old and new
+ mTimePerRow = (newTime + mTimePerRow)/2;
+
+ return NS_OK;
+}
+
+nsListScrollSmoother*
+nsListBoxBodyFrame::GetSmoother()
+{
+ if (!mScrollSmoother) {
+ mScrollSmoother = new nsListScrollSmoother(this);
+ NS_ASSERTION(mScrollSmoother, "out of memory");
+ NS_IF_ADDREF(mScrollSmoother);
+ }
+
+ return mScrollSmoother;
+}
+
+void
+nsListBoxBodyFrame::VerticalScroll(int32_t aPosition)
+{
+ nsIScrollableFrame* scrollFrame
+ = nsLayoutUtils::GetScrollableFrameFor(this);
+ if (!scrollFrame) {
+ return;
+ }
+
+ nsPoint scrollPosition = scrollFrame->GetScrollPosition();
+
+ nsWeakFrame weakFrame(this);
+ scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition),
+ nsIScrollableFrame::INSTANT);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ mYPosition = aPosition;
+}
+
+////////// frame and box retrieval
+
+nsIFrame*
+nsListBoxBodyFrame::GetFirstFrame()
+{
+ mTopFrame = mFrames.FirstChild();
+ return mTopFrame;
+}
+
+nsIFrame*
+nsListBoxBodyFrame::GetLastFrame()
+{
+ return mFrames.LastChild();
+}
+
+bool
+nsListBoxBodyFrame::SupportsOrdinalsInChildren()
+{
+ return false;
+}
+
+////////// lazy row creation and destruction
+
+void
+nsListBoxBodyFrame::CreateRows()
+{
+ // Get our client rect.
+ nsRect clientRect;
+ GetXULClientRect(clientRect);
+
+ // Get the starting y position and the remaining available
+ // height.
+ nscoord availableHeight = GetAvailableHeight();
+
+ if (availableHeight <= 0) {
+ bool fixed = (GetFixedRowSize() != -1);
+ if (fixed)
+ availableHeight = 10;
+ else
+ return;
+ }
+
+ // get the first tree box. If there isn't one create one.
+ bool created = false;
+ nsIFrame* box = GetFirstItemBox(0, &created);
+ nscoord rowHeight = GetRowHeightAppUnits();
+ while (box) {
+ if (created && mRowsToPrepend > 0)
+ --mRowsToPrepend;
+
+ // if the row height is 0 then fail. Wait until someone
+ // laid out and sets the row height.
+ if (rowHeight == 0)
+ return;
+
+ availableHeight -= rowHeight;
+
+ // should we continue? Is the enought height?
+ if (!ContinueReflow(availableHeight))
+ break;
+
+ // get the next tree box. Create one if needed.
+ box = GetNextItemBox(box, 0, &created);
+ }
+
+ mRowsToPrepend = 0;
+ mLinkupFrame = nullptr;
+}
+
+void
+nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose)
+{
+ // We need to destroy frames until our row count has been properly
+ // reduced. A reflow will then pick up and create the new frames.
+ nsIFrame* childFrame = GetFirstFrame();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (childFrame && aRowsToLose > 0) {
+ --aRowsToLose;
+
+ nsIFrame* nextFrame = childFrame->GetNextSibling();
+ RemoveChildFrame(state, childFrame);
+
+ mTopFrame = childFrame = nextFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose)
+{
+ // We need to destroy frames until our row count has been properly
+ // reduced. A reflow will then pick up and create the new frames.
+ nsIFrame* childFrame = GetLastFrame();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (childFrame && aRowsToLose > 0) {
+ --aRowsToLose;
+
+ nsIFrame* prevFrame;
+ prevFrame = childFrame->GetPrevSibling();
+ RemoveChildFrame(state, childFrame);
+
+ mBottomFrame = childFrame = prevFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+static bool
+IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild,
+ nsIFrame** aChildFrame)
+{
+ *aChildFrame = nullptr;
+ if (!aChild->IsXULElement(nsGkAtoms::listitem)) {
+ return false;
+ }
+ nsIFrame* existingFrame = aChild->GetPrimaryFrame();
+ if (existingFrame && existingFrame->GetParent() != aParent) {
+ return false;
+ }
+ *aChildFrame = existingFrame;
+ return true;
+}
+
+//
+// Get the nsIFrame for the first visible listitem, and if none exists,
+// create one.
+//
+nsIFrame*
+nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset, bool* aCreated)
+{
+ if (aCreated)
+ *aCreated = false;
+
+ // Clear ourselves out.
+ mBottomFrame = mTopFrame;
+
+ if (mTopFrame) {
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ }
+
+ // top frame was cleared out
+ mTopFrame = GetFirstFrame();
+ mBottomFrame = mTopFrame;
+
+ if (mTopFrame && mRowsToPrepend <= 0) {
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ }
+
+ // At this point, we either have no frames at all,
+ // or the user has scrolled upwards, leaving frames
+ // to be created at the top. Let's determine which
+ // content needs a new frame first.
+
+ nsCOMPtr<nsIContent> startContent;
+ if (mTopFrame && mRowsToPrepend > 0) {
+ // We need to insert rows before the top frame
+ nsIContent* topContent = mTopFrame->GetContent();
+ nsIContent* topParent = topContent->GetParent();
+ int32_t contentIndex = topParent->IndexOf(topContent);
+ contentIndex -= aOffset;
+ if (contentIndex < 0)
+ return nullptr;
+ startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend);
+ } else {
+ // This will be the first item frame we create. Use the content
+ // at the current index, which is the first index scrolled into view
+ GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent));
+ }
+
+ if (startContent) {
+ nsIFrame* existingFrame;
+ if (!IsListItemChild(this, startContent, &existingFrame)) {
+ return GetFirstItemBox(++aOffset, aCreated);
+ }
+ if (existingFrame) {
+ return existingFrame->IsXULBoxFrame() ? existingFrame : nullptr;
+ }
+
+ // Either append the new frame, or prepend it (at index 0)
+ // XXX check here if frame was even created, it may not have been if
+ // display: none was on listitem content
+ bool isAppend = mRowsToPrepend <= 0;
+
+ nsPresContext* presContext = PresContext();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ nsIFrame* topFrame = nullptr;
+ fc->CreateListBoxContent(this, nullptr, startContent, &topFrame, isAppend);
+ mTopFrame = topFrame;
+ if (mTopFrame) {
+ if (aCreated)
+ *aCreated = true;
+
+ mBottomFrame = mTopFrame;
+
+ return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
+ } else
+ return GetFirstItemBox(++aOffset, 0);
+ }
+
+ return nullptr;
+}
+
+//
+// Get the nsIFrame for the next visible listitem after aBox, and if none
+// exists, create one.
+//
+nsIFrame*
+nsListBoxBodyFrame::GetNextItemBox(nsIFrame* aBox, int32_t aOffset,
+ bool* aCreated)
+{
+ if (aCreated)
+ *aCreated = false;
+
+ nsIFrame* result = aBox->GetNextSibling();
+
+ if (!result || result == mLinkupFrame || mRowsToPrepend > 0) {
+ // No result found. See if there's a content node that wants a frame.
+ nsIContent* prevContent = aBox->GetContent();
+ nsIContent* parentContent = prevContent->GetParent();
+
+ int32_t i = parentContent->IndexOf(prevContent);
+
+ uint32_t childCount = parentContent->GetChildCount();
+ if (((uint32_t)i + aOffset + 1) < childCount) {
+ // There is a content node that wants a frame.
+ nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1);
+
+ nsIFrame* existingFrame;
+ if (!IsListItemChild(this, nextContent, &existingFrame)) {
+ return GetNextItemBox(aBox, ++aOffset, aCreated);
+ }
+ if (!existingFrame) {
+ // Either append the new frame, or insert it after the current frame
+ bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0;
+ nsIFrame* prevFrame = isAppend ? nullptr : aBox;
+
+ nsPresContext* presContext = PresContext();
+ nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
+ fc->CreateListBoxContent(this, prevFrame, nextContent,
+ &result, isAppend);
+
+ if (result) {
+ if (aCreated)
+ *aCreated = true;
+ } else
+ return GetNextItemBox(aBox, ++aOffset, aCreated);
+ } else {
+ result = existingFrame;
+ }
+
+ mLinkupFrame = nullptr;
+ }
+ }
+
+ if (!result)
+ return nullptr;
+
+ mBottomFrame = result;
+
+ NS_ASSERTION(!result->IsXULBoxFrame() || result->GetParent() == this,
+ "returning frame that is not in childlist");
+
+ return result->IsXULBoxFrame() ? result : nullptr;
+}
+
+bool
+nsListBoxBodyFrame::ContinueReflow(nscoord height)
+{
+#ifdef ACCESSIBILITY
+ if (nsIPresShell::IsAccessibilityActive()) {
+ // Create all the frames at once so screen readers and
+ // onscreen keyboards can see the full list right away
+ return true;
+ }
+#endif
+
+ if (height <= 0) {
+ nsIFrame* lastChild = GetLastFrame();
+ nsIFrame* startingPoint = mBottomFrame;
+ if (startingPoint == nullptr) {
+ // We just want to delete everything but the first item.
+ startingPoint = GetFirstFrame();
+ }
+
+ if (lastChild != startingPoint) {
+ // We have some hangers on (probably caused by shrinking the size of the window).
+ // Nuke them.
+ nsIFrame* currFrame = startingPoint->GetNextSibling();
+ nsBoxLayoutState state(PresContext());
+
+ nsCSSFrameConstructor* fc =
+ PresContext()->PresShell()->FrameConstructor();
+ fc->BeginUpdate();
+ while (currFrame) {
+ nsIFrame* nextFrame = currFrame->GetNextSibling();
+ RemoveChildFrame(state, currFrame);
+ currFrame = nextFrame;
+ }
+ fc->EndUpdate();
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ return false;
+ }
+ else
+ return true;
+}
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList)
+{
+ // append them after
+ nsBoxLayoutState state(PresContext());
+ const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nullptr, aFrameList);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenAppended(this, state, newFrames);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // insert the frames to our info list
+ nsBoxLayoutState state(PresContext());
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ return NS_OK;
+}
+
+//
+// Called by nsCSSFrameConstructor when a new listitem content is inserted.
+//
+void
+nsListBoxBodyFrame::OnContentInserted(nsIContent* aChildContent)
+{
+ if (mRowCount >= 0)
+ ++mRowCount;
+
+ // The RDF content builder will build content nodes such that they are all
+ // ready when OnContentInserted is first called, meaning the first call
+ // to CreateRows will create all the frames, but OnContentInserted will
+ // still be called again for each content node - so we need to make sure
+ // that the frame for each content node hasn't already been created.
+ nsIFrame* childFrame = aChildContent->GetPrimaryFrame();
+ if (childFrame)
+ return;
+
+ int32_t siblingIndex;
+ nsCOMPtr<nsIContent> nextSiblingContent;
+ GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex);
+
+ // if we're inserting our item before the first visible content,
+ // then we need to shift all rows down by one
+ if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) {
+ mTopFrame = nullptr;
+ mRowsToPrepend = 1;
+ } else if (nextSiblingContent) {
+ // we may be inserting before a frame that is on screen
+ nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame();
+ mLinkupFrame = nextSiblingFrame;
+ }
+
+ CreateRows();
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+//
+// Called by nsCSSFrameConstructor when listitem content is removed.
+//
+void
+nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext,
+ nsIContent* aContainer,
+ nsIFrame* aChildFrame,
+ nsIContent* aOldNextSibling)
+{
+ NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this,
+ "Removing frame that's not our child... Not good");
+
+ if (mRowCount >= 0)
+ --mRowCount;
+
+ if (aContainer) {
+ if (!aChildFrame) {
+ // The row we are removing is out of view, so we need to try to
+ // determine the index of its next sibling.
+ int32_t siblingIndex = -1;
+ if (aOldNextSibling) {
+ nsCOMPtr<nsIContent> nextSiblingContent;
+ GetListItemNextSibling(aOldNextSibling,
+ getter_AddRefs(nextSiblingContent),
+ siblingIndex);
+ }
+
+ // if the row being removed is off-screen and above the top frame, we need to
+ // adjust our top index and tell the scrollbar to shift up one row.
+ if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) {
+ NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0");
+ --mCurrentIndex;
+ mYPosition = mCurrentIndex*mRowHeight;
+ nsWeakFrame weakChildFrame(aChildFrame);
+ VerticalScroll(mYPosition);
+ if (!weakChildFrame.IsAlive()) {
+ return;
+ }
+ }
+ } else if (mCurrentIndex > 0) {
+ // At this point, we know we have a scrollbar, and we need to know
+ // if we are scrolled to the last row. In this case, the behavior
+ // of the scrollbar is to stay locked to the bottom. Since we are
+ // removing visible content, the first visible row will have to move
+ // down by one, and we will have to insert a new frame at the top.
+
+ // if the last content node has a frame, we are scrolled to the bottom
+ nsIContent* lastChild = nullptr;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ lastChild = child;
+ }
+
+ if (lastChild) {
+ nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame();
+
+ if (lastChildFrame) {
+ mTopFrame = nullptr;
+ mRowsToPrepend = 1;
+ --mCurrentIndex;
+ mYPosition = mCurrentIndex*mRowHeight;
+ nsWeakFrame weakChildFrame(aChildFrame);
+ VerticalScroll(mYPosition);
+ if (!weakChildFrame.IsAlive()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // if we're removing the top row, the new top row is the next row
+ if (mTopFrame && mTopFrame == aChildFrame)
+ mTopFrame = mTopFrame->GetNextSibling();
+
+ // Go ahead and delete the frame.
+ nsBoxLayoutState state(aPresContext);
+ if (aChildFrame) {
+ RemoveChildFrame(state, aChildFrame);
+ }
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex, nsIContent** aContent)
+{
+ *aContent = nullptr;
+
+ int32_t itemsFound = 0;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++itemsFound;
+ if (itemsFound-1 == aIndex) {
+ *aContent = child;
+ NS_IF_ADDREF(*aContent);
+ return;
+ }
+ }
+ }
+}
+
+void
+nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex)
+{
+ *aContent = nullptr;
+ aSiblingIndex = -1;
+ nsIContent *prevKid = nullptr;
+ FlattenedChildIterator iter(mContent);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (child->IsXULElement(nsGkAtoms::listitem)) {
+ ++aSiblingIndex;
+ if (prevKid == aListItem) {
+ *aContent = child;
+ NS_IF_ADDREF(*aContent);
+ return;
+ }
+ }
+ prevKid = child;
+ }
+
+ aSiblingIndex = -1; // no match, so there is no next sibling
+}
+
+void
+nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState,
+ nsIFrame *aFrame)
+{
+ MOZ_ASSERT(mFrames.ContainsFrame(aFrame));
+ MOZ_ASSERT(aFrame != GetContentInsertionFrame());
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ nsIContent* content = aFrame->GetContent();
+ accService->ContentRemoved(PresContext()->PresShell(), content);
+ }
+#endif
+
+ mFrames.RemoveFrame(aFrame);
+ if (mLayoutManager)
+ mLayoutManager->ChildrenRemoved(this, aState, aFrame);
+ aFrame->Destroy();
+}
+
+// Creation Routines ///////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsBoxLayout> NS_NewListBoxLayout();
+
+nsIFrame*
+NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout();
+ return new (aPresShell) nsListBoxBodyFrame(aContext, layout);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame)