layout/xul/nsListBoxBodyFrame.cpp

changeset 0
6474c204b198
     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)

mercurial