Line data Source code
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"
9 : #include "WritingModes.h"
10 :
11 : using namespace mozilla;
12 :
13 : nsContainerFrame*
14 0 : NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, nsFrameState aFlags)
15 : {
16 0 : nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
17 :
18 : // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
19 : // aren't expanded by right floats outside the select.
20 0 : it->AddStateBits(aFlags | NS_BLOCK_FLOAT_MGR);
21 :
22 0 : return it;
23 : }
24 :
25 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
26 :
27 : //---------------------------------------------------------
28 : /**
29 : * This wrapper class lets us redirect mouse hits from the child frame of
30 : * an option element to the element's own frame.
31 : * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
32 : */
33 0 : class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
34 : public:
35 0 : nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
36 : nsIFrame* aFrame, nsDisplayItem* aItem)
37 0 : : nsDisplayWrapList(aBuilder, aFrame, aItem) {}
38 0 : nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
39 : nsIFrame* aFrame, nsDisplayList* aList)
40 0 : : nsDisplayWrapList(aBuilder, aFrame, aList) {}
41 : virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
42 : HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
43 0 : virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
44 0 : return false;
45 : }
46 0 : NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
47 : };
48 :
49 0 : void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
50 : const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
51 : {
52 0 : nsTArray<nsIFrame*> outFrames;
53 0 : mList.HitTest(aBuilder, aRect, aState, &outFrames);
54 :
55 0 : for (uint32_t i = 0; i < outFrames.Length(); i++) {
56 0 : nsIFrame* selectedFrame = outFrames.ElementAt(i);
57 0 : while (selectedFrame &&
58 0 : !(selectedFrame->GetContent() &&
59 0 : selectedFrame->GetContent()->IsHTMLElement(nsGkAtoms::option))) {
60 0 : selectedFrame = selectedFrame->GetParent();
61 : }
62 0 : if (selectedFrame) {
63 0 : aOutFrames->AppendElement(selectedFrame);
64 : } else {
65 : // keep the original result, which could be this frame
66 0 : aOutFrames->AppendElement(outFrames.ElementAt(i));
67 : }
68 : }
69 0 : }
70 :
71 : class nsOptionEventGrabberWrapper : public nsDisplayWrapper
72 : {
73 : public:
74 0 : nsOptionEventGrabberWrapper() {}
75 0 : virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
76 : nsIFrame* aFrame, nsDisplayList* aList) {
77 0 : return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
78 : }
79 0 : virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
80 : nsDisplayItem* aItem) {
81 0 : return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
82 : }
83 : };
84 :
85 0 : static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
86 : {
87 0 : nsIFrame* frame = aSelectsAreaFrame->GetParent();
88 0 : while (frame) {
89 0 : if (frame->IsListControlFrame())
90 0 : return static_cast<nsListControlFrame*>(frame);
91 0 : frame = frame->GetParent();
92 : }
93 0 : return nullptr;
94 : }
95 :
96 : class nsDisplayListFocus : public nsDisplayItem {
97 : public:
98 0 : nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
99 0 : nsSelectsAreaFrame* aFrame) :
100 0 : nsDisplayItem(aBuilder, aFrame) {
101 0 : MOZ_COUNT_CTOR(nsDisplayListFocus);
102 0 : }
103 : #ifdef NS_BUILD_REFCNT_LOGGING
104 0 : virtual ~nsDisplayListFocus() {
105 0 : MOZ_COUNT_DTOR(nsDisplayListFocus);
106 0 : }
107 : #endif
108 :
109 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
110 0 : *aSnap = false;
111 : // override bounds because the list item focus ring may extend outside
112 : // the nsSelectsAreaFrame
113 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
114 0 : return listFrame->GetVisualOverflowRectRelativeToSelf() +
115 0 : listFrame->GetOffsetToCrossDoc(ReferenceFrame());
116 : }
117 0 : virtual void Paint(nsDisplayListBuilder* aBuilder,
118 : gfxContext* aCtx) override {
119 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
120 : // listFrame must be non-null or we wouldn't get called.
121 0 : listFrame->PaintFocus(aCtx->GetDrawTarget(),
122 0 : aBuilder->ToReferenceFrame(listFrame));
123 0 : }
124 0 : NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
125 : };
126 :
127 : void
128 0 : nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
129 : const nsRect& aDirtyRect,
130 : const nsDisplayListSet& aLists)
131 : {
132 0 : if (!aBuilder->IsForEventDelivery()) {
133 0 : BuildDisplayListInternal(aBuilder, aDirtyRect, aLists);
134 0 : return;
135 : }
136 :
137 0 : nsDisplayListCollection set;
138 0 : BuildDisplayListInternal(aBuilder, aDirtyRect, set);
139 :
140 0 : nsOptionEventGrabberWrapper wrapper;
141 0 : wrapper.WrapLists(aBuilder, this, set, aLists);
142 : }
143 :
144 : void
145 0 : nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
146 : const nsRect& aDirtyRect,
147 : const nsDisplayListSet& aLists)
148 : {
149 0 : nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
150 :
151 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(this);
152 0 : if (listFrame && listFrame->IsFocused()) {
153 : // we can't just associate the display item with the list frame,
154 : // because then the list's scrollframe won't clip it (the scrollframe
155 : // only clips contained descendants).
156 0 : aLists.Outlines()->AppendNewToTop(new (aBuilder)
157 0 : nsDisplayListFocus(aBuilder, this));
158 : }
159 0 : }
160 :
161 : void
162 0 : nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
163 : ReflowOutput& aDesiredSize,
164 : const ReflowInput& aReflowInput,
165 : nsReflowStatus& aStatus)
166 : {
167 0 : nsListControlFrame* list = GetEnclosingListFrame(this);
168 0 : NS_ASSERTION(list,
169 : "Must have an nsListControlFrame! Frame constructor is "
170 : "broken");
171 :
172 0 : bool isInDropdownMode = list->IsInDropDownMode();
173 :
174 : // See similar logic in nsListControlFrame::Reflow and
175 : // nsListControlFrame::ReflowAsDropdown. We need to match it here.
176 0 : WritingMode wm = aReflowInput.GetWritingMode();
177 : nscoord oldBSize;
178 0 : if (isInDropdownMode) {
179 : // Store the block size now in case it changes during
180 : // nsBlockFrame::Reflow for some odd reason.
181 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
182 0 : oldBSize = BSize(wm);
183 : } else {
184 0 : oldBSize = NS_UNCONSTRAINEDSIZE;
185 : }
186 : }
187 :
188 0 : nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
189 :
190 : // Check whether we need to suppress scrollbar updates. We want to do
191 : // that if we're in a possible first pass and our block size of a row
192 : // has changed.
193 0 : if (list->MightNeedSecondPass()) {
194 0 : nscoord newBSizeOfARow = list->CalcBSizeOfARow();
195 : // We'll need a second pass if our block size of a row changed. For
196 : // comboboxes, we'll also need it if our block size changed. If
197 : // we're going to do a second pass, suppress scrollbar updates for
198 : // this pass.
199 0 : if (newBSizeOfARow != mBSizeOfARow ||
200 0 : (isInDropdownMode && (oldBSize != aDesiredSize.BSize(wm) ||
201 0 : oldBSize != BSize(wm)))) {
202 0 : mBSizeOfARow = newBSizeOfARow;
203 0 : list->SetSuppressScrollbarUpdate(true);
204 : }
205 : }
206 0 : }
|