1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
38 #include "nsSelectsAreaFrame.h"
40 #include "nsIDOMHTMLOptionElement.h"
41 #include "nsIContent.h"
42 #include "nsListControlFrame.h"
43 #include "nsDisplayList.h"
46 NS_NewSelectsAreaFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
, PRUint32 aFlags
)
48 nsSelectsAreaFrame
* it
= new (aShell
) nsSelectsAreaFrame(aContext
);
51 // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
52 // aren't expanded by right floats outside the select.
53 it
->SetFlags(aFlags
| NS_BLOCK_FLOAT_MGR
);
59 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame
)
61 //---------------------------------------------------------
63 nsSelectsAreaFrame::IsOptionElement(nsIContent
* aContent
)
65 PRBool result
= PR_FALSE
;
67 nsCOMPtr
<nsIDOMHTMLOptionElement
> optElem
;
68 if (NS_SUCCEEDED(aContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLOptionElement
),(void**) getter_AddRefs(optElem
)))) {
69 if (optElem
!= nsnull
) {
77 //---------------------------------------------------------
79 nsSelectsAreaFrame::IsOptionElementFrame(nsIFrame
*aFrame
)
81 nsIContent
*content
= aFrame
->GetContent();
83 return IsOptionElement(content
);
89 * This wrapper class lets us redirect mouse hits from the child frame of
90 * an option element to the element's own frame.
91 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
93 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
95 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
96 nsIFrame
* aFrame
, nsDisplayItem
* aItem
)
97 : nsDisplayWrapList(aBuilder
, aFrame
, aItem
) {}
98 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
,
99 nsIFrame
* aFrame
, nsDisplayList
* aList
)
100 : nsDisplayWrapList(aBuilder
, aFrame
, aList
) {}
101 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
102 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
);
103 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
105 virtual nsDisplayWrapList
* WrapWithClone(nsDisplayListBuilder
* aBuilder
,
106 nsDisplayItem
* aItem
);
109 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
110 const nsRect
& aRect
, HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
)
112 nsTArray
<nsIFrame
*> outFrames
;
113 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
115 for (PRUint32 i
= 0; i
< outFrames
.Length(); i
++) {
116 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
117 while (selectedFrame
&&
118 !nsSelectsAreaFrame::IsOptionElementFrame(selectedFrame
)) {
119 selectedFrame
= selectedFrame
->GetParent();
122 aOutFrames
->AppendElement(selectedFrame
);
124 // keep the original result, which could be this frame
125 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
131 nsDisplayWrapList
* nsDisplayOptionEventGrabber::WrapWithClone(
132 nsDisplayListBuilder
* aBuilder
, nsDisplayItem
* aItem
) {
133 return new (aBuilder
)
134 nsDisplayOptionEventGrabber(aBuilder
, aItem
->GetUnderlyingFrame(), aItem
);
137 class nsOptionEventGrabberWrapper
: public nsDisplayWrapper
140 nsOptionEventGrabberWrapper() {}
141 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
142 nsIFrame
* aFrame
, nsDisplayList
* aList
) {
143 // We can't specify the underlying frame here. We need this list to be
144 // exploded if sorted.
145 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, nsnull
, aList
);
147 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
148 nsDisplayItem
* aItem
) {
149 return new (aBuilder
) nsDisplayOptionEventGrabber(aBuilder
, aItem
->GetUnderlyingFrame(), aItem
);
153 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
)
155 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
157 if (frame
->GetType() == nsGkAtoms::listControlFrame
)
158 return static_cast<nsListControlFrame
*>(frame
);
159 frame
= frame
->GetParent();
164 class nsDisplayListFocus
: public nsDisplayItem
{
166 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
,
167 nsSelectsAreaFrame
* aFrame
) :
168 nsDisplayItem(aBuilder
, aFrame
) {
169 MOZ_COUNT_CTOR(nsDisplayListFocus
);
171 #ifdef NS_BUILD_REFCNT_LOGGING
172 virtual ~nsDisplayListFocus() {
173 MOZ_COUNT_DTOR(nsDisplayListFocus
);
177 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
) {
178 // override bounds because the list item focus ring may extend outside
179 // the nsSelectsAreaFrame
180 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
181 return listFrame
->GetVisualOverflowRect() +
182 aBuilder
->ToReferenceFrame(listFrame
);
184 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
185 nsIRenderingContext
* aCtx
) {
186 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
187 // listFrame must be non-null or we wouldn't get called.
188 listFrame
->PaintFocus(*aCtx
, aBuilder
->ToReferenceFrame(listFrame
));
190 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
194 nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
195 const nsRect
& aDirtyRect
,
196 const nsDisplayListSet
& aLists
)
198 if (!aBuilder
->IsForEventDelivery())
199 return BuildDisplayListInternal(aBuilder
, aDirtyRect
, aLists
);
201 nsDisplayListCollection set
;
202 nsresult rv
= BuildDisplayListInternal(aBuilder
, aDirtyRect
, set
);
203 NS_ENSURE_SUCCESS(rv
, rv
);
205 nsOptionEventGrabberWrapper wrapper
;
206 return wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
210 nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder
* aBuilder
,
211 const nsRect
& aDirtyRect
,
212 const nsDisplayListSet
& aLists
)
214 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
215 NS_ENSURE_SUCCESS(rv
, rv
);
217 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
218 if (listFrame
&& listFrame
->IsFocused()) {
219 // we can't just associate the display item with the list frame,
220 // because then the list's scrollframe won't clip it (the scrollframe
221 // only clips contained descendants).
222 return aLists
.Outlines()->AppendNewToTop(new (aBuilder
)
223 nsDisplayListFocus(aBuilder
, this));
230 nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
231 nsHTMLReflowMetrics
& aDesiredSize
,
232 const nsHTMLReflowState
& aReflowState
,
233 nsReflowStatus
& aStatus
)
235 nsListControlFrame
* list
= GetEnclosingListFrame(this);
237 "Must have an nsListControlFrame! Frame constructor is "
240 PRBool isInDropdownMode
= list
->IsInDropDownMode();
242 // See similar logic in nsListControlFrame::Reflow and
243 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
245 if (isInDropdownMode
) {
246 // Store the height now in case it changes during
247 // nsBlockFrame::Reflow for some odd reason.
248 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
249 oldHeight
= GetSize().height
;
251 oldHeight
= NS_UNCONSTRAINEDSIZE
;
255 nsresult rv
= nsBlockFrame::Reflow(aPresContext
, aDesiredSize
,
256 aReflowState
, aStatus
);
257 NS_ENSURE_SUCCESS(rv
, rv
);
259 // Check whether we need to suppress scrolbar updates. We want to do that if
260 // we're in a possible first pass and our height of a row has changed.
261 if (list
->MightNeedSecondPass()) {
262 nscoord newHeightOfARow
= list
->CalcHeightOfARow();
263 // We'll need a second pass if our height of a row changed. For
264 // comboboxes, we'll also need it if our height changed. If we're going
265 // to do a second pass, suppress scrollbar updates for this pass.
266 if (newHeightOfARow
!= mHeightOfARow
||
267 (isInDropdownMode
&& (oldHeight
!= aDesiredSize
.height
||
268 oldHeight
!= GetSize().height
))) {
269 mHeightOfARow
= newHeightOfARow
;
270 list
->SetSuppressScrollbarUpdate(PR_TRUE
);