nsListBoxBodyFrame.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "nsListBoxBodyFrame.h"
  6. #include "nsListBoxLayout.h"
  7. #include "mozilla/MathAlgorithms.h"
  8. #include "nsCOMPtr.h"
  9. #include "nsGridRowGroupLayout.h"
  10. #include "nsIServiceManager.h"
  11. #include "nsGkAtoms.h"
  12. #include "nsIContent.h"
  13. #include "nsNameSpaceManager.h"
  14. #include "nsIDocument.h"
  15. #include "nsIDOMMouseEvent.h"
  16. #include "nsIDOMElement.h"
  17. #include "nsIDOMNodeList.h"
  18. #include "nsCSSFrameConstructor.h"
  19. #include "nsIScrollableFrame.h"
  20. #include "nsScrollbarFrame.h"
  21. #include "nsView.h"
  22. #include "nsViewManager.h"
  23. #include "nsStyleContext.h"
  24. #include "nsFontMetrics.h"
  25. #include "nsITimer.h"
  26. #include "mozilla/StyleSetHandle.h"
  27. #include "mozilla/StyleSetHandleInlines.h"
  28. #include "nsPIBoxObject.h"
  29. #include "nsLayoutUtils.h"
  30. #include "nsPIListBoxObject.h"
  31. #include "nsContentUtils.h"
  32. #include "ChildIterator.h"
  33. #include "nsRenderingContext.h"
  34. #include "prtime.h"
  35. #include <algorithm>
  36. #ifdef ACCESSIBILITY
  37. #include "nsAccessibilityService.h"
  38. #endif
  39. using namespace mozilla;
  40. using namespace mozilla::dom;
  41. /////////////// nsListScrollSmoother //////////////////
  42. /* A mediator used to smooth out scrolling. It works by seeing if
  43. * we have time to scroll the amount of rows requested. This is determined
  44. * by measuring how long it takes to scroll a row. If we can scroll the
  45. * rows in time we do so. If not we start a timer and skip the request. We
  46. * do this until the timer finally first because the user has stopped moving
  47. * the mouse. Then do all the queued requests in on shot.
  48. */
  49. // the longest amount of time that can go by before the use
  50. // notices it as a delay.
  51. #define USER_TIME_THRESHOLD 150000
  52. // how long it takes to layout a single row initial value.
  53. // we will time this after we scroll a few rows.
  54. #define TIME_PER_ROW_INITAL 50000
  55. // if we decide we can't layout the rows in the amount of time. How long
  56. // do we wait before checking again?
  57. #define SMOOTH_INTERVAL 100
  58. class nsListScrollSmoother final : public nsITimerCallback
  59. {
  60. private:
  61. virtual ~nsListScrollSmoother();
  62. public:
  63. NS_DECL_ISUPPORTS
  64. explicit nsListScrollSmoother(nsListBoxBodyFrame* aOuter);
  65. // nsITimerCallback
  66. NS_DECL_NSITIMERCALLBACK
  67. void Start();
  68. void Stop();
  69. bool IsRunning();
  70. nsCOMPtr<nsITimer> mRepeatTimer;
  71. int32_t mDelta;
  72. nsListBoxBodyFrame* mOuter;
  73. };
  74. nsListScrollSmoother::nsListScrollSmoother(nsListBoxBodyFrame* aOuter)
  75. {
  76. mDelta = 0;
  77. mOuter = aOuter;
  78. }
  79. nsListScrollSmoother::~nsListScrollSmoother()
  80. {
  81. Stop();
  82. }
  83. NS_IMETHODIMP
  84. nsListScrollSmoother::Notify(nsITimer *timer)
  85. {
  86. Stop();
  87. NS_ASSERTION(mOuter, "mOuter is null, see bug #68365");
  88. if (!mOuter) return NS_OK;
  89. // actually do some work.
  90. mOuter->InternalPositionChangedCallback();
  91. return NS_OK;
  92. }
  93. bool
  94. nsListScrollSmoother::IsRunning()
  95. {
  96. return mRepeatTimer ? true : false;
  97. }
  98. void
  99. nsListScrollSmoother::Start()
  100. {
  101. Stop();
  102. mRepeatTimer = do_CreateInstance("@mozilla.org/timer;1");
  103. mRepeatTimer->InitWithCallback(this, SMOOTH_INTERVAL, nsITimer::TYPE_ONE_SHOT);
  104. }
  105. void
  106. nsListScrollSmoother::Stop()
  107. {
  108. if ( mRepeatTimer ) {
  109. mRepeatTimer->Cancel();
  110. mRepeatTimer = nullptr;
  111. }
  112. }
  113. NS_IMPL_ISUPPORTS(nsListScrollSmoother, nsITimerCallback)
  114. /////////////// nsListBoxBodyFrame //////////////////
  115. nsListBoxBodyFrame::nsListBoxBodyFrame(nsStyleContext* aContext,
  116. nsBoxLayout* aLayoutManager)
  117. : nsBoxFrame(aContext, false, aLayoutManager),
  118. mTopFrame(nullptr),
  119. mBottomFrame(nullptr),
  120. mLinkupFrame(nullptr),
  121. mScrollSmoother(nullptr),
  122. mRowsToPrepend(0),
  123. mRowCount(-1),
  124. mRowHeight(0),
  125. mAvailableHeight(0),
  126. mStringWidth(-1),
  127. mCurrentIndex(0),
  128. mOldIndex(0),
  129. mYPosition(0),
  130. mTimePerRow(TIME_PER_ROW_INITAL),
  131. mRowHeightWasSet(false),
  132. mScrolling(false),
  133. mAdjustScroll(false),
  134. mReflowCallbackPosted(false)
  135. {
  136. }
  137. nsListBoxBodyFrame::~nsListBoxBodyFrame()
  138. {
  139. NS_IF_RELEASE(mScrollSmoother);
  140. #if USE_TIMER_TO_DELAY_SCROLLING
  141. StopScrollTracking();
  142. mAutoScrollTimer = nullptr;
  143. #endif
  144. }
  145. NS_QUERYFRAME_HEAD(nsListBoxBodyFrame)
  146. NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
  147. NS_QUERYFRAME_ENTRY(nsListBoxBodyFrame)
  148. NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
  149. ////////// nsIFrame /////////////////
  150. void
  151. nsListBoxBodyFrame::Init(nsIContent* aContent,
  152. nsContainerFrame* aParent,
  153. nsIFrame* aPrevInFlow)
  154. {
  155. nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
  156. // Don't call nsLayoutUtils::GetScrollableFrameFor since we are not its
  157. // scrollframe child yet.
  158. nsIScrollableFrame* scrollFrame = do_QueryFrame(aParent);
  159. if (scrollFrame) {
  160. nsIFrame* verticalScrollbar = scrollFrame->GetScrollbarBox(true);
  161. nsScrollbarFrame* scrollbarFrame = do_QueryFrame(verticalScrollbar);
  162. if (scrollbarFrame) {
  163. scrollbarFrame->SetScrollbarMediatorContent(GetContent());
  164. }
  165. }
  166. RefPtr<nsFontMetrics> fm =
  167. nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
  168. mRowHeight = fm->MaxHeight();
  169. }
  170. void
  171. nsListBoxBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
  172. {
  173. // make sure we cancel any posted callbacks.
  174. if (mReflowCallbackPosted)
  175. PresContext()->PresShell()->CancelReflowCallback(this);
  176. // Revoke any pending position changed events
  177. for (uint32_t i = 0; i < mPendingPositionChangeEvents.Length(); ++i) {
  178. mPendingPositionChangeEvents[i]->Revoke();
  179. }
  180. // Make sure we tell our listbox's box object we're being destroyed.
  181. if (mBoxObject) {
  182. mBoxObject->ClearCachedValues();
  183. }
  184. nsBoxFrame::DestroyFrom(aDestructRoot);
  185. }
  186. nsresult
  187. nsListBoxBodyFrame::AttributeChanged(int32_t aNameSpaceID,
  188. nsIAtom* aAttribute,
  189. int32_t aModType)
  190. {
  191. nsresult rv = NS_OK;
  192. if (aAttribute == nsGkAtoms::rows) {
  193. PresContext()->PresShell()->
  194. FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
  195. }
  196. else
  197. rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
  198. return rv;
  199. }
  200. /* virtual */ void
  201. nsListBoxBodyFrame::MarkIntrinsicISizesDirty()
  202. {
  203. mStringWidth = -1;
  204. nsBoxFrame::MarkIntrinsicISizesDirty();
  205. }
  206. /////////// nsBox ///////////////
  207. NS_IMETHODIMP
  208. nsListBoxBodyFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState)
  209. {
  210. if (mScrolling)
  211. aBoxLayoutState.SetPaintingDisabled(true);
  212. nsresult rv = nsBoxFrame::DoXULLayout(aBoxLayoutState);
  213. // determine the real height for the scrollable area from the total number
  214. // of rows, since non-visible rows don't yet have frames
  215. nsRect rect(nsPoint(0, 0), GetSize());
  216. nsOverflowAreas overflow(rect, rect);
  217. if (mLayoutManager) {
  218. nsIFrame* childFrame = mFrames.FirstChild();
  219. while (childFrame) {
  220. ConsiderChildOverflow(overflow, childFrame);
  221. childFrame = childFrame->GetNextSibling();
  222. }
  223. nsSize prefSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState);
  224. NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
  225. nsRect& o = overflow.Overflow(otype);
  226. o.height = std::max(o.height, prefSize.height);
  227. }
  228. }
  229. FinishAndStoreOverflow(overflow, GetSize());
  230. if (mScrolling)
  231. aBoxLayoutState.SetPaintingDisabled(false);
  232. // if we are scrolled and the row height changed
  233. // make sure we are scrolled to a correct index.
  234. if (mAdjustScroll)
  235. PostReflowCallback();
  236. return rv;
  237. }
  238. nsSize
  239. nsListBoxBodyFrame::GetXULMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState)
  240. {
  241. nsSize result(0, 0);
  242. if (nsContentUtils::HasNonEmptyAttr(GetContent(), kNameSpaceID_None,
  243. nsGkAtoms::sizemode)) {
  244. result = GetXULPrefSize(aBoxLayoutState);
  245. result.height = 0;
  246. nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
  247. if (scrollFrame &&
  248. scrollFrame->GetScrollStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
  249. nsMargin scrollbars =
  250. scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
  251. result.width += scrollbars.left + scrollbars.right;
  252. }
  253. }
  254. return result;
  255. }
  256. nsSize
  257. nsListBoxBodyFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState)
  258. {
  259. nsSize pref = nsBoxFrame::GetXULPrefSize(aBoxLayoutState);
  260. int32_t size = GetFixedRowSize();
  261. if (size > -1)
  262. pref.height = size*GetRowHeightAppUnits();
  263. nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
  264. if (scrollFrame &&
  265. scrollFrame->GetScrollStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
  266. nsMargin scrollbars = scrollFrame->GetDesiredScrollbarSizes(&aBoxLayoutState);
  267. pref.width += scrollbars.left + scrollbars.right;
  268. }
  269. return pref;
  270. }
  271. ///////////// nsIScrollbarMediator ///////////////
  272. void
  273. nsListBoxBodyFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
  274. nsIScrollbarMediator::ScrollSnapMode aSnap)
  275. {
  276. // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
  277. MOZ_ASSERT(aScrollbar != nullptr);
  278. aScrollbar->SetIncrementToPage(aDirection);
  279. nsWeakFrame weakFrame(this);
  280. int32_t newPos = aScrollbar->MoveToNewPosition();
  281. if (!weakFrame.IsAlive()) {
  282. return;
  283. }
  284. UpdateIndex(newPos);
  285. }
  286. void
  287. nsListBoxBodyFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, int32_t aDirection,
  288. nsIScrollbarMediator::ScrollSnapMode aSnap)
  289. {
  290. // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
  291. MOZ_ASSERT(aScrollbar != nullptr);
  292. aScrollbar->SetIncrementToWhole(aDirection);
  293. nsWeakFrame weakFrame(this);
  294. int32_t newPos = aScrollbar->MoveToNewPosition();
  295. if (!weakFrame.IsAlive()) {
  296. return;
  297. }
  298. UpdateIndex(newPos);
  299. }
  300. void
  301. nsListBoxBodyFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, int32_t aDirection,
  302. nsIScrollbarMediator::ScrollSnapMode aSnap)
  303. {
  304. // CSS Scroll Snapping is not enabled for XUL, aSnap is ignored
  305. MOZ_ASSERT(aScrollbar != nullptr);
  306. aScrollbar->SetIncrementToLine(aDirection);
  307. nsWeakFrame weakFrame(this);
  308. int32_t newPos = aScrollbar->MoveToNewPosition();
  309. if (!weakFrame.IsAlive()) {
  310. return;
  311. }
  312. UpdateIndex(newPos);
  313. }
  314. void
  315. nsListBoxBodyFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar)
  316. {
  317. nsWeakFrame weakFrame(this);
  318. int32_t newPos = aScrollbar->MoveToNewPosition();
  319. if (!weakFrame.IsAlive()) {
  320. return;
  321. }
  322. UpdateIndex(newPos);
  323. }
  324. int32_t
  325. nsListBoxBodyFrame::ToRowIndex(nscoord aPos) const
  326. {
  327. return NS_roundf(float(std::max(aPos, 0)) / mRowHeight);
  328. }
  329. void
  330. nsListBoxBodyFrame::ThumbMoved(nsScrollbarFrame* aScrollbar,
  331. nscoord aOldPos,
  332. nscoord aNewPos)
  333. {
  334. if (mScrolling || mRowHeight == 0)
  335. return;
  336. int32_t newIndex = ToRowIndex(aNewPos);
  337. if (newIndex == mCurrentIndex) {
  338. return;
  339. }
  340. int32_t rowDelta = newIndex - mCurrentIndex;
  341. nsListScrollSmoother* smoother = GetSmoother();
  342. // if we can't scroll the rows in time then start a timer. We will eat
  343. // events until the user stops moving and the timer stops.
  344. if (smoother->IsRunning() || Abs(rowDelta)*mTimePerRow > USER_TIME_THRESHOLD) {
  345. smoother->Stop();
  346. smoother->mDelta = rowDelta;
  347. smoother->Start();
  348. return;
  349. }
  350. smoother->Stop();
  351. mCurrentIndex = newIndex;
  352. smoother->mDelta = 0;
  353. if (mCurrentIndex < 0) {
  354. mCurrentIndex = 0;
  355. return;
  356. }
  357. InternalPositionChanged(rowDelta < 0, Abs(rowDelta));
  358. }
  359. void
  360. nsListBoxBodyFrame::VisibilityChanged(bool aVisible)
  361. {
  362. if (mRowHeight == 0)
  363. return;
  364. int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
  365. if (lastPageTopRow < 0)
  366. lastPageTopRow = 0;
  367. int32_t delta = mCurrentIndex - lastPageTopRow;
  368. if (delta > 0) {
  369. mCurrentIndex = lastPageTopRow;
  370. InternalPositionChanged(true, delta);
  371. }
  372. }
  373. nsIFrame*
  374. nsListBoxBodyFrame::GetScrollbarBox(bool aVertical)
  375. {
  376. nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(this);
  377. return scrollFrame ? scrollFrame->GetScrollbarBox(true) : nullptr;
  378. }
  379. void
  380. nsListBoxBodyFrame::UpdateIndex(int32_t aNewPos)
  381. {
  382. int32_t newIndex = ToRowIndex(nsPresContext::CSSPixelsToAppUnits(aNewPos));
  383. if (newIndex == mCurrentIndex) {
  384. return;
  385. }
  386. bool up = newIndex < mCurrentIndex;
  387. int32_t indexDelta = Abs(newIndex - mCurrentIndex);
  388. mCurrentIndex = newIndex;
  389. InternalPositionChanged(up, indexDelta);
  390. }
  391. ///////////// nsIReflowCallback ///////////////
  392. bool
  393. nsListBoxBodyFrame::ReflowFinished()
  394. {
  395. nsAutoScriptBlocker scriptBlocker;
  396. // now create or destroy any rows as needed
  397. CreateRows();
  398. // keep scrollbar in sync
  399. if (mAdjustScroll) {
  400. VerticalScroll(mYPosition);
  401. mAdjustScroll = false;
  402. }
  403. // if the row height changed then mark everything as a style change.
  404. // That will dirty the entire listbox
  405. if (mRowHeightWasSet) {
  406. PresContext()->PresShell()->
  407. FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
  408. int32_t pos = mCurrentIndex * mRowHeight;
  409. if (mYPosition != pos)
  410. mAdjustScroll = true;
  411. mRowHeightWasSet = false;
  412. }
  413. mReflowCallbackPosted = false;
  414. return true;
  415. }
  416. void
  417. nsListBoxBodyFrame::ReflowCallbackCanceled()
  418. {
  419. mReflowCallbackPosted = false;
  420. }
  421. ///////// ListBoxObject ///////////////
  422. int32_t
  423. nsListBoxBodyFrame::GetNumberOfVisibleRows()
  424. {
  425. return mRowHeight ? GetAvailableHeight() / mRowHeight : 0;
  426. }
  427. int32_t
  428. nsListBoxBodyFrame::GetIndexOfFirstVisibleRow()
  429. {
  430. return mCurrentIndex;
  431. }
  432. nsresult
  433. nsListBoxBodyFrame::EnsureIndexIsVisible(int32_t aRowIndex)
  434. {
  435. if (aRowIndex < 0)
  436. return NS_ERROR_ILLEGAL_VALUE;
  437. int32_t rows = 0;
  438. if (mRowHeight)
  439. rows = GetAvailableHeight()/mRowHeight;
  440. if (rows <= 0)
  441. rows = 1;
  442. int32_t bottomIndex = mCurrentIndex + rows;
  443. // if row is visible, ignore
  444. if (mCurrentIndex <= aRowIndex && aRowIndex < bottomIndex)
  445. return NS_OK;
  446. int32_t delta;
  447. bool up = aRowIndex < mCurrentIndex;
  448. if (up) {
  449. delta = mCurrentIndex - aRowIndex;
  450. mCurrentIndex = aRowIndex;
  451. }
  452. else {
  453. // Check to be sure we're not scrolling off the bottom of the tree
  454. if (aRowIndex >= GetRowCount())
  455. return NS_ERROR_ILLEGAL_VALUE;
  456. // Bring it just into view.
  457. delta = 1 + (aRowIndex-bottomIndex);
  458. mCurrentIndex += delta;
  459. }
  460. // Safe to not go off an event here, since this is coming from the
  461. // box object.
  462. DoInternalPositionChangedSync(up, delta);
  463. return NS_OK;
  464. }
  465. nsresult
  466. nsListBoxBodyFrame::ScrollByLines(int32_t aNumLines)
  467. {
  468. int32_t scrollIndex = GetIndexOfFirstVisibleRow(),
  469. visibleRows = GetNumberOfVisibleRows();
  470. scrollIndex += aNumLines;
  471. if (scrollIndex < 0)
  472. scrollIndex = 0;
  473. else {
  474. int32_t numRows = GetRowCount();
  475. int32_t lastPageTopRow = numRows - visibleRows;
  476. if (scrollIndex > lastPageTopRow)
  477. scrollIndex = lastPageTopRow;
  478. }
  479. ScrollToIndex(scrollIndex);
  480. return NS_OK;
  481. }
  482. // walks the DOM to get the zero-based row index of the content
  483. nsresult
  484. nsListBoxBodyFrame::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval)
  485. {
  486. if (aItem) {
  487. *_retval = 0;
  488. nsCOMPtr<nsIContent> itemContent(do_QueryInterface(aItem));
  489. FlattenedChildIterator iter(mContent);
  490. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  491. // we hit a list row, count it
  492. if (child->IsXULElement(nsGkAtoms::listitem)) {
  493. // is this it?
  494. if (child == itemContent)
  495. return NS_OK;
  496. ++(*_retval);
  497. }
  498. }
  499. }
  500. // not found
  501. *_retval = -1;
  502. return NS_OK;
  503. }
  504. nsresult
  505. nsListBoxBodyFrame::GetItemAtIndex(int32_t aIndex, nsIDOMElement** aItem)
  506. {
  507. *aItem = nullptr;
  508. if (aIndex < 0)
  509. return NS_OK;
  510. int32_t itemCount = 0;
  511. FlattenedChildIterator iter(mContent);
  512. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  513. // we hit a list row, check if it is the one we are looking for
  514. if (child->IsXULElement(nsGkAtoms::listitem)) {
  515. // is this it?
  516. if (itemCount == aIndex) {
  517. return CallQueryInterface(child, aItem);
  518. }
  519. ++itemCount;
  520. }
  521. }
  522. // not found
  523. return NS_OK;
  524. }
  525. /////////// nsListBoxBodyFrame ///////////////
  526. int32_t
  527. nsListBoxBodyFrame::GetRowCount()
  528. {
  529. if (mRowCount < 0)
  530. ComputeTotalRowCount();
  531. return mRowCount;
  532. }
  533. int32_t
  534. nsListBoxBodyFrame::GetFixedRowSize()
  535. {
  536. nsresult dummy;
  537. nsAutoString rows;
  538. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
  539. if (!rows.IsEmpty())
  540. return rows.ToInteger(&dummy);
  541. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::size, rows);
  542. if (!rows.IsEmpty())
  543. return rows.ToInteger(&dummy);
  544. return -1;
  545. }
  546. void
  547. nsListBoxBodyFrame::SetRowHeight(nscoord aRowHeight)
  548. {
  549. if (aRowHeight > mRowHeight) {
  550. mRowHeight = aRowHeight;
  551. // signal we need to dirty everything
  552. // and we want to be notified after reflow
  553. // so we can create or destory rows as needed
  554. mRowHeightWasSet = true;
  555. PostReflowCallback();
  556. }
  557. }
  558. nscoord
  559. nsListBoxBodyFrame::GetAvailableHeight()
  560. {
  561. nsIScrollableFrame* scrollFrame =
  562. nsLayoutUtils::GetScrollableFrameFor(this);
  563. if (scrollFrame) {
  564. return scrollFrame->GetScrollPortRect().height;
  565. }
  566. return 0;
  567. }
  568. nscoord
  569. nsListBoxBodyFrame::GetYPosition()
  570. {
  571. return mYPosition;
  572. }
  573. nscoord
  574. nsListBoxBodyFrame::ComputeIntrinsicISize(nsBoxLayoutState& aBoxLayoutState)
  575. {
  576. if (mStringWidth != -1)
  577. return mStringWidth;
  578. nscoord largestWidth = 0;
  579. int32_t index = 0;
  580. nsCOMPtr<nsIDOMElement> firstRowEl;
  581. GetItemAtIndex(index, getter_AddRefs(firstRowEl));
  582. nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
  583. if (firstRowContent) {
  584. RefPtr<nsStyleContext> styleContext;
  585. nsPresContext *presContext = aBoxLayoutState.PresContext();
  586. styleContext = presContext->StyleSet()->
  587. ResolveStyleFor(firstRowContent->AsElement(), nullptr);
  588. nscoord width = 0;
  589. nsMargin margin(0,0,0,0);
  590. if (styleContext->StylePadding()->GetPadding(margin))
  591. width += margin.LeftRight();
  592. width += styleContext->StyleBorder()->GetComputedBorder().LeftRight();
  593. if (styleContext->StyleMargin()->GetMargin(margin))
  594. width += margin.LeftRight();
  595. FlattenedChildIterator iter(mContent);
  596. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  597. if (child->IsXULElement(nsGkAtoms::listitem)) {
  598. nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext();
  599. if (rendContext) {
  600. nsAutoString value;
  601. uint32_t textCount = child->GetChildCount();
  602. for (uint32_t j = 0; j < textCount; ++j) {
  603. nsIContent* text = child->GetChildAt(j);
  604. if (text && text->IsNodeOfType(nsINode::eTEXT)) {
  605. text->AppendTextTo(value);
  606. }
  607. }
  608. RefPtr<nsFontMetrics> fm =
  609. nsLayoutUtils::GetFontMetricsForStyleContext(styleContext);
  610. nscoord textWidth =
  611. nsLayoutUtils::AppUnitWidthOfStringBidi(value, this, *fm,
  612. *rendContext);
  613. textWidth += width;
  614. if (textWidth > largestWidth)
  615. largestWidth = textWidth;
  616. }
  617. }
  618. }
  619. }
  620. mStringWidth = largestWidth;
  621. return mStringWidth;
  622. }
  623. void
  624. nsListBoxBodyFrame::ComputeTotalRowCount()
  625. {
  626. mRowCount = 0;
  627. FlattenedChildIterator iter(mContent);
  628. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  629. if (child->IsXULElement(nsGkAtoms::listitem)) {
  630. ++mRowCount;
  631. }
  632. }
  633. }
  634. void
  635. nsListBoxBodyFrame::PostReflowCallback()
  636. {
  637. if (!mReflowCallbackPosted) {
  638. mReflowCallbackPosted = true;
  639. PresContext()->PresShell()->PostReflowCallback(this);
  640. }
  641. }
  642. ////////// scrolling
  643. nsresult
  644. nsListBoxBodyFrame::ScrollToIndex(int32_t aRowIndex)
  645. {
  646. if (( aRowIndex < 0 ) || (mRowHeight == 0))
  647. return NS_OK;
  648. int32_t newIndex = aRowIndex;
  649. int32_t delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
  650. bool up = newIndex < mCurrentIndex;
  651. // Check to be sure we're not scrolling off the bottom of the tree
  652. int32_t lastPageTopRow = GetRowCount() - (GetAvailableHeight() / mRowHeight);
  653. if (lastPageTopRow < 0)
  654. lastPageTopRow = 0;
  655. if (aRowIndex > lastPageTopRow)
  656. return NS_OK;
  657. mCurrentIndex = newIndex;
  658. nsWeakFrame weak(this);
  659. // Since we're going to flush anyway, we need to not do this off an event
  660. DoInternalPositionChangedSync(up, delta);
  661. if (!weak.IsAlive()) {
  662. return NS_OK;
  663. }
  664. // This change has to happen immediately.
  665. // Flush any pending reflow commands.
  666. // XXXbz why, exactly?
  667. mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
  668. return NS_OK;
  669. }
  670. nsresult
  671. nsListBoxBodyFrame::InternalPositionChangedCallback()
  672. {
  673. nsListScrollSmoother* smoother = GetSmoother();
  674. if (smoother->mDelta == 0)
  675. return NS_OK;
  676. mCurrentIndex += smoother->mDelta;
  677. if (mCurrentIndex < 0)
  678. mCurrentIndex = 0;
  679. return DoInternalPositionChangedSync(smoother->mDelta < 0,
  680. smoother->mDelta < 0 ?
  681. -smoother->mDelta : smoother->mDelta);
  682. }
  683. nsresult
  684. nsListBoxBodyFrame::InternalPositionChanged(bool aUp, int32_t aDelta)
  685. {
  686. RefPtr<nsPositionChangedEvent> ev =
  687. new nsPositionChangedEvent(this, aUp, aDelta);
  688. nsresult rv = NS_DispatchToCurrentThread(ev);
  689. if (NS_SUCCEEDED(rv)) {
  690. if (!mPendingPositionChangeEvents.AppendElement(ev)) {
  691. rv = NS_ERROR_OUT_OF_MEMORY;
  692. ev->Revoke();
  693. }
  694. }
  695. return rv;
  696. }
  697. nsresult
  698. nsListBoxBodyFrame::DoInternalPositionChangedSync(bool aUp, int32_t aDelta)
  699. {
  700. nsWeakFrame weak(this);
  701. // Process all the pending position changes first
  702. nsTArray< RefPtr<nsPositionChangedEvent> > temp;
  703. temp.SwapElements(mPendingPositionChangeEvents);
  704. for (uint32_t i = 0; i < temp.Length(); ++i) {
  705. if (weak.IsAlive()) {
  706. temp[i]->Run();
  707. }
  708. temp[i]->Revoke();
  709. }
  710. if (!weak.IsAlive()) {
  711. return NS_OK;
  712. }
  713. return DoInternalPositionChanged(aUp, aDelta);
  714. }
  715. nsresult
  716. nsListBoxBodyFrame::DoInternalPositionChanged(bool aUp, int32_t aDelta)
  717. {
  718. if (aDelta == 0)
  719. return NS_OK;
  720. RefPtr<nsPresContext> presContext(PresContext());
  721. nsBoxLayoutState state(presContext);
  722. // begin timing how long it takes to scroll a row
  723. PRTime start = PR_Now();
  724. nsWeakFrame weakThis(this);
  725. mContent->GetComposedDoc()->FlushPendingNotifications(Flush_Layout);
  726. if (!weakThis.IsAlive()) {
  727. return NS_OK;
  728. }
  729. {
  730. nsAutoScriptBlocker scriptBlocker;
  731. int32_t visibleRows = 0;
  732. if (mRowHeight)
  733. visibleRows = GetAvailableHeight()/mRowHeight;
  734. if (aDelta < visibleRows) {
  735. int32_t loseRows = aDelta;
  736. if (aUp) {
  737. // scrolling up, destroy rows from the bottom downwards
  738. ReverseDestroyRows(loseRows);
  739. mRowsToPrepend += aDelta;
  740. mLinkupFrame = nullptr;
  741. }
  742. else {
  743. // scrolling down, destroy rows from the top upwards
  744. DestroyRows(loseRows);
  745. mRowsToPrepend = 0;
  746. }
  747. }
  748. else {
  749. // We have scrolled so much that all of our current frames will
  750. // go off screen, so blow them all away. Weeee!
  751. nsIFrame *currBox = mFrames.FirstChild();
  752. nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
  753. fc->BeginUpdate();
  754. while (currBox) {
  755. nsIFrame *nextBox = currBox->GetNextSibling();
  756. RemoveChildFrame(state, currBox);
  757. currBox = nextBox;
  758. }
  759. fc->EndUpdate();
  760. }
  761. // clear frame markers so that CreateRows will re-create
  762. mTopFrame = mBottomFrame = nullptr;
  763. mYPosition = mCurrentIndex*mRowHeight;
  764. mScrolling = true;
  765. presContext->PresShell()->
  766. FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
  767. }
  768. if (!weakThis.IsAlive()) {
  769. return NS_OK;
  770. }
  771. // Flush calls CreateRows
  772. // XXXbz there has to be a better way to do this than flushing!
  773. presContext->PresShell()->FlushPendingNotifications(Flush_Layout);
  774. if (!weakThis.IsAlive()) {
  775. return NS_OK;
  776. }
  777. mScrolling = false;
  778. VerticalScroll(mYPosition);
  779. PRTime end = PR_Now();
  780. int32_t newTime = int32_t(end - start) / aDelta;
  781. // average old and new
  782. mTimePerRow = (newTime + mTimePerRow)/2;
  783. return NS_OK;
  784. }
  785. nsListScrollSmoother*
  786. nsListBoxBodyFrame::GetSmoother()
  787. {
  788. if (!mScrollSmoother) {
  789. mScrollSmoother = new nsListScrollSmoother(this);
  790. NS_ASSERTION(mScrollSmoother, "out of memory");
  791. NS_IF_ADDREF(mScrollSmoother);
  792. }
  793. return mScrollSmoother;
  794. }
  795. void
  796. nsListBoxBodyFrame::VerticalScroll(int32_t aPosition)
  797. {
  798. nsIScrollableFrame* scrollFrame
  799. = nsLayoutUtils::GetScrollableFrameFor(this);
  800. if (!scrollFrame) {
  801. return;
  802. }
  803. nsPoint scrollPosition = scrollFrame->GetScrollPosition();
  804. nsWeakFrame weakFrame(this);
  805. scrollFrame->ScrollTo(nsPoint(scrollPosition.x, aPosition),
  806. nsIScrollableFrame::INSTANT);
  807. if (!weakFrame.IsAlive()) {
  808. return;
  809. }
  810. mYPosition = aPosition;
  811. }
  812. ////////// frame and box retrieval
  813. nsIFrame*
  814. nsListBoxBodyFrame::GetFirstFrame()
  815. {
  816. mTopFrame = mFrames.FirstChild();
  817. return mTopFrame;
  818. }
  819. nsIFrame*
  820. nsListBoxBodyFrame::GetLastFrame()
  821. {
  822. return mFrames.LastChild();
  823. }
  824. bool
  825. nsListBoxBodyFrame::SupportsOrdinalsInChildren()
  826. {
  827. return false;
  828. }
  829. ////////// lazy row creation and destruction
  830. void
  831. nsListBoxBodyFrame::CreateRows()
  832. {
  833. // Get our client rect.
  834. nsRect clientRect;
  835. GetXULClientRect(clientRect);
  836. // Get the starting y position and the remaining available
  837. // height.
  838. nscoord availableHeight = GetAvailableHeight();
  839. if (availableHeight <= 0) {
  840. bool fixed = (GetFixedRowSize() != -1);
  841. if (fixed)
  842. availableHeight = 10;
  843. else
  844. return;
  845. }
  846. // get the first tree box. If there isn't one create one.
  847. bool created = false;
  848. nsIFrame* box = GetFirstItemBox(0, &created);
  849. nscoord rowHeight = GetRowHeightAppUnits();
  850. while (box) {
  851. if (created && mRowsToPrepend > 0)
  852. --mRowsToPrepend;
  853. // if the row height is 0 then fail. Wait until someone
  854. // laid out and sets the row height.
  855. if (rowHeight == 0)
  856. return;
  857. availableHeight -= rowHeight;
  858. // should we continue? Is the enought height?
  859. if (!ContinueReflow(availableHeight))
  860. break;
  861. // get the next tree box. Create one if needed.
  862. box = GetNextItemBox(box, 0, &created);
  863. }
  864. mRowsToPrepend = 0;
  865. mLinkupFrame = nullptr;
  866. }
  867. void
  868. nsListBoxBodyFrame::DestroyRows(int32_t& aRowsToLose)
  869. {
  870. // We need to destroy frames until our row count has been properly
  871. // reduced. A reflow will then pick up and create the new frames.
  872. nsIFrame* childFrame = GetFirstFrame();
  873. nsBoxLayoutState state(PresContext());
  874. nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
  875. fc->BeginUpdate();
  876. while (childFrame && aRowsToLose > 0) {
  877. --aRowsToLose;
  878. nsIFrame* nextFrame = childFrame->GetNextSibling();
  879. RemoveChildFrame(state, childFrame);
  880. mTopFrame = childFrame = nextFrame;
  881. }
  882. fc->EndUpdate();
  883. PresContext()->PresShell()->
  884. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  885. NS_FRAME_HAS_DIRTY_CHILDREN);
  886. }
  887. void
  888. nsListBoxBodyFrame::ReverseDestroyRows(int32_t& aRowsToLose)
  889. {
  890. // We need to destroy frames until our row count has been properly
  891. // reduced. A reflow will then pick up and create the new frames.
  892. nsIFrame* childFrame = GetLastFrame();
  893. nsBoxLayoutState state(PresContext());
  894. nsCSSFrameConstructor* fc = PresContext()->PresShell()->FrameConstructor();
  895. fc->BeginUpdate();
  896. while (childFrame && aRowsToLose > 0) {
  897. --aRowsToLose;
  898. nsIFrame* prevFrame;
  899. prevFrame = childFrame->GetPrevSibling();
  900. RemoveChildFrame(state, childFrame);
  901. mBottomFrame = childFrame = prevFrame;
  902. }
  903. fc->EndUpdate();
  904. PresContext()->PresShell()->
  905. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  906. NS_FRAME_HAS_DIRTY_CHILDREN);
  907. }
  908. static bool
  909. IsListItemChild(nsListBoxBodyFrame* aParent, nsIContent* aChild,
  910. nsIFrame** aChildFrame)
  911. {
  912. *aChildFrame = nullptr;
  913. if (!aChild->IsXULElement(nsGkAtoms::listitem)) {
  914. return false;
  915. }
  916. nsIFrame* existingFrame = aChild->GetPrimaryFrame();
  917. if (existingFrame && existingFrame->GetParent() != aParent) {
  918. return false;
  919. }
  920. *aChildFrame = existingFrame;
  921. return true;
  922. }
  923. //
  924. // Get the nsIFrame for the first visible listitem, and if none exists,
  925. // create one.
  926. //
  927. nsIFrame*
  928. nsListBoxBodyFrame::GetFirstItemBox(int32_t aOffset, bool* aCreated)
  929. {
  930. if (aCreated)
  931. *aCreated = false;
  932. // Clear ourselves out.
  933. mBottomFrame = mTopFrame;
  934. if (mTopFrame) {
  935. return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
  936. }
  937. // top frame was cleared out
  938. mTopFrame = GetFirstFrame();
  939. mBottomFrame = mTopFrame;
  940. if (mTopFrame && mRowsToPrepend <= 0) {
  941. return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
  942. }
  943. // At this point, we either have no frames at all,
  944. // or the user has scrolled upwards, leaving frames
  945. // to be created at the top. Let's determine which
  946. // content needs a new frame first.
  947. nsCOMPtr<nsIContent> startContent;
  948. if (mTopFrame && mRowsToPrepend > 0) {
  949. // We need to insert rows before the top frame
  950. nsIContent* topContent = mTopFrame->GetContent();
  951. nsIContent* topParent = topContent->GetParent();
  952. int32_t contentIndex = topParent->IndexOf(topContent);
  953. contentIndex -= aOffset;
  954. if (contentIndex < 0)
  955. return nullptr;
  956. startContent = topParent->GetChildAt(contentIndex - mRowsToPrepend);
  957. } else {
  958. // This will be the first item frame we create. Use the content
  959. // at the current index, which is the first index scrolled into view
  960. GetListItemContentAt(mCurrentIndex+aOffset, getter_AddRefs(startContent));
  961. }
  962. if (startContent) {
  963. nsIFrame* existingFrame;
  964. if (!IsListItemChild(this, startContent, &existingFrame)) {
  965. return GetFirstItemBox(++aOffset, aCreated);
  966. }
  967. if (existingFrame) {
  968. return existingFrame->IsXULBoxFrame() ? existingFrame : nullptr;
  969. }
  970. // Either append the new frame, or prepend it (at index 0)
  971. // XXX check here if frame was even created, it may not have been if
  972. // display: none was on listitem content
  973. bool isAppend = mRowsToPrepend <= 0;
  974. nsPresContext* presContext = PresContext();
  975. nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
  976. nsIFrame* topFrame = nullptr;
  977. fc->CreateListBoxContent(this, nullptr, startContent, &topFrame, isAppend);
  978. mTopFrame = topFrame;
  979. if (mTopFrame) {
  980. if (aCreated)
  981. *aCreated = true;
  982. mBottomFrame = mTopFrame;
  983. return mTopFrame->IsXULBoxFrame() ? mTopFrame : nullptr;
  984. } else
  985. return GetFirstItemBox(++aOffset, 0);
  986. }
  987. return nullptr;
  988. }
  989. //
  990. // Get the nsIFrame for the next visible listitem after aBox, and if none
  991. // exists, create one.
  992. //
  993. nsIFrame*
  994. nsListBoxBodyFrame::GetNextItemBox(nsIFrame* aBox, int32_t aOffset,
  995. bool* aCreated)
  996. {
  997. if (aCreated)
  998. *aCreated = false;
  999. nsIFrame* result = aBox->GetNextSibling();
  1000. if (!result || result == mLinkupFrame || mRowsToPrepend > 0) {
  1001. // No result found. See if there's a content node that wants a frame.
  1002. nsIContent* prevContent = aBox->GetContent();
  1003. nsIContent* parentContent = prevContent->GetParent();
  1004. int32_t i = parentContent->IndexOf(prevContent);
  1005. uint32_t childCount = parentContent->GetChildCount();
  1006. if (((uint32_t)i + aOffset + 1) < childCount) {
  1007. // There is a content node that wants a frame.
  1008. nsIContent *nextContent = parentContent->GetChildAt(i + aOffset + 1);
  1009. nsIFrame* existingFrame;
  1010. if (!IsListItemChild(this, nextContent, &existingFrame)) {
  1011. return GetNextItemBox(aBox, ++aOffset, aCreated);
  1012. }
  1013. if (!existingFrame) {
  1014. // Either append the new frame, or insert it after the current frame
  1015. bool isAppend = result != mLinkupFrame && mRowsToPrepend <= 0;
  1016. nsIFrame* prevFrame = isAppend ? nullptr : aBox;
  1017. nsPresContext* presContext = PresContext();
  1018. nsCSSFrameConstructor* fc = presContext->PresShell()->FrameConstructor();
  1019. fc->CreateListBoxContent(this, prevFrame, nextContent,
  1020. &result, isAppend);
  1021. if (result) {
  1022. if (aCreated)
  1023. *aCreated = true;
  1024. } else
  1025. return GetNextItemBox(aBox, ++aOffset, aCreated);
  1026. } else {
  1027. result = existingFrame;
  1028. }
  1029. mLinkupFrame = nullptr;
  1030. }
  1031. }
  1032. if (!result)
  1033. return nullptr;
  1034. mBottomFrame = result;
  1035. NS_ASSERTION(!result->IsXULBoxFrame() || result->GetParent() == this,
  1036. "returning frame that is not in childlist");
  1037. return result->IsXULBoxFrame() ? result : nullptr;
  1038. }
  1039. bool
  1040. nsListBoxBodyFrame::ContinueReflow(nscoord height)
  1041. {
  1042. #ifdef ACCESSIBILITY
  1043. if (nsIPresShell::IsAccessibilityActive()) {
  1044. // Create all the frames at once so screen readers and
  1045. // onscreen keyboards can see the full list right away
  1046. return true;
  1047. }
  1048. #endif
  1049. if (height <= 0) {
  1050. nsIFrame* lastChild = GetLastFrame();
  1051. nsIFrame* startingPoint = mBottomFrame;
  1052. if (startingPoint == nullptr) {
  1053. // We just want to delete everything but the first item.
  1054. startingPoint = GetFirstFrame();
  1055. }
  1056. if (lastChild != startingPoint) {
  1057. // We have some hangers on (probably caused by shrinking the size of the window).
  1058. // Nuke them.
  1059. nsIFrame* currFrame = startingPoint->GetNextSibling();
  1060. nsBoxLayoutState state(PresContext());
  1061. nsCSSFrameConstructor* fc =
  1062. PresContext()->PresShell()->FrameConstructor();
  1063. fc->BeginUpdate();
  1064. while (currFrame) {
  1065. nsIFrame* nextFrame = currFrame->GetNextSibling();
  1066. RemoveChildFrame(state, currFrame);
  1067. currFrame = nextFrame;
  1068. }
  1069. fc->EndUpdate();
  1070. PresContext()->PresShell()->
  1071. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1072. NS_FRAME_HAS_DIRTY_CHILDREN);
  1073. }
  1074. return false;
  1075. }
  1076. else
  1077. return true;
  1078. }
  1079. NS_IMETHODIMP
  1080. nsListBoxBodyFrame::ListBoxAppendFrames(nsFrameList& aFrameList)
  1081. {
  1082. // append them after
  1083. nsBoxLayoutState state(PresContext());
  1084. const nsFrameList::Slice& newFrames = mFrames.AppendFrames(nullptr, aFrameList);
  1085. if (mLayoutManager)
  1086. mLayoutManager->ChildrenAppended(this, state, newFrames);
  1087. PresContext()->PresShell()->
  1088. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1089. NS_FRAME_HAS_DIRTY_CHILDREN);
  1090. return NS_OK;
  1091. }
  1092. NS_IMETHODIMP
  1093. nsListBoxBodyFrame::ListBoxInsertFrames(nsIFrame* aPrevFrame,
  1094. nsFrameList& aFrameList)
  1095. {
  1096. // insert the frames to our info list
  1097. nsBoxLayoutState state(PresContext());
  1098. const nsFrameList::Slice& newFrames =
  1099. mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
  1100. if (mLayoutManager)
  1101. mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
  1102. PresContext()->PresShell()->
  1103. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1104. NS_FRAME_HAS_DIRTY_CHILDREN);
  1105. return NS_OK;
  1106. }
  1107. //
  1108. // Called by nsCSSFrameConstructor when a new listitem content is inserted.
  1109. //
  1110. void
  1111. nsListBoxBodyFrame::OnContentInserted(nsIContent* aChildContent)
  1112. {
  1113. if (mRowCount >= 0)
  1114. ++mRowCount;
  1115. // The RDF content builder will build content nodes such that they are all
  1116. // ready when OnContentInserted is first called, meaning the first call
  1117. // to CreateRows will create all the frames, but OnContentInserted will
  1118. // still be called again for each content node - so we need to make sure
  1119. // that the frame for each content node hasn't already been created.
  1120. nsIFrame* childFrame = aChildContent->GetPrimaryFrame();
  1121. if (childFrame)
  1122. return;
  1123. int32_t siblingIndex;
  1124. nsCOMPtr<nsIContent> nextSiblingContent;
  1125. GetListItemNextSibling(aChildContent, getter_AddRefs(nextSiblingContent), siblingIndex);
  1126. // if we're inserting our item before the first visible content,
  1127. // then we need to shift all rows down by one
  1128. if (siblingIndex >= 0 && siblingIndex-1 <= mCurrentIndex) {
  1129. mTopFrame = nullptr;
  1130. mRowsToPrepend = 1;
  1131. } else if (nextSiblingContent) {
  1132. // we may be inserting before a frame that is on screen
  1133. nsIFrame* nextSiblingFrame = nextSiblingContent->GetPrimaryFrame();
  1134. mLinkupFrame = nextSiblingFrame;
  1135. }
  1136. CreateRows();
  1137. PresContext()->PresShell()->
  1138. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1139. NS_FRAME_HAS_DIRTY_CHILDREN);
  1140. }
  1141. //
  1142. // Called by nsCSSFrameConstructor when listitem content is removed.
  1143. //
  1144. void
  1145. nsListBoxBodyFrame::OnContentRemoved(nsPresContext* aPresContext,
  1146. nsIContent* aContainer,
  1147. nsIFrame* aChildFrame,
  1148. nsIContent* aOldNextSibling)
  1149. {
  1150. NS_ASSERTION(!aChildFrame || aChildFrame->GetParent() == this,
  1151. "Removing frame that's not our child... Not good");
  1152. if (mRowCount >= 0)
  1153. --mRowCount;
  1154. if (aContainer) {
  1155. if (!aChildFrame) {
  1156. // The row we are removing is out of view, so we need to try to
  1157. // determine the index of its next sibling.
  1158. int32_t siblingIndex = -1;
  1159. if (aOldNextSibling) {
  1160. nsCOMPtr<nsIContent> nextSiblingContent;
  1161. GetListItemNextSibling(aOldNextSibling,
  1162. getter_AddRefs(nextSiblingContent),
  1163. siblingIndex);
  1164. }
  1165. // if the row being removed is off-screen and above the top frame, we need to
  1166. // adjust our top index and tell the scrollbar to shift up one row.
  1167. if (siblingIndex >= 0 && siblingIndex-1 < mCurrentIndex) {
  1168. NS_PRECONDITION(mCurrentIndex > 0, "mCurrentIndex > 0");
  1169. --mCurrentIndex;
  1170. mYPosition = mCurrentIndex*mRowHeight;
  1171. nsWeakFrame weakChildFrame(aChildFrame);
  1172. VerticalScroll(mYPosition);
  1173. if (!weakChildFrame.IsAlive()) {
  1174. return;
  1175. }
  1176. }
  1177. } else if (mCurrentIndex > 0) {
  1178. // At this point, we know we have a scrollbar, and we need to know
  1179. // if we are scrolled to the last row. In this case, the behavior
  1180. // of the scrollbar is to stay locked to the bottom. Since we are
  1181. // removing visible content, the first visible row will have to move
  1182. // down by one, and we will have to insert a new frame at the top.
  1183. // if the last content node has a frame, we are scrolled to the bottom
  1184. nsIContent* lastChild = nullptr;
  1185. FlattenedChildIterator iter(mContent);
  1186. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  1187. lastChild = child;
  1188. }
  1189. if (lastChild) {
  1190. nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame();
  1191. if (lastChildFrame) {
  1192. mTopFrame = nullptr;
  1193. mRowsToPrepend = 1;
  1194. --mCurrentIndex;
  1195. mYPosition = mCurrentIndex*mRowHeight;
  1196. nsWeakFrame weakChildFrame(aChildFrame);
  1197. VerticalScroll(mYPosition);
  1198. if (!weakChildFrame.IsAlive()) {
  1199. return;
  1200. }
  1201. }
  1202. }
  1203. }
  1204. }
  1205. // if we're removing the top row, the new top row is the next row
  1206. if (mTopFrame && mTopFrame == aChildFrame)
  1207. mTopFrame = mTopFrame->GetNextSibling();
  1208. // Go ahead and delete the frame.
  1209. nsBoxLayoutState state(aPresContext);
  1210. if (aChildFrame) {
  1211. RemoveChildFrame(state, aChildFrame);
  1212. }
  1213. PresContext()->PresShell()->
  1214. FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1215. NS_FRAME_HAS_DIRTY_CHILDREN);
  1216. }
  1217. void
  1218. nsListBoxBodyFrame::GetListItemContentAt(int32_t aIndex, nsIContent** aContent)
  1219. {
  1220. *aContent = nullptr;
  1221. int32_t itemsFound = 0;
  1222. FlattenedChildIterator iter(mContent);
  1223. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  1224. if (child->IsXULElement(nsGkAtoms::listitem)) {
  1225. ++itemsFound;
  1226. if (itemsFound-1 == aIndex) {
  1227. *aContent = child;
  1228. NS_IF_ADDREF(*aContent);
  1229. return;
  1230. }
  1231. }
  1232. }
  1233. }
  1234. void
  1235. nsListBoxBodyFrame::GetListItemNextSibling(nsIContent* aListItem, nsIContent** aContent, int32_t& aSiblingIndex)
  1236. {
  1237. *aContent = nullptr;
  1238. aSiblingIndex = -1;
  1239. nsIContent *prevKid = nullptr;
  1240. FlattenedChildIterator iter(mContent);
  1241. for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
  1242. if (child->IsXULElement(nsGkAtoms::listitem)) {
  1243. ++aSiblingIndex;
  1244. if (prevKid == aListItem) {
  1245. *aContent = child;
  1246. NS_IF_ADDREF(*aContent);
  1247. return;
  1248. }
  1249. }
  1250. prevKid = child;
  1251. }
  1252. aSiblingIndex = -1; // no match, so there is no next sibling
  1253. }
  1254. void
  1255. nsListBoxBodyFrame::RemoveChildFrame(nsBoxLayoutState &aState,
  1256. nsIFrame *aFrame)
  1257. {
  1258. MOZ_ASSERT(mFrames.ContainsFrame(aFrame));
  1259. MOZ_ASSERT(aFrame != GetContentInsertionFrame());
  1260. #ifdef ACCESSIBILITY
  1261. nsAccessibilityService* accService = nsIPresShell::AccService();
  1262. if (accService) {
  1263. nsIContent* content = aFrame->GetContent();
  1264. accService->ContentRemoved(PresContext()->PresShell(), content);
  1265. }
  1266. #endif
  1267. mFrames.RemoveFrame(aFrame);
  1268. if (mLayoutManager)
  1269. mLayoutManager->ChildrenRemoved(this, aState, aFrame);
  1270. aFrame->Destroy();
  1271. }
  1272. // Creation Routines ///////////////////////////////////////////////////////////////////////
  1273. already_AddRefed<nsBoxLayout> NS_NewListBoxLayout();
  1274. nsIFrame*
  1275. NS_NewListBoxBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  1276. {
  1277. nsCOMPtr<nsBoxLayout> layout = NS_NewListBoxLayout();
  1278. return new (aPresShell) nsListBoxBodyFrame(aContext, layout);
  1279. }
  1280. NS_IMPL_FRAMEARENA_HELPERS(nsListBoxBodyFrame)