1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsSelectsAreaFrame.h"
8 #include "mozilla/PresShell.h"
9 #include "nsIContent.h"
10 #include "nsListControlFrame.h"
11 #include "nsDisplayList.h"
12 #include "WritingModes.h"
14 using namespace mozilla
;
16 nsContainerFrame
* NS_NewSelectsAreaFrame(PresShell
* aShell
,
17 ComputedStyle
* aStyle
) {
18 nsSelectsAreaFrame
* it
=
19 new (aShell
) nsSelectsAreaFrame(aStyle
, aShell
->GetPresContext());
21 // We need NS_BLOCK_STATIC_BFC to ensure that the options inside the select
22 // aren't expanded by right floats outside the select.
23 it
->AddStateBits(NS_BLOCK_STATIC_BFC
);
28 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame
)
30 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
) {
31 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
33 if (frame
->IsListControlFrame())
34 return static_cast<nsListControlFrame
*>(frame
);
35 frame
= frame
->GetParent();
40 void nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
41 ReflowOutput
& aDesiredSize
,
42 const ReflowInput
& aReflowInput
,
43 nsReflowStatus
& aStatus
) {
44 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
46 nsListControlFrame
* list
= GetEnclosingListFrame(this);
48 "Must have an nsListControlFrame! Frame constructor is "
51 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
53 // Check whether we need to suppress scrollbar updates. We want to do
54 // that if we're in a possible first pass and our block size of a row
56 if (list
->MightNeedSecondPass()) {
57 nscoord newBSizeOfARow
= list
->CalcBSizeOfARow();
58 // We'll need a second pass if our block size of a row changed. For
59 // comboboxes, we'll also need it if our block size changed. If
60 // we're going to do a second pass, suppress scrollbar updates for
62 if (newBSizeOfARow
!= mBSizeOfARow
) {
63 mBSizeOfARow
= newBSizeOfARow
;
64 list
->SetSuppressScrollbarUpdate(true);
71 * This wrapper class lets us redirect mouse hits from the child frame of
72 * an option element to the element's own frame.
73 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
75 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
77 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
79 : nsDisplayWrapList(aBuilder
, aFrame
, aItem
) {}
80 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
82 : nsDisplayWrapList(aBuilder
, aFrame
, aList
) {}
83 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
85 nsTArray
<nsIFrame
*>* aOutFrames
) override
;
86 virtual bool ShouldFlattenAway(nsDisplayListBuilder
* aBuilder
) override
{
89 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
{
90 GetChildren()->Paint(aBuilder
, aCtx
,
91 mFrame
->PresContext()->AppUnitsPerDevPixel());
93 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
96 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
99 nsTArray
<nsIFrame
*>* aOutFrames
) {
100 nsTArray
<nsIFrame
*> outFrames
;
101 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
103 for (uint32_t i
= 0; i
< outFrames
.Length(); i
++) {
104 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
105 while (selectedFrame
&&
106 !(selectedFrame
->GetContent() &&
107 selectedFrame
->GetContent()->IsHTMLElement(nsGkAtoms::option
))) {
108 selectedFrame
= selectedFrame
->GetParent();
111 aOutFrames
->AppendElement(selectedFrame
);
113 // keep the original result, which could be this frame
114 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
119 class nsOptionEventGrabberWrapper
: public nsDisplayItemWrapper
{
121 nsOptionEventGrabberWrapper() = default;
122 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
124 nsDisplayList
* aList
) override
{
125 return MakeDisplayItem
<nsDisplayOptionEventGrabber
>(aBuilder
, aFrame
,
128 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
129 nsDisplayItem
* aItem
) override
{
130 return MakeDisplayItem
<nsDisplayOptionEventGrabber
>(aBuilder
,
131 aItem
->Frame(), aItem
);
135 class nsDisplayListFocus
: public nsPaintedDisplayItem
{
137 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
, nsSelectsAreaFrame
* aFrame
)
138 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
139 MOZ_COUNT_CTOR(nsDisplayListFocus
);
141 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayListFocus
)
143 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
144 bool* aSnap
) const override
{
146 // override bounds because the list item focus ring may extend outside
147 // the nsSelectsAreaFrame
148 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
149 return listFrame
->InkOverflowRectRelativeToSelf() +
150 listFrame
->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame();
152 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
153 gfxContext
* aCtx
) override
{
154 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
155 // listFrame must be non-null or we wouldn't get called.
156 listFrame
->PaintFocus(
157 aCtx
->GetDrawTarget(),
158 listFrame
->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame());
160 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
163 } // namespace mozilla
165 void nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
166 const nsDisplayListSet
& aLists
) {
167 if (!aBuilder
->IsForEventDelivery()) {
168 BuildDisplayListInternal(aBuilder
, aLists
);
172 nsDisplayListCollection
set(aBuilder
);
173 BuildDisplayListInternal(aBuilder
, set
);
175 nsOptionEventGrabberWrapper wrapper
;
176 wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
179 void nsSelectsAreaFrame::BuildDisplayListInternal(
180 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
181 nsBlockFrame::BuildDisplayList(aBuilder
, aLists
);
183 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
184 if (listFrame
&& listFrame
->IsFocused()) {
185 // we can't just associate the display item with the list frame,
186 // because then the list's scrollframe won't clip it (the scrollframe
187 // only clips contained descendants).
188 aLists
.Outlines()->AppendNewToTop
<nsDisplayListFocus
>(aBuilder
, this);