1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/xul/nsListBoxBodyFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1492 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsListBoxBodyFrame.h" 1.10 + 1.11 +#include "nsListBoxLayout.h" 1.12 + 1.13 +#include "nsCOMPtr.h" 1.14 +#include "nsGridRowGroupLayout.h" 1.15 +#include "nsIServiceManager.h" 1.16 +#include "nsGkAtoms.h" 1.17 +#include "nsIContent.h" 1.18 +#include "nsNameSpaceManager.h" 1.19 +#include "nsIDocument.h" 1.20 +#include "nsIDOMMouseEvent.h" 1.21 +#include "nsIDOMElement.h" 1.22 +#include "nsIDOMNodeList.h" 1.23 +#include "nsCSSFrameConstructor.h" 1.24 +#include "nsIScrollableFrame.h" 1.25 +#include "nsScrollbarFrame.h" 1.26 +#include "nsView.h" 1.27 +#include "nsViewManager.h" 1.28 +#include "nsStyleContext.h" 1.29 +#include "nsFontMetrics.h" 1.30 +#include "nsITimer.h" 1.31 +#include "nsAutoPtr.h" 1.32 +#include "nsStyleSet.h" 1.33 +#include "nsPIBoxObject.h" 1.34 +#include "nsINodeInfo.h" 1.35 +#include "nsLayoutUtils.h" 1.36 +#include "nsPIListBoxObject.h" 1.37 +#include "nsContentUtils.h" 1.38 +#include "ChildIterator.h" 1.39 +#include "nsRenderingContext.h" 1.40 +#include "prtime.h" 1.41 +#include <algorithm> 1.42 + 1.43 +#ifdef ACCESSIBILITY 1.44 +#include "nsAccessibilityService.h" 1.45 +#endif 1.46 + 1.47 +using namespace mozilla::dom; 1.48 + 1.49 +/////////////// nsListScrollSmoother ////////////////// 1.50 + 1.51 +/* A mediator used to smooth out scrolling. It works by seeing if 1.52 + * we have time to scroll the amount of rows requested. This is determined 1.53 + * by measuring how long it takes to scroll a row. If we can scroll the 1.54 + * rows in time we do so. If not we start a timer and skip the request. We 1.55 + * do this until the timer finally first because the user has stopped moving 1.56 + * the mouse. Then do all the queued requests in on shot. 1.57 + */ 1.58 + 1.59 +// the longest amount of time that can go by before the use 1.60 +// notices it as a delay. 1.61 +#define USER_TIME_THRESHOLD 150000 1.62 + 1.63 +// how long it takes to layout a single row initial value. 1.64 +// we will time this after we scroll a few rows. 1.65 +#define TIME_PER_ROW_INITAL 50000 1.66 + 1.67 +// if we decide we can't layout the rows in the amount of time. How long 1.68 +// do we wait before checking again? 1.69 +#define SMOOTH_INTERVAL 100 1.70 + 1.71 +class nsListScrollSmoother : public nsITimerCallback 1.72 +{ 1.73 +public: 1.74 + NS_DECL_ISUPPORTS 1.75 + 1.76 + nsListScrollSmoother(nsListBoxBodyFrame* aOuter); 1.77 + virtual ~nsListScrollSmoother(); 1.78 + 1.79 + // nsITimerCallback 1.80 + NS_DECL_NSITIMERCALLBACK 1.81 + 1.82 + void Start(); 1.83 + void Stop(); 1.84 + bool IsRunning(); 1.85 + 1.86 + nsCOMPtr<nsITimer> mRepeatTimer; 1.87 + int32_t mDelta; 1.88 + nsListBoxBodyFrame* mOuter; 1.89 +}; 1.90 + 1.91 +nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter) 1.92 +{ 1.93 + mDelta = 0; 1.94 + mOuter = aOuter; 1.95 +} 1.96 + 1.97 +nsListScrollSmoother::~nsListScrollSmoother() 1.98 +{ 1.99 + Stop(); 1.100 +} 1.101 + 1.102 +NS_IMETHODIMP 1.103 +nsListScrollSmoother::Notify(nsITimer *timer) 1.104 +{ 1.105 + Stop(); 1.106 + 1.107 + NS_ASSERTION(mOuter, "mOuter is null, see bug #68365"); 1.108 + if (!mOuter) return NS_OK; 1.109 + 1.110 + // actually do some work. 1.111 + mOuter->InternalPositionChangedCallback(); 1.112 + return NS_OK; 1.113 +} 1.114 + 1.115 +bool 1.116 +nsListScrollSmoother::IsRunning() 1.117 +{ 1.118 + return mRepeatTimer ? true : false; 1.119 +} 1.120 + 1.121 +void 1.122 +nsListScrollSmoother::Start() 1.123 +{ 1.124 + Stop(); 1.125 + mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.126 + mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT); 1.127 +} 1.128 + 1.129 +void 1.130 +nsListScrollSmoother::Stop() 1.131 +{ 1.132 + if ( mRepeatTimer ) { 1.133 + mRepeatTimer->Cancel(); 1.134 + mRepeatTimer = nullptr; 1.135 + } 1.136 +} 1.137 + 1.138 +NS_IMPL_ISUPPORTS(nsListScrollSmoother, nsITimerCallback) 1.139 + 1.140 +/////////////// nsListBoxBodyFrame ////////////////// 1.141 + 1.142 +nsListBoxBodyFrame::nsListBoxBodyFrame(nsIPresShell* aPresShell, 1.143 + nsStyleContext* aContext, 1.144 + nsBoxLayout* aLayoutManager) 1.145 + : nsBoxFrame(aPresShell, aContext, false, aLayoutManager), 1.146 + mTopFrame(nullptr), 1.147 + mBottomFrame(nullptr), 1.148 + mLinkupFrame(nullptr), 1.149 + mScrollSmoother(nullptr), 1.150 + mRowsToPrepend(0), 1.151 + mRowCount(-1), 1.152 + mRowHeight(0), 1.153 + mAvailableHeight(0), 1.154 + mStringWidth(-1), 1.155 + mCurrentIndex(0), 1.156 + mOldIndex(0), 1.157 + mYPosition(0), 1.158 + mTimePerRow(TIME_PER_ROW_INITAL), 1.159 + mRowHeightWasSet(false), 1.160 + mScrolling(false), 1.161 + mAdjustScroll(false), 1.162 + mReflowCallbackPosted(false) 1.163 +{ 1.164 +} 1.165 + 1.166 +nsListBoxBodyFrame::~nsListBoxBodyFrame() 1.167 +{ 1.168 + NS_IF_RELEASE(mScrollSmoother); 1.169 + 1.170 +#if USE_TIMER_TO_DELAY_SCROLLING 1.171 + StopScrollTracking(); 1.172 + mAutoScrollTimer = nullptr; 1.173 +#endif 1.174 + 1.175 +} 1.176 + 1.177 +NS_QUERYFRAME_HEAD(nsListBoxBodyFrame) 1.178 + NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) 1.179 + NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame) 1.180 +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) 1.181 + 1.182 +////////// nsIFrame ///////////////// 1.183 + 1.184 +void 1.185 +nsListBoxBodyFrame::Init(nsIContent* aContent, 1.186 + nsIFrame* aParent, 1.187 + nsIFrame* aPrevInFlow) 1.188 +{ 1.189 + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); 1.190 + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); 1.191 + if (scrollFrame) { 1.192 + nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true); 1.193 + nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar); 1.194 + if (scrollbarFrame) { 1.195 + scrollbarFrame->SetScrollbarMediatorContent(GetContent()); 1.196 + } 1.197 + } 1.198 + nsRefPtr<nsFontMetrics> fm; 1.199 + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); 1.200 + mRowHeight = fm->MaxHeight(); 1.201 +} 1.202 + 1.203 +void 1.204 +nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.205 +{ 1.206 + // make sure we cancel any posted callbacks. 1.207 + if (mReflowCallbackPosted) 1.208 + PresContext()->PresShell()->CancelReflowCallback(this); 1.209 + 1.210 + // Revoke any pending position changed events 1.211 + for (uint32_t i = 0; i < mPendingPositionChangeEvents.Length(); ++i) { 1.212 + mPendingPositionChangeEvents[i]->Revoke(); 1.213 + } 1.214 + 1.215 + // Make sure we tell our listbox's box object we're being destroyed. 1.216 + if (mBoxObject) { 1.217 + mBoxObject->ClearCachedValues(); 1.218 + } 1.219 + 1.220 + nsBoxFrame::DestroyFrom(aDestructRoot); 1.221 +} 1.222 + 1.223 +nsresult 1.224 +nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID, 1.225 + nsIAtom* aAttribute, 1.226 + int32_t aModType) 1.227 +{ 1.228 + nsresult rv = NS_OK; 1.229 + 1.230 + if (aAttribute == nsGkAtoms::rows) { 1.231 + PresContext()->PresShell()-> 1.232 + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); 1.233 + } 1.234 + else 1.235 + rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 1.236 + 1.237 + return rv; 1.238 + 1.239 +} 1.240 + 1.241 +/* virtual */ void 1.242 +nsListBoxBodyFrame::MarkIntrinsicWidthsDirty() 1.243 +{ 1.244 + mStringWidth = -1; 1.245 + nsBoxFrame::MarkIntrinsicWidthsDirty(); 1.246 +} 1.247 + 1.248 +/////////// nsBox /////////////// 1.249 + 1.250 +NS_IMETHODIMP 1.251 +nsListBoxBodyFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState) 1.252 +{ 1.253 + if (mScrolling) 1.254 + aBoxLayoutState.SetPaintingDisabled(true); 1.255 + 1.256 + nsresult rv = nsBoxFrame::DoLayout(aBoxLayoutState); 1.257 + 1.258 + // determine the real height for the scrollable area from the total number 1.259 + // of rows, since non-visible rows don't yet have frames 1.260 + nsRect rect(nsPoint(0, 0), GetSize()); 1.261 + nsOverflowAreas overflow(rect, rect); 1.262 + if (mLayoutManager) { 1.263 + nsIFrame* childFrame = mFrames.FirstChild(); 1.264 + while (childFrame) { 1.265 + ConsiderChildOverflow(overflow, childFrame); 1.266 + childFrame = childFrame->GetNextSibling(); 1.267 + } 1.268 + 1.269 + nsSize prefSize = mLayoutManager->GetPrefSize(this, aBoxLayoutState); 1.270 + NS_FOR_FRAME_OVERFLOW_TYPES(otype) { 1.271 + nsRect& o = overflow.Overflow(otype); 1.272 + o.height = std::max(o.height, prefSize.height); 1.273 + } 1.274 + } 1.275 + FinishAndStoreOverflow(overflow, GetSize()); 1.276 + 1.277 + if (mScrolling) 1.278 + aBoxLayoutState.SetPaintingDisabled(false); 1.279 + 1.280 + // if we are scrolled and the row height changed 1.281 + // make sure we are scrolled to a correct index. 1.282 + if (mAdjustScroll) 1.283 + PostReflowCallback(); 1.284 + 1.285 + return rv; 1.286 +} 1.287 + 1.288 +nsSize 1.289 +nsListBoxBodyFrame::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) 1.290 +{ 1.291 + nsSize result(0, 0); 1.292 + if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None, 1.293 + nsGkAtoms::sizemode)) { 1.294 + result = GetPrefSize(aBoxLayoutState); 1.295 + result.height = 0; 1.296 + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); 1.297 + if (scrollFrame && 1.298 + scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) { 1.299 + nsMargin scrollbars = 1.300 + scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState); 1.301 + result.width += scrollbars.left + scrollbars.right; 1.302 + } 1.303 + } 1.304 + return result; 1.305 +} 1.306 + 1.307 +nsSize 1.308 +nsListBoxBodyFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState) 1.309 +{ 1.310 + nsSize pref = nsBoxFrame::GetPrefSize(aBoxLayoutState); 1.311 + 1.312 + int32_t size = GetFixedRowSize(); 1.313 + if (size > -1) 1.314 + pref.height = size*GetRowHeightAppUnits(); 1.315 + 1.316 + nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this); 1.317 + if (scrollFrame && 1.318 + scrollFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) { 1.319 + nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState); 1.320 + pref.width += scrollbars.left + scrollbars.right; 1.321 + } 1.322 + return pref; 1.323 +} 1.324 + 1.325 +///////////// nsIScrollbarMediator /////////////// 1.326 + 1.327 +NS_IMETHODIMP 1.328 +nsListBoxBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) 1.329 +{ 1.330 + if (mScrolling || mRowHeight == 0) 1.331 + return NS_OK; 1.332 + 1.333 + nscoord oldTwipIndex, newTwipIndex; 1.334 + oldTwipIndex = mCurrentIndex*mRowHeight; 1.335 + newTwipIndex = nsPresContext::CSSPixelsToAppUnits(aNewIndex); 1.336 + int32_t twipDelta = newTwipIndex > oldTwipIndex ? newTwipIndex - oldTwipIndex : oldTwipIndex - newTwipIndex; 1.337 + 1.338 + int32_t rowDelta = twipDelta / mRowHeight; 1.339 + int32_t remainder = twipDelta % mRowHeight; 1.340 + if (remainder > (mRowHeight/2)) 1.341 + rowDelta++; 1.342 + 1.343 + if (rowDelta == 0) 1.344 + return NS_OK; 1.345 + 1.346 + // update the position to be row based. 1.347 + 1.348 + int32_t newIndex = newTwipIndex > oldTwipIndex ? mCurrentIndex + rowDelta : mCurrentIndex - rowDelta; 1.349 + //aNewIndex = newIndex*mRowHeight/mOnePixel; 1.350 + 1.351 + nsListScrollSmoother* smoother = GetSmoother(); 1.352 + 1.353 + // if we can't scroll the rows in time then start a timer. We will eat 1.354 + // events until the user stops moving and the timer stops. 1.355 + if (smoother->IsRunning() || rowDelta*mTimePerRow > USER_TIME_THRESHOLD) { 1.356 + 1.357 + smoother->Stop(); 1.358 + 1.359 + smoother->mDelta = newTwipIndex > oldTwipIndex ? rowDelta : -rowDelta; 1.360 + 1.361 + smoother->Start(); 1.362 + 1.363 + return NS_OK; 1.364 + } 1.365 + 1.366 + smoother->Stop(); 1.367 + 1.368 + mCurrentIndex = newIndex; 1.369 + smoother->mDelta = 0; 1.370 + 1.371 + if (mCurrentIndex < 0) { 1.372 + mCurrentIndex = 0; 1.373 + return NS_OK; 1.374 + } 1.375 + 1.376 + return InternalPositionChanged(newTwipIndex < oldTwipIndex, rowDelta); 1.377 +} 1.378 + 1.379 +NS_IMETHODIMP 1.380 +nsListBoxBodyFrame::VisibilityChanged(bool aVisible) 1.381 +{ 1.382 + if (mRowHeight == 0) 1.383 + return NS_OK; 1.384 + 1.385 + int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight); 1.386 + if (lastPageTopRow < 0) 1.387 + lastPageTopRow = 0; 1.388 + int32_t delta = mCurrentIndex - lastPageTopRow; 1.389 + if (delta > 0) { 1.390 + mCurrentIndex = lastPageTopRow; 1.391 + InternalPositionChanged(true, delta); 1.392 + } 1.393 + 1.394 + return NS_OK; 1.395 +} 1.396 + 1.397 +NS_IMETHODIMP 1.398 +nsListBoxBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) 1.399 +{ 1.400 + if (aOldIndex == aNewIndex) 1.401 + return NS_OK; 1.402 + if (aNewIndex < aOldIndex) 1.403 + mCurrentIndex--; 1.404 + else mCurrentIndex++; 1.405 + if (mCurrentIndex < 0) { 1.406 + mCurrentIndex = 0; 1.407 + return NS_OK; 1.408 + } 1.409 + InternalPositionChanged(aNewIndex < aOldIndex, 1); 1.410 + 1.411 + return NS_OK; 1.412 +} 1.413 + 1.414 +///////////// nsIReflowCallback /////////////// 1.415 + 1.416 +bool 1.417 +nsListBoxBodyFrame::ReflowFinished() 1.418 +{ 1.419 + nsAutoScriptBlocker scriptBlocker; 1.420 + // now create or destroy any rows as needed 1.421 + CreateRows(); 1.422 + 1.423 + // keep scrollbar in sync 1.424 + if (mAdjustScroll) { 1.425 + VerticalScroll(mYPosition); 1.426 + mAdjustScroll = false; 1.427 + } 1.428 + 1.429 + // if the row height changed then mark everything as a style change. 1.430 + // That will dirty the entire listbox 1.431 + if (mRowHeightWasSet) { 1.432 + PresContext()->PresShell()-> 1.433 + FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); 1.434 + int32_t pos = mCurrentIndex * mRowHeight; 1.435 + if (mYPosition != pos) 1.436 + mAdjustScroll = true; 1.437 + mRowHeightWasSet = false; 1.438 + } 1.439 + 1.440 + mReflowCallbackPosted = false; 1.441 + return true; 1.442 +} 1.443 + 1.444 +void 1.445 +nsListBoxBodyFrame::ReflowCallbackCanceled() 1.446 +{ 1.447 + mReflowCallbackPosted = false; 1.448 +} 1.449 + 1.450 +///////// nsIListBoxObject /////////////// 1.451 + 1.452 +nsresult 1.453 +nsListBoxBodyFrame::GetRowCount(int32_t* aResult) 1.454 +{ 1.455 + *aResult = GetRowCount(); 1.456 + return NS_OK; 1.457 +} 1.458 + 1.459 +nsresult 1.460 +nsListBoxBodyFrame::GetNumberOfVisibleRows(int32_t *aResult) 1.461 +{ 1.462 + *aResult= mRowHeight ? GetAvailableHeight() / mRowHeight : 0; 1.463 + return NS_OK; 1.464 +} 1.465 + 1.466 +nsresult 1.467 +nsListBoxBodyFrame::GetIndexOfFirstVisibleRow(int32_t *aResult) 1.468 +{ 1.469 + *aResult = mCurrentIndex; 1.470 + return NS_OK; 1.471 +} 1.472 + 1.473 +nsresult 1.474 +nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex) 1.475 +{ 1.476 + if (aRowIndex < 0) 1.477 + return NS_ERROR_ILLEGAL_VALUE; 1.478 + 1.479 + int32_t rows = 0; 1.480 + if (mRowHeight) 1.481 + rows = GetAvailableHeight()/mRowHeight; 1.482 + if (rows <= 0) 1.483 + rows = 1; 1.484 + int32_t bottomIndex = mCurrentIndex + rows; 1.485 + 1.486 + // if row is visible, ignore 1.487 + if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex) 1.488 + return NS_OK; 1.489 + 1.490 + int32_t delta; 1.491 + 1.492 + bool up = aRowIndex < mCurrentIndex; 1.493 + if (up) { 1.494 + delta = mCurrentIndex - aRowIndex; 1.495 + mCurrentIndex = aRowIndex; 1.496 + } 1.497 + else { 1.498 + // Check to be sure we're not scrolling off the bottom of the tree 1.499 + if (aRowIndex >= GetRowCount()) 1.500 + return NS_ERROR_ILLEGAL_VALUE; 1.501 + 1.502 + // Bring it just into view. 1.503 + delta = 1 + (aRowIndex-bottomIndex); 1.504 + mCurrentIndex += delta; 1.505 + } 1.506 + 1.507 + // Safe to not go off an event here, since this is coming from the 1.508 + // box object. 1.509 + DoInternalPositionChangedSync(up, delta); 1.510 + return NS_OK; 1.511 +} 1.512 + 1.513 +nsresult 1.514 +nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines) 1.515 +{ 1.516 + int32_t scrollIndex, visibleRows; 1.517 + GetIndexOfFirstVisibleRow(&scrollIndex); 1.518 + GetNumberOfVisibleRows(&visibleRows); 1.519 + 1.520 + scrollIndex += aNumLines; 1.521 + 1.522 + if (scrollIndex < 0) 1.523 + scrollIndex = 0; 1.524 + else { 1.525 + int32_t numRows = GetRowCount(); 1.526 + int32_t lastPageTopRow = numRows - visibleRows; 1.527 + if (scrollIndex > lastPageTopRow) 1.528 + scrollIndex = lastPageTopRow; 1.529 + } 1.530 + 1.531 + ScrollToIndex(scrollIndex); 1.532 + 1.533 + return NS_OK; 1.534 +} 1.535 + 1.536 +// walks the DOM to get the zero-based row index of the content 1.537 +nsresult 1.538 +nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) 1.539 +{ 1.540 + if (aItem) { 1.541 + *_retval = 0; 1.542 + nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem)); 1.543 + 1.544 + FlattenedChildIterator iter(mContent); 1.545 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.546 + // we hit a list row, count it 1.547 + if (child->Tag() == nsGkAtoms::listitem) { 1.548 + // is this it? 1.549 + if (child == itemContent) 1.550 + return NS_OK; 1.551 + 1.552 + ++(*_retval); 1.553 + } 1.554 + } 1.555 + } 1.556 + 1.557 + // not found 1.558 + *_retval = -1; 1.559 + return NS_OK; 1.560 +} 1.561 + 1.562 +nsresult 1.563 +nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex, nsIDOMElement** aItem) 1.564 +{ 1.565 + *aItem = nullptr; 1.566 + if (aIndex < 0) 1.567 + return NS_OK; 1.568 + 1.569 + int32_t itemCount = 0; 1.570 + FlattenedChildIterator iter(mContent); 1.571 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.572 + // we hit a list row, check if it is the one we are looking for 1.573 + if (child->Tag() == nsGkAtoms::listitem) { 1.574 + // is this it? 1.575 + if (itemCount == aIndex) { 1.576 + return CallQueryInterface(child, aItem); 1.577 + } 1.578 + ++itemCount; 1.579 + } 1.580 + } 1.581 + 1.582 + // not found 1.583 + return NS_OK; 1.584 +} 1.585 + 1.586 +/////////// nsListBoxBodyFrame /////////////// 1.587 + 1.588 +int32_t 1.589 +nsListBoxBodyFrame::GetRowCount() 1.590 +{ 1.591 + if (mRowCount < 0) 1.592 + ComputeTotalRowCount(); 1.593 + return mRowCount; 1.594 +} 1.595 + 1.596 +int32_t 1.597 +nsListBoxBodyFrame::GetFixedRowSize() 1.598 +{ 1.599 + nsresult dummy; 1.600 + 1.601 + nsAutoString rows; 1.602 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows); 1.603 + if (!rows.IsEmpty()) 1.604 + return rows.ToInteger(&dummy); 1.605 + 1.606 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows); 1.607 + 1.608 + if (!rows.IsEmpty()) 1.609 + return rows.ToInteger(&dummy); 1.610 + 1.611 + return -1; 1.612 +} 1.613 + 1.614 +void 1.615 +nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight) 1.616 +{ 1.617 + if (aRowHeight > mRowHeight) { 1.618 + mRowHeight = aRowHeight; 1.619 + 1.620 + // signal we need to dirty everything 1.621 + // and we want to be notified after reflow 1.622 + // so we can create or destory rows as needed 1.623 + mRowHeightWasSet = true; 1.624 + PostReflowCallback(); 1.625 + } 1.626 +} 1.627 + 1.628 +nscoord 1.629 +nsListBoxBodyFrame::GetAvailableHeight() 1.630 +{ 1.631 + nsIScrollableFrame* scrollFrame = 1.632 + nsLayoutUtils::GetScrollableFrameFor(this); 1.633 + if (scrollFrame) { 1.634 + return scrollFrame->GetScrollPortRect().height; 1.635 + } 1.636 + return 0; 1.637 +} 1.638 + 1.639 +nscoord 1.640 +nsListBoxBodyFrame::GetYPosition() 1.641 +{ 1.642 + return mYPosition; 1.643 +} 1.644 + 1.645 +nscoord 1.646 +nsListBoxBodyFrame::ComputeIntrinsicWidth(nsBoxLayoutState& aBoxLayoutState) 1.647 +{ 1.648 + if (mStringWidth != -1) 1.649 + return mStringWidth; 1.650 + 1.651 + nscoord largestWidth = 0; 1.652 + 1.653 + int32_t index = 0; 1.654 + nsCOMPtr<nsIDOMElement> firstRowEl; 1.655 + GetItemAtIndex(index, getter_AddRefs(firstRowEl)); 1.656 + nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl)); 1.657 + 1.658 + if (firstRowContent) { 1.659 + nsRefPtr<nsStyleContext> styleContext; 1.660 + nsPresContext *presContext = aBoxLayoutState.PresContext(); 1.661 + styleContext = presContext->StyleSet()-> 1.662 + ResolveStyleFor(firstRowContent->AsElement(), nullptr); 1.663 + 1.664 + nscoord width = 0; 1.665 + nsMargin margin(0,0,0,0); 1.666 + 1.667 + if (styleContext->StylePadding()->GetPadding(margin)) 1.668 + width += margin.LeftRight(); 1.669 + width += styleContext->StyleBorder()->GetComputedBorder().LeftRight(); 1.670 + if (styleContext->StyleMargin()->GetMargin(margin)) 1.671 + width += margin.LeftRight(); 1.672 + 1.673 + FlattenedChildIterator iter(mContent); 1.674 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.675 + if (child->Tag() == nsGkAtoms::listitem) { 1.676 + nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); 1.677 + if (rendContext) { 1.678 + nsAutoString value; 1.679 + uint32_t textCount = child->GetChildCount(); 1.680 + for (uint32_t j = 0; j < textCount; ++j) { 1.681 + nsIContent* text = child->GetChildAt(j); 1.682 + if (text && text->IsNodeOfType(nsINode::eTEXT)) { 1.683 + text->AppendTextTo(value); 1.684 + } 1.685 + } 1.686 + 1.687 + nsRefPtr<nsFontMetrics> fm; 1.688 + nsLayoutUtils::GetFontMetricsForStyleContext(styleContext, 1.689 + getter_AddRefs(fm)); 1.690 + rendContext->SetFont(fm); 1.691 + 1.692 + nscoord textWidth = 1.693 + nsLayoutUtils::GetStringWidth(this, rendContext, value.get(), value.Length()); 1.694 + textWidth += width; 1.695 + 1.696 + if (textWidth > largestWidth) 1.697 + largestWidth = textWidth; 1.698 + } 1.699 + } 1.700 + } 1.701 + } 1.702 + 1.703 + mStringWidth = largestWidth; 1.704 + return mStringWidth; 1.705 +} 1.706 + 1.707 +void 1.708 +nsListBoxBodyFrame::ComputeTotalRowCount() 1.709 +{ 1.710 + mRowCount = 0; 1.711 + FlattenedChildIterator iter(mContent); 1.712 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.713 + if (child->Tag() == nsGkAtoms::listitem) { 1.714 + ++mRowCount; 1.715 + } 1.716 + } 1.717 +} 1.718 + 1.719 +void 1.720 +nsListBoxBodyFrame::PostReflowCallback() 1.721 +{ 1.722 + if (!mReflowCallbackPosted) { 1.723 + mReflowCallbackPosted = true; 1.724 + PresContext()->PresShell()->PostReflowCallback(this); 1.725 + } 1.726 +} 1.727 + 1.728 +////////// scrolling 1.729 + 1.730 +nsresult 1.731 +nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex) 1.732 +{ 1.733 + if (( aRowIndex < 0 ) || (mRowHeight == 0)) 1.734 + return NS_OK; 1.735 + 1.736 + int32_t newIndex = aRowIndex; 1.737 + int32_t delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex; 1.738 + bool up = newIndex < mCurrentIndex; 1.739 + 1.740 + // Check to be sure we're not scrolling off the bottom of the tree 1.741 + int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight); 1.742 + if (lastPageTopRow < 0) 1.743 + lastPageTopRow = 0; 1.744 + 1.745 + if (aRowIndex > lastPageTopRow) 1.746 + return NS_OK; 1.747 + 1.748 + mCurrentIndex = newIndex; 1.749 + 1.750 + nsWeakFrame weak(this); 1.751 + 1.752 + // Since we're going to flush anyway, we need to not do this off an event 1.753 + DoInternalPositionChangedSync(up, delta); 1.754 + 1.755 + if (!weak.IsAlive()) { 1.756 + return NS_OK; 1.757 + } 1.758 + 1.759 + // This change has to happen immediately. 1.760 + // Flush any pending reflow commands. 1.761 + // XXXbz why, exactly? 1.762 + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); 1.763 + 1.764 + return NS_OK; 1.765 +} 1.766 + 1.767 +nsresult 1.768 +nsListBoxBodyFrame::InternalPositionChangedCallback() 1.769 +{ 1.770 + nsListScrollSmoother* smoother = GetSmoother(); 1.771 + 1.772 + if (smoother->mDelta == 0) 1.773 + return NS_OK; 1.774 + 1.775 + mCurrentIndex += smoother->mDelta; 1.776 + 1.777 + if (mCurrentIndex < 0) 1.778 + mCurrentIndex = 0; 1.779 + 1.780 + return DoInternalPositionChangedSync(smoother->mDelta < 0, 1.781 + smoother->mDelta < 0 ? 1.782 + -smoother->mDelta : smoother->mDelta); 1.783 +} 1.784 + 1.785 +nsresult 1.786 +nsListBoxBodyFrame::InternalPositionChanged(bool aUp, int32_t aDelta) 1.787 +{ 1.788 + nsRefPtr<nsPositionChangedEvent> ev = 1.789 + new nsPositionChangedEvent(this, aUp, aDelta); 1.790 + nsresult rv = NS_DispatchToCurrentThread(ev); 1.791 + if (NS_SUCCEEDED(rv)) { 1.792 + if (!mPendingPositionChangeEvents.AppendElement(ev)) { 1.793 + rv = NS_ERROR_OUT_OF_MEMORY; 1.794 + ev->Revoke(); 1.795 + } 1.796 + } 1.797 + return rv; 1.798 +} 1.799 + 1.800 +nsresult 1.801 +nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, int32_t aDelta) 1.802 +{ 1.803 + nsWeakFrame weak(this); 1.804 + 1.805 + // Process all the pending position changes first 1.806 + nsTArray< nsRefPtr<nsPositionChangedEvent> > temp; 1.807 + temp.SwapElements(mPendingPositionChangeEvents); 1.808 + for (uint32_t i = 0; i < temp.Length(); ++i) { 1.809 + if (weak.IsAlive()) { 1.810 + temp[i]->Run(); 1.811 + } 1.812 + temp[i]->Revoke(); 1.813 + } 1.814 + 1.815 + if (!weak.IsAlive()) { 1.816 + return NS_OK; 1.817 + } 1.818 + 1.819 + return DoInternalPositionChanged(aUp, aDelta); 1.820 +} 1.821 + 1.822 +nsresult 1.823 +nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, int32_t aDelta) 1.824 +{ 1.825 + if (aDelta == 0) 1.826 + return NS_OK; 1.827 + 1.828 + nsRefPtr<nsPresContext> presContext(PresContext()); 1.829 + nsBoxLayoutState state(presContext); 1.830 + 1.831 + // begin timing how long it takes to scroll a row 1.832 + PRTime start = PR_Now(); 1.833 + 1.834 + nsWeakFrame weakThis(this); 1.835 + mContent->GetDocument()->FlushPendingNotifications(Flush_Layout); 1.836 + if (!weakThis.IsAlive()) { 1.837 + return NS_OK; 1.838 + } 1.839 + 1.840 + { 1.841 + nsAutoScriptBlocker scriptBlocker; 1.842 + 1.843 + int32_t visibleRows = 0; 1.844 + if (mRowHeight) 1.845 + visibleRows = GetAvailableHeight()/mRowHeight; 1.846 + 1.847 + if (aDelta < visibleRows) { 1.848 + int32_t loseRows = aDelta; 1.849 + if (aUp) { 1.850 + // scrolling up, destroy rows from the bottom downwards 1.851 + ReverseDestroyRows(loseRows); 1.852 + mRowsToPrepend += aDelta; 1.853 + mLinkupFrame = nullptr; 1.854 + } 1.855 + else { 1.856 + // scrolling down, destroy rows from the top upwards 1.857 + DestroyRows(loseRows); 1.858 + mRowsToPrepend = 0; 1.859 + } 1.860 + } 1.861 + else { 1.862 + // We have scrolled so much that all of our current frames will 1.863 + // go off screen, so blow them all away. Weeee! 1.864 + nsIFrame *currBox = mFrames.FirstChild(); 1.865 + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); 1.866 + fc->BeginUpdate(); 1.867 + while (currBox) { 1.868 + nsIFrame *nextBox = currBox->GetNextSibling(); 1.869 + RemoveChildFrame(state, currBox); 1.870 + currBox = nextBox; 1.871 + } 1.872 + fc->EndUpdate(); 1.873 + } 1.874 + 1.875 + // clear frame markers so that CreateRows will re-create 1.876 + mTopFrame = mBottomFrame = nullptr; 1.877 + 1.878 + mYPosition = mCurrentIndex*mRowHeight; 1.879 + mScrolling = true; 1.880 + presContext->PresShell()-> 1.881 + FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); 1.882 + } 1.883 + if (!weakThis.IsAlive()) { 1.884 + return NS_OK; 1.885 + } 1.886 + // Flush calls CreateRows 1.887 + // XXXbz there has to be a better way to do this than flushing! 1.888 + presContext->PresShell()->FlushPendingNotifications(Flush_Layout); 1.889 + if (!weakThis.IsAlive()) { 1.890 + return NS_OK; 1.891 + } 1.892 + 1.893 + mScrolling = false; 1.894 + 1.895 + VerticalScroll(mYPosition); 1.896 + 1.897 + PRTime end = PR_Now(); 1.898 + 1.899 + int32_t newTime = int32_t(end - start) / aDelta; 1.900 + 1.901 + // average old and new 1.902 + mTimePerRow = (newTime + mTimePerRow)/2; 1.903 + 1.904 + return NS_OK; 1.905 +} 1.906 + 1.907 +nsListScrollSmoother* 1.908 +nsListBoxBodyFrame::GetSmoother() 1.909 +{ 1.910 + if (!mScrollSmoother) { 1.911 + mScrollSmoother = new nsListScrollSmoother(this); 1.912 + NS_ASSERTION(mScrollSmoother, "out of memory"); 1.913 + NS_IF_ADDREF(mScrollSmoother); 1.914 + } 1.915 + 1.916 + return mScrollSmoother; 1.917 +} 1.918 + 1.919 +void 1.920 +nsListBoxBodyFrame::VerticalScroll(int32_t aPosition) 1.921 +{ 1.922 + nsIScrollableFrame* scrollFrame 1.923 + = nsLayoutUtils::GetScrollableFrameFor(this); 1.924 + if (!scrollFrame) { 1.925 + return; 1.926 + } 1.927 + 1.928 + nsPoint scrollPosition = scrollFrame->GetScrollPosition(); 1.929 + 1.930 + nsWeakFrame weakFrame(this); 1.931 + scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition), 1.932 + nsIScrollableFrame::INSTANT); 1.933 + if (!weakFrame.IsAlive()) { 1.934 + return; 1.935 + } 1.936 + 1.937 + mYPosition = aPosition; 1.938 +} 1.939 + 1.940 +////////// frame and box retrieval 1.941 + 1.942 +nsIFrame* 1.943 +nsListBoxBodyFrame::GetFirstFrame() 1.944 +{ 1.945 + mTopFrame = mFrames.FirstChild(); 1.946 + return mTopFrame; 1.947 +} 1.948 + 1.949 +nsIFrame* 1.950 +nsListBoxBodyFrame::GetLastFrame() 1.951 +{ 1.952 + return mFrames.LastChild(); 1.953 +} 1.954 + 1.955 +bool 1.956 +nsListBoxBodyFrame::SupportsOrdinalsInChildren() 1.957 +{ 1.958 + return false; 1.959 +} 1.960 + 1.961 +////////// lazy row creation and destruction 1.962 + 1.963 +void 1.964 +nsListBoxBodyFrame::CreateRows() 1.965 +{ 1.966 + // Get our client rect. 1.967 + nsRect clientRect; 1.968 + GetClientRect(clientRect); 1.969 + 1.970 + // Get the starting y position and the remaining available 1.971 + // height. 1.972 + nscoord availableHeight = GetAvailableHeight(); 1.973 + 1.974 + if (availableHeight <= 0) { 1.975 + bool fixed = (GetFixedRowSize() != -1); 1.976 + if (fixed) 1.977 + availableHeight = 10; 1.978 + else 1.979 + return; 1.980 + } 1.981 + 1.982 + // get the first tree box. If there isn't one create one. 1.983 + bool created = false; 1.984 + nsIFrame* box = GetFirstItemBox(0, &created); 1.985 + nscoord rowHeight = GetRowHeightAppUnits(); 1.986 + while (box) { 1.987 + if (created && mRowsToPrepend > 0) 1.988 + --mRowsToPrepend; 1.989 + 1.990 + // if the row height is 0 then fail. Wait until someone 1.991 + // laid out and sets the row height. 1.992 + if (rowHeight == 0) 1.993 + return; 1.994 + 1.995 + availableHeight -= rowHeight; 1.996 + 1.997 + // should we continue? Is the enought height? 1.998 + if (!ContinueReflow(availableHeight)) 1.999 + break; 1.1000 + 1.1001 + // get the next tree box. Create one if needed. 1.1002 + box = GetNextItemBox(box, 0, &created); 1.1003 + } 1.1004 + 1.1005 + mRowsToPrepend = 0; 1.1006 + mLinkupFrame = nullptr; 1.1007 +} 1.1008 + 1.1009 +void 1.1010 +nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose) 1.1011 +{ 1.1012 + // We need to destroy frames until our row count has been properly 1.1013 + // reduced. A reflow will then pick up and create the new frames. 1.1014 + nsIFrame* childFrame = GetFirstFrame(); 1.1015 + nsBoxLayoutState state(PresContext()); 1.1016 + 1.1017 + nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor(); 1.1018 + fc->BeginUpdate(); 1.1019 + while (childFrame && aRowsToLose > 0) { 1.1020 + --aRowsToLose; 1.1021 + 1.1022 + nsIFrame* nextFrame = childFrame->GetNextSibling(); 1.1023 + RemoveChildFrame(state, childFrame); 1.1024 + 1.1025 + mTopFrame = childFrame = nextFrame; 1.1026 + } 1.1027 + fc->EndUpdate(); 1.1028 + 1.1029 + PresContext()->PresShell()-> 1.1030 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1031 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1032 +} 1.1033 + 1.1034 +void 1.1035 +nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose) 1.1036 +{ 1.1037 + // We need to destroy frames until our row count has been properly 1.1038 + // reduced. A reflow will then pick up and create the new frames. 1.1039 + nsIFrame* childFrame = GetLastFrame(); 1.1040 + nsBoxLayoutState state(PresContext()); 1.1041 + 1.1042 + nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor(); 1.1043 + fc->BeginUpdate(); 1.1044 + while (childFrame && aRowsToLose > 0) { 1.1045 + --aRowsToLose; 1.1046 + 1.1047 + nsIFrame* prevFrame; 1.1048 + prevFrame = childFrame->GetPrevSibling(); 1.1049 + RemoveChildFrame(state, childFrame); 1.1050 + 1.1051 + mBottomFrame = childFrame = prevFrame; 1.1052 + } 1.1053 + fc->EndUpdate(); 1.1054 + 1.1055 + PresContext()->PresShell()-> 1.1056 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1057 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1058 +} 1.1059 + 1.1060 +static bool 1.1061 +IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild, 1.1062 + nsIFrame** aChildFrame) 1.1063 +{ 1.1064 + *aChildFrame = nullptr; 1.1065 + if (!aChild->IsXUL() || aChild->Tag() != nsGkAtoms::listitem) { 1.1066 + return false; 1.1067 + } 1.1068 + nsIFrame* existingFrame = aChild->GetPrimaryFrame(); 1.1069 + if (existingFrame && existingFrame->GetParent() != aParent) { 1.1070 + return false; 1.1071 + } 1.1072 + *aChildFrame = existingFrame; 1.1073 + return true; 1.1074 +} 1.1075 + 1.1076 +// 1.1077 +// Get the nsIFrame for the first visible listitem, and if none exists, 1.1078 +// create one. 1.1079 +// 1.1080 +nsIFrame* 1.1081 +nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset, bool* aCreated) 1.1082 +{ 1.1083 + if (aCreated) 1.1084 + *aCreated = false; 1.1085 + 1.1086 + // Clear ourselves out. 1.1087 + mBottomFrame = mTopFrame; 1.1088 + 1.1089 + if (mTopFrame) { 1.1090 + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; 1.1091 + } 1.1092 + 1.1093 + // top frame was cleared out 1.1094 + mTopFrame = GetFirstFrame(); 1.1095 + mBottomFrame = mTopFrame; 1.1096 + 1.1097 + if (mTopFrame && mRowsToPrepend <= 0) { 1.1098 + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; 1.1099 + } 1.1100 + 1.1101 + // At this point, we either have no frames at all, 1.1102 + // or the user has scrolled upwards, leaving frames 1.1103 + // to be created at the top. Let's determine which 1.1104 + // content needs a new frame first. 1.1105 + 1.1106 + nsCOMPtr<nsIContent> startContent; 1.1107 + if (mTopFrame && mRowsToPrepend > 0) { 1.1108 + // We need to insert rows before the top frame 1.1109 + nsIContent* topContent = mTopFrame->GetContent(); 1.1110 + nsIContent* topParent = topContent->GetParent(); 1.1111 + int32_t contentIndex = topParent->IndexOf(topContent); 1.1112 + contentIndex -= aOffset; 1.1113 + if (contentIndex < 0) 1.1114 + return nullptr; 1.1115 + startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend); 1.1116 + } else { 1.1117 + // This will be the first item frame we create. Use the content 1.1118 + // at the current index, which is the first index scrolled into view 1.1119 + GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent)); 1.1120 + } 1.1121 + 1.1122 + if (startContent) { 1.1123 + nsIFrame* existingFrame; 1.1124 + if (!IsListItemChild(this, startContent, &existingFrame)) { 1.1125 + return GetFirstItemBox(++aOffset, aCreated); 1.1126 + } 1.1127 + if (existingFrame) { 1.1128 + return existingFrame->IsBoxFrame() ? existingFrame : nullptr; 1.1129 + } 1.1130 + 1.1131 + // Either append the new frame, or prepend it (at index 0) 1.1132 + // XXX check here if frame was even created, it may not have been if 1.1133 + // display: none was on listitem content 1.1134 + bool isAppend = mRowsToPrepend <= 0; 1.1135 + 1.1136 + nsPresContext* presContext = PresContext(); 1.1137 + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); 1.1138 + nsIFrame* topFrame = nullptr; 1.1139 + fc->CreateListBoxContent(presContext, this, nullptr, startContent, 1.1140 + &topFrame, isAppend, false, nullptr); 1.1141 + mTopFrame = topFrame; 1.1142 + if (mTopFrame) { 1.1143 + if (aCreated) 1.1144 + *aCreated = true; 1.1145 + 1.1146 + mBottomFrame = mTopFrame; 1.1147 + 1.1148 + return mTopFrame->IsBoxFrame() ? mTopFrame : nullptr; 1.1149 + } else 1.1150 + return GetFirstItemBox(++aOffset, 0); 1.1151 + } 1.1152 + 1.1153 + return nullptr; 1.1154 +} 1.1155 + 1.1156 +// 1.1157 +// Get the nsIFrame for the next visible listitem after aBox, and if none 1.1158 +// exists, create one. 1.1159 +// 1.1160 +nsIFrame* 1.1161 +nsListBoxBodyFrame::GetNextItemBox(nsIFrame* aBox, int32_t aOffset, 1.1162 + bool* aCreated) 1.1163 +{ 1.1164 + if (aCreated) 1.1165 + *aCreated = false; 1.1166 + 1.1167 + nsIFrame* result = aBox->GetNextSibling(); 1.1168 + 1.1169 + if (!result || result == mLinkupFrame || mRowsToPrepend > 0) { 1.1170 + // No result found. See if there's a content node that wants a frame. 1.1171 + nsIContent* prevContent = aBox->GetContent(); 1.1172 + nsIContent* parentContent = prevContent->GetParent(); 1.1173 + 1.1174 + int32_t i = parentContent->IndexOf(prevContent); 1.1175 + 1.1176 + uint32_t childCount = parentContent->GetChildCount(); 1.1177 + if (((uint32_t)i + aOffset + 1) < childCount) { 1.1178 + // There is a content node that wants a frame. 1.1179 + nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1); 1.1180 + 1.1181 + nsIFrame* existingFrame; 1.1182 + if (!IsListItemChild(this, nextContent, &existingFrame)) { 1.1183 + return GetNextItemBox(aBox, ++aOffset, aCreated); 1.1184 + } 1.1185 + if (!existingFrame) { 1.1186 + // Either append the new frame, or insert it after the current frame 1.1187 + bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0; 1.1188 + nsIFrame* prevFrame = isAppend ? nullptr : aBox; 1.1189 + 1.1190 + nsPresContext* presContext = PresContext(); 1.1191 + nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor(); 1.1192 + fc->CreateListBoxContent(presContext, this, prevFrame, nextContent, 1.1193 + &result, isAppend, false, nullptr); 1.1194 + 1.1195 + if (result) { 1.1196 + if (aCreated) 1.1197 + *aCreated = true; 1.1198 + } else 1.1199 + return GetNextItemBox(aBox, ++aOffset, aCreated); 1.1200 + } else { 1.1201 + result = existingFrame; 1.1202 + } 1.1203 + 1.1204 + mLinkupFrame = nullptr; 1.1205 + } 1.1206 + } 1.1207 + 1.1208 + if (!result) 1.1209 + return nullptr; 1.1210 + 1.1211 + mBottomFrame = result; 1.1212 + 1.1213 + NS_ASSERTION(!result->IsBoxFrame() || result->GetParent() == this, 1.1214 + "returning frame that is not in childlist"); 1.1215 + 1.1216 + return result->IsBoxFrame() ? result : nullptr; 1.1217 +} 1.1218 + 1.1219 +bool 1.1220 +nsListBoxBodyFrame::ContinueReflow(nscoord height) 1.1221 +{ 1.1222 +#ifdef ACCESSIBILITY 1.1223 + if (nsIPresShell::IsAccessibilityActive()) { 1.1224 + // Create all the frames at once so screen readers and 1.1225 + // onscreen keyboards can see the full list right away 1.1226 + return true; 1.1227 + } 1.1228 +#endif 1.1229 + 1.1230 + if (height <= 0) { 1.1231 + nsIFrame* lastChild = GetLastFrame(); 1.1232 + nsIFrame* startingPoint = mBottomFrame; 1.1233 + if (startingPoint == nullptr) { 1.1234 + // We just want to delete everything but the first item. 1.1235 + startingPoint = GetFirstFrame(); 1.1236 + } 1.1237 + 1.1238 + if (lastChild != startingPoint) { 1.1239 + // We have some hangers on (probably caused by shrinking the size of the window). 1.1240 + // Nuke them. 1.1241 + nsIFrame* currFrame = startingPoint->GetNextSibling(); 1.1242 + nsBoxLayoutState state(PresContext()); 1.1243 + 1.1244 + nsCSSFrameConstructor* fc = 1.1245 + PresContext()->PresShell()->FrameConstructor(); 1.1246 + fc->BeginUpdate(); 1.1247 + while (currFrame) { 1.1248 + nsIFrame* nextFrame = currFrame->GetNextSibling(); 1.1249 + RemoveChildFrame(state, currFrame); 1.1250 + currFrame = nextFrame; 1.1251 + } 1.1252 + fc->EndUpdate(); 1.1253 + 1.1254 + PresContext()->PresShell()-> 1.1255 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1256 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1257 + } 1.1258 + return false; 1.1259 + } 1.1260 + else 1.1261 + return true; 1.1262 +} 1.1263 + 1.1264 +NS_IMETHODIMP 1.1265 +nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList) 1.1266 +{ 1.1267 + // append them after 1.1268 + nsBoxLayoutState state(PresContext()); 1.1269 + const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nullptr, aFrameList); 1.1270 + if (mLayoutManager) 1.1271 + mLayoutManager->ChildrenAppended(this, state, newFrames); 1.1272 + PresContext()->PresShell()-> 1.1273 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1274 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1275 + 1.1276 + return NS_OK; 1.1277 +} 1.1278 + 1.1279 +NS_IMETHODIMP 1.1280 +nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame, 1.1281 + nsFrameList& aFrameList) 1.1282 +{ 1.1283 + // insert the frames to our info list 1.1284 + nsBoxLayoutState state(PresContext()); 1.1285 + const nsFrameList::Slice& newFrames = 1.1286 + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); 1.1287 + if (mLayoutManager) 1.1288 + mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames); 1.1289 + PresContext()->PresShell()-> 1.1290 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1291 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1292 + 1.1293 + return NS_OK; 1.1294 +} 1.1295 + 1.1296 +// 1.1297 +// Called by nsCSSFrameConstructor when a new listitem content is inserted. 1.1298 +// 1.1299 +void 1.1300 +nsListBoxBodyFrame::OnContentInserted(nsPresContext* aPresContext, nsIContent* aChildContent) 1.1301 +{ 1.1302 + if (mRowCount >= 0) 1.1303 + ++mRowCount; 1.1304 + 1.1305 + // The RDF content builder will build content nodes such that they are all 1.1306 + // ready when OnContentInserted is first called, meaning the first call 1.1307 + // to CreateRows will create all the frames, but OnContentInserted will 1.1308 + // still be called again for each content node - so we need to make sure 1.1309 + // that the frame for each content node hasn't already been created. 1.1310 + nsIFrame* childFrame = aChildContent->GetPrimaryFrame(); 1.1311 + if (childFrame) 1.1312 + return; 1.1313 + 1.1314 + int32_t siblingIndex; 1.1315 + nsCOMPtr<nsIContent> nextSiblingContent; 1.1316 + GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex); 1.1317 + 1.1318 + // if we're inserting our item before the first visible content, 1.1319 + // then we need to shift all rows down by one 1.1320 + if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) { 1.1321 + mTopFrame = nullptr; 1.1322 + mRowsToPrepend = 1; 1.1323 + } else if (nextSiblingContent) { 1.1324 + // we may be inserting before a frame that is on screen 1.1325 + nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame(); 1.1326 + mLinkupFrame = nextSiblingFrame; 1.1327 + } 1.1328 + 1.1329 + CreateRows(); 1.1330 + PresContext()->PresShell()-> 1.1331 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1332 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1333 +} 1.1334 + 1.1335 +// 1.1336 +// Called by nsCSSFrameConstructor when listitem content is removed. 1.1337 +// 1.1338 +void 1.1339 +nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext, 1.1340 + nsIContent* aContainer, 1.1341 + nsIFrame* aChildFrame, 1.1342 + nsIContent* aOldNextSibling) 1.1343 +{ 1.1344 + NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this, 1.1345 + "Removing frame that's not our child... Not good"); 1.1346 + 1.1347 + if (mRowCount >= 0) 1.1348 + --mRowCount; 1.1349 + 1.1350 + if (aContainer) { 1.1351 + if (!aChildFrame) { 1.1352 + // The row we are removing is out of view, so we need to try to 1.1353 + // determine the index of its next sibling. 1.1354 + int32_t siblingIndex = -1; 1.1355 + if (aOldNextSibling) { 1.1356 + nsCOMPtr<nsIContent> nextSiblingContent; 1.1357 + GetListItemNextSibling(aOldNextSibling, 1.1358 + getter_AddRefs(nextSiblingContent), 1.1359 + siblingIndex); 1.1360 + } 1.1361 + 1.1362 + // if the row being removed is off-screen and above the top frame, we need to 1.1363 + // adjust our top index and tell the scrollbar to shift up one row. 1.1364 + if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) { 1.1365 + NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0"); 1.1366 + --mCurrentIndex; 1.1367 + mYPosition = mCurrentIndex*mRowHeight; 1.1368 + nsWeakFrame weakChildFrame(aChildFrame); 1.1369 + VerticalScroll(mYPosition); 1.1370 + if (!weakChildFrame.IsAlive()) { 1.1371 + return; 1.1372 + } 1.1373 + } 1.1374 + } else if (mCurrentIndex > 0) { 1.1375 + // At this point, we know we have a scrollbar, and we need to know 1.1376 + // if we are scrolled to the last row. In this case, the behavior 1.1377 + // of the scrollbar is to stay locked to the bottom. Since we are 1.1378 + // removing visible content, the first visible row will have to move 1.1379 + // down by one, and we will have to insert a new frame at the top. 1.1380 + 1.1381 + // if the last content node has a frame, we are scrolled to the bottom 1.1382 + nsIContent* lastChild = nullptr; 1.1383 + FlattenedChildIterator iter(mContent); 1.1384 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.1385 + lastChild = child; 1.1386 + } 1.1387 + 1.1388 + if (lastChild) { 1.1389 + nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame(); 1.1390 + 1.1391 + if (lastChildFrame) { 1.1392 + mTopFrame = nullptr; 1.1393 + mRowsToPrepend = 1; 1.1394 + --mCurrentIndex; 1.1395 + mYPosition = mCurrentIndex*mRowHeight; 1.1396 + nsWeakFrame weakChildFrame(aChildFrame); 1.1397 + VerticalScroll(mYPosition); 1.1398 + if (!weakChildFrame.IsAlive()) { 1.1399 + return; 1.1400 + } 1.1401 + } 1.1402 + } 1.1403 + } 1.1404 + } 1.1405 + 1.1406 + // if we're removing the top row, the new top row is the next row 1.1407 + if (mTopFrame && mTopFrame == aChildFrame) 1.1408 + mTopFrame = mTopFrame->GetNextSibling(); 1.1409 + 1.1410 + // Go ahead and delete the frame. 1.1411 + nsBoxLayoutState state(aPresContext); 1.1412 + if (aChildFrame) { 1.1413 + RemoveChildFrame(state, aChildFrame); 1.1414 + } 1.1415 + 1.1416 + PresContext()->PresShell()-> 1.1417 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1418 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1419 +} 1.1420 + 1.1421 +void 1.1422 +nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex, nsIContent** aContent) 1.1423 +{ 1.1424 + *aContent = nullptr; 1.1425 + 1.1426 + int32_t itemsFound = 0; 1.1427 + FlattenedChildIterator iter(mContent); 1.1428 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.1429 + if (child->Tag() == nsGkAtoms::listitem) { 1.1430 + ++itemsFound; 1.1431 + if (itemsFound-1 == aIndex) { 1.1432 + *aContent = child; 1.1433 + NS_IF_ADDREF(*aContent); 1.1434 + return; 1.1435 + } 1.1436 + } 1.1437 + } 1.1438 +} 1.1439 + 1.1440 +void 1.1441 +nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex) 1.1442 +{ 1.1443 + *aContent = nullptr; 1.1444 + aSiblingIndex = -1; 1.1445 + nsIContent *prevKid = nullptr; 1.1446 + FlattenedChildIterator iter(mContent); 1.1447 + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { 1.1448 + if (child->Tag() == nsGkAtoms::listitem) { 1.1449 + ++aSiblingIndex; 1.1450 + if (prevKid == aListItem) { 1.1451 + *aContent = child; 1.1452 + NS_IF_ADDREF(*aContent); 1.1453 + return; 1.1454 + } 1.1455 + } 1.1456 + prevKid = child; 1.1457 + } 1.1458 + 1.1459 + aSiblingIndex = -1; // no match, so there is no next sibling 1.1460 +} 1.1461 + 1.1462 +void 1.1463 +nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState, 1.1464 + nsIFrame *aFrame) 1.1465 +{ 1.1466 + MOZ_ASSERT(mFrames.ContainsFrame(aFrame)); 1.1467 + MOZ_ASSERT(aFrame != GetContentInsertionFrame()); 1.1468 + 1.1469 +#ifdef ACCESSIBILITY 1.1470 + nsAccessibilityService* accService = nsIPresShell::AccService(); 1.1471 + if (accService) { 1.1472 + nsIContent* content = aFrame->GetContent(); 1.1473 + accService->ContentRemoved(PresContext()->PresShell(), content->GetParent(), 1.1474 + content); 1.1475 + } 1.1476 +#endif 1.1477 + 1.1478 + mFrames.RemoveFrame(aFrame); 1.1479 + if (mLayoutManager) 1.1480 + mLayoutManager->ChildrenRemoved(this, aState, aFrame); 1.1481 + aFrame->Destroy(); 1.1482 +} 1.1483 + 1.1484 +// Creation Routines /////////////////////////////////////////////////////////////////////// 1.1485 + 1.1486 +already_AddRefed<nsBoxLayout> NS_NewListBoxLayout(); 1.1487 + 1.1488 +nsIFrame* 1.1489 +NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.1490 +{ 1.1491 + nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout(); 1.1492 + return new (aPresShell) nsListBoxBodyFrame(aPresShell, aContext, layout); 1.1493 +} 1.1494 + 1.1495 +NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame)