1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsSelectsAreaFrame.h"
7 #include "nsIDOMHTMLOptionElement.h"
8 #include "nsIContent.h"
9 #include "nsListControlFrame.h"
10 #include "nsDisplayList.h"
13 NS_NewSelectsAreaFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
, PRUint32 aFlags
)
15 nsSelectsAreaFrame
* it
= new (aShell
) nsSelectsAreaFrame(aContext
);
18 // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
19 // aren't expanded by right floats outside the select.
20 it
->SetFlags(aFlags
| NS_BLOCK_FLOAT_MGR
);
26 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame
)
28 //---------------------------------------------------------
30 * This wrapper class lets us redirect mouse hits from the child frame of
31 * an option element to the element's own frame.
32 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
34 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
36 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
37 nsIFrame
* aFrame
, nsDisplayItem
* aItem
)
38 : nsDisplayWrapList(aBuilder
, aFrame
, aItem
) {}
39 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
40 nsIFrame
* aFrame
, nsDisplayList
* aList
)
41 : nsDisplayWrapList(aBuilder
, aFrame
, aList
) {}
42 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
43 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
);
44 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
46 virtual nsDisplayWrapList
* WrapWithClone(nsDisplayListBuilder
* aBuilder
,
47 nsDisplayItem
* aItem
);
50 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
51 const nsRect
& aRect
, HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
)
53 nsTArray
<nsIFrame
*> outFrames
;
54 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
56 for (PRUint32 i
= 0; i
< outFrames
.Length(); i
++) {
57 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
58 while (selectedFrame
&&
59 !(selectedFrame
->GetContent() &&
60 selectedFrame
->GetContent()->IsHTML(nsGkAtoms::option
))) {
61 selectedFrame
= selectedFrame
->GetParent();
64 aOutFrames
->AppendElement(selectedFrame
);
66 // keep the original result, which could be this frame
67 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
73 nsDisplayWrapList
* nsDisplayOptionEventGrabber::WrapWithClone(
74 nsDisplayListBuilder
* aBuilder
, nsDisplayItem
* aItem
) {
76 nsDisplayOptionEventGrabber(aBuilder
, aItem
->GetUnderlyingFrame(), aItem
);
79 class nsOptionEventGrabberWrapper
: public nsDisplayWrapper
82 nsOptionEventGrabberWrapper() {}
83 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
84 nsIFrame
* aFrame
, nsDisplayList
* aList
) {
85 // We can't specify the underlying frame here. We need this list to be
86 // exploded if sorted.
87 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, nullptr, aList
);
89 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
90 nsDisplayItem
* aItem
) {
91 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aItem
->GetUnderlyingFrame(), aItem
);
95 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
)
97 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
99 if (frame
->GetType() == nsGkAtoms::listControlFrame
)
100 return static_cast<nsListControlFrame
*>(frame
);
101 frame
= frame
->GetParent();
106 class nsDisplayListFocus
: public nsDisplayItem
{
108 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
,
109 nsSelectsAreaFrame
* aFrame
) :
110 nsDisplayItem(aBuilder
, aFrame
) {
111 MOZ_COUNT_CTOR(nsDisplayListFocus
);
113 #ifdef NS_BUILD_REFCNT_LOGGING
114 virtual ~nsDisplayListFocus() {
115 MOZ_COUNT_DTOR(nsDisplayListFocus
);
119 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) {
121 // override bounds because the list item focus ring may extend outside
122 // the nsSelectsAreaFrame
123 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
124 return listFrame
->GetVisualOverflowRectRelativeToSelf() +
125 aBuilder
->ToReferenceFrame(listFrame
);
127 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
128 nsRenderingContext
* aCtx
) {
129 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
130 // listFrame must be non-null or we wouldn't get called.
131 listFrame
->PaintFocus(*aCtx
, aBuilder
->ToReferenceFrame(listFrame
));
133 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
137 nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
138 const nsRect
& aDirtyRect
,
139 const nsDisplayListSet
& aLists
)
141 if (!aBuilder
->IsForEventDelivery())
142 return BuildDisplayListInternal(aBuilder
, aDirtyRect
, aLists
);
144 nsDisplayListCollection set
;
145 nsresult rv
= BuildDisplayListInternal(aBuilder
, aDirtyRect
, set
);
146 NS_ENSURE_SUCCESS(rv
, rv
);
148 nsOptionEventGrabberWrapper wrapper
;
149 return wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
153 nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder
* aBuilder
,
154 const nsRect
& aDirtyRect
,
155 const nsDisplayListSet
& aLists
)
157 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
158 NS_ENSURE_SUCCESS(rv
, rv
);
160 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
161 if (listFrame
&& listFrame
->IsFocused()) {
162 // we can't just associate the display item with the list frame,
163 // because then the list's scrollframe won't clip it (the scrollframe
164 // only clips contained descendants).
165 return aLists
.Outlines()->AppendNewToTop(new (aBuilder
)
166 nsDisplayListFocus(aBuilder
, this));
173 nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
174 nsHTMLReflowMetrics
& aDesiredSize
,
175 const nsHTMLReflowState
& aReflowState
,
176 nsReflowStatus
& aStatus
)
178 nsListControlFrame
* list
= GetEnclosingListFrame(this);
180 "Must have an nsListControlFrame! Frame constructor is "
183 bool isInDropdownMode
= list
->IsInDropDownMode();
185 // See similar logic in nsListControlFrame::Reflow and
186 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
188 if (isInDropdownMode
) {
189 // Store the height now in case it changes during
190 // nsBlockFrame::Reflow for some odd reason.
191 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
192 oldHeight
= GetSize().height
;
194 oldHeight
= NS_UNCONSTRAINEDSIZE
;
198 nsresult rv
= nsBlockFrame::Reflow(aPresContext
, aDesiredSize
,
199 aReflowState
, aStatus
);
200 NS_ENSURE_SUCCESS(rv
, rv
);
202 // Check whether we need to suppress scrolbar updates. We want to do that if
203 // we're in a possible first pass and our height of a row has changed.
204 if (list
->MightNeedSecondPass()) {
205 nscoord newHeightOfARow
= list
->CalcHeightOfARow();
206 // We'll need a second pass if our height of a row changed. For
207 // comboboxes, we'll also need it if our height changed. If we're going
208 // to do a second pass, suppress scrollbar updates for this pass.
209 if (newHeightOfARow
!= mHeightOfARow
||
210 (isInDropdownMode
&& (oldHeight
!= aDesiredSize
.height
||
211 oldHeight
!= GetSize().height
))) {
212 mHeightOfARow
= newHeightOfARow
;
213 list
->SetSuppressScrollbarUpdate(true);