Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / forms / nsSelectsAreaFrame.cpp
bloba82dc8ff6fc13280c49256fcf7e5ebef19babba7
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
13 * License.
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.
22 * Contributor(s):
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"
39 #include "nsCOMPtr.h"
40 #include "nsIDOMHTMLOptionElement.h"
41 #include "nsIContent.h"
42 #include "nsListControlFrame.h"
43 #include "nsDisplayList.h"
45 nsIFrame*
46 NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRUint32 aFlags)
48 nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
50 if (it) {
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);
56 return it;
59 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
61 //---------------------------------------------------------
62 PRBool
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) {
70 result = PR_TRUE;
74 return result;
77 //---------------------------------------------------------
78 PRBool
79 nsSelectsAreaFrame::IsOptionElementFrame(nsIFrame *aFrame)
81 nsIContent *content = aFrame->GetContent();
82 if (content) {
83 return IsOptionElement(content);
85 return PR_FALSE;
88 /**
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 {
94 public:
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();
121 if (selectedFrame) {
122 aOutFrames->AppendElement(selectedFrame);
123 } else {
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
139 public:
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();
156 while (frame) {
157 if (frame->GetType() == nsGkAtoms::listControlFrame)
158 return static_cast<nsListControlFrame*>(frame);
159 frame = frame->GetParent();
161 return nsnull;
164 class nsDisplayListFocus : public nsDisplayItem {
165 public:
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);
175 #endif
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)
193 NS_IMETHODIMP
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);
209 nsresult
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));
226 return NS_OK;
229 NS_IMETHODIMP
230 nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
231 nsHTMLReflowMetrics& aDesiredSize,
232 const nsHTMLReflowState& aReflowState,
233 nsReflowStatus& aStatus)
235 nsListControlFrame* list = GetEnclosingListFrame(this);
236 NS_ASSERTION(list,
237 "Must have an nsListControlFrame! Frame constructor is "
238 "broken");
240 PRBool isInDropdownMode = list->IsInDropDownMode();
242 // See similar logic in nsListControlFrame::Reflow and
243 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
244 nscoord oldHeight;
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;
250 } else {
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);
274 return rv;