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