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