/src/mozilla-central/layout/forms/nsComboboxControlFrame.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
7 | | #include "nsComboboxControlFrame.h" |
8 | | |
9 | | #include "gfxContext.h" |
10 | | #include "gfxUtils.h" |
11 | | #include "mozilla/gfx/2D.h" |
12 | | #include "mozilla/gfx/PathHelpers.h" |
13 | | #include "nsCOMPtr.h" |
14 | | #include "nsFocusManager.h" |
15 | | #include "nsCheckboxRadioFrame.h" |
16 | | #include "nsGkAtoms.h" |
17 | | #include "nsCSSAnonBoxes.h" |
18 | | #include "nsHTMLParts.h" |
19 | | #include "nsIFormControl.h" |
20 | | #include "nsNameSpaceManager.h" |
21 | | #include "nsIListControlFrame.h" |
22 | | #include "nsPIDOMWindow.h" |
23 | | #include "nsIPresShell.h" |
24 | | #include "mozilla/PresState.h" |
25 | | #include "nsView.h" |
26 | | #include "nsViewManager.h" |
27 | | #include "nsIContentInlines.h" |
28 | | #include "nsIDOMEventListener.h" |
29 | | #include "nsISelectControlFrame.h" |
30 | | #include "nsContentUtils.h" |
31 | | #include "mozilla/dom/Event.h" |
32 | | #include "mozilla/dom/HTMLSelectElement.h" |
33 | | #include "nsIDocument.h" |
34 | | #include "nsIScrollableFrame.h" |
35 | | #include "nsListControlFrame.h" |
36 | | #include "mozilla/ServoStyleSet.h" |
37 | | #include "nsNodeInfoManager.h" |
38 | | #include "nsContentCreatorFunctions.h" |
39 | | #include "nsLayoutUtils.h" |
40 | | #include "nsDisplayList.h" |
41 | | #include "nsITheme.h" |
42 | | #include "nsStyleConsts.h" |
43 | | #include "mozilla/Likely.h" |
44 | | #include <algorithm> |
45 | | #include "nsTextNode.h" |
46 | | #include "mozilla/AsyncEventDispatcher.h" |
47 | | #include "mozilla/EventStates.h" |
48 | | #include "mozilla/LookAndFeel.h" |
49 | | #include "mozilla/MouseEvents.h" |
50 | | #include "mozilla/Unused.h" |
51 | | #include "gfx2DGlue.h" |
52 | | #include "mozilla/widget/nsAutoRollup.h" |
53 | | #include "nsILayoutHistoryState.h" |
54 | | |
55 | | #ifdef XP_WIN |
56 | | #define COMBOBOX_ROLLUP_CONSUME_EVENT 0 |
57 | | #else |
58 | 0 | #define COMBOBOX_ROLLUP_CONSUME_EVENT 1 |
59 | | #endif |
60 | | |
61 | | using namespace mozilla; |
62 | | using namespace mozilla::gfx; |
63 | | |
64 | | NS_IMETHODIMP |
65 | | nsComboboxControlFrame::RedisplayTextEvent::Run() |
66 | 0 | { |
67 | 0 | if (mControlFrame) |
68 | 0 | mControlFrame->HandleRedisplayTextEvent(); |
69 | 0 | return NS_OK; |
70 | 0 | } |
71 | | |
72 | | |
73 | | #define FIX_FOR_BUG_53259 |
74 | | |
75 | | // Drop down list event management. |
76 | | // The combo box uses the following strategy for managing the drop-down list. |
77 | | // If the combo box or its arrow button is clicked on the drop-down list is displayed |
78 | | // If mouse exits the combo box with the drop-down list displayed the drop-down list |
79 | | // is asked to capture events |
80 | | // The drop-down list will capture all events including mouse down and up and will always |
81 | | // return with ListWasSelected method call regardless of whether an item in the list was |
82 | | // actually selected. |
83 | | // The ListWasSelected code will turn off mouse-capture for the drop-down list. |
84 | | // The drop-down list does not explicitly set capture when it is in the drop-down mode. |
85 | | |
86 | | |
87 | | /** |
88 | | * Helper class that listens to the combo boxes button. If the button is pressed the |
89 | | * combo box is toggled to open or close. this is used by Accessibility which presses |
90 | | * that button Programmatically. |
91 | | */ |
92 | | class nsComboButtonListener final : public nsIDOMEventListener |
93 | | { |
94 | | private: |
95 | 0 | virtual ~nsComboButtonListener() {} |
96 | | |
97 | | public: |
98 | | NS_DECL_ISUPPORTS |
99 | | |
100 | | NS_IMETHOD HandleEvent(dom::Event*) override |
101 | 0 | { |
102 | 0 | mComboBox->ShowDropDown(!mComboBox->IsDroppedDown()); |
103 | 0 | return NS_OK; |
104 | 0 | } |
105 | | |
106 | | explicit nsComboButtonListener(nsComboboxControlFrame* aCombobox) |
107 | 0 | { |
108 | 0 | mComboBox = aCombobox; |
109 | 0 | } |
110 | | |
111 | | nsComboboxControlFrame* mComboBox; |
112 | | }; |
113 | | |
114 | | NS_IMPL_ISUPPORTS(nsComboButtonListener, |
115 | | nsIDOMEventListener) |
116 | | |
117 | | // static class data member for Bug 32920 |
118 | | nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr; |
119 | | |
120 | | nsComboboxControlFrame* |
121 | | NS_NewComboboxControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle, nsFrameState aStateFlags) |
122 | 0 | { |
123 | 0 | nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aStyle); |
124 | 0 |
|
125 | 0 | if (it) { |
126 | 0 | // set the state flags (if any are provided) |
127 | 0 | it->AddStateBits(aStateFlags); |
128 | 0 | } |
129 | 0 |
|
130 | 0 | return it; |
131 | 0 | } |
132 | | |
133 | | NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame) |
134 | | |
135 | | //----------------------------------------------------------- |
136 | | // Reflow Debugging Macros |
137 | | // These let us "see" how many reflow counts are happening |
138 | | //----------------------------------------------------------- |
139 | | #ifdef DO_REFLOW_COUNTER |
140 | | |
141 | | #define MAX_REFLOW_CNT 1024 |
142 | | static int32_t gTotalReqs = 0;; |
143 | | static int32_t gTotalReflows = 0;; |
144 | | static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT]; |
145 | | static int32_t gReflowControlCnt[MAX_REFLOW_CNT]; |
146 | | static int32_t gReflowInx = -1; |
147 | | |
148 | | #define REFLOW_COUNTER() \ |
149 | | if (mReflowId > -1) \ |
150 | | gReflowControlCnt[mReflowId]++; |
151 | | |
152 | | #define REFLOW_COUNTER_REQUEST() \ |
153 | | if (mReflowId > -1) \ |
154 | | gReflowControlCntRQ[mReflowId]++; |
155 | | |
156 | | #define REFLOW_COUNTER_DUMP(__desc) \ |
157 | | if (mReflowId > -1) {\ |
158 | | gTotalReqs += gReflowControlCntRQ[mReflowId];\ |
159 | | gTotalReflows += gReflowControlCnt[mReflowId];\ |
160 | | printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \ |
161 | | mReflowId, (__desc), \ |
162 | | gReflowControlCnt[mReflowId], \ |
163 | | gReflowControlCntRQ[mReflowId],\ |
164 | | gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\ |
165 | | } |
166 | | |
167 | | #define REFLOW_COUNTER_INIT() \ |
168 | | if (gReflowInx < MAX_REFLOW_CNT) { \ |
169 | | gReflowInx++; \ |
170 | | mReflowId = gReflowInx; \ |
171 | | gReflowControlCnt[mReflowId] = 0; \ |
172 | | gReflowControlCntRQ[mReflowId] = 0; \ |
173 | | } else { \ |
174 | | mReflowId = -1; \ |
175 | | } |
176 | | |
177 | | // reflow messages |
178 | | #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1)) |
179 | | #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) |
180 | | #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) |
181 | | #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) |
182 | | |
183 | | #else //------------- |
184 | | |
185 | | #define REFLOW_COUNTER_REQUEST() |
186 | | #define REFLOW_COUNTER() |
187 | | #define REFLOW_COUNTER_DUMP(__desc) |
188 | | #define REFLOW_COUNTER_INIT() |
189 | | |
190 | | #define REFLOW_DEBUG_MSG(_msg) |
191 | | #define REFLOW_DEBUG_MSG2(_msg1, _msg2) |
192 | | #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) |
193 | | #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) |
194 | | |
195 | | |
196 | | #endif |
197 | | |
198 | | //------------------------------------------ |
199 | | // This is for being VERY noisy |
200 | | //------------------------------------------ |
201 | | #ifdef DO_VERY_NOISY |
202 | | #define REFLOW_NOISY_MSG(_msg1) printf((_msg1)) |
203 | | #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) |
204 | | #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) |
205 | | #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) |
206 | | #else |
207 | | #define REFLOW_NOISY_MSG(_msg) |
208 | | #define REFLOW_NOISY_MSG2(_msg1, _msg2) |
209 | | #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) |
210 | | #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) |
211 | | #endif |
212 | | |
213 | | //------------------------------------------ |
214 | | // Displays value in pixels or twips |
215 | | //------------------------------------------ |
216 | | #ifdef DO_PIXELS |
217 | | #define PX(__v) __v / 15 |
218 | | #else |
219 | | #define PX(__v) __v |
220 | | #endif |
221 | | |
222 | | //------------------------------------------------------ |
223 | | //-- Done with macros |
224 | | //------------------------------------------------------ |
225 | | |
226 | | nsComboboxControlFrame::nsComboboxControlFrame(ComputedStyle* aStyle) |
227 | | : nsBlockFrame(aStyle, kClassID) |
228 | | , mDisplayFrame(nullptr) |
229 | | , mButtonFrame(nullptr) |
230 | | , mDropdownFrame(nullptr) |
231 | | , mListControlFrame(nullptr) |
232 | | , mDisplayISize(0) |
233 | | , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX) |
234 | | , mDisplayedIndex(-1) |
235 | | , mLastDropDownBeforeScreenBCoord(nscoord_MIN) |
236 | | , mLastDropDownAfterScreenBCoord(nscoord_MIN) |
237 | | , mDroppedDown(false) |
238 | | , mInRedisplayText(false) |
239 | | , mDelayedShowDropDown(false) |
240 | | , mIsOpenInParentProcess(false) |
241 | 0 | { |
242 | 0 | REFLOW_COUNTER_INIT() |
243 | 0 | } |
244 | | |
245 | | //-------------------------------------------------------------- |
246 | | nsComboboxControlFrame::~nsComboboxControlFrame() |
247 | 0 | { |
248 | 0 | REFLOW_COUNTER_DUMP("nsCCF"); |
249 | 0 | } |
250 | | |
251 | | //-------------------------------------------------------------- |
252 | | |
253 | 0 | NS_QUERYFRAME_HEAD(nsComboboxControlFrame) |
254 | 0 | NS_QUERYFRAME_ENTRY(nsComboboxControlFrame) |
255 | 0 | NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame) |
256 | 0 | NS_QUERYFRAME_ENTRY(nsIFormControlFrame) |
257 | 0 | NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) |
258 | 0 | NS_QUERYFRAME_ENTRY(nsISelectControlFrame) |
259 | 0 | NS_QUERYFRAME_ENTRY(nsIStatefulFrame) |
260 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) |
261 | | |
262 | | #ifdef ACCESSIBILITY |
263 | | a11y::AccType |
264 | | nsComboboxControlFrame::AccessibleType() |
265 | 0 | { |
266 | 0 | return a11y::eHTMLComboboxType; |
267 | 0 | } |
268 | | #endif |
269 | | |
270 | | void |
271 | | nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint) |
272 | 0 | { |
273 | 0 | AutoWeakFrame weakFrame(this); |
274 | 0 | if (aOn) { |
275 | 0 | nsListControlFrame::ComboboxFocusSet(); |
276 | 0 | sFocused = this; |
277 | 0 | if (mDelayedShowDropDown) { |
278 | 0 | ShowDropDown(true); // might destroy us |
279 | 0 | if (!weakFrame.IsAlive()) { |
280 | 0 | return; |
281 | 0 | } |
282 | 0 | } |
283 | 0 | } else { |
284 | 0 | sFocused = nullptr; |
285 | 0 | mDelayedShowDropDown = false; |
286 | 0 | if (mDroppedDown) { |
287 | 0 | mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us |
288 | 0 | if (!weakFrame.IsAlive()) { |
289 | 0 | return; |
290 | 0 | } |
291 | 0 | } |
292 | 0 | // May delete |this|. |
293 | 0 | mListControlFrame->FireOnInputAndOnChange(); |
294 | 0 | } |
295 | 0 |
|
296 | 0 | if (!weakFrame.IsAlive()) { |
297 | 0 | return; |
298 | 0 | } |
299 | 0 | |
300 | 0 | // This is needed on a temporary basis. It causes the focus |
301 | 0 | // rect to be drawn. This is much faster than ReResolvingStyle |
302 | 0 | // Bug 32920 |
303 | 0 | InvalidateFrame(); |
304 | 0 | } |
305 | | |
306 | | void |
307 | | nsComboboxControlFrame::ShowPopup(bool aShowPopup) |
308 | 0 | { |
309 | 0 | // TODO(kuoe0) Remove this function when content-select is enabled. |
310 | 0 |
|
311 | 0 | nsView* view = mDropdownFrame->GetView(); |
312 | 0 | nsViewManager* viewManager = view->GetViewManager(); |
313 | 0 |
|
314 | 0 | if (aShowPopup) { |
315 | 0 | nsRect rect = mDropdownFrame->GetRect(); |
316 | 0 | rect.x = rect.y = 0; |
317 | 0 | viewManager->ResizeView(view, rect); |
318 | 0 | viewManager->SetViewVisibility(view, nsViewVisibility_kShow); |
319 | 0 | } else { |
320 | 0 | viewManager->SetViewVisibility(view, nsViewVisibility_kHide); |
321 | 0 | nsRect emptyRect(0, 0, 0, 0); |
322 | 0 | viewManager->ResizeView(view, emptyRect); |
323 | 0 | } |
324 | 0 |
|
325 | 0 | // fire a popup dom event if it is safe to do so |
326 | 0 | nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell(); |
327 | 0 | if (shell && nsContentUtils::IsSafeToRunScript()) { |
328 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
329 | 0 | WidgetMouseEvent event(true, aShowPopup ? eXULPopupShowing : eXULPopupHiding, |
330 | 0 | nullptr, WidgetMouseEvent::eReal); |
331 | 0 |
|
332 | 0 | shell->HandleDOMEventWithTarget(mContent, &event, &status); |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | bool |
337 | | nsComboboxControlFrame::ShowList(bool aShowList) |
338 | 0 | { |
339 | 0 |
|
340 | 0 | // TODO(kuoe0) Remove this function when content-select is enabled. |
341 | 0 | // |
342 | 0 | // This function is used to handle the widget/view stuff, so we just return |
343 | 0 | // when content-select is enabled. And the following callee, ShowPopup(), will |
344 | 0 | // also be ignored, it is only used to show and hide the widget. |
345 | 0 | if (nsLayoutUtils::IsContentSelectEnabled()) { |
346 | 0 | return true; |
347 | 0 | } |
348 | 0 | |
349 | 0 | nsView* view = mDropdownFrame->GetView(); |
350 | 0 | if (aShowList) { |
351 | 0 | NS_ASSERTION(!view->HasWidget(), |
352 | 0 | "We shouldn't have a widget before we need to display the popup"); |
353 | 0 |
|
354 | 0 | // Create the widget for the drop-down list |
355 | 0 | view->GetViewManager()->SetViewFloating(view, true); |
356 | 0 |
|
357 | 0 | nsWidgetInitData widgetData; |
358 | 0 | widgetData.mWindowType = eWindowType_popup; |
359 | 0 | widgetData.mBorderStyle = eBorderStyle_default; |
360 | 0 | view->CreateWidgetForPopup(&widgetData); |
361 | 0 | } else { |
362 | 0 | nsIWidget* widget = view->GetWidget(); |
363 | 0 | if (widget) { |
364 | 0 | // We must do this before ShowPopup in case it destroys us (bug 813442). |
365 | 0 | widget->CaptureRollupEvents(this, false); |
366 | 0 | } |
367 | 0 | } |
368 | 0 |
|
369 | 0 | AutoWeakFrame weakFrame(this); |
370 | 0 | ShowPopup(aShowList); // might destroy us |
371 | 0 | if (!weakFrame.IsAlive()) { |
372 | 0 | return false; |
373 | 0 | } |
374 | 0 | |
375 | 0 | mDroppedDown = aShowList; |
376 | 0 | nsIWidget* widget = view->GetWidget(); |
377 | 0 | if (mDroppedDown) { |
378 | 0 | // The listcontrol frame will call back to the nsComboboxControlFrame's |
379 | 0 | // ListWasSelected which will stop the capture. |
380 | 0 | mListControlFrame->AboutToDropDown(); |
381 | 0 | mListControlFrame->CaptureMouseEvents(true); |
382 | 0 | if (widget) { |
383 | 0 | widget->CaptureRollupEvents(this, true); |
384 | 0 | } |
385 | 0 | } else { |
386 | 0 | if (widget) { |
387 | 0 | view->DestroyWidget(); |
388 | 0 | } |
389 | 0 | } |
390 | 0 |
|
391 | 0 | return weakFrame.IsAlive(); |
392 | 0 | } |
393 | | |
394 | | class nsResizeDropdownAtFinalPosition final |
395 | | : public nsIReflowCallback, public Runnable |
396 | | { |
397 | | public: |
398 | | explicit nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame) |
399 | | : mozilla::Runnable("nsResizeDropdownAtFinalPosition") |
400 | | , mFrame(aFrame) |
401 | 0 | { |
402 | 0 | } |
403 | | |
404 | | protected: |
405 | | ~nsResizeDropdownAtFinalPosition() |
406 | 0 | { |
407 | 0 | } |
408 | | |
409 | | public: |
410 | | virtual bool ReflowFinished() override |
411 | 0 | { |
412 | 0 | Run(); |
413 | 0 | NS_RELEASE_THIS(); |
414 | 0 | return false; |
415 | 0 | } |
416 | | |
417 | | virtual void ReflowCallbackCanceled() override |
418 | 0 | { |
419 | 0 | NS_RELEASE_THIS(); |
420 | 0 | } |
421 | | |
422 | | NS_IMETHOD Run() override |
423 | 0 | { |
424 | 0 | if (mFrame.IsAlive()) { |
425 | 0 | static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())-> |
426 | 0 | AbsolutelyPositionDropDown(); |
427 | 0 | } |
428 | 0 | return NS_OK; |
429 | 0 | } |
430 | | |
431 | | WeakFrame mFrame; |
432 | | }; |
433 | | |
434 | | void |
435 | | nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext, |
436 | | const ReflowInput& aReflowInput) |
437 | 0 | { |
438 | 0 | // All we want out of it later on, really, is the block size of a row, so we |
439 | 0 | // don't even need to cache mDropdownFrame's ascent or anything. If we don't |
440 | 0 | // need to reflow it, just bail out here. |
441 | 0 | if (!aReflowInput.ShouldReflowAllKids() && |
442 | 0 | !NS_SUBTREE_DIRTY(mDropdownFrame)) { |
443 | 0 | return; |
444 | 0 | } |
445 | 0 | |
446 | 0 | // XXXbz this will, for small-block-size dropdowns, have extra space |
447 | 0 | // on the appropriate edge for the scrollbar we don't show... but |
448 | 0 | // that's the best we can do here for now. |
449 | 0 | WritingMode wm = mDropdownFrame->GetWritingMode(); |
450 | 0 | LogicalSize availSize = aReflowInput.AvailableSize(wm); |
451 | 0 | availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; |
452 | 0 | ReflowInput kidReflowInput(aPresContext, aReflowInput, mDropdownFrame, |
453 | 0 | availSize); |
454 | 0 |
|
455 | 0 | // If the dropdown's intrinsic inline size is narrower than our |
456 | 0 | // specified inline size, then expand it out. We want our border-box |
457 | 0 | // inline size to end up the same as the dropdown's so account for |
458 | 0 | // both sets of mComputedBorderPadding. |
459 | 0 | nscoord forcedISize = aReflowInput.ComputedISize() + |
460 | 0 | aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) - |
461 | 0 | kidReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm); |
462 | 0 | kidReflowInput.SetComputedISize(std::max(kidReflowInput.ComputedISize(), |
463 | 0 | forcedISize)); |
464 | 0 |
|
465 | 0 | // ensure we start off hidden |
466 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled() && |
467 | 0 | !mDroppedDown && GetStateBits() & NS_FRAME_FIRST_REFLOW) { |
468 | 0 | nsView* view = mDropdownFrame->GetView(); |
469 | 0 | nsViewManager* viewManager = view->GetViewManager(); |
470 | 0 | viewManager->SetViewVisibility(view, nsViewVisibility_kHide); |
471 | 0 | nsRect emptyRect(0, 0, 0, 0); |
472 | 0 | viewManager->ResizeView(view, emptyRect); |
473 | 0 | } |
474 | 0 |
|
475 | 0 | // Allow the child to move/size/change-visibility its view if it's currently |
476 | 0 | // dropped down |
477 | 0 | int32_t flags = mDroppedDown ? 0 |
478 | 0 | : NS_FRAME_NO_MOVE_FRAME | |
479 | 0 | NS_FRAME_NO_VISIBILITY | |
480 | 0 | NS_FRAME_NO_SIZE_VIEW; |
481 | 0 |
|
482 | 0 | //XXX Can this be different from the dropdown's writing mode? |
483 | 0 | // That would be odd! |
484 | 0 | // Note that we don't need to pass the true frame position or container size |
485 | 0 | // to ReflowChild or FinishReflowChild here; it will be positioned as needed |
486 | 0 | // by AbsolutelyPositionDropDown(). |
487 | 0 | WritingMode outerWM = GetWritingMode(); |
488 | 0 | const nsSize dummyContainerSize; |
489 | 0 | ReflowOutput desiredSize(aReflowInput); |
490 | 0 | nsReflowStatus ignoredStatus; |
491 | 0 | ReflowChild(mDropdownFrame, aPresContext, desiredSize, |
492 | 0 | kidReflowInput, outerWM, LogicalPoint(outerWM), |
493 | 0 | dummyContainerSize, flags, ignoredStatus); |
494 | 0 |
|
495 | 0 | // Set the child's width and height to its desired size |
496 | 0 | FinishReflowChild(mDropdownFrame, aPresContext, desiredSize, &kidReflowInput, |
497 | 0 | outerWM, LogicalPoint(outerWM), dummyContainerSize, flags); |
498 | 0 | } |
499 | | |
500 | | nsPoint |
501 | | nsComboboxControlFrame::GetCSSTransformTranslation() |
502 | 0 | { |
503 | 0 | nsIFrame* frame = this; |
504 | 0 | bool is3DTransform = false; |
505 | 0 | Matrix transform; |
506 | 0 | while (frame) { |
507 | 0 | nsIFrame* parent; |
508 | 0 | Matrix4x4Flagged ctm = frame->GetTransformMatrix(nullptr, &parent); |
509 | 0 | Matrix matrix; |
510 | 0 | if (ctm.Is2D(&matrix)) { |
511 | 0 | transform = transform * matrix; |
512 | 0 | } else { |
513 | 0 | is3DTransform = true; |
514 | 0 | break; |
515 | 0 | } |
516 | 0 | frame = parent; |
517 | 0 | } |
518 | 0 | nsPoint translation; |
519 | 0 | if (!is3DTransform && !transform.HasNonTranslation()) { |
520 | 0 | nsPresContext* pc = PresContext(); |
521 | 0 | // To get the translation introduced only by transforms we subtract the |
522 | 0 | // regular non-transform translation. |
523 | 0 | nsRootPresContext* rootPC = pc->GetRootPresContext(); |
524 | 0 | if (rootPC) { |
525 | 0 | int32_t apd = pc->AppUnitsPerDevPixel(); |
526 | 0 | translation.x = NSFloatPixelsToAppUnits(transform._31, apd); |
527 | 0 | translation.y = NSFloatPixelsToAppUnits(transform._32, apd); |
528 | 0 | translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame()); |
529 | 0 | } |
530 | 0 | } |
531 | 0 | return translation; |
532 | 0 | } |
533 | | |
534 | | class nsAsyncRollup : public Runnable |
535 | | { |
536 | | public: |
537 | | explicit nsAsyncRollup(nsComboboxControlFrame* aFrame) |
538 | | : mozilla::Runnable("nsAsyncRollup") |
539 | | , mFrame(aFrame) |
540 | 0 | { |
541 | 0 | } |
542 | | NS_IMETHOD Run() override |
543 | 0 | { |
544 | 0 | if (mFrame.IsAlive()) { |
545 | 0 | static_cast<nsComboboxControlFrame*>(mFrame.GetFrame()) |
546 | 0 | ->RollupFromList(); |
547 | 0 | } |
548 | 0 | return NS_OK; |
549 | 0 | } |
550 | | WeakFrame mFrame; |
551 | | }; |
552 | | |
553 | | class nsAsyncResize : public Runnable |
554 | | { |
555 | | public: |
556 | | explicit nsAsyncResize(nsComboboxControlFrame* aFrame) |
557 | | : mozilla::Runnable("nsAsyncResize") |
558 | | , mFrame(aFrame) |
559 | 0 | { |
560 | 0 | } |
561 | | NS_IMETHOD Run() override |
562 | 0 | { |
563 | 0 | if (mFrame.IsAlive()) { |
564 | 0 | nsComboboxControlFrame* combo = |
565 | 0 | static_cast<nsComboboxControlFrame*>(mFrame.GetFrame()); |
566 | 0 | static_cast<nsListControlFrame*>(combo->mDropdownFrame)-> |
567 | 0 | SetSuppressScrollbarUpdate(true); |
568 | 0 | nsCOMPtr<nsIPresShell> shell = mFrame->PresShell(); |
569 | 0 | shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize, |
570 | 0 | NS_FRAME_IS_DIRTY); |
571 | 0 | shell->FlushPendingNotifications(FlushType::Layout); |
572 | 0 | if (mFrame.IsAlive()) { |
573 | 0 | combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame()); |
574 | 0 | static_cast<nsListControlFrame*>(combo->mDropdownFrame)-> |
575 | 0 | SetSuppressScrollbarUpdate(false); |
576 | 0 | if (combo->mDelayedShowDropDown) { |
577 | 0 | combo->ShowDropDown(true); |
578 | 0 | } |
579 | 0 | } |
580 | 0 | } |
581 | 0 | return NS_OK; |
582 | 0 | } |
583 | | WeakFrame mFrame; |
584 | | }; |
585 | | |
586 | | void |
587 | | nsComboboxControlFrame::GetAvailableDropdownSpace(WritingMode aWM, |
588 | | nscoord* aBefore, |
589 | | nscoord* aAfter, |
590 | | LogicalPoint* aTranslation) |
591 | 0 | { |
592 | 0 | MOZ_ASSERT(!XRE_IsContentProcess()); |
593 | 0 | // Note: At first glance, it appears that you could simply get the |
594 | 0 | // absolute bounding box for the dropdown list by first getting its |
595 | 0 | // view, then getting the view's nsIWidget, then asking the nsIWidget |
596 | 0 | // for its AbsoluteBounds. |
597 | 0 | // The problem with this approach, is that the dropdown list's bcoord |
598 | 0 | // location can change based on whether the dropdown is placed after |
599 | 0 | // or before the display frame. The approach taken here is to get the |
600 | 0 | // absolute position of the display frame and use its location to |
601 | 0 | // determine if the dropdown will go offscreen. |
602 | 0 |
|
603 | 0 | // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms. |
604 | 0 | // In the special case that our transform is only a 2D translation we |
605 | 0 | // introduce this hack so that the dropdown will show up in the right place. |
606 | 0 | // Use null container size when converting a vector from logical to physical. |
607 | 0 | const nsSize nullContainerSize; |
608 | 0 | *aTranslation = LogicalPoint(aWM, GetCSSTransformTranslation(), |
609 | 0 | nullContainerSize); |
610 | 0 | *aBefore = 0; |
611 | 0 | *aAfter = 0; |
612 | 0 |
|
613 | 0 | nsRect screen = nsCheckboxRadioFrame::GetUsableScreenRect(PresContext()); |
614 | 0 | nsSize containerSize = screen.Size(); |
615 | 0 | LogicalRect logicalScreen(aWM, screen, containerSize); |
616 | 0 | if (mLastDropDownAfterScreenBCoord == nscoord_MIN) { |
617 | 0 | LogicalRect thisScreenRect(aWM, GetScreenRectInAppUnits(), |
618 | 0 | containerSize); |
619 | 0 | mLastDropDownAfterScreenBCoord = thisScreenRect.BEnd(aWM) + |
620 | 0 | aTranslation->B(aWM); |
621 | 0 | mLastDropDownBeforeScreenBCoord = thisScreenRect.BStart(aWM) + |
622 | 0 | aTranslation->B(aWM); |
623 | 0 | } |
624 | 0 |
|
625 | 0 | nscoord minBCoord; |
626 | 0 | nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext(); |
627 | 0 | nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr; |
628 | 0 | if (root) { |
629 | 0 | minBCoord = LogicalRect(aWM, |
630 | 0 | root->GetScreenRectInAppUnits(), |
631 | 0 | containerSize).BStart(aWM); |
632 | 0 | if (mLastDropDownAfterScreenBCoord < minBCoord) { |
633 | 0 | // Don't allow the drop-down to be placed before the content area. |
634 | 0 | return; |
635 | 0 | } |
636 | 0 | } else { |
637 | 0 | minBCoord = logicalScreen.BStart(aWM); |
638 | 0 | } |
639 | 0 |
|
640 | 0 | nscoord after = logicalScreen.BEnd(aWM) - mLastDropDownAfterScreenBCoord; |
641 | 0 | nscoord before = mLastDropDownBeforeScreenBCoord - minBCoord; |
642 | 0 |
|
643 | 0 | // If the difference between the space before and after is less |
644 | 0 | // than a row-block-size, then we favor the space after. |
645 | 0 | if (before >= after) { |
646 | 0 | nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame); |
647 | 0 | nscoord rowBSize = lcf->GetBSizeOfARow(); |
648 | 0 | if (before < after + rowBSize) { |
649 | 0 | before -= rowBSize; |
650 | 0 | } |
651 | 0 | } |
652 | 0 |
|
653 | 0 | *aAfter = after; |
654 | 0 | *aBefore = before; |
655 | 0 | } |
656 | | |
657 | | nsComboboxControlFrame::DropDownPositionState |
658 | | nsComboboxControlFrame::AbsolutelyPositionDropDown() |
659 | 0 | { |
660 | 0 | if (XRE_IsContentProcess()) { |
661 | 0 | return eDropDownPositionSuppressed; |
662 | 0 | } |
663 | 0 | |
664 | 0 | WritingMode wm = GetWritingMode(); |
665 | 0 | LogicalPoint translation(wm); |
666 | 0 | nscoord before, after; |
667 | 0 | mLastDropDownAfterScreenBCoord = nscoord_MIN; |
668 | 0 | GetAvailableDropdownSpace(wm, &before, &after, &translation); |
669 | 0 | if (before <= 0 && after <= 0) { |
670 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled() && IsDroppedDown()) { |
671 | 0 | // Hide the view immediately to minimize flicker. |
672 | 0 | nsView* view = mDropdownFrame->GetView(); |
673 | 0 | view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide); |
674 | 0 | NS_DispatchToCurrentThread(new nsAsyncRollup(this)); |
675 | 0 | } |
676 | 0 | return eDropDownPositionSuppressed; |
677 | 0 | } |
678 | 0 |
|
679 | 0 | LogicalSize dropdownSize = mDropdownFrame->GetLogicalSize(wm); |
680 | 0 | nscoord bSize = std::max(before, after); |
681 | 0 | nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame); |
682 | 0 | if (bSize < dropdownSize.BSize(wm)) { |
683 | 0 | if (lcf->GetNumDisplayRows() > 1) { |
684 | 0 | // The drop-down doesn't fit and currently shows more than 1 row - |
685 | 0 | // schedule a resize to show fewer rows. |
686 | 0 | NS_DispatchToCurrentThread(new nsAsyncResize(this)); |
687 | 0 | return eDropDownPositionPendingResize; |
688 | 0 | } |
689 | 0 | } else if (bSize > (dropdownSize.BSize(wm) + lcf->GetBSizeOfARow() * 1.5) && |
690 | 0 | lcf->GetDropdownCanGrow()) { |
691 | 0 | // The drop-down fits but there is room for at least 1.5 more rows - |
692 | 0 | // schedule a resize to show more rows if it has more rows to show. |
693 | 0 | // (1.5 rows for good measure to avoid any rounding issues that would |
694 | 0 | // lead to a loop of reflow requests) |
695 | 0 | NS_DispatchToCurrentThread(new nsAsyncResize(this)); |
696 | 0 | return eDropDownPositionPendingResize; |
697 | 0 | } |
698 | 0 | |
699 | 0 | // Position the drop-down after if there is room, otherwise place it before |
700 | 0 | // if there is room. If there is no room for it on either side then place |
701 | 0 | // it after (to avoid overlapping UI like the URL bar). |
702 | 0 | bool b = dropdownSize.BSize(wm)<= after || dropdownSize.BSize(wm) > before; |
703 | 0 | LogicalPoint dropdownPosition(wm, 0, b ? BSize(wm) : -dropdownSize.BSize(wm)); |
704 | 0 |
|
705 | 0 | // Don't position the view unless the position changed since it might cause |
706 | 0 | // a call to NotifyGeometryChange() and an infinite loop here. |
707 | 0 | nsSize containerSize = GetSize(); |
708 | 0 | const LogicalPoint currentPos = |
709 | 0 | mDropdownFrame->GetLogicalPosition(containerSize); |
710 | 0 | const LogicalPoint newPos = dropdownPosition + translation; |
711 | 0 | if (currentPos != newPos) { |
712 | 0 | mDropdownFrame->SetPosition(wm, newPos, containerSize); |
713 | 0 | nsContainerFrame::PositionFrameView(mDropdownFrame); |
714 | 0 | } |
715 | 0 | return eDropDownPositionFinal; |
716 | 0 | } |
717 | | |
718 | | void |
719 | | nsComboboxControlFrame::NotifyGeometryChange() |
720 | 0 | { |
721 | 0 | if (XRE_IsContentProcess()) { |
722 | 0 | return; |
723 | 0 | } |
724 | 0 | |
725 | 0 | // We don't need to resize if we're not dropped down since ShowDropDown |
726 | 0 | // does that, or if we're dirty then the reflow callback does it, |
727 | 0 | // or if we have a delayed ShowDropDown pending. |
728 | 0 | if (IsDroppedDown() && |
729 | 0 | !(GetStateBits() & NS_FRAME_IS_DIRTY) && |
730 | 0 | !mDelayedShowDropDown) { |
731 | 0 | // Async because we're likely in a middle of a scroll here so |
732 | 0 | // frame/view positions are in flux. |
733 | 0 | RefPtr<nsResizeDropdownAtFinalPosition> resize = |
734 | 0 | new nsResizeDropdownAtFinalPosition(this); |
735 | 0 | NS_DispatchToCurrentThread(resize); |
736 | 0 | } |
737 | 0 | } |
738 | | |
739 | | //---------------------------------------------------------- |
740 | | // |
741 | | //---------------------------------------------------------- |
742 | | #ifdef DO_REFLOW_DEBUG |
743 | | static int myCounter = 0; |
744 | | |
745 | | static void printSize(char * aDesc, nscoord aSize) |
746 | | { |
747 | | printf(" %s: ", aDesc); |
748 | | if (aSize == NS_UNCONSTRAINEDSIZE) { |
749 | | printf("UC"); |
750 | | } else { |
751 | | printf("%d", PX(aSize)); |
752 | | } |
753 | | } |
754 | | #endif |
755 | | |
756 | | //------------------------------------------------------------------- |
757 | | //-- Main Reflow for the Combobox |
758 | | //------------------------------------------------------------------- |
759 | | |
760 | | bool |
761 | | nsComboboxControlFrame::HasDropDownButton() const |
762 | 0 | { |
763 | 0 | const nsStyleDisplay* disp = StyleDisplay(); |
764 | 0 | return disp->mAppearance == StyleAppearance::Menulist && |
765 | 0 | (!IsThemed(disp) || |
766 | 0 | PresContext()->GetTheme()->ThemeNeedsComboboxDropmarker()); |
767 | 0 | } |
768 | | |
769 | | nscoord |
770 | | nsComboboxControlFrame::GetIntrinsicISize(gfxContext* aRenderingContext, |
771 | | nsLayoutUtils::IntrinsicISizeType aType) |
772 | 0 | { |
773 | 0 | // get the scrollbar width, we'll use this later |
774 | 0 | nscoord scrollbarWidth = 0; |
775 | 0 | nsPresContext* presContext = PresContext(); |
776 | 0 | if (mListControlFrame) { |
777 | 0 | nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame); |
778 | 0 | NS_ASSERTION(scrollable, "List must be a scrollable frame"); |
779 | 0 | scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth( |
780 | 0 | presContext, aRenderingContext, GetWritingMode()); |
781 | 0 | } |
782 | 0 |
|
783 | 0 | nscoord displayISize = 0; |
784 | 0 | if (MOZ_LIKELY(mDisplayFrame)) { |
785 | 0 | displayISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, |
786 | 0 | mDisplayFrame, |
787 | 0 | aType); |
788 | 0 | } |
789 | 0 |
|
790 | 0 | if (mDropdownFrame) { |
791 | 0 | nscoord dropdownContentISize; |
792 | 0 | bool isUsingOverlayScrollbars = |
793 | 0 | LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0; |
794 | 0 | if (aType == nsLayoutUtils::MIN_ISIZE) { |
795 | 0 | dropdownContentISize = mDropdownFrame->GetMinISize(aRenderingContext); |
796 | 0 | if (isUsingOverlayScrollbars) { |
797 | 0 | dropdownContentISize += scrollbarWidth; |
798 | 0 | } |
799 | 0 | } else { |
800 | 0 | NS_ASSERTION(aType == nsLayoutUtils::PREF_ISIZE, "Unexpected type"); |
801 | 0 | dropdownContentISize = mDropdownFrame->GetPrefISize(aRenderingContext); |
802 | 0 | if (isUsingOverlayScrollbars) { |
803 | 0 | dropdownContentISize += scrollbarWidth; |
804 | 0 | } |
805 | 0 | } |
806 | 0 | dropdownContentISize = NSCoordSaturatingSubtract(dropdownContentISize, |
807 | 0 | scrollbarWidth, |
808 | 0 | nscoord_MAX); |
809 | 0 |
|
810 | 0 | displayISize = std::max(dropdownContentISize, displayISize); |
811 | 0 | } |
812 | 0 |
|
813 | 0 | // add room for the dropmarker button if there is one |
814 | 0 | if (HasDropDownButton()) { |
815 | 0 | displayISize += scrollbarWidth; |
816 | 0 | } |
817 | 0 |
|
818 | 0 | return displayISize; |
819 | 0 |
|
820 | 0 | } |
821 | | |
822 | | nscoord |
823 | | nsComboboxControlFrame::GetMinISize(gfxContext *aRenderingContext) |
824 | 0 | { |
825 | 0 | nscoord minISize; |
826 | 0 | DISPLAY_MIN_INLINE_SIZE(this, minISize); |
827 | 0 | minISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE); |
828 | 0 | return minISize; |
829 | 0 | } |
830 | | |
831 | | nscoord |
832 | | nsComboboxControlFrame::GetPrefISize(gfxContext *aRenderingContext) |
833 | 0 | { |
834 | 0 | nscoord prefISize; |
835 | 0 | DISPLAY_PREF_INLINE_SIZE(this, prefISize); |
836 | 0 | prefISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE); |
837 | 0 | return prefISize; |
838 | 0 | } |
839 | | |
840 | | void |
841 | | nsComboboxControlFrame::Reflow(nsPresContext* aPresContext, |
842 | | ReflowOutput& aDesiredSize, |
843 | | const ReflowInput& aReflowInput, |
844 | | nsReflowStatus& aStatus) |
845 | 0 | { |
846 | 0 | MarkInReflow(); |
847 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
848 | 0 | // Constraints we try to satisfy: |
849 | 0 |
|
850 | 0 | // 1) Default inline size of button is the vertical scrollbar size |
851 | 0 | // 2) If the inline size of button is bigger than our inline size, set |
852 | 0 | // inline size of button to 0. |
853 | 0 | // 3) Default block size of button is block size of display area |
854 | 0 | // 4) Inline size of display area is whatever is left over from our |
855 | 0 | // inline size after allocating inline size for the button. |
856 | 0 | // 5) Block Size of display area is GetBSizeOfARow() on the |
857 | 0 | // mListControlFrame. |
858 | 0 |
|
859 | 0 | if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) { |
860 | 0 | NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!"); |
861 | 0 | return; |
862 | 0 | } |
863 | 0 |
|
864 | 0 | // Make sure the displayed text is the same as the selected option, bug 297389. |
865 | 0 | if (!mDroppedDown) { |
866 | 0 | mDisplayedIndex = mListControlFrame->GetSelectedIndex(); |
867 | 0 | } |
868 | 0 | // In dropped down mode the "selected index" is the hovered menu item, |
869 | 0 | // we want the last selected item which is |mDisplayedIndex| in this case. |
870 | 0 | RedisplayText(); |
871 | 0 |
|
872 | 0 | // First reflow our dropdown so that we know how tall we should be. |
873 | 0 | ReflowDropdown(aPresContext, aReflowInput); |
874 | 0 | RefPtr<nsResizeDropdownAtFinalPosition> resize = |
875 | 0 | new nsResizeDropdownAtFinalPosition(this); |
876 | 0 | if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) { |
877 | 0 | // The reflow callback queue doesn't AddRef so we keep it alive until |
878 | 0 | // it's released in its ReflowFinished / ReflowCallbackCanceled. |
879 | 0 | Unused << resize.forget(); |
880 | 0 | } |
881 | 0 |
|
882 | 0 | // Get the width of the vertical scrollbar. That will be the inline |
883 | 0 | // size of the dropdown button. |
884 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
885 | 0 | nscoord buttonISize; |
886 | 0 | if (!HasDropDownButton()) { |
887 | 0 | buttonISize = 0; |
888 | 0 | } |
889 | 0 | else { |
890 | 0 | nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame); |
891 | 0 | NS_ASSERTION(scrollable, "List must be a scrollable frame"); |
892 | 0 | buttonISize = scrollable->GetNondisappearingScrollbarWidth( |
893 | 0 | PresContext(), aReflowInput.mRenderingContext, wm); |
894 | 0 | if (buttonISize > aReflowInput.ComputedISize()) { |
895 | 0 | buttonISize = 0; |
896 | 0 | } |
897 | 0 | } |
898 | 0 |
|
899 | 0 | mDisplayISize = aReflowInput.ComputedISize() - buttonISize; |
900 | 0 |
|
901 | 0 | nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); |
902 | 0 |
|
903 | 0 | // The button should occupy the same space as a scrollbar |
904 | 0 | nsSize containerSize = aDesiredSize.PhysicalSize(); |
905 | 0 | LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize); |
906 | 0 |
|
907 | 0 | buttonRect.IStart(wm) = |
908 | 0 | aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) + |
909 | 0 | mDisplayISize - |
910 | 0 | (aReflowInput.ComputedLogicalBorderPadding().IEnd(wm) - |
911 | 0 | aReflowInput.ComputedLogicalPadding().IEnd(wm)); |
912 | 0 | buttonRect.ISize(wm) = buttonISize; |
913 | 0 |
|
914 | 0 | buttonRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm); |
915 | 0 | buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) + |
916 | 0 | this->GetLogicalUsedPadding(wm).BStartEnd(wm); |
917 | 0 |
|
918 | 0 | mButtonFrame->SetRect(buttonRect, containerSize); |
919 | 0 |
|
920 | 0 | if (!aStatus.IsInlineBreakBefore() && |
921 | 0 | !aStatus.IsFullyComplete()) { |
922 | 0 | // This frame didn't fit inside a fragmentation container. Splitting |
923 | 0 | // a nsComboboxControlFrame makes no sense, so we override the status here. |
924 | 0 | aStatus.Reset(); |
925 | 0 | } |
926 | 0 | } |
927 | | |
928 | | //-------------------------------------------------------------- |
929 | | |
930 | | #ifdef DEBUG_FRAME_DUMP |
931 | | nsresult |
932 | | nsComboboxControlFrame::GetFrameName(nsAString& aResult) const |
933 | | { |
934 | | return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult); |
935 | | } |
936 | | #endif |
937 | | |
938 | | |
939 | | //---------------------------------------------------------------------- |
940 | | // nsIComboboxControlFrame |
941 | | //---------------------------------------------------------------------- |
942 | | void |
943 | | nsComboboxControlFrame::ShowDropDown(bool aDoDropDown) |
944 | 0 | { |
945 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled()) { |
946 | 0 | // TODO(kuoe0) remove this assertion after content-select is enabled |
947 | 0 | MOZ_ASSERT(!XRE_IsContentProcess()); |
948 | 0 | } |
949 | 0 | mDelayedShowDropDown = false; |
950 | 0 | EventStates eventStates = mContent->AsElement()->State(); |
951 | 0 | if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) { |
952 | 0 | return; |
953 | 0 | } |
954 | 0 | |
955 | 0 | if (!mDroppedDown && aDoDropDown) { |
956 | 0 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
957 | 0 | if (!fm || fm->GetFocusedElement() == GetContent()) { |
958 | 0 | DropDownPositionState state = AbsolutelyPositionDropDown(); |
959 | 0 | if (state == eDropDownPositionFinal) { |
960 | 0 | ShowList(aDoDropDown); // might destroy us |
961 | 0 | } else if (state == eDropDownPositionPendingResize) { |
962 | 0 | // Delay until after the resize reflow, see nsAsyncResize. |
963 | 0 | mDelayedShowDropDown = true; |
964 | 0 | } |
965 | 0 | } else { |
966 | 0 | // Delay until we get focus, see SetFocus(). |
967 | 0 | mDelayedShowDropDown = true; |
968 | 0 | } |
969 | 0 | } else if (mDroppedDown && !aDoDropDown) { |
970 | 0 | ShowList(aDoDropDown); // might destroy us |
971 | 0 | } |
972 | 0 | } |
973 | | |
974 | | void |
975 | | nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame) |
976 | 0 | { |
977 | 0 | mDropdownFrame = aDropDownFrame; |
978 | 0 | mListControlFrame = do_QueryFrame(mDropdownFrame); |
979 | 0 | if (!sFocused && nsContentUtils::IsFocusedContent(GetContent())) { |
980 | 0 | sFocused = this; |
981 | 0 | nsListControlFrame::ComboboxFocusSet(); |
982 | 0 | } |
983 | 0 | } |
984 | | |
985 | | nsIFrame* |
986 | | nsComboboxControlFrame::GetDropDown() |
987 | 0 | { |
988 | 0 | return mDropdownFrame; |
989 | 0 | } |
990 | | |
991 | | /////////////////////////////////////////////////////////////// |
992 | | |
993 | | |
994 | | NS_IMETHODIMP |
995 | | nsComboboxControlFrame::RedisplaySelectedText() |
996 | 0 | { |
997 | 0 | nsAutoScriptBlocker scriptBlocker; |
998 | 0 | mDisplayedIndex = mListControlFrame->GetSelectedIndex(); |
999 | 0 | return RedisplayText(); |
1000 | 0 | } |
1001 | | |
1002 | | |
1003 | | nsresult |
1004 | | nsComboboxControlFrame::RedisplayText() |
1005 | 0 | { |
1006 | 0 | nsString previewValue; |
1007 | 0 | nsString previousText(mDisplayedOptionTextOrPreview); |
1008 | 0 |
|
1009 | 0 | auto* selectElement = static_cast<dom::HTMLSelectElement*>(GetContent()); |
1010 | 0 | selectElement->GetPreviewValue(previewValue); |
1011 | 0 | // Get the text to display |
1012 | 0 | if (!previewValue.IsEmpty()) { |
1013 | 0 | mDisplayedOptionTextOrPreview = previewValue; |
1014 | 0 | } else if (mDisplayedIndex != -1) { |
1015 | 0 | mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview); |
1016 | 0 | } else { |
1017 | 0 | mDisplayedOptionTextOrPreview.Truncate(); |
1018 | 0 | } |
1019 | 0 |
|
1020 | 0 | REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n", |
1021 | 0 | NS_LossyConvertUTF16toASCII(mDisplayedOptionTextOrPreview).get()); |
1022 | 0 |
|
1023 | 0 | // Send reflow command because the new text maybe larger |
1024 | 0 | nsresult rv = NS_OK; |
1025 | 0 | if (mDisplayContent && |
1026 | 0 | !previousText.Equals(mDisplayedOptionTextOrPreview)) { |
1027 | 0 | // Don't call ActuallyDisplayText(true) directly here since that |
1028 | 0 | // could cause recursive frame construction. See bug 283117 and the comment in |
1029 | 0 | // HandleRedisplayTextEvent() below. |
1030 | 0 |
|
1031 | 0 | // Revoke outstanding events to avoid out-of-order events which could mean |
1032 | 0 | // displaying the wrong text. |
1033 | 0 | mRedisplayTextEvent.Revoke(); |
1034 | 0 |
|
1035 | 0 | NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), |
1036 | 0 | "If we happen to run our redisplay event now, we might kill " |
1037 | 0 | "ourselves!"); |
1038 | 0 |
|
1039 | 0 | mRedisplayTextEvent = new RedisplayTextEvent(this); |
1040 | 0 | nsContentUtils::AddScriptRunner(mRedisplayTextEvent.get()); |
1041 | 0 | } |
1042 | 0 | return rv; |
1043 | 0 | } |
1044 | | |
1045 | | void |
1046 | | nsComboboxControlFrame::HandleRedisplayTextEvent() |
1047 | 0 | { |
1048 | 0 | // First, make sure that the content model is up to date and we've |
1049 | 0 | // constructed the frames for all our content in the right places. |
1050 | 0 | // Otherwise they'll end up under the wrong insertion frame when we |
1051 | 0 | // ActuallyDisplayText, since that flushes out the content sink by |
1052 | 0 | // calling SetText on a DOM node with aNotify set to true. See bug |
1053 | 0 | // 289730. |
1054 | 0 | AutoWeakFrame weakThis(this); |
1055 | 0 | PresContext()->Document()-> |
1056 | 0 | FlushPendingNotifications(FlushType::ContentAndNotify); |
1057 | 0 | if (!weakThis.IsAlive()) |
1058 | 0 | return; |
1059 | 0 | |
1060 | 0 | // Redirect frame insertions during this method (see GetContentInsertionFrame()) |
1061 | 0 | // so that any reframing that the frame constructor forces upon us is inserted |
1062 | 0 | // into the correct parent (mDisplayFrame). See bug 282607. |
1063 | 0 | MOZ_ASSERT(!mInRedisplayText, "Nested RedisplayText"); |
1064 | 0 | mInRedisplayText = true; |
1065 | 0 | mRedisplayTextEvent.Forget(); |
1066 | 0 |
|
1067 | 0 | ActuallyDisplayText(true); |
1068 | 0 | // XXXbz This should perhaps be eResize. Check. |
1069 | 0 | PresShell()->FrameNeedsReflow(mDisplayFrame, |
1070 | 0 | nsIPresShell::eStyleChange, |
1071 | 0 | NS_FRAME_IS_DIRTY); |
1072 | 0 |
|
1073 | 0 | mInRedisplayText = false; |
1074 | 0 | } |
1075 | | |
1076 | | void |
1077 | | nsComboboxControlFrame::ActuallyDisplayText(bool aNotify) |
1078 | 0 | { |
1079 | 0 | if (mDisplayedOptionTextOrPreview.IsEmpty()) { |
1080 | 0 | // Have to use a non-breaking space for line-block-size calculations |
1081 | 0 | // to be right |
1082 | 0 | static const char16_t space = 0xA0; |
1083 | 0 | mDisplayContent->SetText(&space, 1, aNotify); |
1084 | 0 | } else { |
1085 | 0 | mDisplayContent->SetText(mDisplayedOptionTextOrPreview, aNotify); |
1086 | 0 | } |
1087 | 0 | } |
1088 | | |
1089 | | int32_t |
1090 | | nsComboboxControlFrame::GetIndexOfDisplayArea() |
1091 | 0 | { |
1092 | 0 | return mDisplayedIndex; |
1093 | 0 | } |
1094 | | |
1095 | | //---------------------------------------------------------------------- |
1096 | | // nsISelectControlFrame |
1097 | | //---------------------------------------------------------------------- |
1098 | | NS_IMETHODIMP |
1099 | | nsComboboxControlFrame::DoneAddingChildren(bool aIsDone) |
1100 | 0 | { |
1101 | 0 | nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame); |
1102 | 0 | if (!listFrame) |
1103 | 0 | return NS_ERROR_FAILURE; |
1104 | 0 | |
1105 | 0 | return listFrame->DoneAddingChildren(aIsDone); |
1106 | 0 | } |
1107 | | |
1108 | | NS_IMETHODIMP |
1109 | | nsComboboxControlFrame::AddOption(int32_t aIndex) |
1110 | 0 | { |
1111 | 0 | if (aIndex <= mDisplayedIndex) { |
1112 | 0 | ++mDisplayedIndex; |
1113 | 0 | } |
1114 | 0 |
|
1115 | 0 | nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame); |
1116 | 0 | return lcf->AddOption(aIndex); |
1117 | 0 | } |
1118 | | |
1119 | | |
1120 | | NS_IMETHODIMP |
1121 | | nsComboboxControlFrame::RemoveOption(int32_t aIndex) |
1122 | 0 | { |
1123 | 0 | AutoWeakFrame weakThis(this); |
1124 | 0 | if (mListControlFrame->GetNumberOfOptions() > 0) { |
1125 | 0 | if (aIndex < mDisplayedIndex) { |
1126 | 0 | --mDisplayedIndex; |
1127 | 0 | } else if (aIndex == mDisplayedIndex) { |
1128 | 0 | mDisplayedIndex = 0; // IE6 compat |
1129 | 0 | RedisplayText(); |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | else { |
1133 | 0 | // If we removed the last option, we need to blank things out |
1134 | 0 | mDisplayedIndex = -1; |
1135 | 0 | RedisplayText(); |
1136 | 0 | } |
1137 | 0 |
|
1138 | 0 | if (!weakThis.IsAlive()) |
1139 | 0 | return NS_OK; |
1140 | 0 | |
1141 | 0 | nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame); |
1142 | 0 | return lcf->RemoveOption(aIndex); |
1143 | 0 | } |
1144 | | |
1145 | | NS_IMETHODIMP |
1146 | | nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) |
1147 | 0 | { |
1148 | 0 | nsAutoScriptBlocker scriptBlocker; |
1149 | 0 | mDisplayedIndex = aNewIndex; |
1150 | 0 | RedisplayText(); |
1151 | 0 | NS_ASSERTION(mDropdownFrame, "No dropdown frame!"); |
1152 | 0 |
|
1153 | 0 | nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame); |
1154 | 0 | NS_ASSERTION(listFrame, "No list frame!"); |
1155 | 0 |
|
1156 | 0 | return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex); |
1157 | 0 | } |
1158 | | |
1159 | | // End nsISelectControlFrame |
1160 | | //---------------------------------------------------------------------- |
1161 | | |
1162 | | nsresult |
1163 | | nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext, |
1164 | | WidgetGUIEvent* aEvent, |
1165 | | nsEventStatus* aEventStatus) |
1166 | 0 | { |
1167 | 0 | NS_ENSURE_ARG_POINTER(aEventStatus); |
1168 | 0 |
|
1169 | 0 | if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { |
1170 | 0 | return NS_OK; |
1171 | 0 | } |
1172 | 0 | |
1173 | 0 | EventStates eventStates = mContent->AsElement()->State(); |
1174 | 0 | if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { |
1175 | 0 | return NS_OK; |
1176 | 0 | } |
1177 | 0 | |
1178 | | #if COMBOBOX_ROLLUP_CONSUME_EVENT == 0 |
1179 | | if (aEvent->mMessage == eMouseDown) { |
1180 | | if (GetContent() == mozilla::widget::nsAutoRollup::GetLastRollup()) { |
1181 | | // This event did a Rollup on this control - prevent it from opening |
1182 | | // the dropdown again! |
1183 | | *aEventStatus = nsEventStatus_eConsumeNoDefault; |
1184 | | return NS_OK; |
1185 | | } |
1186 | | } |
1187 | | #endif |
1188 | | |
1189 | 0 | // If we have style that affects how we are selected, feed event down to |
1190 | 0 | // nsFrame::HandleEvent so that selection takes place when appropriate. |
1191 | 0 | if (IsContentDisabled()) { |
1192 | 0 | return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus); |
1193 | 0 | } |
1194 | 0 | return NS_OK; |
1195 | 0 | } |
1196 | | |
1197 | | |
1198 | | nsresult |
1199 | | nsComboboxControlFrame::SetFormProperty(nsAtom* aName, const nsAString& aValue) |
1200 | 0 | { |
1201 | 0 | nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame); |
1202 | 0 | if (!fcFrame) { |
1203 | 0 | return NS_NOINTERFACE; |
1204 | 0 | } |
1205 | 0 | |
1206 | 0 | return fcFrame->SetFormProperty(aName, aValue); |
1207 | 0 | } |
1208 | | |
1209 | | nsContainerFrame* |
1210 | 0 | nsComboboxControlFrame::GetContentInsertionFrame() { |
1211 | 0 | return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame(); |
1212 | 0 | } |
1213 | | |
1214 | | void |
1215 | | nsComboboxControlFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) |
1216 | 0 | { |
1217 | 0 | aResult.AppendElement(OwnedAnonBox(mDropdownFrame)); |
1218 | 0 | aResult.AppendElement(OwnedAnonBox(mDisplayFrame)); |
1219 | 0 | } |
1220 | | |
1221 | | nsresult |
1222 | | nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) |
1223 | 0 | { |
1224 | 0 | // The frames used to display the combo box and the button used to popup the dropdown list |
1225 | 0 | // are created through anonymous content. The dropdown list is not created through anonymous |
1226 | 0 | // content because its frame is initialized specifically for the drop-down case and it is placed |
1227 | 0 | // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the |
1228 | 0 | // layout of the display and button. |
1229 | 0 | // |
1230 | 0 | // Note: The value attribute of the display content is set when an item is selected in the dropdown list. |
1231 | 0 | // If the content specified below does not honor the value attribute than nothing will be displayed. |
1232 | 0 |
|
1233 | 0 | // For now the content that is created corresponds to two input buttons. It would be better to create the |
1234 | 0 | // tag as something other than input, but then there isn't any way to create a button frame since it |
1235 | 0 | // isn't possible to set the display type in CSS2 to create a button frame. |
1236 | 0 |
|
1237 | 0 | // create content used for display |
1238 | 0 | //nsAtom* tag = NS_Atomize("mozcombodisplay"); |
1239 | 0 |
|
1240 | 0 | // Add a child text content node for the label |
1241 | 0 |
|
1242 | 0 | nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager(); |
1243 | 0 |
|
1244 | 0 | mDisplayContent = new nsTextNode(nimgr); |
1245 | 0 |
|
1246 | 0 | // set the value of the text node |
1247 | 0 | mDisplayedIndex = mListControlFrame->GetSelectedIndex(); |
1248 | 0 | if (mDisplayedIndex != -1) { |
1249 | 0 | mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview); |
1250 | 0 | } |
1251 | 0 | ActuallyDisplayText(false); |
1252 | 0 |
|
1253 | 0 | if (!aElements.AppendElement(mDisplayContent)) |
1254 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1255 | 0 | |
1256 | 0 | mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button); |
1257 | 0 | if (!mButtonContent) |
1258 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1259 | 0 | |
1260 | 0 | // make someone to listen to the button. If its pressed by someone like Accessibility |
1261 | 0 | // then open or close the combo box. |
1262 | 0 | mButtonListener = new nsComboButtonListener(this); |
1263 | 0 | mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener, |
1264 | 0 | false, false); |
1265 | 0 |
|
1266 | 0 | mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type, |
1267 | 0 | NS_LITERAL_STRING("button"), false); |
1268 | 0 | // Set tabindex="-1" so that the button is not tabbable |
1269 | 0 | mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, |
1270 | 0 | NS_LITERAL_STRING("-1"), false); |
1271 | 0 |
|
1272 | 0 | WritingMode wm = GetWritingMode(); |
1273 | 0 | if (wm.IsVertical()) { |
1274 | 0 | mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation, |
1275 | 0 | wm.IsVerticalRL() ? NS_LITERAL_STRING("left") |
1276 | 0 | : NS_LITERAL_STRING("right"), |
1277 | 0 | false); |
1278 | 0 | } |
1279 | 0 |
|
1280 | 0 | if (!aElements.AppendElement(mButtonContent)) |
1281 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1282 | 0 | |
1283 | 0 | return NS_OK; |
1284 | 0 | } |
1285 | | |
1286 | | void |
1287 | | nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, |
1288 | | uint32_t aFilter) |
1289 | 0 | { |
1290 | 0 | if (mDisplayContent) { |
1291 | 0 | aElements.AppendElement(mDisplayContent); |
1292 | 0 | } |
1293 | 0 |
|
1294 | 0 | if (mButtonContent) { |
1295 | 0 | aElements.AppendElement(mButtonContent); |
1296 | 0 | } |
1297 | 0 | } |
1298 | | |
1299 | | nsIContent* |
1300 | | nsComboboxControlFrame::GetDisplayNode() const |
1301 | 0 | { |
1302 | 0 | return mDisplayContent; |
1303 | 0 | } |
1304 | | |
1305 | | // XXXbz this is a for-now hack. Now that display:inline-block works, |
1306 | | // need to revisit this. |
1307 | | class nsComboboxDisplayFrame final : public nsBlockFrame |
1308 | | { |
1309 | | public: |
1310 | | NS_DECL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame) |
1311 | | |
1312 | | nsComboboxDisplayFrame(ComputedStyle* aStyle, |
1313 | | nsComboboxControlFrame* aComboBox) |
1314 | | : nsBlockFrame(aStyle, kClassID) |
1315 | | , mComboBox(aComboBox) |
1316 | 0 | {} |
1317 | | |
1318 | | #ifdef DEBUG_FRAME_DUMP |
1319 | | nsresult GetFrameName(nsAString& aResult) const override |
1320 | | { |
1321 | | return MakeFrameName(NS_LITERAL_STRING("ComboboxDisplay"), aResult); |
1322 | | } |
1323 | | #endif |
1324 | | |
1325 | | virtual bool IsFrameOfType(uint32_t aFlags) const override |
1326 | 0 | { |
1327 | 0 | return nsBlockFrame::IsFrameOfType(aFlags & |
1328 | 0 | ~(nsIFrame::eReplacedContainsBlock)); |
1329 | 0 | } |
1330 | | |
1331 | | virtual void Reflow(nsPresContext* aPresContext, |
1332 | | ReflowOutput& aDesiredSize, |
1333 | | const ReflowInput& aReflowInput, |
1334 | | nsReflowStatus& aStatus) override; |
1335 | | |
1336 | | virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1337 | | const nsDisplayListSet& aLists) override; |
1338 | | |
1339 | | protected: |
1340 | | nsComboboxControlFrame* mComboBox; |
1341 | | }; |
1342 | | |
1343 | | NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame) |
1344 | | |
1345 | | void |
1346 | | nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext, |
1347 | | ReflowOutput& aDesiredSize, |
1348 | | const ReflowInput& aReflowInput, |
1349 | | nsReflowStatus& aStatus) |
1350 | 0 | { |
1351 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
1352 | 0 |
|
1353 | 0 | ReflowInput state(aReflowInput); |
1354 | 0 | if (state.ComputedBSize() == NS_INTRINSICSIZE) { |
1355 | 0 | // Note that the only way we can have a computed block size here is |
1356 | 0 | // if the combobox had a specified block size. If it didn't, size |
1357 | 0 | // based on what our rows look like, for lack of anything better. |
1358 | 0 | state.SetComputedBSize(mComboBox->mListControlFrame->GetBSizeOfARow()); |
1359 | 0 | } |
1360 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
1361 | 0 | nscoord computedISize = mComboBox->mDisplayISize - |
1362 | 0 | state.ComputedLogicalBorderPadding().IStartEnd(wm); |
1363 | 0 | if (computedISize < 0) { |
1364 | 0 | computedISize = 0; |
1365 | 0 | } |
1366 | 0 | state.SetComputedISize(computedISize); |
1367 | 0 | nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus); |
1368 | 0 | aStatus.Reset(); // this type of frame can't be split |
1369 | 0 | } |
1370 | | |
1371 | | void |
1372 | | nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1373 | | const nsDisplayListSet& aLists) |
1374 | 0 | { |
1375 | 0 | nsDisplayListCollection set(aBuilder); |
1376 | 0 | nsBlockFrame::BuildDisplayList(aBuilder, set); |
1377 | 0 |
|
1378 | 0 | // remove background items if parent frame is themed |
1379 | 0 | if (mComboBox->IsThemed()) { |
1380 | 0 | set.BorderBackground()->DeleteAll(aBuilder); |
1381 | 0 | } |
1382 | 0 |
|
1383 | 0 | set.MoveTo(aLists); |
1384 | 0 | } |
1385 | | |
1386 | | nsIFrame* |
1387 | | nsComboboxControlFrame::CreateFrameForDisplayNode() |
1388 | 0 | { |
1389 | 0 | MOZ_ASSERT(mDisplayContent); |
1390 | 0 |
|
1391 | 0 | // Get PresShell |
1392 | 0 | nsIPresShell *shell = PresShell(); |
1393 | 0 | ServoStyleSet* styleSet = shell->StyleSet(); |
1394 | 0 |
|
1395 | 0 | // create the ComputedStyle for the anonymous block frame and text frame |
1396 | 0 | RefPtr<ComputedStyle> computedStyle; |
1397 | 0 | computedStyle = styleSet-> |
1398 | 0 | ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame(), |
1399 | 0 | mComputedStyle); |
1400 | 0 |
|
1401 | 0 | RefPtr<ComputedStyle> textComputedStyle; |
1402 | 0 | textComputedStyle = |
1403 | 0 | styleSet->ResolveStyleForText(mDisplayContent, mComputedStyle); |
1404 | 0 |
|
1405 | 0 | // Start by creating our anonymous block frame |
1406 | 0 | mDisplayFrame = new (shell) nsComboboxDisplayFrame(computedStyle, this); |
1407 | 0 | mDisplayFrame->Init(mContent, this, nullptr); |
1408 | 0 |
|
1409 | 0 | // Create a text frame and put it inside the block frame |
1410 | 0 | nsIFrame* textFrame = NS_NewTextFrame(shell, textComputedStyle); |
1411 | 0 |
|
1412 | 0 | // initialize the text frame |
1413 | 0 | textFrame->Init(mDisplayContent, mDisplayFrame, nullptr); |
1414 | 0 | mDisplayContent->SetPrimaryFrame(textFrame); |
1415 | 0 |
|
1416 | 0 | nsFrameList textList(textFrame, textFrame); |
1417 | 0 | mDisplayFrame->SetInitialChildList(kPrincipalList, textList); |
1418 | 0 | return mDisplayFrame; |
1419 | 0 | } |
1420 | | |
1421 | | void |
1422 | | nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
1423 | 0 | { |
1424 | 0 | if (sFocused == this) { |
1425 | 0 | sFocused = nullptr; |
1426 | 0 | } |
1427 | 0 |
|
1428 | 0 | // Revoke any pending RedisplayTextEvent |
1429 | 0 | mRedisplayTextEvent.Revoke(); |
1430 | 0 |
|
1431 | 0 | nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); |
1432 | 0 |
|
1433 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled() && mDroppedDown) { |
1434 | 0 | MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame"); |
1435 | 0 | nsView* view = mDropdownFrame->GetView(); |
1436 | 0 | MOZ_ASSERT(view); |
1437 | 0 | nsIWidget* widget = view->GetWidget(); |
1438 | 0 | if (widget) { |
1439 | 0 | widget->CaptureRollupEvents(this, false); |
1440 | 0 | } |
1441 | 0 | } |
1442 | 0 |
|
1443 | 0 | // Cleanup frames in popup child list |
1444 | 0 | mPopupFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData); |
1445 | 0 | aPostDestroyData.AddAnonymousContent(mDisplayContent.forget()); |
1446 | 0 | aPostDestroyData.AddAnonymousContent(mButtonContent.forget()); |
1447 | 0 | nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
1448 | 0 | } |
1449 | | |
1450 | | const nsFrameList& |
1451 | | nsComboboxControlFrame::GetChildList(ChildListID aListID) const |
1452 | 0 | { |
1453 | 0 | if (kSelectPopupList == aListID) { |
1454 | 0 | return mPopupFrames; |
1455 | 0 | } |
1456 | 0 | return nsBlockFrame::GetChildList(aListID); |
1457 | 0 | } |
1458 | | |
1459 | | void |
1460 | | nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const |
1461 | 0 | { |
1462 | 0 | nsBlockFrame::GetChildLists(aLists); |
1463 | 0 | mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList); |
1464 | 0 | } |
1465 | | |
1466 | | void |
1467 | | nsComboboxControlFrame::SetInitialChildList(ChildListID aListID, |
1468 | | nsFrameList& aChildList) |
1469 | 0 | { |
1470 | 0 | if (kSelectPopupList == aListID) { |
1471 | 0 | mPopupFrames.SetFrames(aChildList); |
1472 | 0 | } else { |
1473 | 0 | for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) { |
1474 | 0 | nsCOMPtr<nsIFormControl> formControl = |
1475 | 0 | do_QueryInterface(e.get()->GetContent()); |
1476 | 0 | if (formControl && formControl->ControlType() == NS_FORM_BUTTON_BUTTON) { |
1477 | 0 | mButtonFrame = e.get(); |
1478 | 0 | break; |
1479 | 0 | } |
1480 | 0 | } |
1481 | 0 | NS_ASSERTION(mButtonFrame, "missing button frame in initial child list"); |
1482 | 0 | nsBlockFrame::SetInitialChildList(aListID, aChildList); |
1483 | 0 | } |
1484 | 0 | } |
1485 | | |
1486 | | //---------------------------------------------------------------------- |
1487 | | //nsIRollupListener |
1488 | | //---------------------------------------------------------------------- |
1489 | | bool |
1490 | | nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush, |
1491 | | const nsIntPoint* pos, nsIContent** aLastRolledUp) |
1492 | 0 | { |
1493 | 0 | if (aLastRolledUp) { |
1494 | 0 | *aLastRolledUp = nullptr; |
1495 | 0 | } |
1496 | 0 |
|
1497 | 0 | if (!mDroppedDown) { |
1498 | 0 | return false; |
1499 | 0 | } |
1500 | 0 | |
1501 | 0 | bool consume = !!COMBOBOX_ROLLUP_CONSUME_EVENT; |
1502 | 0 | AutoWeakFrame weakFrame(this); |
1503 | 0 | mListControlFrame->AboutToRollup(); // might destroy us |
1504 | 0 | if (!weakFrame.IsAlive()) { |
1505 | 0 | return consume; |
1506 | 0 | } |
1507 | 0 | ShowDropDown(false); // might destroy us |
1508 | 0 | if (weakFrame.IsAlive()) { |
1509 | 0 | mListControlFrame->CaptureMouseEvents(false); |
1510 | 0 | } |
1511 | 0 |
|
1512 | 0 | if (!nsLayoutUtils::IsContentSelectEnabled() && |
1513 | 0 | aFlush && weakFrame.IsAlive()) { |
1514 | 0 | // The popup's visibility doesn't update until the minimize animation has |
1515 | 0 | // finished, so call UpdateWidgetGeometry to update it right away. |
1516 | 0 | nsViewManager* viewManager = mDropdownFrame->GetView()->GetViewManager(); |
1517 | 0 | viewManager->UpdateWidgetGeometry(); // might destroy us |
1518 | 0 | } |
1519 | 0 |
|
1520 | 0 | if (!weakFrame.IsAlive()) { |
1521 | 0 | return consume; |
1522 | 0 | } |
1523 | 0 | |
1524 | 0 | if (aLastRolledUp) { |
1525 | 0 | *aLastRolledUp = GetContent(); |
1526 | 0 | } |
1527 | 0 | return consume; |
1528 | 0 | } |
1529 | | |
1530 | | nsIWidget* |
1531 | | nsComboboxControlFrame::GetRollupWidget() |
1532 | 0 | { |
1533 | 0 | if (nsLayoutUtils::IsContentSelectEnabled()) { |
1534 | 0 | return nullptr; |
1535 | 0 | } |
1536 | 0 | |
1537 | 0 | nsView* view = mDropdownFrame->GetView(); |
1538 | 0 | MOZ_ASSERT(view); |
1539 | 0 | return view->GetWidget(); |
1540 | 0 | } |
1541 | | |
1542 | | void |
1543 | | nsComboboxControlFrame::RollupFromList() |
1544 | 0 | { |
1545 | 0 | if (ShowList(false)) |
1546 | 0 | mListControlFrame->CaptureMouseEvents(false); |
1547 | 0 | } |
1548 | | |
1549 | | int32_t |
1550 | | nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex) |
1551 | 0 | { |
1552 | 0 | int32_t index = mRecentSelectedIndex; |
1553 | 0 | if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX) |
1554 | 0 | mRecentSelectedIndex = aIndex; |
1555 | 0 | return index; |
1556 | 0 | } |
1557 | | |
1558 | | class nsDisplayComboboxFocus : public nsDisplayItem |
1559 | | { |
1560 | | public: |
1561 | | nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder, |
1562 | | nsComboboxControlFrame* aFrame) |
1563 | 0 | : nsDisplayItem(aBuilder, aFrame) { |
1564 | 0 | MOZ_COUNT_CTOR(nsDisplayComboboxFocus); |
1565 | 0 | } |
1566 | | #ifdef NS_BUILD_REFCNT_LOGGING |
1567 | | virtual ~nsDisplayComboboxFocus() { |
1568 | | MOZ_COUNT_DTOR(nsDisplayComboboxFocus); |
1569 | | } |
1570 | | #endif |
1571 | | |
1572 | | virtual void Paint(nsDisplayListBuilder* aBuilder, |
1573 | | gfxContext* aCtx) override; |
1574 | | NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS) |
1575 | | }; |
1576 | | |
1577 | | void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder, |
1578 | | gfxContext* aCtx) |
1579 | 0 | { |
1580 | 0 | static_cast<nsComboboxControlFrame*>(mFrame) |
1581 | 0 | ->PaintFocus(*aCtx->GetDrawTarget(), ToReferenceFrame()); |
1582 | 0 | } |
1583 | | |
1584 | | void |
1585 | | nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1586 | | const nsDisplayListSet& aLists) |
1587 | 0 | { |
1588 | 0 | if (aBuilder->IsForEventDelivery()) { |
1589 | 0 | // Don't allow children to receive events. |
1590 | 0 | // REVIEW: following old GetFrameForPoint |
1591 | 0 | DisplayBorderBackgroundOutline(aBuilder, aLists); |
1592 | 0 | } else { |
1593 | 0 | // REVIEW: Our in-flow child frames are inline-level so they will paint in our |
1594 | 0 | // content list, so we don't need to mess with layers. |
1595 | 0 | nsBlockFrame::BuildDisplayList(aBuilder, aLists); |
1596 | 0 | } |
1597 | 0 |
|
1598 | 0 | // draw a focus indicator only when focus rings should be drawn |
1599 | 0 | nsIDocument* doc = mContent->GetComposedDoc(); |
1600 | 0 | if (doc) { |
1601 | 0 | nsPIDOMWindowOuter* window = doc->GetWindow(); |
1602 | 0 | if (window && window->ShouldShowFocusRing()) { |
1603 | 0 | nsPresContext *presContext = PresContext(); |
1604 | 0 | const nsStyleDisplay *disp = StyleDisplay(); |
1605 | 0 | if ((!IsThemed(disp) || |
1606 | 0 | !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) && |
1607 | 0 | mDisplayFrame && IsVisibleForPainting(aBuilder)) { |
1608 | 0 | aLists.Content()->AppendToTop( |
1609 | 0 | MakeDisplayItem<nsDisplayComboboxFocus>(aBuilder, this)); |
1610 | 0 | } |
1611 | 0 | } |
1612 | 0 | } |
1613 | 0 |
|
1614 | 0 | DisplaySelectionOverlay(aBuilder, aLists.Content()); |
1615 | 0 | } |
1616 | | |
1617 | | void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt) |
1618 | 0 | { |
1619 | 0 | /* Do we need to do anything? */ |
1620 | 0 | EventStates eventStates = mContent->AsElement()->State(); |
1621 | 0 | if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this) |
1622 | 0 | return; |
1623 | 0 | |
1624 | 0 | int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); |
1625 | 0 |
|
1626 | 0 | nsRect clipRect = mDisplayFrame->GetRect() + aPt; |
1627 | 0 | aDrawTarget.PushClipRect(NSRectToSnappedRect(clipRect, |
1628 | 0 | appUnitsPerDevPixel, |
1629 | 0 | aDrawTarget)); |
1630 | 0 |
|
1631 | 0 | // REVIEW: Why does the old code paint mDisplayFrame again? We've |
1632 | 0 | // already painted it in the children above. So clipping it here won't do |
1633 | 0 | // us much good. |
1634 | 0 |
|
1635 | 0 | ///////////////////// |
1636 | 0 | // draw focus |
1637 | 0 |
|
1638 | 0 | StrokeOptions strokeOptions; |
1639 | 0 | nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED); |
1640 | 0 | ColorPattern color(ToDeviceColor(StyleColor()->mColor)); |
1641 | 0 | nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); |
1642 | 0 | clipRect.width -= onePixel; |
1643 | 0 | clipRect.height -= onePixel; |
1644 | 0 | Rect r = ToRect(nsLayoutUtils::RectToGfxRect(clipRect, appUnitsPerDevPixel)); |
1645 | 0 | StrokeSnappedEdgesOfRect(r, aDrawTarget, color, strokeOptions); |
1646 | 0 |
|
1647 | 0 | aDrawTarget.PopClip(); |
1648 | 0 | } |
1649 | | |
1650 | | //--------------------------------------------------------- |
1651 | | // gets the content (an option) by index and then set it as |
1652 | | // being selected or not selected |
1653 | | //--------------------------------------------------------- |
1654 | | NS_IMETHODIMP |
1655 | | nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) |
1656 | 0 | { |
1657 | 0 | if (mDroppedDown) { |
1658 | 0 | nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame); |
1659 | 0 | if (selectFrame) { |
1660 | 0 | selectFrame->OnOptionSelected(aIndex, aSelected); |
1661 | 0 | } |
1662 | 0 | } else { |
1663 | 0 | if (aSelected) { |
1664 | 0 | nsAutoScriptBlocker blocker; |
1665 | 0 | mDisplayedIndex = aIndex; |
1666 | 0 | RedisplayText(); |
1667 | 0 | } else { |
1668 | 0 | AutoWeakFrame weakFrame(this); |
1669 | 0 | RedisplaySelectedText(); |
1670 | 0 | if (weakFrame.IsAlive()) { |
1671 | 0 | FireValueChangeEvent(); // Fire after old option is unselected |
1672 | 0 | } |
1673 | 0 | } |
1674 | 0 | } |
1675 | 0 |
|
1676 | 0 | return NS_OK; |
1677 | 0 | } |
1678 | | |
1679 | | void nsComboboxControlFrame::FireValueChangeEvent() |
1680 | 0 | { |
1681 | 0 | // Fire ValueChange event to indicate data value of combo box has changed |
1682 | 0 | nsContentUtils::AddScriptRunner( |
1683 | 0 | new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), |
1684 | 0 | CanBubble::eYes, ChromeOnlyDispatch::eNo)); |
1685 | 0 | } |
1686 | | |
1687 | | void |
1688 | | nsComboboxControlFrame::OnContentReset() |
1689 | 0 | { |
1690 | 0 | if (mListControlFrame) { |
1691 | 0 | mListControlFrame->OnContentReset(); |
1692 | 0 | } |
1693 | 0 | } |
1694 | | |
1695 | | |
1696 | | //-------------------------------------------------------- |
1697 | | // nsIStatefulFrame |
1698 | | //-------------------------------------------------------- |
1699 | | UniquePtr<PresState> |
1700 | | nsComboboxControlFrame::SaveState() |
1701 | 0 | { |
1702 | 0 | UniquePtr<PresState> state = NewPresState(); |
1703 | 0 | state->droppedDown() = mDroppedDown; |
1704 | 0 | return state; |
1705 | 0 | } |
1706 | | |
1707 | | NS_IMETHODIMP |
1708 | | nsComboboxControlFrame::RestoreState(PresState* aState) |
1709 | 0 | { |
1710 | 0 | if (!aState) { |
1711 | 0 | return NS_ERROR_FAILURE; |
1712 | 0 | } |
1713 | 0 | ShowList(aState->droppedDown()); // might destroy us |
1714 | 0 | return NS_OK; |
1715 | 0 | } |
1716 | | |
1717 | | // Append a suffix so that the state key for the combobox is different |
1718 | | // from the state key the list control uses to sometimes save the scroll |
1719 | | // position for the same Element |
1720 | | NS_IMETHODIMP |
1721 | | nsComboboxControlFrame::GenerateStateKey(nsIContent* aContent, |
1722 | | nsIDocument* aDocument, |
1723 | | nsACString& aKey) |
1724 | 0 | { |
1725 | 0 | nsresult rv = nsContentUtils::GenerateStateKey(aContent, aDocument, aKey); |
1726 | 0 | if (NS_FAILED(rv) || aKey.IsEmpty()) { |
1727 | 0 | return rv; |
1728 | 0 | } |
1729 | 0 | aKey.AppendLiteral("CCF"); |
1730 | 0 | return NS_OK; |
1731 | 0 | } |
1732 | | |
1733 | | // Fennec uses a custom combobox built-in widget. |
1734 | | // |
1735 | | |
1736 | | /* static */ |
1737 | | bool |
1738 | | nsComboboxControlFrame::ToolkitHasNativePopup() |
1739 | 0 | { |
1740 | | #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS |
1741 | | return true; |
1742 | | #else |
1743 | | return false; |
1744 | 0 | #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ |
1745 | 0 | } |