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
, nsFrameState 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
) MOZ_OVERRIDE
;
40 virtual bool ShouldFlattenAway(nsDisplayListBuilder
* aBuilder
) MOZ_OVERRIDE
{
43 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
46 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
47 const nsRect
& aRect
, HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
)
49 nsTArray
<nsIFrame
*> outFrames
;
50 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
52 for (uint32_t i
= 0; i
< outFrames
.Length(); i
++) {
53 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
54 while (selectedFrame
&&
55 !(selectedFrame
->GetContent() &&
56 selectedFrame
->GetContent()->IsHTML(nsGkAtoms::option
))) {
57 selectedFrame
= selectedFrame
->GetParent();
60 aOutFrames
->AppendElement(selectedFrame
);
62 // keep the original result, which could be this frame
63 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
68 class nsOptionEventGrabberWrapper
: public nsDisplayWrapper
71 nsOptionEventGrabberWrapper() {}
72 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
73 nsIFrame
* aFrame
, nsDisplayList
* aList
) {
74 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aFrame
, aList
);
76 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
77 nsDisplayItem
* aItem
) {
78 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aItem
->Frame(), aItem
);
82 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
)
84 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
86 if (frame
->GetType() == nsGkAtoms::listControlFrame
)
87 return static_cast<nsListControlFrame
*>(frame
);
88 frame
= frame
->GetParent();
93 class nsDisplayListFocus
: public nsDisplayItem
{
95 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
,
96 nsSelectsAreaFrame
* aFrame
) :
97 nsDisplayItem(aBuilder
, aFrame
) {
98 MOZ_COUNT_CTOR(nsDisplayListFocus
);
100 #ifdef NS_BUILD_REFCNT_LOGGING
101 virtual ~nsDisplayListFocus() {
102 MOZ_COUNT_DTOR(nsDisplayListFocus
);
106 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) MOZ_OVERRIDE
{
108 // override bounds because the list item focus ring may extend outside
109 // the nsSelectsAreaFrame
110 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
111 return listFrame
->GetVisualOverflowRectRelativeToSelf() +
112 listFrame
->GetOffsetToCrossDoc(ReferenceFrame());
114 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
115 nsRenderingContext
* aCtx
) MOZ_OVERRIDE
{
116 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
117 // listFrame must be non-null or we wouldn't get called.
118 listFrame
->PaintFocus(*aCtx
, aBuilder
->ToReferenceFrame(listFrame
));
120 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
124 nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
125 const nsRect
& aDirtyRect
,
126 const nsDisplayListSet
& aLists
)
128 if (!aBuilder
->IsForEventDelivery()) {
129 BuildDisplayListInternal(aBuilder
, aDirtyRect
, aLists
);
133 nsDisplayListCollection set
;
134 BuildDisplayListInternal(aBuilder
, aDirtyRect
, set
);
136 nsOptionEventGrabberWrapper wrapper
;
137 wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
141 nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder
* aBuilder
,
142 const nsRect
& aDirtyRect
,
143 const nsDisplayListSet
& aLists
)
145 nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
147 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
148 if (listFrame
&& listFrame
->IsFocused()) {
149 // we can't just associate the display item with the list frame,
150 // because then the list's scrollframe won't clip it (the scrollframe
151 // only clips contained descendants).
152 aLists
.Outlines()->AppendNewToTop(new (aBuilder
)
153 nsDisplayListFocus(aBuilder
, this));
158 nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
159 nsHTMLReflowMetrics
& aDesiredSize
,
160 const nsHTMLReflowState
& aReflowState
,
161 nsReflowStatus
& aStatus
)
163 nsListControlFrame
* list
= GetEnclosingListFrame(this);
165 "Must have an nsListControlFrame! Frame constructor is "
168 bool isInDropdownMode
= list
->IsInDropDownMode();
170 // See similar logic in nsListControlFrame::Reflow and
171 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
173 if (isInDropdownMode
) {
174 // Store the height now in case it changes during
175 // nsBlockFrame::Reflow for some odd reason.
176 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
177 oldHeight
= GetSize().height
;
179 oldHeight
= NS_UNCONSTRAINEDSIZE
;
183 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowState
, aStatus
);
185 // Check whether we need to suppress scrollbar updates. We want to do that if
186 // we're in a possible first pass and our height of a row has changed.
187 if (list
->MightNeedSecondPass()) {
188 nscoord newHeightOfARow
= list
->CalcHeightOfARow();
189 // We'll need a second pass if our height of a row changed. For
190 // comboboxes, we'll also need it if our height changed. If we're going
191 // to do a second pass, suppress scrollbar updates for this pass.
192 if (newHeightOfARow
!= mHeightOfARow
||
193 (isInDropdownMode
&& (oldHeight
!= aDesiredSize
.Height() ||
194 oldHeight
!= GetSize().height
))) {
195 mHeightOfARow
= newHeightOfARow
;
196 list
->SetSuppressScrollbarUpdate(true);