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());
23 NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame
)
25 NS_QUERYFRAME_HEAD(nsSelectsAreaFrame
)
26 NS_QUERYFRAME_ENTRY(nsSelectsAreaFrame
)
27 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
29 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
) {
30 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
32 if (frame
->IsListControlFrame())
33 return static_cast<nsListControlFrame
*>(frame
);
34 frame
= frame
->GetParent();
39 void nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
40 ReflowOutput
& aDesiredSize
,
41 const ReflowInput
& aReflowInput
,
42 nsReflowStatus
& aStatus
) {
43 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
45 nsListControlFrame
* list
= GetEnclosingListFrame(this);
47 "Must have an nsListControlFrame! Frame constructor is "
50 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
52 // Check whether we need to suppress scrollbar updates. We want to do
53 // that if we're in a possible first pass and our block size of a row
55 if (list
->MightNeedSecondPass()) {
56 nscoord newBSizeOfARow
= list
->CalcBSizeOfARow();
57 // We'll need a second pass if our block size of a row changed. For
58 // comboboxes, we'll also need it if our block size changed. If
59 // we're going to do a second pass, suppress scrollbar updates for
61 if (newBSizeOfARow
!= mBSizeOfARow
) {
62 mBSizeOfARow
= newBSizeOfARow
;
63 list
->SetSuppressScrollbarUpdate(true);
70 * This wrapper class lets us redirect mouse hits from the child frame of
71 * an option element to the element's own frame.
72 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
74 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
76 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
78 : nsDisplayWrapList(aBuilder
, aFrame
, aItem
) {}
79 nsDisplayOptionEventGrabber(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
81 : nsDisplayWrapList(aBuilder
, aFrame
, aList
) {}
82 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
84 nsTArray
<nsIFrame
*>* aOutFrames
) override
;
85 virtual bool ShouldFlattenAway(nsDisplayListBuilder
* aBuilder
) override
{
88 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
{
89 GetChildren()->Paint(aBuilder
, aCtx
,
90 mFrame
->PresContext()->AppUnitsPerDevPixel());
92 NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER
)
95 void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
98 nsTArray
<nsIFrame
*>* aOutFrames
) {
99 nsTArray
<nsIFrame
*> outFrames
;
100 mList
.HitTest(aBuilder
, aRect
, aState
, &outFrames
);
102 for (uint32_t i
= 0; i
< outFrames
.Length(); i
++) {
103 nsIFrame
* selectedFrame
= outFrames
.ElementAt(i
);
104 while (selectedFrame
&&
105 !(selectedFrame
->GetContent() &&
106 selectedFrame
->GetContent()->IsHTMLElement(nsGkAtoms::option
))) {
107 selectedFrame
= selectedFrame
->GetParent();
110 aOutFrames
->AppendElement(selectedFrame
);
112 // keep the original result, which could be this frame
113 aOutFrames
->AppendElement(outFrames
.ElementAt(i
));
118 class nsOptionEventGrabberWrapper
: public nsDisplayItemWrapper
{
120 nsOptionEventGrabberWrapper() = default;
121 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
123 nsDisplayList
* aList
) override
{
124 return MakeDisplayItem
<nsDisplayOptionEventGrabber
>(aBuilder
, aFrame
,
127 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
128 nsDisplayItem
* aItem
) override
{
129 return MakeDisplayItem
<nsDisplayOptionEventGrabber
>(aBuilder
,
130 aItem
->Frame(), aItem
);
134 class nsDisplayListFocus
: public nsPaintedDisplayItem
{
136 nsDisplayListFocus(nsDisplayListBuilder
* aBuilder
, nsSelectsAreaFrame
* aFrame
)
137 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
138 MOZ_COUNT_CTOR(nsDisplayListFocus
);
140 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayListFocus
)
142 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
143 bool* aSnap
) const override
{
145 // override bounds because the list item focus ring may extend outside
146 // the nsSelectsAreaFrame
147 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
148 return listFrame
->InkOverflowRectRelativeToSelf() +
149 listFrame
->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame();
151 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
152 gfxContext
* aCtx
) override
{
153 nsListControlFrame
* listFrame
= GetEnclosingListFrame(Frame());
154 // listFrame must be non-null or we wouldn't get called.
155 listFrame
->PaintFocus(
156 aCtx
->GetDrawTarget(),
157 listFrame
->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame());
159 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS
)
162 } // namespace mozilla
164 void nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
165 const nsDisplayListSet
& aLists
) {
166 if (!aBuilder
->IsForEventDelivery()) {
167 BuildDisplayListInternal(aBuilder
, aLists
);
171 nsDisplayListCollection
set(aBuilder
);
172 BuildDisplayListInternal(aBuilder
, set
);
174 nsOptionEventGrabberWrapper wrapper
;
175 wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
178 void nsSelectsAreaFrame::BuildDisplayListInternal(
179 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
180 nsBlockFrame::BuildDisplayList(aBuilder
, aLists
);
182 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
183 if (listFrame
&& listFrame
->IsFocused()) {
184 // we can't just associate the display item with the list frame,
185 // because then the list's scrollframe won't clip it (the scrollframe
186 // only clips contained descendants).
187 aLists
.Outlines()->AppendNewToTop
<nsDisplayListFocus
>(aBuilder
, this);