1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : * Mats Palmgren <matspal@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 : #include "nsCOMPtr.h"
40 : #include "nsReadableUtils.h"
41 : #include "nsComboboxControlFrame.h"
42 : #include "nsIDOMEventTarget.h"
43 : #include "nsFrameManager.h"
44 : #include "nsFormControlFrame.h"
45 : #include "nsGfxButtonControlFrame.h"
46 : #include "nsGkAtoms.h"
47 : #include "nsCSSAnonBoxes.h"
48 : #include "nsHTMLParts.h"
49 : #include "nsIFormControl.h"
50 : #include "nsINameSpaceManager.h"
51 : #include "nsIDOMElement.h"
52 : #include "nsIListControlFrame.h"
53 : #include "nsIDOMHTMLCollection.h"
54 : #include "nsIDOMHTMLSelectElement.h"
55 : #include "nsIDOMHTMLOptionElement.h"
56 : #include "nsPIDOMWindow.h"
57 : #include "nsIPresShell.h"
58 : #include "nsIView.h"
59 : #include "nsIViewManager.h"
60 : #include "nsEventDispatcher.h"
61 : #include "nsEventListenerManager.h"
62 : #include "nsIDOMNode.h"
63 : #include "nsIPrivateDOMEvent.h"
64 : #include "nsISelectControlFrame.h"
65 : #include "nsXPCOM.h"
66 : #include "nsISupportsPrimitives.h"
67 : #include "nsIComponentManager.h"
68 : #include "nsContentUtils.h"
69 : #include "nsTextFragment.h"
70 : #include "nsCSSFrameConstructor.h"
71 : #include "nsIDocument.h"
72 : #include "nsINodeInfo.h"
73 : #include "nsIScrollableFrame.h"
74 : #include "nsListControlFrame.h"
75 : #include "nsContentCID.h"
76 : #ifdef ACCESSIBILITY
77 : #include "nsAccessibilityService.h"
78 : #endif
79 : #include "nsIServiceManager.h"
80 : #include "nsGUIEvent.h"
81 : #include "nsAutoPtr.h"
82 : #include "nsStyleSet.h"
83 : #include "nsNodeInfoManager.h"
84 : #include "nsContentCreatorFunctions.h"
85 : #include "nsLayoutUtils.h"
86 : #include "nsDisplayList.h"
87 : #include "nsITheme.h"
88 : #include "nsThemeConstants.h"
89 : #include "nsAsyncDOMEvent.h"
90 : #include "nsRenderingContext.h"
91 : #include "mozilla/Preferences.h"
92 :
93 : using namespace mozilla;
94 :
95 : NS_IMETHODIMP
96 0 : nsComboboxControlFrame::RedisplayTextEvent::Run()
97 : {
98 0 : if (mControlFrame)
99 0 : mControlFrame->HandleRedisplayTextEvent();
100 0 : return NS_OK;
101 : }
102 :
103 : class nsPresState;
104 :
105 : #define FIX_FOR_BUG_53259
106 :
107 : // Drop down list event management.
108 : // The combo box uses the following strategy for managing the drop-down list.
109 : // If the combo box or it's arrow button is clicked on the drop-down list is displayed
110 : // If mouse exit's the combo box with the drop-down list displayed the drop-down list
111 : // is asked to capture events
112 : // The drop-down list will capture all events including mouse down and up and will always
113 : // return with ListWasSelected method call regardless of whether an item in the list was
114 : // actually selected.
115 : // The ListWasSelected code will turn off mouse-capture for the drop-down list.
116 : // The drop-down list does not explicitly set capture when it is in the drop-down mode.
117 :
118 :
119 : //XXX: This is temporary. It simulates pseudo states by using a attribute selector on
120 :
121 : const PRInt32 kSizeNotSet = -1;
122 :
123 : /**
124 : * Helper class that listens to the combo boxes button. If the button is pressed the
125 : * combo box is toggled to open or close. this is used by Accessibility which presses
126 : * that button Programmatically.
127 : */
128 : class nsComboButtonListener : public nsIDOMEventListener
129 : {
130 : public:
131 : NS_DECL_ISUPPORTS
132 :
133 0 : NS_IMETHOD HandleEvent(nsIDOMEvent*)
134 : {
135 0 : mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
136 0 : return NS_OK;
137 : }
138 :
139 0 : nsComboButtonListener(nsComboboxControlFrame* aCombobox)
140 0 : {
141 0 : mComboBox = aCombobox;
142 0 : }
143 :
144 0 : virtual ~nsComboButtonListener() {}
145 :
146 : nsComboboxControlFrame* mComboBox;
147 : };
148 :
149 0 : NS_IMPL_ISUPPORTS1(nsComboButtonListener,
150 : nsIDOMEventListener)
151 :
152 : // static class data member for Bug 32920
153 : nsComboboxControlFrame * nsComboboxControlFrame::mFocused = nsnull;
154 :
155 : nsIFrame*
156 0 : NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aStateFlags)
157 : {
158 0 : nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
159 :
160 0 : if (it) {
161 : // set the state flags (if any are provided)
162 0 : it->AddStateBits(aStateFlags);
163 : }
164 :
165 0 : return it;
166 : }
167 :
168 0 : NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
169 :
170 : namespace {
171 :
172 0 : class DestroyWidgetRunnable : public nsRunnable {
173 : public:
174 : NS_DECL_NSIRUNNABLE
175 :
176 0 : explicit DestroyWidgetRunnable(nsIContent* aCombobox) :
177 : mCombobox(aCombobox),
178 0 : mWidget(GetWidget())
179 : {
180 0 : }
181 :
182 : private:
183 : nsIWidget* GetWidget(nsIView** aOutView = nsnull) const;
184 :
185 : private:
186 : nsCOMPtr<nsIContent> mCombobox;
187 : nsIWidget* mWidget;
188 : };
189 :
190 0 : NS_IMETHODIMP DestroyWidgetRunnable::Run()
191 : {
192 0 : nsIView* view = nsnull;
193 0 : nsIWidget* currentWidget = GetWidget(&view);
194 : // Make sure that we are destroying the same widget as what was requested
195 : // when the event was fired.
196 0 : if (view && mWidget && mWidget == currentWidget) {
197 0 : view->DestroyWidget();
198 : }
199 0 : return NS_OK;
200 : }
201 :
202 0 : nsIWidget* DestroyWidgetRunnable::GetWidget(nsIView** aOutView) const
203 : {
204 0 : nsIFrame* primaryFrame = mCombobox->GetPrimaryFrame();
205 0 : nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(primaryFrame);
206 0 : if (comboboxFrame) {
207 0 : nsIFrame* dropdown = comboboxFrame->GetDropDown();
208 0 : if (dropdown) {
209 0 : nsIView* view = dropdown->GetView();
210 0 : NS_ASSERTION(view, "nsComboboxControlFrame view is null");
211 0 : if (aOutView) {
212 0 : *aOutView = view;
213 : }
214 0 : if (view) {
215 0 : return view->GetWidget();
216 : }
217 : }
218 : }
219 0 : return nsnull;
220 : }
221 :
222 : }
223 :
224 : //-----------------------------------------------------------
225 : // Reflow Debugging Macros
226 : // These let us "see" how many reflow counts are happening
227 : //-----------------------------------------------------------
228 : #ifdef DO_REFLOW_COUNTER
229 :
230 : #define MAX_REFLOW_CNT 1024
231 : static PRInt32 gTotalReqs = 0;;
232 : static PRInt32 gTotalReflows = 0;;
233 : static PRInt32 gReflowControlCntRQ[MAX_REFLOW_CNT];
234 : static PRInt32 gReflowControlCnt[MAX_REFLOW_CNT];
235 : static PRInt32 gReflowInx = -1;
236 :
237 : #define REFLOW_COUNTER() \
238 : if (mReflowId > -1) \
239 : gReflowControlCnt[mReflowId]++;
240 :
241 : #define REFLOW_COUNTER_REQUEST() \
242 : if (mReflowId > -1) \
243 : gReflowControlCntRQ[mReflowId]++;
244 :
245 : #define REFLOW_COUNTER_DUMP(__desc) \
246 : if (mReflowId > -1) {\
247 : gTotalReqs += gReflowControlCntRQ[mReflowId];\
248 : gTotalReflows += gReflowControlCnt[mReflowId];\
249 : printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
250 : mReflowId, (__desc), \
251 : gReflowControlCnt[mReflowId], \
252 : gReflowControlCntRQ[mReflowId],\
253 : gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
254 : }
255 :
256 : #define REFLOW_COUNTER_INIT() \
257 : if (gReflowInx < MAX_REFLOW_CNT) { \
258 : gReflowInx++; \
259 : mReflowId = gReflowInx; \
260 : gReflowControlCnt[mReflowId] = 0; \
261 : gReflowControlCntRQ[mReflowId] = 0; \
262 : } else { \
263 : mReflowId = -1; \
264 : }
265 :
266 : // reflow messages
267 : #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
268 : #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
269 : #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
270 : #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
271 :
272 : #else //-------------
273 :
274 : #define REFLOW_COUNTER_REQUEST()
275 : #define REFLOW_COUNTER()
276 : #define REFLOW_COUNTER_DUMP(__desc)
277 : #define REFLOW_COUNTER_INIT()
278 :
279 : #define REFLOW_DEBUG_MSG(_msg)
280 : #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
281 : #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
282 : #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
283 :
284 :
285 : #endif
286 :
287 : //------------------------------------------
288 : // This is for being VERY noisy
289 : //------------------------------------------
290 : #ifdef DO_VERY_NOISY
291 : #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
292 : #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
293 : #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
294 : #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
295 : #else
296 : #define REFLOW_NOISY_MSG(_msg)
297 : #define REFLOW_NOISY_MSG2(_msg1, _msg2)
298 : #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
299 : #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
300 : #endif
301 :
302 : //------------------------------------------
303 : // Displays value in pixels or twips
304 : //------------------------------------------
305 : #ifdef DO_PIXELS
306 : #define PX(__v) __v / 15
307 : #else
308 : #define PX(__v) __v
309 : #endif
310 :
311 : //------------------------------------------------------
312 : //-- Done with macros
313 : //------------------------------------------------------
314 :
315 0 : nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
316 : : nsBlockFrame(aContext),
317 0 : mDisplayWidth(0)
318 : {
319 0 : mListControlFrame = nsnull;
320 0 : mDroppedDown = false;
321 0 : mDisplayFrame = nsnull;
322 0 : mButtonFrame = nsnull;
323 0 : mDropdownFrame = nsnull;
324 :
325 0 : mInRedisplayText = false;
326 :
327 0 : mRecentSelectedIndex = NS_SKIP_NOTIFY_INDEX;
328 :
329 : REFLOW_COUNTER_INIT()
330 0 : }
331 :
332 : //--------------------------------------------------------------
333 0 : nsComboboxControlFrame::~nsComboboxControlFrame()
334 : {
335 : REFLOW_COUNTER_DUMP("nsCCF");
336 0 : }
337 :
338 : //--------------------------------------------------------------
339 :
340 0 : NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
341 0 : NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
342 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
343 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
344 0 : NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
345 0 : NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
346 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
347 :
348 : #ifdef ACCESSIBILITY
349 : already_AddRefed<nsAccessible>
350 0 : nsComboboxControlFrame::CreateAccessible()
351 : {
352 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
353 0 : if (accService) {
354 : return accService->CreateHTMLComboboxAccessible(mContent,
355 0 : PresContext()->PresShell());
356 : }
357 :
358 0 : return nsnull;
359 : }
360 : #endif
361 :
362 : void
363 0 : nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
364 : {
365 0 : nsWeakFrame weakFrame(this);
366 0 : if (aOn) {
367 0 : nsListControlFrame::ComboboxFocusSet();
368 0 : mFocused = this;
369 : } else {
370 0 : mFocused = nsnull;
371 0 : if (mDroppedDown) {
372 0 : mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
373 0 : if (!weakFrame.IsAlive()) {
374 : return;
375 : }
376 : }
377 : // May delete |this|.
378 0 : mListControlFrame->FireOnChange();
379 : }
380 :
381 0 : if (!weakFrame.IsAlive()) {
382 : return;
383 : }
384 :
385 : // This is needed on a temporary basis. It causes the focus
386 : // rect to be drawn. This is much faster than ReResolvingStyle
387 : // Bug 32920
388 0 : Invalidate(nsRect(0,0,mRect.width,mRect.height));
389 : }
390 :
391 : void
392 0 : nsComboboxControlFrame::ShowPopup(bool aShowPopup)
393 : {
394 0 : nsIView* view = mDropdownFrame->GetView();
395 0 : nsIViewManager* viewManager = view->GetViewManager();
396 :
397 0 : if (aShowPopup) {
398 0 : nsRect rect = mDropdownFrame->GetRect();
399 0 : rect.x = rect.y = 0;
400 0 : viewManager->ResizeView(view, rect);
401 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
402 : } else {
403 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
404 0 : nsRect emptyRect(0, 0, 0, 0);
405 0 : viewManager->ResizeView(view, emptyRect);
406 : }
407 :
408 : // fire a popup dom event
409 0 : nsEventStatus status = nsEventStatus_eIgnore;
410 : nsMouseEvent event(true, aShowPopup ?
411 : NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nsnull,
412 0 : nsMouseEvent::eReal);
413 :
414 0 : nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
415 0 : if (shell)
416 0 : shell->HandleDOMEventWithTarget(mContent, &event, &status);
417 0 : }
418 :
419 : bool
420 0 : nsComboboxControlFrame::ShowList(bool aShowList)
421 : {
422 0 : nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
423 :
424 0 : nsWeakFrame weakFrame(this);
425 :
426 0 : if (aShowList) {
427 0 : nsIView* view = mDropdownFrame->GetView();
428 0 : NS_ASSERTION(!view->HasWidget(),
429 : "We shoudldn't have a widget before we need to display the popup");
430 :
431 : // Create the widget for the drop-down list
432 0 : view->GetViewManager()->SetViewFloating(view, true);
433 :
434 0 : nsWidgetInitData widgetData;
435 0 : widgetData.mWindowType = eWindowType_popup;
436 0 : widgetData.mBorderStyle = eBorderStyle_default;
437 0 : view->CreateWidgetForPopup(&widgetData);
438 : }
439 :
440 0 : ShowPopup(aShowList); // might destroy us
441 0 : if (!weakFrame.IsAlive()) {
442 0 : return false;
443 : }
444 :
445 0 : mDroppedDown = aShowList;
446 0 : if (mDroppedDown) {
447 : // The listcontrol frame will call back to the nsComboboxControlFrame's
448 : // ListWasSelected which will stop the capture.
449 0 : mListControlFrame->AboutToDropDown();
450 0 : mListControlFrame->CaptureMouseEvents(true);
451 : }
452 :
453 : // XXXbz so why do we need to flush here, exactly?
454 0 : shell->GetDocument()->FlushPendingNotifications(Flush_Layout);
455 0 : if (!weakFrame.IsAlive()) {
456 0 : return false;
457 : }
458 :
459 0 : nsIFrame* listFrame = do_QueryFrame(mListControlFrame);
460 0 : if (listFrame) {
461 0 : nsIView* view = listFrame->GetView();
462 0 : NS_ASSERTION(view, "nsComboboxControlFrame view is null");
463 0 : if (view) {
464 0 : nsIWidget* widget = view->GetWidget();
465 0 : if (widget) {
466 0 : widget->CaptureRollupEvents(this, mDroppedDown, mDroppedDown);
467 :
468 0 : if (!aShowList) {
469 : nsCOMPtr<nsIRunnable> widgetDestroyer =
470 0 : new DestroyWidgetRunnable(GetContent());
471 0 : NS_DispatchToMainThread(widgetDestroyer);
472 : }
473 : }
474 : }
475 : }
476 :
477 0 : return weakFrame.IsAlive();
478 : }
479 :
480 : nsresult
481 0 : nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
482 : const nsHTMLReflowState& aReflowState)
483 : {
484 : // All we want out of it later on, really, is the height of a row, so we
485 : // don't even need to cache mDropdownFrame's ascent or anything. If we don't
486 : // need to reflow it, just bail out here.
487 0 : if (!aReflowState.ShouldReflowAllKids() &&
488 0 : !NS_SUBTREE_DIRTY(mDropdownFrame)) {
489 0 : return NS_OK;
490 : }
491 :
492 : // XXXbz this will, for small-height dropdowns, have extra space on the right
493 : // edge for the scrollbar we don't show... but that's the best we can do here
494 : // for now.
495 0 : nsSize availSize(aReflowState.availableWidth, NS_UNCONSTRAINEDSIZE);
496 : nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
497 0 : availSize);
498 :
499 : // If the dropdown's intrinsic width is narrower than our specified width,
500 : // then expand it out. We want our border-box width to end up the same as
501 : // the dropdown's so account for both sets of mComputedBorderPadding.
502 0 : nscoord forcedWidth = aReflowState.ComputedWidth() +
503 0 : aReflowState.mComputedBorderPadding.LeftRight() -
504 0 : kidReflowState.mComputedBorderPadding.LeftRight();
505 0 : kidReflowState.SetComputedWidth(NS_MAX(kidReflowState.ComputedWidth(),
506 0 : forcedWidth));
507 :
508 : // ensure we start off hidden
509 0 : if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
510 0 : nsIView* view = mDropdownFrame->GetView();
511 0 : nsIViewManager* viewManager = view->GetViewManager();
512 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
513 0 : nsRect emptyRect(0, 0, 0, 0);
514 0 : viewManager->ResizeView(view, emptyRect);
515 : }
516 :
517 : // Allow the child to move/size/change-visibility its view if it's currently
518 : // dropped down
519 0 : PRInt32 flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
520 0 : if (mDroppedDown) {
521 0 : flags = 0;
522 : }
523 0 : nsRect rect = mDropdownFrame->GetRect();
524 0 : nsHTMLReflowMetrics desiredSize;
525 : nsReflowStatus ignoredStatus;
526 : nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize,
527 : kidReflowState, rect.x, rect.y, flags,
528 0 : ignoredStatus);
529 :
530 : // Set the child's width and height to it's desired size
531 : FinishReflowChild(mDropdownFrame, aPresContext, &kidReflowState,
532 0 : desiredSize, rect.x, rect.y, flags);
533 0 : return rv;
534 : }
535 :
536 : nsPoint
537 0 : nsComboboxControlFrame::GetCSSTransformTranslation()
538 : {
539 0 : nsIFrame* frame = this;
540 0 : bool is3DTransform = false;
541 0 : gfxMatrix transform;
542 0 : while (frame) {
543 : nsIFrame* parent;
544 0 : gfx3DMatrix ctm = frame->GetTransformMatrix(nsnull, &parent);
545 0 : gfxMatrix matrix;
546 0 : if (ctm.Is2D(&matrix)) {
547 0 : transform = transform * matrix;
548 : } else {
549 0 : is3DTransform = true;
550 0 : break;
551 : }
552 0 : frame = parent;
553 : }
554 0 : nsPoint translation;
555 0 : if (!is3DTransform && !transform.HasNonTranslation()) {
556 0 : nsPresContext* pc = PresContext();
557 0 : gfxPoint pixelTranslation = transform.GetTranslation();
558 0 : PRInt32 apd = pc->AppUnitsPerDevPixel();
559 0 : translation.x = NSFloatPixelsToAppUnits(float(pixelTranslation.x), apd);
560 0 : translation.y = NSFloatPixelsToAppUnits(float(pixelTranslation.y), apd);
561 : // To get the translation introduced only by transforms we subtract the
562 : // regular non-transform translation.
563 0 : nsRootPresContext* rootPC = pc->GetRootPresContext();
564 0 : if (rootPC) {
565 0 : translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
566 : } else {
567 0 : translation.x = translation.y = 0;
568 : }
569 : }
570 : return translation;
571 : }
572 :
573 : void
574 0 : nsComboboxControlFrame::AbsolutelyPositionDropDown()
575 : {
576 : // Position the dropdown list. It is positioned below the display frame if there is enough
577 : // room on the screen to display the entire list. Otherwise it is placed above the display
578 : // frame.
579 :
580 : // Note: As first glance, it appears that you could simply get the absolute bounding box for the
581 : // dropdown list by first getting its view, then getting the view's nsIWidget, then asking the nsIWidget
582 : // for it's AbsoluteBounds. The problem with this approach, is that the dropdown lists y location can
583 : // change based on whether the dropdown is placed below or above the display frame.
584 : // The approach, taken here is to get use the absolute position of the display frame and use it's location
585 : // to determine if the dropdown will go offscreen.
586 :
587 : // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
588 : // In the special case that our transform is only a 2D translation we
589 : // introduce this hack so that the dropdown will show up in the right place.
590 0 : nsPoint translation = GetCSSTransformTranslation();
591 :
592 : // Use the height calculated for the area frame so it includes both
593 : // the display and button heights.
594 0 : nscoord dropdownYOffset = GetRect().height;
595 0 : nsSize dropdownSize = mDropdownFrame->GetSize();
596 :
597 0 : nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
598 :
599 : // Check to see if the drop-down list will go offscreen
600 0 : if ((GetScreenRectInAppUnits() + translation).YMost() + dropdownSize.height > screen.YMost()) {
601 : // move the dropdown list up
602 0 : dropdownYOffset = - (dropdownSize.height);
603 : }
604 :
605 0 : nsPoint dropdownPosition;
606 0 : const nsStyleVisibility* vis = GetStyleVisibility();
607 0 : if (vis->mDirection == NS_STYLE_DIRECTION_RTL) {
608 : // Align the right edge of the drop-down with the right edge of the control.
609 0 : dropdownPosition.x = GetRect().width - dropdownSize.width;
610 : } else {
611 0 : dropdownPosition.x = 0;
612 : }
613 0 : dropdownPosition.y = dropdownYOffset;
614 :
615 0 : mDropdownFrame->SetPosition(dropdownPosition + translation);
616 0 : }
617 :
618 : //----------------------------------------------------------
619 : //
620 : //----------------------------------------------------------
621 : #ifdef DO_REFLOW_DEBUG
622 : static int myCounter = 0;
623 :
624 : static void printSize(char * aDesc, nscoord aSize)
625 : {
626 : printf(" %s: ", aDesc);
627 : if (aSize == NS_UNCONSTRAINEDSIZE) {
628 : printf("UC");
629 : } else {
630 : printf("%d", PX(aSize));
631 : }
632 : }
633 : #endif
634 :
635 : //-------------------------------------------------------------------
636 : //-- Main Reflow for the Combobox
637 : //-------------------------------------------------------------------
638 :
639 : nscoord
640 0 : nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext* aRenderingContext,
641 : nsLayoutUtils::IntrinsicWidthType aType)
642 : {
643 : // get the scrollbar width, we'll use this later
644 0 : nscoord scrollbarWidth = 0;
645 0 : nsPresContext* presContext = PresContext();
646 0 : if (mListControlFrame) {
647 0 : nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
648 0 : NS_ASSERTION(scrollable, "List must be a scrollable frame");
649 : scrollbarWidth =
650 0 : scrollable->GetDesiredScrollbarSizes(presContext, aRenderingContext).LeftRight();
651 : }
652 :
653 0 : nscoord displayWidth = 0;
654 0 : if (NS_LIKELY(mDisplayFrame)) {
655 : displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
656 : mDisplayFrame,
657 0 : aType);
658 : }
659 :
660 0 : if (mDropdownFrame) {
661 : nscoord dropdownContentWidth;
662 0 : if (aType == nsLayoutUtils::MIN_WIDTH) {
663 0 : dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
664 : } else {
665 0 : NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
666 0 : dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
667 : }
668 : dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
669 : scrollbarWidth,
670 0 : nscoord_MAX);
671 :
672 0 : displayWidth = NS_MAX(dropdownContentWidth, displayWidth);
673 : }
674 :
675 : // add room for the dropmarker button if there is one
676 0 : if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker())
677 0 : displayWidth += scrollbarWidth;
678 :
679 0 : return displayWidth;
680 :
681 : }
682 :
683 : nscoord
684 0 : nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
685 : {
686 : nscoord minWidth;
687 0 : DISPLAY_MIN_WIDTH(this, minWidth);
688 0 : minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
689 0 : return minWidth;
690 : }
691 :
692 : nscoord
693 0 : nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
694 : {
695 : nscoord prefWidth;
696 0 : DISPLAY_PREF_WIDTH(this, prefWidth);
697 0 : prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
698 0 : return prefWidth;
699 : }
700 :
701 : NS_IMETHODIMP
702 0 : nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
703 : nsHTMLReflowMetrics& aDesiredSize,
704 : const nsHTMLReflowState& aReflowState,
705 : nsReflowStatus& aStatus)
706 : {
707 : // Constraints we try to satisfy:
708 :
709 : // 1) Default width of button is the vertical scrollbar size
710 : // 2) If the width of button is bigger than our width, set width of
711 : // button to 0.
712 : // 3) Default height of button is height of display area
713 : // 4) Width of display area is whatever is left over from our width after
714 : // allocating width for the button.
715 : // 5) Height of display area is GetHeightOfARow() on the
716 : // mListControlFrame.
717 :
718 0 : if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
719 0 : NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
720 0 : return NS_ERROR_UNEXPECTED;
721 : }
722 :
723 : // Make sure the displayed text is the same as the selected option, bug 297389.
724 : PRInt32 selectedIndex;
725 0 : nsAutoString selectedOptionText;
726 0 : if (!mDroppedDown) {
727 0 : selectedIndex = mListControlFrame->GetSelectedIndex();
728 : }
729 : else {
730 : // In dropped down mode the "selected index" is the hovered menu item,
731 : // we want the last selected item which is |mDisplayedIndex| in this case.
732 0 : selectedIndex = mDisplayedIndex;
733 : }
734 0 : if (selectedIndex != -1) {
735 0 : mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
736 : }
737 0 : if (mDisplayedOptionText != selectedOptionText) {
738 0 : RedisplayText(selectedIndex);
739 : }
740 :
741 : // First reflow our dropdown so that we know how tall we should be.
742 0 : ReflowDropdown(aPresContext, aReflowState);
743 :
744 : // Get the width of the vertical scrollbar. That will be the width of the
745 : // dropdown button.
746 : nscoord buttonWidth;
747 0 : const nsStyleDisplay *disp = GetStyleDisplay();
748 0 : if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
749 0 : buttonWidth = 0;
750 : }
751 : else {
752 0 : nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
753 0 : NS_ASSERTION(scrollable, "List must be a scrollable frame");
754 : buttonWidth =
755 : scrollable->GetDesiredScrollbarSizes(PresContext(),
756 0 : aReflowState.rendContext).LeftRight();
757 0 : if (buttonWidth > aReflowState.ComputedWidth()) {
758 0 : buttonWidth = 0;
759 : }
760 : }
761 :
762 0 : mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
763 :
764 : nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
765 0 : aStatus);
766 0 : NS_ENSURE_SUCCESS(rv, rv);
767 :
768 : // Now set the correct width and height on our button. The width we need to
769 : // set always, the height only if we had an auto height.
770 0 : nsRect buttonRect = mButtonFrame->GetRect();
771 : // If we have a non-intrinsic computed height, our kids should have sized
772 : // themselves properly on their own.
773 0 : if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) {
774 : // The display frame is going to be the right height and width at this
775 : // point. Use its height as the button height.
776 0 : nsRect displayRect = mDisplayFrame->GetRect();
777 0 : buttonRect.height = displayRect.height;
778 0 : buttonRect.y = displayRect.y;
779 : }
780 : #ifdef DEBUG
781 : else {
782 0 : nscoord buttonHeight = mButtonFrame->GetSize().height;
783 0 : nscoord displayHeight = mDisplayFrame->GetSize().height;
784 :
785 : // The button and display area should be equal heights, unless the computed
786 : // height on the combobox is too small to fit their borders and padding.
787 0 : NS_ASSERTION(buttonHeight == displayHeight ||
788 : (aReflowState.ComputedHeight() < buttonHeight &&
789 : buttonHeight ==
790 : mButtonFrame->GetUsedBorderAndPadding().TopBottom()) ||
791 : (aReflowState.ComputedHeight() < displayHeight &&
792 : displayHeight ==
793 : mDisplayFrame->GetUsedBorderAndPadding().TopBottom()),
794 : "Different heights?");
795 : }
796 : #endif
797 :
798 0 : if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
799 : // Make sure the right edge of the button frame stays where it is now
800 0 : buttonRect.x -= buttonWidth - buttonRect.width;
801 : }
802 0 : buttonRect.width = buttonWidth;
803 0 : mButtonFrame->SetRect(buttonRect);
804 :
805 0 : return rv;
806 : }
807 :
808 : //--------------------------------------------------------------
809 :
810 : nsIAtom*
811 0 : nsComboboxControlFrame::GetType() const
812 : {
813 0 : return nsGkAtoms::comboboxControlFrame;
814 : }
815 :
816 : #ifdef NS_DEBUG
817 : NS_IMETHODIMP
818 0 : nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
819 : {
820 0 : return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
821 : }
822 : #endif
823 :
824 :
825 : //----------------------------------------------------------------------
826 : // nsIComboboxControlFrame
827 : //----------------------------------------------------------------------
828 : void
829 0 : nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
830 : {
831 0 : nsEventStates eventStates = mContent->AsElement()->State();
832 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
833 0 : return;
834 : }
835 :
836 0 : if (!mDroppedDown && aDoDropDown) {
837 0 : if (mListControlFrame) {
838 0 : mListControlFrame->SyncViewWithFrame();
839 : }
840 0 : ShowList(aDoDropDown); // might destroy us
841 0 : } else if (mDroppedDown && !aDoDropDown) {
842 0 : ShowList(aDoDropDown); // might destroy us
843 : }
844 : }
845 :
846 : void
847 0 : nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
848 : {
849 0 : mDropdownFrame = aDropDownFrame;
850 0 : mListControlFrame = do_QueryFrame(mDropdownFrame);
851 0 : }
852 :
853 : nsIFrame*
854 0 : nsComboboxControlFrame::GetDropDown()
855 : {
856 0 : return mDropdownFrame;
857 : }
858 :
859 : ///////////////////////////////////////////////////////////////
860 :
861 : NS_IMETHODIMP
862 0 : nsComboboxControlFrame::RedisplaySelectedText()
863 : {
864 0 : nsAutoScriptBlocker scriptBlocker;
865 0 : return RedisplayText(mListControlFrame->GetSelectedIndex());
866 : }
867 :
868 : nsresult
869 0 : nsComboboxControlFrame::RedisplayText(PRInt32 aIndex)
870 : {
871 : // Get the text to display
872 0 : if (aIndex != -1) {
873 0 : mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
874 : } else {
875 0 : mDisplayedOptionText.Truncate();
876 : }
877 0 : mDisplayedIndex = aIndex;
878 :
879 : REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
880 : NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
881 :
882 : // Send reflow command because the new text maybe larger
883 0 : nsresult rv = NS_OK;
884 0 : if (mDisplayContent) {
885 : // Don't call ActuallyDisplayText(true) directly here since that
886 : // could cause recursive frame construction. See bug 283117 and the comment in
887 : // HandleRedisplayTextEvent() below.
888 :
889 : // Revoke outstanding events to avoid out-of-order events which could mean
890 : // displaying the wrong text.
891 0 : mRedisplayTextEvent.Revoke();
892 :
893 0 : NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
894 : "If we happen to run our redisplay event now, we might kill "
895 : "ourselves!");
896 :
897 0 : nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
898 0 : mRedisplayTextEvent = event;
899 0 : if (!nsContentUtils::AddScriptRunner(event))
900 0 : mRedisplayTextEvent.Forget();
901 : }
902 0 : return rv;
903 : }
904 :
905 : void
906 0 : nsComboboxControlFrame::HandleRedisplayTextEvent()
907 : {
908 : // First, make sure that the content model is up to date and we've
909 : // constructed the frames for all our content in the right places.
910 : // Otherwise they'll end up under the wrong insertion frame when we
911 : // ActuallyDisplayText, since that flushes out the content sink by
912 : // calling SetText on a DOM node with aNotify set to true. See bug
913 : // 289730.
914 0 : nsWeakFrame weakThis(this);
915 0 : PresContext()->Document()->
916 0 : FlushPendingNotifications(Flush_ContentAndNotify);
917 0 : if (!weakThis.IsAlive())
918 : return;
919 :
920 : // Redirect frame insertions during this method (see GetContentInsertionFrame())
921 : // so that any reframing that the frame constructor forces upon us is inserted
922 : // into the correct parent (mDisplayFrame). See bug 282607.
923 0 : NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
924 0 : mInRedisplayText = true;
925 0 : mRedisplayTextEvent.Forget();
926 :
927 0 : ActuallyDisplayText(true);
928 : // XXXbz This should perhaps be eResize. Check.
929 0 : PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
930 : nsIPresShell::eStyleChange,
931 0 : NS_FRAME_IS_DIRTY);
932 :
933 0 : mInRedisplayText = false;
934 : }
935 :
936 : void
937 0 : nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
938 : {
939 0 : if (mDisplayedOptionText.IsEmpty()) {
940 : // Have to use a non-breaking space for line-height calculations
941 : // to be right
942 : static const PRUnichar space = 0xA0;
943 0 : mDisplayContent->SetText(&space, 1, aNotify);
944 : } else {
945 0 : mDisplayContent->SetText(mDisplayedOptionText, aNotify);
946 : }
947 0 : }
948 :
949 : PRInt32
950 0 : nsComboboxControlFrame::GetIndexOfDisplayArea()
951 : {
952 0 : return mDisplayedIndex;
953 : }
954 :
955 : //----------------------------------------------------------------------
956 : // nsISelectControlFrame
957 : //----------------------------------------------------------------------
958 : NS_IMETHODIMP
959 0 : nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
960 : {
961 0 : nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
962 0 : if (!listFrame)
963 0 : return NS_ERROR_FAILURE;
964 :
965 0 : return listFrame->DoneAddingChildren(aIsDone);
966 : }
967 :
968 : NS_IMETHODIMP
969 0 : nsComboboxControlFrame::AddOption(PRInt32 aIndex)
970 : {
971 0 : if (aIndex <= mDisplayedIndex) {
972 0 : ++mDisplayedIndex;
973 : }
974 :
975 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
976 0 : return lcf->AddOption(aIndex);
977 : }
978 :
979 :
980 : NS_IMETHODIMP
981 0 : nsComboboxControlFrame::RemoveOption(PRInt32 aIndex)
982 : {
983 0 : nsWeakFrame weakThis(this);
984 0 : if (mListControlFrame->GetNumberOfOptions() > 0) {
985 0 : if (aIndex < mDisplayedIndex) {
986 0 : --mDisplayedIndex;
987 0 : } else if (aIndex == mDisplayedIndex) {
988 0 : mDisplayedIndex = 0; // IE6 compat
989 0 : RedisplayText(mDisplayedIndex);
990 : }
991 : }
992 : else {
993 : // If we removed the last option, we need to blank things out
994 0 : RedisplayText(-1);
995 : }
996 :
997 0 : if (!weakThis.IsAlive())
998 0 : return NS_OK;
999 :
1000 0 : nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1001 0 : return lcf->RemoveOption(aIndex);
1002 : }
1003 :
1004 : NS_IMETHODIMP
1005 0 : nsComboboxControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 aNewIndex)
1006 : {
1007 0 : nsAutoScriptBlocker scriptBlocker;
1008 0 : RedisplayText(aNewIndex);
1009 0 : NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
1010 :
1011 0 : nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1012 0 : NS_ASSERTION(listFrame, "No list frame!");
1013 :
1014 0 : return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
1015 : }
1016 :
1017 : // End nsISelectControlFrame
1018 : //----------------------------------------------------------------------
1019 :
1020 : NS_IMETHODIMP
1021 0 : nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1022 : nsGUIEvent* aEvent,
1023 : nsEventStatus* aEventStatus)
1024 : {
1025 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
1026 :
1027 0 : if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1028 0 : return NS_OK;
1029 : }
1030 :
1031 0 : nsEventStates eventStates = mContent->AsElement()->State();
1032 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1033 0 : return NS_OK;
1034 : }
1035 :
1036 : // If we have style that affects how we are selected, feed event down to
1037 : // nsFrame::HandleEvent so that selection takes place when appropriate.
1038 0 : const nsStyleUserInterface* uiStyle = GetStyleUserInterface();
1039 0 : if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
1040 0 : return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1041 :
1042 0 : return NS_OK;
1043 : }
1044 :
1045 :
1046 : nsresult
1047 0 : nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
1048 : {
1049 0 : nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1050 0 : if (!fcFrame) {
1051 0 : return NS_NOINTERFACE;
1052 : }
1053 :
1054 0 : return fcFrame->SetFormProperty(aName, aValue);
1055 : }
1056 :
1057 : nsresult
1058 0 : nsComboboxControlFrame::GetFormProperty(nsIAtom* aName, nsAString& aValue) const
1059 : {
1060 0 : nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1061 0 : if (!fcFrame) {
1062 0 : return NS_ERROR_FAILURE;
1063 : }
1064 :
1065 0 : return fcFrame->GetFormProperty(aName, aValue);
1066 : }
1067 :
1068 : nsIFrame*
1069 0 : nsComboboxControlFrame::GetContentInsertionFrame() {
1070 0 : return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1071 : }
1072 :
1073 : nsresult
1074 0 : nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1075 : {
1076 : // The frames used to display the combo box and the button used to popup the dropdown list
1077 : // are created through anonymous content. The dropdown list is not created through anonymous
1078 : // content because it's frame is initialized specifically for the drop-down case and it is placed
1079 : // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1080 : // layout of the display and button.
1081 : //
1082 : // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1083 : // If the content specified below does not honor the value attribute than nothing will be displayed.
1084 :
1085 : // For now the content that is created corresponds to two input buttons. It would be better to create the
1086 : // tag as something other than input, but then there isn't any way to create a button frame since it
1087 : // isn't possible to set the display type in CSS2 to create a button frame.
1088 :
1089 : // create content used for display
1090 : //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
1091 :
1092 : // Add a child text content node for the label
1093 :
1094 0 : nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1095 :
1096 0 : NS_NewTextNode(getter_AddRefs(mDisplayContent), nimgr);
1097 0 : if (!mDisplayContent)
1098 0 : return NS_ERROR_OUT_OF_MEMORY;
1099 :
1100 : // set the value of the text node
1101 0 : mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1102 0 : if (mDisplayedIndex != -1) {
1103 0 : mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
1104 : }
1105 0 : ActuallyDisplayText(false);
1106 :
1107 0 : if (!aElements.AppendElement(mDisplayContent))
1108 0 : return NS_ERROR_OUT_OF_MEMORY;
1109 :
1110 0 : nsCOMPtr<nsINodeInfo> nodeInfo;
1111 : nodeInfo = nimgr->GetNodeInfo(nsGkAtoms::button, nsnull, kNameSpaceID_XHTML,
1112 0 : nsIDOMNode::ELEMENT_NODE);
1113 :
1114 : // create button which drops the list down
1115 0 : NS_NewHTMLElement(getter_AddRefs(mButtonContent), nodeInfo.forget(),
1116 0 : dom::NOT_FROM_PARSER);
1117 0 : if (!mButtonContent)
1118 0 : return NS_ERROR_OUT_OF_MEMORY;
1119 :
1120 : // make someone to listen to the button. If its pressed by someone like Accessibility
1121 : // then open or close the combo box.
1122 0 : mButtonListener = new nsComboButtonListener(this);
1123 0 : mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
1124 0 : false, false);
1125 :
1126 : mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1127 0 : NS_LITERAL_STRING("button"), false);
1128 : // Set tabindex="-1" so that the button is not tabbable
1129 : mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1130 0 : NS_LITERAL_STRING("-1"), false);
1131 :
1132 0 : if (!aElements.AppendElement(mButtonContent))
1133 0 : return NS_ERROR_OUT_OF_MEMORY;
1134 :
1135 0 : return NS_OK;
1136 : }
1137 :
1138 : void
1139 0 : nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
1140 : PRUint32 aFilter)
1141 : {
1142 0 : aElements.MaybeAppendElement(mDisplayContent);
1143 0 : aElements.MaybeAppendElement(mButtonContent);
1144 0 : }
1145 :
1146 : // XXXbz this is a for-now hack. Now that display:inline-block works,
1147 : // need to revisit this.
1148 0 : class nsComboboxDisplayFrame : public nsBlockFrame {
1149 : public:
1150 : NS_DECL_FRAMEARENA_HELPERS
1151 :
1152 0 : nsComboboxDisplayFrame (nsStyleContext* aContext,
1153 : nsComboboxControlFrame* aComboBox)
1154 : : nsBlockFrame(aContext),
1155 0 : mComboBox(aComboBox)
1156 0 : {}
1157 :
1158 : // Need this so that line layout knows that this block's width
1159 : // depends on the available width.
1160 : virtual nsIAtom* GetType() const;
1161 :
1162 0 : virtual bool IsFrameOfType(PRUint32 aFlags) const
1163 : {
1164 : return nsBlockFrame::IsFrameOfType(aFlags &
1165 0 : ~(nsIFrame::eReplacedContainsBlock));
1166 : }
1167 :
1168 : NS_IMETHOD Reflow(nsPresContext* aPresContext,
1169 : nsHTMLReflowMetrics& aDesiredSize,
1170 : const nsHTMLReflowState& aReflowState,
1171 : nsReflowStatus& aStatus);
1172 :
1173 : NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
1174 : const nsRect& aDirtyRect,
1175 : const nsDisplayListSet& aLists);
1176 :
1177 : protected:
1178 : nsComboboxControlFrame* mComboBox;
1179 : };
1180 :
1181 0 : NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1182 :
1183 : nsIAtom*
1184 0 : nsComboboxDisplayFrame::GetType() const
1185 : {
1186 0 : return nsGkAtoms::comboboxDisplayFrame;
1187 : }
1188 :
1189 : NS_IMETHODIMP
1190 0 : nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
1191 : nsHTMLReflowMetrics& aDesiredSize,
1192 : const nsHTMLReflowState& aReflowState,
1193 : nsReflowStatus& aStatus)
1194 : {
1195 0 : nsHTMLReflowState state(aReflowState);
1196 0 : if (state.ComputedHeight() == NS_INTRINSICSIZE) {
1197 : // Note that the only way we can have a computed height here is if the
1198 : // combobox had a specified height. If it didn't, size based on what our
1199 : // rows look like, for lack of anything better.
1200 0 : state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
1201 : }
1202 : nscoord computedWidth = mComboBox->mDisplayWidth -
1203 0 : state.mComputedBorderPadding.LeftRight();
1204 0 : if (computedWidth < 0) {
1205 0 : computedWidth = 0;
1206 : }
1207 0 : state.SetComputedWidth(computedWidth);
1208 :
1209 0 : return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1210 : }
1211 :
1212 : NS_IMETHODIMP
1213 0 : nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1214 : const nsRect& aDirtyRect,
1215 : const nsDisplayListSet& aLists)
1216 : {
1217 0 : nsDisplayListCollection set;
1218 0 : nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1219 0 : if (NS_FAILED(rv))
1220 0 : return rv;
1221 :
1222 : // remove background items if parent frame is themed
1223 0 : if (mComboBox->IsThemed()) {
1224 0 : set.BorderBackground()->DeleteAll();
1225 : }
1226 :
1227 0 : set.MoveTo(aLists);
1228 :
1229 0 : return NS_OK;
1230 : }
1231 :
1232 : nsIFrame*
1233 0 : nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
1234 : {
1235 0 : NS_PRECONDITION(nsnull != aContent, "null ptr");
1236 :
1237 0 : NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
1238 :
1239 0 : if (mDisplayContent != aContent) {
1240 : // We only handle the frames for mDisplayContent here
1241 0 : return nsnull;
1242 : }
1243 :
1244 : // Get PresShell
1245 0 : nsIPresShell *shell = PresContext()->PresShell();
1246 0 : nsStyleSet *styleSet = shell->StyleSet();
1247 :
1248 : // create the style contexts for the anonymous block frame and text frame
1249 0 : nsRefPtr<nsStyleContext> styleContext;
1250 : styleContext = styleSet->
1251 : ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
1252 0 : mStyleContext);
1253 0 : if (NS_UNLIKELY(!styleContext)) {
1254 0 : return nsnull;
1255 : }
1256 :
1257 0 : nsRefPtr<nsStyleContext> textStyleContext;
1258 0 : textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
1259 0 : if (NS_UNLIKELY(!textStyleContext)) {
1260 0 : return nsnull;
1261 : }
1262 :
1263 : // Start by by creating our anonymous block frame
1264 0 : mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1265 0 : if (NS_UNLIKELY(!mDisplayFrame)) {
1266 0 : return nsnull;
1267 : }
1268 :
1269 0 : nsresult rv = mDisplayFrame->Init(mContent, this, nsnull);
1270 0 : if (NS_FAILED(rv)) {
1271 0 : mDisplayFrame->Destroy();
1272 0 : mDisplayFrame = nsnull;
1273 0 : return nsnull;
1274 : }
1275 :
1276 : // Create a text frame and put it inside the block frame
1277 0 : nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
1278 0 : if (NS_UNLIKELY(!textFrame)) {
1279 0 : return nsnull;
1280 : }
1281 :
1282 : // initialize the text frame
1283 0 : rv = textFrame->Init(aContent, mDisplayFrame, nsnull);
1284 0 : if (NS_FAILED(rv)) {
1285 0 : mDisplayFrame->Destroy();
1286 0 : mDisplayFrame = nsnull;
1287 0 : textFrame->Destroy();
1288 0 : textFrame = nsnull;
1289 0 : return nsnull;
1290 : }
1291 0 : mDisplayContent->SetPrimaryFrame(textFrame);
1292 :
1293 0 : nsFrameList textList(textFrame, textFrame);
1294 0 : mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1295 0 : return mDisplayFrame;
1296 : }
1297 :
1298 : void
1299 0 : nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
1300 : {
1301 : // Revoke any pending RedisplayTextEvent
1302 0 : mRedisplayTextEvent.Revoke();
1303 :
1304 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1305 :
1306 0 : if (mDroppedDown) {
1307 : // Get parent view
1308 0 : nsIFrame * listFrame = do_QueryFrame(mListControlFrame);
1309 0 : if (listFrame) {
1310 0 : nsIView* view = listFrame->GetView();
1311 0 : NS_ASSERTION(view, "nsComboboxControlFrame view is null");
1312 0 : if (view) {
1313 0 : nsIWidget* widget = view->GetWidget();
1314 0 : if (widget)
1315 0 : widget->CaptureRollupEvents(this, false, true);
1316 : }
1317 : }
1318 : }
1319 :
1320 : // Cleanup frames in popup child list
1321 0 : mPopupFrames.DestroyFramesFrom(aDestructRoot);
1322 0 : nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
1323 0 : nsContentUtils::DestroyAnonymousContent(&mButtonContent);
1324 0 : nsBlockFrame::DestroyFrom(aDestructRoot);
1325 0 : }
1326 :
1327 : const nsFrameList&
1328 0 : nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1329 : {
1330 0 : if (kSelectPopupList == aListID) {
1331 0 : return mPopupFrames;
1332 : }
1333 0 : return nsBlockFrame::GetChildList(aListID);
1334 : }
1335 :
1336 : void
1337 0 : nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1338 : {
1339 0 : nsBlockFrame::GetChildLists(aLists);
1340 0 : mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1341 0 : }
1342 :
1343 : NS_IMETHODIMP
1344 0 : nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
1345 : nsFrameList& aChildList)
1346 : {
1347 0 : nsresult rv = NS_OK;
1348 0 : if (kSelectPopupList == aListID) {
1349 0 : mPopupFrames.SetFrames(aChildList);
1350 : } else {
1351 0 : for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
1352 : nsCOMPtr<nsIFormControl> formControl =
1353 0 : do_QueryInterface(e.get()->GetContent());
1354 0 : if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
1355 0 : mButtonFrame = e.get();
1356 : break;
1357 : }
1358 : }
1359 0 : NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1360 0 : rv = nsBlockFrame::SetInitialChildList(aListID, aChildList);
1361 : }
1362 0 : return rv;
1363 : }
1364 :
1365 : //----------------------------------------------------------------------
1366 : //nsIRollupListener
1367 : //----------------------------------------------------------------------
1368 : nsIContent*
1369 0 : nsComboboxControlFrame::Rollup(PRUint32 aCount, bool aGetLastRolledUp)
1370 : {
1371 0 : if (mDroppedDown) {
1372 0 : nsWeakFrame weakFrame(this);
1373 0 : mListControlFrame->AboutToRollup(); // might destroy us
1374 0 : if (!weakFrame.IsAlive())
1375 0 : return nsnull;
1376 0 : ShowDropDown(false); // might destroy us
1377 0 : if (!weakFrame.IsAlive())
1378 0 : return nsnull;
1379 0 : mListControlFrame->CaptureMouseEvents(false);
1380 : }
1381 :
1382 0 : return nsnull;
1383 : }
1384 :
1385 : void
1386 0 : nsComboboxControlFrame::RollupFromList()
1387 : {
1388 0 : if (ShowList(false))
1389 0 : mListControlFrame->CaptureMouseEvents(false);
1390 0 : }
1391 :
1392 : PRInt32
1393 0 : nsComboboxControlFrame::UpdateRecentIndex(PRInt32 aIndex)
1394 : {
1395 0 : PRInt32 index = mRecentSelectedIndex;
1396 0 : if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1397 0 : mRecentSelectedIndex = aIndex;
1398 0 : return index;
1399 : }
1400 :
1401 : class nsDisplayComboboxFocus : public nsDisplayItem {
1402 : public:
1403 0 : nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
1404 : nsComboboxControlFrame* aFrame)
1405 0 : : nsDisplayItem(aBuilder, aFrame) {
1406 0 : MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1407 0 : }
1408 : #ifdef NS_BUILD_REFCNT_LOGGING
1409 0 : virtual ~nsDisplayComboboxFocus() {
1410 0 : MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1411 0 : }
1412 : #endif
1413 :
1414 : virtual void Paint(nsDisplayListBuilder* aBuilder,
1415 : nsRenderingContext* aCtx);
1416 0 : NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
1417 : };
1418 :
1419 0 : void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1420 : nsRenderingContext* aCtx)
1421 : {
1422 : static_cast<nsComboboxControlFrame*>(mFrame)
1423 0 : ->PaintFocus(*aCtx, ToReferenceFrame());
1424 0 : }
1425 :
1426 : NS_IMETHODIMP
1427 0 : nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1428 : const nsRect& aDirtyRect,
1429 : const nsDisplayListSet& aLists)
1430 : {
1431 : #ifdef NOISY
1432 : printf("%p paint at (%d, %d, %d, %d)\n", this,
1433 : aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1434 : #endif
1435 :
1436 0 : if (aBuilder->IsForEventDelivery()) {
1437 : // Don't allow children to receive events.
1438 : // REVIEW: following old GetFrameForPoint
1439 0 : nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
1440 0 : NS_ENSURE_SUCCESS(rv, rv);
1441 : } else {
1442 : // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1443 : // content list, so we don't need to mess with layers.
1444 0 : nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1445 0 : NS_ENSURE_SUCCESS(rv, rv);
1446 : }
1447 :
1448 : // draw a focus indicator only when focus rings should be drawn
1449 0 : nsIDocument* doc = mContent->GetCurrentDoc();
1450 0 : if (doc) {
1451 0 : nsPIDOMWindow* window = doc->GetWindow();
1452 0 : if (window && window->ShouldShowFocusRing()) {
1453 0 : nsPresContext *presContext = PresContext();
1454 0 : const nsStyleDisplay *disp = GetStyleDisplay();
1455 0 : if ((!IsThemed(disp) ||
1456 0 : !presContext->GetTheme()->ThemeDrawsFocusForWidget(presContext, this, disp->mAppearance)) &&
1457 0 : mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1458 : nsresult rv = aLists.Content()->AppendNewToTop(
1459 0 : new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
1460 0 : NS_ENSURE_SUCCESS(rv, rv);
1461 : }
1462 : }
1463 : }
1464 :
1465 0 : return DisplaySelectionOverlay(aBuilder, aLists.Content());
1466 : }
1467 :
1468 0 : void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext,
1469 : nsPoint aPt)
1470 : {
1471 : /* Do we need to do anything? */
1472 0 : nsEventStates eventStates = mContent->AsElement()->State();
1473 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || mFocused != this)
1474 0 : return;
1475 :
1476 0 : aRenderingContext.PushState();
1477 0 : nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1478 0 : aRenderingContext.IntersectClip(clipRect);
1479 :
1480 : // REVIEW: Why does the old code paint mDisplayFrame again? We've
1481 : // already painted it in the children above. So clipping it here won't do
1482 : // us much good.
1483 :
1484 : /////////////////////
1485 : // draw focus
1486 :
1487 0 : aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
1488 0 : aRenderingContext.SetColor(GetStyleColor()->mColor);
1489 :
1490 : //aRenderingContext.DrawRect(clipRect);
1491 :
1492 0 : nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1493 0 : clipRect.width -= onePixel;
1494 0 : clipRect.height -= onePixel;
1495 0 : aRenderingContext.DrawLine(clipRect.TopLeft(), clipRect.TopRight());
1496 0 : aRenderingContext.DrawLine(clipRect.TopRight(), clipRect.BottomRight());
1497 0 : aRenderingContext.DrawLine(clipRect.BottomRight(), clipRect.BottomLeft());
1498 0 : aRenderingContext.DrawLine(clipRect.BottomLeft(), clipRect.TopLeft());
1499 :
1500 0 : aRenderingContext.PopState();
1501 : }
1502 :
1503 : //---------------------------------------------------------
1504 : // gets the content (an option) by index and then set it as
1505 : // being selected or not selected
1506 : //---------------------------------------------------------
1507 : NS_IMETHODIMP
1508 0 : nsComboboxControlFrame::OnOptionSelected(PRInt32 aIndex, bool aSelected)
1509 : {
1510 0 : if (mDroppedDown) {
1511 0 : nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1512 0 : if (selectFrame) {
1513 0 : selectFrame->OnOptionSelected(aIndex, aSelected);
1514 : }
1515 : } else {
1516 0 : if (aSelected) {
1517 0 : nsAutoScriptBlocker blocker;
1518 0 : RedisplayText(aIndex);
1519 : } else {
1520 0 : nsWeakFrame weakFrame(this);
1521 0 : RedisplaySelectedText();
1522 0 : if (weakFrame.IsAlive()) {
1523 0 : FireValueChangeEvent(); // Fire after old option is unselected
1524 : }
1525 : }
1526 : }
1527 :
1528 0 : return NS_OK;
1529 : }
1530 :
1531 0 : void nsComboboxControlFrame::FireValueChangeEvent()
1532 : {
1533 : // Fire ValueChange event to indicate data value of combo box has changed
1534 : nsContentUtils::AddScriptRunner(
1535 0 : new nsAsyncDOMEvent(mContent, NS_LITERAL_STRING("ValueChange"), true,
1536 0 : false));
1537 0 : }
1538 :
1539 : void
1540 0 : nsComboboxControlFrame::OnContentReset()
1541 : {
1542 0 : if (mListControlFrame) {
1543 0 : mListControlFrame->OnContentReset();
1544 : }
1545 0 : }
1546 :
1547 :
1548 : //--------------------------------------------------------
1549 : // nsIStatefulFrame
1550 : //--------------------------------------------------------
1551 : NS_IMETHODIMP
1552 0 : nsComboboxControlFrame::SaveState(SpecialStateID aStateID,
1553 : nsPresState** aState)
1554 : {
1555 0 : if (!mListControlFrame)
1556 0 : return NS_ERROR_FAILURE;
1557 :
1558 0 : nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1559 0 : return stateful->SaveState(aStateID, aState);
1560 : }
1561 :
1562 : NS_IMETHODIMP
1563 0 : nsComboboxControlFrame::RestoreState(nsPresState* aState)
1564 : {
1565 0 : if (!mListControlFrame)
1566 0 : return NS_ERROR_FAILURE;
1567 :
1568 0 : nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1569 0 : NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
1570 0 : return stateful->RestoreState(aState);
1571 : }
1572 :
1573 :
1574 : //
1575 : // Camino uses a native widget for the combobox
1576 : // popup, which affects drawing and event
1577 : // handling here and in nsListControlFrame.
1578 : //
1579 : // Also, Fennec use a custom combobox built-in widget
1580 : //
1581 :
1582 : /* static */
1583 : bool
1584 0 : nsComboboxControlFrame::ToolkitHasNativePopup()
1585 : {
1586 : #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1587 : return true;
1588 : #else
1589 0 : return false;
1590 : #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
1591 : }
1592 :
|