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