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"
6 #include "nsIContent.h"
7 #include "nsListControlFrame.h"
8 #include "nsDisplayList.h"
11 NS_NewSelectsAreaFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
, uint32_t aFlags
)
13 nsSelectsAreaFrame
* it
= new (aShell
) nsSelectsAreaFrame(aContext
);
15 // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
16 // aren't expanded by right floats outside the select.
17 it
->SetFlags(aFlags
| NS_BLOCK_FLOAT_MGR
);
22 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame
)
24 //---------------------------------------------------------
26 * This wrapper class lets us redirect mouse hits from the child frame of
27 * an option element to the element's own frame.
28 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
30 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
32 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
33 nsIFrame
* aFrame
, nsDisplayItem
* aItem
)
34 : nsDisplayWrapList(aBuilder
, aFrame
, aItem
) {}
35 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
36 nsIFrame
* aFrame
, nsDisplayList
* aList
)
37 : nsDisplayWrapList(aBuilder
, aFrame
, aList
) {}
38 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
39 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
);
40 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
43 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
44 const nsRect
& aRect
, HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
)
46 nsTArray
<nsIFrame
*> outFrames
;
47 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
49 for (uint32_t i
= 0; i
< outFrames
.Length(); i
++) {
50 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
51 while (selectedFrame
&&
52 !(selectedFrame
->GetContent() &&
53 selectedFrame
->GetContent()->IsHTML(nsGkAtoms::option
))) {
54 selectedFrame
= selectedFrame
->GetParent();
57 aOutFrames
->AppendElement(selectedFrame
);
59 // keep the original result, which could be this frame
60 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
65 class nsOptionEventGrabberWrapper
: public nsDisplayWrapper
68 nsOptionEventGrabberWrapper() {}
69 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
70 nsIFrame
* aFrame
, nsDisplayList
* aList
) {
71 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aFrame
, aList
);
73 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
74 nsDisplayItem
* aItem
) {
75 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aItem
->Frame(), aItem
);
79 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
)
81 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
83 if (frame
->GetType() == nsGkAtoms::listControlFrame
)
84 return static_cast<nsListControlFrame
*>(frame
);
85 frame
= frame
->GetParent();
90 class nsDisplayListFocus
: public nsDisplayItem
{
92 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
,
93 nsSelectsAreaFrame
* aFrame
) :
94 nsDisplayItem(aBuilder
, aFrame
) {
95 MOZ_COUNT_CTOR(nsDisplayListFocus
);
97 #ifdef NS_BUILD_REFCNT_LOGGING
98 virtual ~nsDisplayListFocus() {
99 MOZ_COUNT_DTOR(nsDisplayListFocus
);
103 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) {
105 // override bounds because the list item focus ring may extend outside
106 // the nsSelectsAreaFrame
107 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
108 return listFrame
->GetVisualOverflowRectRelativeToSelf() +
109 listFrame
->GetOffsetToCrossDoc(ReferenceFrame());
111 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
112 nsRenderingContext
* aCtx
) {
113 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
114 // listFrame must be non-null or we wouldn't get called.
115 listFrame
->PaintFocus(*aCtx
, aBuilder
->ToReferenceFrame(listFrame
));
117 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
121 nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
122 const nsRect
& aDirtyRect
,
123 const nsDisplayListSet
& aLists
)
125 if (!aBuilder
->IsForEventDelivery()) {
126 BuildDisplayListInternal(aBuilder
, aDirtyRect
, aLists
);
130 nsDisplayListCollection set
;
131 BuildDisplayListInternal(aBuilder
, aDirtyRect
, set
);
133 nsOptionEventGrabberWrapper wrapper
;
134 wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
138 nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder
* aBuilder
,
139 const nsRect
& aDirtyRect
,
140 const nsDisplayListSet
& aLists
)
142 nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
144 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
145 if (listFrame
&& listFrame
->IsFocused()) {
146 // we can't just associate the display item with the list frame,
147 // because then the list's scrollframe won't clip it (the scrollframe
148 // only clips contained descendants).
149 aLists
.Outlines()->AppendNewToTop(new (aBuilder
)
150 nsDisplayListFocus(aBuilder
, this));
155 nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
156 nsHTMLReflowMetrics
& aDesiredSize
,
157 const nsHTMLReflowState
& aReflowState
,
158 nsReflowStatus
& aStatus
)
160 nsListControlFrame
* list
= GetEnclosingListFrame(this);
162 "Must have an nsListControlFrame! Frame constructor is "
165 bool isInDropdownMode
= list
->IsInDropDownMode();
167 // See similar logic in nsListControlFrame::Reflow and
168 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
170 if (isInDropdownMode
) {
171 // Store the height now in case it changes during
172 // nsBlockFrame::Reflow for some odd reason.
173 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
174 oldHeight
= GetSize().height
;
176 oldHeight
= NS_UNCONSTRAINEDSIZE
;
180 nsresult rv
= nsBlockFrame::Reflow(aPresContext
, aDesiredSize
,
181 aReflowState
, aStatus
);
182 NS_ENSURE_SUCCESS(rv
, rv
);
184 // Check whether we need to suppress scrollbar updates. We want to do that if
185 // we're in a possible first pass and our height of a row has changed.
186 if (list
->MightNeedSecondPass()) {
187 nscoord newHeightOfARow
= list
->CalcHeightOfARow();
188 // We'll need a second pass if our height of a row changed. For
189 // comboboxes, we'll also need it if our height changed. If we're going
190 // to do a second pass, suppress scrollbar updates for this pass.
191 if (newHeightOfARow
!= mHeightOfARow
||
192 (isInDropdownMode
&& (oldHeight
!= aDesiredSize
.Height() ||
193 oldHeight
!= GetSize().height
))) {
194 mHeightOfARow
= newHeightOfARow
;
195 list
->SetSuppressScrollbarUpdate(true);