1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsUnicharUtils.h"
9 #include "nsListControlFrame.h"
10 #include "nsFormControlFrame.h" // for COMPARE macro
11 #include "nsGkAtoms.h"
12 #include "nsIDOMHTMLSelectElement.h"
13 #include "nsIDOMHTMLOptionElement.h"
14 #include "nsComboboxControlFrame.h"
15 #include "nsIDOMHTMLOptGroupElement.h"
16 #include "nsIPresShell.h"
17 #include "nsEventStateManager.h"
18 #include "nsIDOMMouseEvent.h"
19 #include "nsIXULRuntime.h"
20 #include "nsFontMetrics.h"
21 #include "nsIScrollableFrame.h"
22 #include "nsCSSRendering.h"
23 #include "nsIDOMEventListener.h"
24 #include "nsLayoutUtils.h"
25 #include "nsDisplayList.h"
26 #include "nsContentUtils.h"
27 #include "mozilla/Attributes.h"
28 #include "mozilla/dom/HTMLOptionsCollection.h"
29 #include "mozilla/dom/HTMLSelectElement.h"
30 #include "mozilla/LookAndFeel.h"
31 #include "mozilla/MouseEvents.h"
32 #include "mozilla/Preferences.h"
33 #include "mozilla/TextEvents.h"
36 using namespace mozilla
;
39 const uint32_t kMaxDropDownRows
= 20; // This matches the setting for 4.x browsers
40 const int32_t kNothingSelected
= -1;
43 nsListControlFrame
* nsListControlFrame::mFocused
= nullptr;
44 nsString
* nsListControlFrame::sIncrementalString
= nullptr;
46 // Using for incremental typing navigation
47 #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
48 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
49 // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
50 // need to find a good place to put them together.
51 // if someone changes one, please also change the other.
53 DOMTimeStamp
nsListControlFrame::gLastKeyTime
= 0;
55 /******************************************************************************
57 * This class is responsible for propagating events to the nsListControlFrame.
58 * Frames are not refcounted so they can't be used as event listeners.
59 *****************************************************************************/
61 class nsListEventListener MOZ_FINAL
: public nsIDOMEventListener
64 nsListEventListener(nsListControlFrame
*aFrame
)
67 void SetFrame(nsListControlFrame
*aFrame
) { mFrame
= aFrame
; }
70 NS_DECL_NSIDOMEVENTLISTENER
73 nsListControlFrame
*mFrame
;
76 //---------------------------------------------------------
78 NS_NewListControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
80 nsListControlFrame
* it
=
81 new (aPresShell
) nsListControlFrame(aPresShell
, aPresShell
->GetDocument(), aContext
);
83 it
->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION
);
88 NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame
)
90 //---------------------------------------------------------
91 nsListControlFrame::nsListControlFrame(
92 nsIPresShell
* aShell
, nsIDocument
* aDocument
, nsStyleContext
* aContext
)
93 : nsHTMLScrollFrame(aShell
, aContext
, false),
94 mMightNeedSecondPass(false),
95 mHasPendingInterruptAtStartOfReflow(false),
96 mDropdownCanGrow(false),
97 mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE
)
99 mComboboxFrame
= nullptr;
100 mChangesSinceDragStart
= false;
103 mIsAllContentHere
= false;
104 mIsAllFramesHere
= false;
105 mHasBeenInitialized
= false;
107 mPostChildrenLoadedReset
= false;
109 mControlSelectMode
= false;
112 //---------------------------------------------------------
113 nsListControlFrame::~nsListControlFrame()
115 mComboboxFrame
= nullptr;
118 // for Bug 47302 (remove this comment later)
120 nsListControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
122 // get the receiver interface from the browser button's content node
123 ENSURE_TRUE(mContent
);
125 // Clear the frame pointer on our event listener, just in case the
126 // event listener can outlive the frame.
128 mEventListener
->SetFrame(nullptr);
130 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
131 mEventListener
, false);
132 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
133 mEventListener
, false);
134 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
135 mEventListener
, false);
136 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
137 mEventListener
, false);
138 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
139 mEventListener
, false);
141 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), false);
142 nsHTMLScrollFrame::DestroyFrom(aDestructRoot
);
146 nsListControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
147 const nsRect
& aDirtyRect
,
148 const nsDisplayListSet
& aLists
)
150 // We allow visibility:hidden <select>s to contain visible options.
152 // Don't allow painting of list controls when painting is suppressed.
153 // XXX why do we need this here? we should never reach this. Maybe
154 // because these can have widgets? Hmm
155 if (aBuilder
->IsBackgroundOnly())
158 DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
160 if (IsInDropDownMode()) {
161 NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor
) == 255,
162 "need an opaque backstop color");
163 // XXX Because we have an opaque widget and we get called to paint with
164 // this frame as the root of a stacking context we need make sure to draw
165 // some opaque color over the whole widget. (Bug 511323)
166 aLists
.BorderBackground()->AppendNewToBottom(
167 new (aBuilder
) nsDisplaySolidColor(aBuilder
,
168 this, nsRect(aBuilder
->ToReferenceFrame(this), GetSize()),
169 mLastDropdownBackstopColor
));
172 nsHTMLScrollFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
176 * This is called by the SelectsAreaFrame, which is the same
177 * as the frame returned by GetOptionsContainer. It's the frame which is
179 * @param aPt the offset of this frame, relative to the rendering reference
182 void nsListControlFrame::PaintFocus(nsRenderingContext
& aRC
, nsPoint aPt
)
184 if (mFocused
!= this) return;
186 nsPresContext
* presContext
= PresContext();
188 nsIFrame
* containerFrame
= GetOptionsContainer();
189 if (!containerFrame
) return;
191 nsIFrame
* childframe
= nullptr;
192 nsCOMPtr
<nsIContent
> focusedContent
= GetCurrentOption();
193 if (focusedContent
) {
194 childframe
= focusedContent
->GetPrimaryFrame();
199 // get the child rect
200 fRect
= childframe
->GetRect();
201 // get it into our coordinates
202 fRect
.MoveBy(childframe
->GetParent()->GetOffsetTo(this));
204 float inflation
= nsLayoutUtils::FontSizeInflationFor(this);
205 fRect
.x
= fRect
.y
= 0;
206 fRect
.width
= GetScrollPortRect().width
;
207 fRect
.height
= CalcFallbackRowHeight(inflation
);
208 fRect
.MoveBy(containerFrame
->GetOffsetTo(this));
212 bool lastItemIsSelected
= false;
213 if (focusedContent
) {
214 nsCOMPtr
<nsIDOMHTMLOptionElement
> domOpt
=
215 do_QueryInterface(focusedContent
);
217 domOpt
->GetSelected(&lastItemIsSelected
);
221 // set up back stop colors and then ask L&F service for the real colors
223 LookAndFeel::GetColor(lastItemIsSelected
?
224 LookAndFeel::eColorID_WidgetSelectForeground
:
225 LookAndFeel::eColorID_WidgetSelectBackground
);
227 nsCSSRendering::PaintFocus(presContext
, aRC
, fRect
, color
);
231 nsListControlFrame::InvalidateFocus()
233 if (mFocused
!= this)
236 nsIFrame
* containerFrame
= GetOptionsContainer();
237 if (containerFrame
) {
238 containerFrame
->InvalidateFrame();
242 NS_QUERYFRAME_HEAD(nsListControlFrame
)
243 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
244 NS_QUERYFRAME_ENTRY(nsIListControlFrame
)
245 NS_QUERYFRAME_ENTRY(nsISelectControlFrame
)
246 NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame
)
250 nsListControlFrame::AccessibleType()
252 return a11y::eHTMLSelectListType
;
257 GetMaxOptionHeight(nsIFrame
* aContainer
)
260 for (nsIFrame
* option
= aContainer
->GetFirstPrincipalChild();
261 option
; option
= option
->GetNextSibling()) {
262 nscoord optionHeight
;
263 if (nsCOMPtr
<nsIDOMHTMLOptGroupElement
>
264 (do_QueryInterface(option
->GetContent()))) {
266 optionHeight
= GetMaxOptionHeight(option
);
269 optionHeight
= option
->GetSize().height
;
271 if (result
< optionHeight
)
272 result
= optionHeight
;
277 //-----------------------------------------------------------------
278 // Main Reflow for ListBox/Dropdown
279 //-----------------------------------------------------------------
282 nsListControlFrame::CalcHeightOfARow()
284 // Calculate the height of a single row in the listbox or dropdown list by
285 // using the tallest thing in the subtree, since there may be option groups
286 // in addition to option elements, either of which may be visible or
287 // invisible, may use different fonts, etc.
288 int32_t heightOfARow
= GetMaxOptionHeight(GetOptionsContainer());
290 // Check to see if we have zero items (and optimize by checking
291 // heightOfARow first)
292 if (heightOfARow
== 0 && GetNumberOfOptions() == 0) {
293 float inflation
= nsLayoutUtils::FontSizeInflationFor(this);
294 heightOfARow
= CalcFallbackRowHeight(inflation
);
301 nsListControlFrame::GetPrefWidth(nsRenderingContext
*aRenderingContext
)
304 DISPLAY_PREF_WIDTH(this, result
);
306 // Always add scrollbar widths to the pref-width of the scrolled
307 // content. Combobox frames depend on this happening in the dropdown,
308 // and standalone listboxes are overflow:scroll so they need it too.
309 result
= GetScrolledFrame()->GetPrefWidth(aRenderingContext
);
310 result
= NSCoordSaturatingAdd(result
,
311 GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight());
317 nsListControlFrame::GetMinWidth(nsRenderingContext
*aRenderingContext
)
320 DISPLAY_MIN_WIDTH(this, result
);
322 // Always add scrollbar widths to the min-width of the scrolled
323 // content. Combobox frames depend on this happening in the dropdown,
324 // and standalone listboxes are overflow:scroll so they need it too.
325 result
= GetScrolledFrame()->GetMinWidth(aRenderingContext
);
326 result
+= GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight();
332 nsListControlFrame::Reflow(nsPresContext
* aPresContext
,
333 nsHTMLReflowMetrics
& aDesiredSize
,
334 const nsHTMLReflowState
& aReflowState
,
335 nsReflowStatus
& aStatus
)
337 NS_PRECONDITION(aReflowState
.ComputedWidth() != NS_UNCONSTRAINEDSIZE
,
338 "Must have a computed width");
342 mHasPendingInterruptAtStartOfReflow
= aPresContext
->HasPendingInterrupt();
344 // If all the content and frames are here
345 // then initialize it before reflow
346 if (mIsAllContentHere
&& !mHasBeenInitialized
) {
347 if (false == mIsAllFramesHere
) {
348 CheckIfAllFramesHere();
350 if (mIsAllFramesHere
&& !mHasBeenInitialized
) {
351 mHasBeenInitialized
= true;
355 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
) {
356 nsFormControlFrame::RegUnRegAccessKey(this, true);
359 if (IsInDropDownMode()) {
360 return ReflowAsDropdown(aPresContext
, aDesiredSize
, aReflowState
, aStatus
);
364 * Due to the fact that our intrinsic height depends on the heights of our
365 * kids, we end up having to do two-pass reflow, in general -- the first pass
366 * to find the intrinsic height and a second pass to reflow the scrollframe
367 * at that height (which will size the scrollbars correctly, etc).
369 * Naturaly, we want to avoid doing the second reflow as much as possible.
370 * We can skip it in the following cases (in all of which the first reflow is
371 * already happening at the right height):
373 * - We're reflowing with a constrained computed height -- just use that
375 * - We're not dirty and have no dirty kids and shouldn't be reflowing all
376 * kids. In this case, our cached max height of a child is not going to
378 * - We do our first reflow using our cached max height of a child, then
379 * compute the new max height and it's the same as the old one.
382 bool autoHeight
= (aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
);
384 mMightNeedSecondPass
= autoHeight
&&
385 (NS_SUBTREE_DIRTY(this) || aReflowState
.ShouldReflowAllKids());
387 nsHTMLReflowState
state(aReflowState
);
388 int32_t length
= GetNumberOfRows();
390 nscoord oldHeightOfARow
= HeightOfARow();
392 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
) && autoHeight
) {
393 // When not doing an initial reflow, and when the height is auto, start off
394 // with our computed height set to what we'd expect our height to be.
395 nscoord computedHeight
= CalcIntrinsicHeight(oldHeightOfARow
, length
);
396 computedHeight
= state
.ApplyMinMaxHeight(computedHeight
);
397 state
.SetComputedHeight(computedHeight
);
400 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
402 NS_ENSURE_SUCCESS(rv
, rv
);
404 if (!mMightNeedSecondPass
) {
405 NS_ASSERTION(!autoHeight
|| HeightOfARow() == oldHeightOfARow
,
406 "How did our height of a row change if nothing was dirty?");
407 NS_ASSERTION(!autoHeight
||
408 !(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
409 "How do we not need a second pass during initial reflow at "
411 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
412 "Shouldn't be suppressing if we don't need a second pass!");
414 // Update our mNumDisplayRows based on our new row height now that we
415 // know it. Note that if autoHeight and we landed in this code then we
416 // already set mNumDisplayRows in CalcIntrinsicHeight. Also note that we
417 // can't use HeightOfARow() here because that just uses a cached value
418 // that we didn't compute.
419 nscoord rowHeight
= CalcHeightOfARow();
420 if (rowHeight
== 0) {
421 // Just pick something
424 mNumDisplayRows
= std::max(1, state
.ComputedHeight() / rowHeight
);
431 mMightNeedSecondPass
= false;
433 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
434 // will have suppressed the scrollbar update.
435 if (!IsScrollbarUpdateSuppressed()) {
436 // All done. No need to do more reflow.
437 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
438 "Shouldn't be suppressing if the height of a row has not "
443 SetSuppressScrollbarUpdate(false);
445 // Gotta reflow again.
446 // XXXbz We're just changing the height here; do we need to dirty ourselves
447 // or anything like that? We might need to, per the letter of the reflow
448 // protocol, but things seem to work fine without it... Is that just an
449 // implementation detail of nsHTMLScrollFrame that we're depending on?
450 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
,
451 nsDidReflowStatus::FINISHED
);
453 // Now compute the height we want to have
454 nscoord computedHeight
= CalcIntrinsicHeight(HeightOfARow(), length
);
455 computedHeight
= state
.ApplyMinMaxHeight(computedHeight
);
456 state
.SetComputedHeight(computedHeight
);
458 nsHTMLScrollFrame::WillReflow(aPresContext
);
460 // XXXbz to make the ascent really correct, we should add our
461 // mComputedPadding.top to it (and subtract it from descent). Need that
462 // because nsGfxScrollFrame just adds in the border....
463 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
467 nsListControlFrame::ReflowAsDropdown(nsPresContext
* aPresContext
,
468 nsHTMLReflowMetrics
& aDesiredSize
,
469 const nsHTMLReflowState
& aReflowState
,
470 nsReflowStatus
& aStatus
)
472 NS_PRECONDITION(aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
,
473 "We should not have a computed height here!");
475 mMightNeedSecondPass
= NS_SUBTREE_DIRTY(this) ||
476 aReflowState
.ShouldReflowAllKids();
479 nscoord oldHeightOfARow
= HeightOfARow();
480 nscoord oldVisibleHeight
= (GetStateBits() & NS_FRAME_FIRST_REFLOW
) ?
481 NS_UNCONSTRAINEDSIZE
: GetScrolledFrame()->GetSize().height
;
484 nsHTMLReflowState
state(aReflowState
);
486 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
487 // When not doing an initial reflow, and when the height is auto, start off
488 // with our computed height set to what we'd expect our height to be.
489 // Note: At this point, mLastDropdownComputedHeight can be
490 // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain
491 // the height. That's fine; just do the same thing as last time.
492 state
.SetComputedHeight(mLastDropdownComputedHeight
);
495 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
497 NS_ENSURE_SUCCESS(rv
, rv
);
499 if (!mMightNeedSecondPass
) {
500 NS_ASSERTION(oldVisibleHeight
== GetScrolledFrame()->GetSize().height
,
501 "How did our kid's height change if nothing was dirty?");
502 NS_ASSERTION(HeightOfARow() == oldHeightOfARow
,
503 "How did our height of a row change if nothing was dirty?");
504 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
505 "Shouldn't be suppressing if we don't need a second pass!");
506 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
507 "How can we avoid a second pass during first reflow?");
511 mMightNeedSecondPass
= false;
513 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
514 // will have suppressed the scrollbar update.
515 if (!IsScrollbarUpdateSuppressed()) {
516 // All done. No need to do more reflow.
517 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
518 "How can we avoid a second pass during first reflow?");
522 SetSuppressScrollbarUpdate(false);
524 nscoord visibleHeight
= GetScrolledFrame()->GetSize().height
;
525 nscoord heightOfARow
= HeightOfARow();
527 // Gotta reflow again.
528 // XXXbz We're just changing the height here; do we need to dirty ourselves
529 // or anything like that? We might need to, per the letter of the reflow
530 // protocol, but things seem to work fine without it... Is that just an
531 // implementation detail of nsHTMLScrollFrame that we're depending on?
532 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
,
533 nsDidReflowStatus::FINISHED
);
535 // Now compute the height we want to have.
536 // Note: no need to apply min/max constraints, since we have no such
537 // rules applied to the combobox dropdown.
539 mDropdownCanGrow
= false;
540 if (visibleHeight
<= 0 || heightOfARow
<= 0) {
541 // Looks like we have no options. Just size us to a single row height.
542 state
.SetComputedHeight(heightOfARow
);
545 nsComboboxControlFrame
* combobox
= static_cast<nsComboboxControlFrame
*>(mComboboxFrame
);
547 nscoord above
, below
;
548 combobox
->GetAvailableDropdownSpace(&above
, &below
, &translation
);
549 if (above
<= 0 && below
<= 0) {
550 state
.SetComputedHeight(heightOfARow
);
552 mDropdownCanGrow
= GetNumberOfRows() > 1;
554 nscoord bp
= aReflowState
.ComputedPhysicalBorderPadding().TopBottom();
555 nscoord availableHeight
= std::max(above
, below
) - bp
;
558 if (visibleHeight
<= availableHeight
) {
559 // The dropdown fits in the available height.
560 rows
= GetNumberOfRows();
561 mNumDisplayRows
= clamped
<uint32_t>(rows
, 1, kMaxDropDownRows
);
562 if (mNumDisplayRows
== rows
) {
563 newHeight
= visibleHeight
; // use the exact height
565 newHeight
= mNumDisplayRows
* heightOfARow
; // approximate
568 rows
= availableHeight
/ heightOfARow
;
569 mNumDisplayRows
= clamped
<uint32_t>(rows
, 1, kMaxDropDownRows
);
570 newHeight
= mNumDisplayRows
* heightOfARow
; // approximate
572 state
.SetComputedHeight(newHeight
);
573 mDropdownCanGrow
= visibleHeight
- newHeight
>= heightOfARow
&&
574 mNumDisplayRows
!= kMaxDropDownRows
;
578 mLastDropdownComputedHeight
= state
.ComputedHeight();
580 nsHTMLScrollFrame::WillReflow(aPresContext
);
581 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
585 nsListControlFrame::GetScrollbarStyles() const
587 // We can't express this in the style system yet; when we can, this can go away
588 // and GetScrollbarStyles can be devirtualized
589 int32_t verticalStyle
= IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
590 : NS_STYLE_OVERFLOW_SCROLL
;
591 return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN
, verticalStyle
);
595 nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
597 return !IsInDropDownMode();
600 //---------------------------------------------------------
602 nsListControlFrame::GetContentInsertionFrame() {
603 return GetOptionsContainer()->GetContentInsertionFrame();
606 //---------------------------------------------------------
608 nsListControlFrame::ExtendedSelection(int32_t aStartIndex
,
612 return SetOptionsSelectedFromFrame(aStartIndex
, aEndIndex
,
616 //---------------------------------------------------------
618 nsListControlFrame::SingleSelection(int32_t aClickedIndex
, bool aDoToggle
)
620 if (mComboboxFrame
) {
621 mComboboxFrame
->UpdateRecentIndex(GetSelectedIndex());
624 bool wasChanged
= false;
625 // Get Current selection
627 wasChanged
= ToggleOptionSelectedFromFrame(aClickedIndex
);
629 wasChanged
= SetOptionsSelectedFromFrame(aClickedIndex
, aClickedIndex
,
632 nsWeakFrame
weakFrame(this);
633 ScrollToIndex(aClickedIndex
);
634 if (!weakFrame
.IsAlive()) {
639 bool isCurrentOptionChanged
= mEndSelectionIndex
!= aClickedIndex
;
641 mStartSelectionIndex
= aClickedIndex
;
642 mEndSelectionIndex
= aClickedIndex
;
646 if (isCurrentOptionChanged
) {
647 FireMenuItemActiveEvent();
655 nsListControlFrame::InitSelectionRange(int32_t aClickedIndex
)
658 // If nothing is selected, set the start selection depending on where
659 // the user clicked and what the initial selection is:
660 // - if the user clicked *before* selectedIndex, set the start index to
661 // the end of the first contiguous selection.
662 // - if the user clicked *after* the end of the first contiguous
663 // selection, set the start index to selectedIndex.
664 // - if the user clicked *within* the first contiguous selection, set the
665 // start index to selectedIndex.
666 // The last two rules, of course, boil down to the same thing: if the user
667 // clicked >= selectedIndex, return selectedIndex.
669 // This makes it so that shift click works properly when you first click
670 // in a multiple select.
672 int32_t selectedIndex
= GetSelectedIndex();
673 if (selectedIndex
>= 0) {
674 // Get the end of the contiguous selection
675 nsRefPtr
<dom::HTMLOptionsCollection
> options
= GetOptions();
676 NS_ASSERTION(options
, "Collection of options is null!");
677 uint32_t numOptions
= options
->Length();
678 // Push i to one past the last selected index in the group.
680 for (i
= selectedIndex
+ 1; i
< numOptions
; i
++) {
681 if (!options
->ItemAsOption(i
)->Selected()) {
686 if (aClickedIndex
< selectedIndex
) {
687 // User clicked before selection, so start selection at end of
688 // contiguous selection
689 mStartSelectionIndex
= i
-1;
690 mEndSelectionIndex
= selectedIndex
;
692 // User clicked after selection, so start selection at start of
693 // contiguous selection
694 mStartSelectionIndex
= selectedIndex
;
695 mEndSelectionIndex
= i
-1;
701 CountOptionsAndOptgroups(nsIFrame
* aFrame
)
704 nsFrameList::Enumerator
e(aFrame
->PrincipalChildList());
705 for (; !e
.AtEnd(); e
.Next()) {
706 nsIFrame
* child
= e
.get();
707 nsIContent
* content
= child
->GetContent();
709 if (content
->IsHTML(nsGkAtoms::option
)) {
712 nsCOMPtr
<nsIDOMHTMLOptGroupElement
> optgroup
= do_QueryInterface(content
);
715 optgroup
->GetLabel(label
);
716 if (label
.Length() > 0) {
719 count
+= CountOptionsAndOptgroups(child
);
728 nsListControlFrame::GetNumberOfRows()
730 return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
733 //---------------------------------------------------------
735 nsListControlFrame::PerformSelection(int32_t aClickedIndex
,
739 bool wasChanged
= false;
741 if (aClickedIndex
== kNothingSelected
) {
743 else if (GetMultiple()) {
745 // Make sure shift+click actually does something expected when
746 // the user has never clicked on the select
747 if (mStartSelectionIndex
== kNothingSelected
) {
748 InitSelectionRange(aClickedIndex
);
751 // Get the range from beginning (low) to end (high)
752 // Shift *always* works, even if the current option is disabled
755 if (mStartSelectionIndex
== kNothingSelected
) {
756 startIndex
= aClickedIndex
;
757 endIndex
= aClickedIndex
;
758 } else if (mStartSelectionIndex
<= aClickedIndex
) {
759 startIndex
= mStartSelectionIndex
;
760 endIndex
= aClickedIndex
;
762 startIndex
= aClickedIndex
;
763 endIndex
= mStartSelectionIndex
;
766 // Clear only if control was not pressed
767 wasChanged
= ExtendedSelection(startIndex
, endIndex
, !aIsControl
);
768 nsWeakFrame
weakFrame(this);
769 ScrollToIndex(aClickedIndex
);
770 if (!weakFrame
.IsAlive()) {
774 if (mStartSelectionIndex
== kNothingSelected
) {
775 mStartSelectionIndex
= aClickedIndex
;
778 bool isCurrentOptionChanged
= mEndSelectionIndex
!= aClickedIndex
;
780 mEndSelectionIndex
= aClickedIndex
;
784 if (isCurrentOptionChanged
) {
785 FireMenuItemActiveEvent();
788 } else if (aIsControl
) {
789 wasChanged
= SingleSelection(aClickedIndex
, true); // might destroy us
791 wasChanged
= SingleSelection(aClickedIndex
, false); // might destroy us
794 wasChanged
= SingleSelection(aClickedIndex
, false); // might destroy us
800 //---------------------------------------------------------
802 nsListControlFrame::HandleListSelection(nsIDOMEvent
* aEvent
,
803 int32_t aClickedIndex
)
805 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
809 mouseEvent
->GetMetaKey(&isControl
);
811 mouseEvent
->GetCtrlKey(&isControl
);
813 mouseEvent
->GetShiftKey(&isShift
);
814 return PerformSelection(aClickedIndex
, isShift
, isControl
); // might destroy us
817 //---------------------------------------------------------
819 nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents
)
821 // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
822 // so we never want to do mouse capturing. Note that we only bail if the list
823 // is in drop-down mode, and the caller is requesting capture (we let release capture
824 // requests go through to ensure that we can release capture requested via other
825 // code paths, if any exist).
826 if (aGrabMouseEvents
&& IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
829 if (aGrabMouseEvents
) {
830 nsIPresShell::SetCapturingContent(mContent
, CAPTURE_IGNOREALLOWED
);
832 nsIContent
* capturingContent
= nsIPresShell::GetCapturingContent();
834 bool dropDownIsHidden
= false;
835 if (IsInDropDownMode()) {
836 dropDownIsHidden
= !mComboboxFrame
->IsDroppedDown();
838 if (capturingContent
== mContent
|| dropDownIsHidden
) {
839 // only clear the capturing content if *we* are the ones doing the
840 // capturing (or if the dropdown is hidden, in which case NO-ONE should
841 // be capturing anything - it could be a scrollbar inside this listbox
842 // which is actually grabbing
843 // This shouldn't be necessary. We should simply ensure that events targeting
844 // scrollbars are never visible to DOM consumers.
845 nsIPresShell::SetCapturingContent(nullptr, 0);
850 //---------------------------------------------------------
852 nsListControlFrame::HandleEvent(nsPresContext
* aPresContext
,
853 WidgetGUIEvent
* aEvent
,
854 nsEventStatus
* aEventStatus
)
856 NS_ENSURE_ARG_POINTER(aEventStatus
);
858 /*const char * desc[] = {"NS_MOUSE_MOVE",
859 "NS_MOUSE_LEFT_BUTTON_UP",
860 "NS_MOUSE_LEFT_BUTTON_DOWN",
861 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
862 "NS_MOUSE_MIDDLE_BUTTON_UP",
863 "NS_MOUSE_MIDDLE_BUTTON_DOWN",
864 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
865 "NS_MOUSE_RIGHT_BUTTON_UP",
866 "NS_MOUSE_RIGHT_BUTTON_DOWN",
867 "NS_MOUSE_ENTER_SYNTH",
868 "NS_MOUSE_EXIT_SYNTH",
869 "NS_MOUSE_LEFT_DOUBLECLICK",
870 "NS_MOUSE_MIDDLE_DOUBLECLICK",
871 "NS_MOUSE_RIGHT_DOUBLECLICK",
872 "NS_MOUSE_LEFT_CLICK",
873 "NS_MOUSE_MIDDLE_CLICK",
874 "NS_MOUSE_RIGHT_CLICK"};
875 int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
876 if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
877 printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
879 printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
882 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
)
885 // do we have style that affects how we are selected?
886 // do we have user-input style?
887 const nsStyleUserInterface
* uiStyle
= StyleUserInterface();
888 if (uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_NONE
|| uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
)
889 return nsFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
891 nsEventStates eventStates
= mContent
->AsElement()->State();
892 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
))
895 return nsHTMLScrollFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
899 //---------------------------------------------------------
901 nsListControlFrame::SetInitialChildList(ChildListID aListID
,
902 nsFrameList
& aChildList
)
904 // First check to see if all the content has been added
905 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
906 if (!mIsAllContentHere
) {
907 mIsAllFramesHere
= false;
908 mHasBeenInitialized
= false;
910 nsresult rv
= nsHTMLScrollFrame::SetInitialChildList(aListID
, aChildList
);
912 // If all the content is here now check
913 // to see if all the frames have been created
914 /*if (mIsAllContentHere) {
915 // If all content and frames are here
916 // the reset/initialize
917 if (CheckIfAllFramesHere()) {
918 ResetList(aPresContext);
919 mHasBeenInitialized = true;
926 //---------------------------------------------------------
928 nsListControlFrame::Init(nsIContent
* aContent
,
930 nsIFrame
* aPrevInFlow
)
932 nsHTMLScrollFrame::Init(aContent
, aParent
, aPrevInFlow
);
934 // we shouldn't have to unregister this listener because when
935 // our frame goes away all these content node go away as well
936 // because our frame is the only one who references them.
937 // we need to hook up our listeners before the editor is initialized
938 mEventListener
= new nsListEventListener(this);
940 mContent
->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
941 mEventListener
, false, false);
942 mContent
->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
943 mEventListener
, false, false);
944 mContent
->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
945 mEventListener
, false, false);
946 mContent
->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
947 mEventListener
, false, false);
948 mContent
->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
949 mEventListener
, false, false);
951 mStartSelectionIndex
= kNothingSelected
;
952 mEndSelectionIndex
= kNothingSelected
;
954 mLastDropdownBackstopColor
= PresContext()->DefaultBackgroundColor();
956 if (IsInDropDownMode()) {
957 AddStateBits(NS_FRAME_IN_POPUP
);
961 dom::HTMLOptionsCollection
*
962 nsListControlFrame::GetOptions() const
964 dom::HTMLSelectElement
* select
=
965 dom::HTMLSelectElement::FromContentOrNull(mContent
);
966 NS_ENSURE_TRUE(select
, nullptr);
968 return select
->Options();
971 dom::HTMLOptionElement
*
972 nsListControlFrame::GetOption(uint32_t aIndex
) const
974 dom::HTMLSelectElement
* select
=
975 dom::HTMLSelectElement::FromContentOrNull(mContent
);
976 NS_ENSURE_TRUE(select
, nullptr);
978 return select
->Item(aIndex
);
982 nsListControlFrame::OnOptionSelected(int32_t aIndex
, bool aSelected
)
985 ScrollToIndex(aIndex
);
991 nsListControlFrame::OnContentReset()
997 nsListControlFrame::ResetList(bool aAllowScrolling
)
999 // if all the frames aren't here
1000 // don't bother reseting
1001 if (!mIsAllFramesHere
) {
1005 if (aAllowScrolling
) {
1006 mPostChildrenLoadedReset
= true;
1008 // Scroll to the selected index
1009 int32_t indexToSelect
= kNothingSelected
;
1011 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement(do_QueryInterface(mContent
));
1012 NS_ASSERTION(selectElement
, "No select element!");
1013 if (selectElement
) {
1014 selectElement
->GetSelectedIndex(&indexToSelect
);
1015 nsWeakFrame
weakFrame(this);
1016 ScrollToIndex(indexToSelect
);
1017 if (!weakFrame
.IsAlive()) {
1023 mStartSelectionIndex
= kNothingSelected
;
1024 mEndSelectionIndex
= kNothingSelected
;
1026 // Combobox will redisplay itself with the OnOptionSelected event
1030 nsListControlFrame::SetFocus(bool aOn
, bool aRepaint
)
1044 void nsListControlFrame::ComboboxFocusSet()
1050 nsListControlFrame::SetComboboxFrame(nsIFrame
* aComboboxFrame
)
1052 if (nullptr != aComboboxFrame
) {
1053 mComboboxFrame
= do_QueryFrame(aComboboxFrame
);
1058 nsListControlFrame::GetOptionText(uint32_t aIndex
, nsAString
& aStr
)
1061 if (dom::HTMLOptionElement
* optionElement
= GetOption(aIndex
)) {
1062 optionElement
->GetText(aStr
);
1067 nsListControlFrame::GetSelectedIndex()
1069 dom::HTMLSelectElement
* select
=
1070 dom::HTMLSelectElement::FromContentOrNull(mContent
);
1071 return select
->SelectedIndex();
1074 dom::HTMLOptionElement
*
1075 nsListControlFrame::GetCurrentOption()
1077 // The mEndSelectionIndex is what is currently being selected. Use
1078 // the selected index if this is kNothingSelected.
1079 int32_t focusedIndex
= (mEndSelectionIndex
== kNothingSelected
) ?
1080 GetSelectedIndex() : mEndSelectionIndex
;
1082 if (focusedIndex
!= kNothingSelected
) {
1083 return GetOption(SafeCast
<uint32_t>(focusedIndex
));
1086 // There is no selected item. Return the first non-disabled item.
1087 nsRefPtr
<dom::HTMLSelectElement
> selectElement
=
1088 dom::HTMLSelectElement::FromContent(mContent
);
1090 for (uint32_t i
= 0, length
= selectElement
->Length(); i
< length
; ++i
) {
1091 dom::HTMLOptionElement
* node
= selectElement
->Item(i
);
1096 if (!selectElement
->IsOptionDisabled(node
)) {
1105 nsListControlFrame::IsInDropDownMode() const
1107 return (mComboboxFrame
!= nullptr);
1111 nsListControlFrame::GetNumberOfOptions()
1113 dom::HTMLOptionsCollection
* options
= GetOptions();
1118 return options
->Length();
1121 //----------------------------------------------------------------------
1122 // nsISelectControlFrame
1123 //----------------------------------------------------------------------
1124 bool nsListControlFrame::CheckIfAllFramesHere()
1126 // Get the number of optgroups and options
1127 //int32_t numContentItems = 0;
1128 nsCOMPtr
<nsIDOMNode
> node(do_QueryInterface(mContent
));
1130 // XXX Need to find a fail proff way to determine that
1131 // all the frames are there
1132 mIsAllFramesHere
= true;//NS_OK == CountAllChild(node, numContentItems);
1134 // now make sure we have a frame each piece of content
1136 return mIsAllFramesHere
;
1140 nsListControlFrame::DoneAddingChildren(bool aIsDone
)
1142 mIsAllContentHere
= aIsDone
;
1143 if (mIsAllContentHere
) {
1144 // Here we check to see if all the frames have been created
1145 // for all the content.
1146 // If so, then we can initialize;
1147 if (!mIsAllFramesHere
) {
1148 // if all the frames are now present we can initialize
1149 if (CheckIfAllFramesHere()) {
1150 mHasBeenInitialized
= true;
1159 nsListControlFrame::AddOption(int32_t aIndex
)
1161 #ifdef DO_REFLOW_DEBUG
1162 printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId
, this, aIndex
);
1165 if (!mIsAllContentHere
) {
1166 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
1167 if (!mIsAllContentHere
) {
1168 mIsAllFramesHere
= false;
1169 mHasBeenInitialized
= false;
1171 mIsAllFramesHere
= (aIndex
== static_cast<int32_t>(GetNumberOfOptions()-1));
1175 // Make sure we scroll to the selected option as needed
1176 mNeedToReset
= true;
1178 if (!mHasBeenInitialized
) {
1182 mPostChildrenLoadedReset
= mIsAllContentHere
;
1187 DecrementAndClamp(int32_t aSelectionIndex
, int32_t aLength
)
1189 return aLength
== 0 ? kNothingSelected
: std::max(0, aSelectionIndex
- 1);
1193 nsListControlFrame::RemoveOption(int32_t aIndex
)
1195 NS_PRECONDITION(aIndex
>= 0, "negative <option> index");
1197 // Need to reset if we're a dropdown
1198 if (IsInDropDownMode()) {
1199 mNeedToReset
= true;
1200 mPostChildrenLoadedReset
= mIsAllContentHere
;
1203 if (mStartSelectionIndex
!= kNothingSelected
) {
1204 NS_ASSERTION(mEndSelectionIndex
!= kNothingSelected
, "");
1205 int32_t numOptions
= GetNumberOfOptions();
1206 // NOTE: numOptions is the new number of options whereas aIndex is the
1207 // unadjusted index of the removed option (hence the <= below).
1208 NS_ASSERTION(aIndex
<= numOptions
, "out-of-bounds <option> index");
1210 int32_t forward
= mEndSelectionIndex
- mStartSelectionIndex
;
1211 int32_t* low
= forward
>= 0 ? &mStartSelectionIndex
: &mEndSelectionIndex
;
1212 int32_t* high
= forward
>= 0 ? &mEndSelectionIndex
: &mStartSelectionIndex
;
1214 *low
= ::DecrementAndClamp(*low
, numOptions
);
1215 if (aIndex
<= *high
)
1216 *high
= ::DecrementAndClamp(*high
, numOptions
);
1221 NS_ASSERTION(mEndSelectionIndex
== kNothingSelected
, "");
1227 //---------------------------------------------------------
1228 // Set the option selected in the DOM. This method is named
1229 // as it is because it indicates that the frame is the source
1230 // of this event rather than the receiver.
1232 nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex
,
1237 nsRefPtr
<dom::HTMLSelectElement
> selectElement
=
1238 dom::HTMLSelectElement::FromContent(mContent
);
1240 uint32_t mask
= dom::HTMLSelectElement::NOTIFY
;
1242 mask
|= dom::HTMLSelectElement::IS_SELECTED
;
1246 mask
|= dom::HTMLSelectElement::CLEAR_ALL
;
1249 return selectElement
->SetOptionsSelectedByIndex(aStartIndex
, aEndIndex
, mask
);
1253 nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex
)
1255 nsRefPtr
<dom::HTMLOptionElement
> option
=
1256 GetOption(static_cast<uint32_t>(aIndex
));
1257 NS_ENSURE_TRUE(option
, false);
1259 nsRefPtr
<dom::HTMLSelectElement
> selectElement
=
1260 dom::HTMLSelectElement::FromContent(mContent
);
1262 uint32_t mask
= dom::HTMLSelectElement::NOTIFY
;
1263 if (!option
->Selected()) {
1264 mask
|= dom::HTMLSelectElement::IS_SELECTED
;
1267 return selectElement
->SetOptionsSelectedByIndex(aIndex
, aIndex
, mask
);
1271 // Dispatch event and such
1273 nsListControlFrame::UpdateSelection()
1275 if (mIsAllFramesHere
) {
1276 // if it's a combobox, display the new text
1277 nsWeakFrame
weakFrame(this);
1278 if (mComboboxFrame
) {
1279 mComboboxFrame
->RedisplaySelectedText();
1281 // if it's a listbox, fire on change
1282 else if (mIsAllContentHere
) {
1285 return weakFrame
.IsAlive();
1291 nsListControlFrame::ComboboxFinish(int32_t aIndex
)
1295 if (mComboboxFrame
) {
1296 nsWeakFrame
weakFrame(this);
1297 PerformSelection(aIndex
, false, false); // might destroy us
1298 if (!weakFrame
.IsAlive() || !mComboboxFrame
) {
1302 int32_t displayIndex
= mComboboxFrame
->GetIndexOfDisplayArea();
1303 if (displayIndex
!= aIndex
) {
1304 mComboboxFrame
->RedisplaySelectedText(); // might destroy us
1307 if (weakFrame
.IsAlive() && mComboboxFrame
) {
1308 mComboboxFrame
->RollupFromList(); // might destroy us
1313 // Send out an onchange notification.
1315 nsListControlFrame::FireOnChange()
1317 if (mComboboxFrame
) {
1318 // Return hit without changing anything
1319 int32_t index
= mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1320 if (index
== NS_SKIP_NOTIFY_INDEX
)
1323 // See if the selection actually changed
1324 if (index
== GetSelectedIndex())
1328 // Dispatch the change event.
1329 nsContentUtils::DispatchTrustedEvent(mContent
->OwnerDoc(), mContent
,
1330 NS_LITERAL_STRING("change"), true,
1335 nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex
, int32_t aNewIndex
)
1337 if (mComboboxFrame
) {
1338 // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
1339 // event for this setting of selectedIndex.
1340 mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1343 nsWeakFrame
weakFrame(this);
1344 ScrollToIndex(aNewIndex
);
1345 if (!weakFrame
.IsAlive()) {
1348 mStartSelectionIndex
= aNewIndex
;
1349 mEndSelectionIndex
= aNewIndex
;
1352 #ifdef ACCESSIBILITY
1353 FireMenuItemActiveEvent();
1359 //----------------------------------------------------------------------
1360 // End nsISelectControlFrame
1361 //----------------------------------------------------------------------
1364 nsListControlFrame::SetFormProperty(nsIAtom
* aName
,
1365 const nsAString
& aValue
)
1367 if (nsGkAtoms::selected
== aName
) {
1368 return NS_ERROR_INVALID_ARG
; // Selected is readonly according to spec.
1369 } else if (nsGkAtoms::selectedindex
== aName
) {
1370 // You shouldn't be calling me for this!!!
1371 return NS_ERROR_INVALID_ARG
;
1374 // We should be told about selectedIndex by the DOM element through
1381 nsListControlFrame::AboutToDropDown()
1383 NS_ASSERTION(IsInDropDownMode(),
1384 "AboutToDropDown called without being in dropdown mode");
1386 // Our widget doesn't get invalidated on changes to the rest of the document,
1387 // so compute and store this color at the start of a dropdown so we don't
1388 // get weird painting behaviour.
1389 // We start looking for backgrounds above the combobox frame to avoid
1390 // duplicating the combobox frame's background and compose each background
1391 // color we find underneath until we have an opaque color, or run out of
1392 // backgrounds. We compose with the PresContext default background color,
1393 // which is always opaque, in case we don't end up with an opaque color.
1394 // This gives us a very poor approximation of translucency.
1395 nsIFrame
* comboboxFrame
= do_QueryFrame(mComboboxFrame
);
1396 nsStyleContext
* context
= comboboxFrame
->StyleContext()->GetParent();
1397 mLastDropdownBackstopColor
= NS_RGBA(0,0,0,0);
1398 while (NS_GET_A(mLastDropdownBackstopColor
) < 255 && context
) {
1399 mLastDropdownBackstopColor
=
1400 NS_ComposeColors(context
->StyleBackground()->mBackgroundColor
,
1401 mLastDropdownBackstopColor
);
1402 context
= context
->GetParent();
1404 mLastDropdownBackstopColor
=
1405 NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
1406 mLastDropdownBackstopColor
);
1408 if (mIsAllContentHere
&& mIsAllFramesHere
&& mHasBeenInitialized
) {
1409 nsWeakFrame
weakFrame(this);
1410 ScrollToIndex(GetSelectedIndex());
1411 if (!weakFrame
.IsAlive()) {
1414 #ifdef ACCESSIBILITY
1415 FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1418 mItemSelectionStarted
= false;
1421 // We are about to be rolledup from the outside (ComboboxFrame)
1423 nsListControlFrame::AboutToRollup()
1425 // We've been updating the combobox with the keyboard up until now, but not
1426 // with the mouse. The problem is, even with mouse selection, we are
1427 // updating the <select>. So if the mouse goes over an option just before
1428 // he leaves the box and clicks, that's what the <select> will show.
1430 // To deal with this we say "whatever is in the combobox is canonical."
1431 // - IF the combobox is different from the current selected index, we
1434 if (IsInDropDownMode()) {
1435 ComboboxFinish(mComboboxFrame
->GetIndexOfDisplayArea()); // might destroy us
1440 nsListControlFrame::DidReflow(nsPresContext
* aPresContext
,
1441 const nsHTMLReflowState
* aReflowState
,
1442 nsDidReflowStatus aStatus
)
1445 bool wasInterrupted
= !mHasPendingInterruptAtStartOfReflow
&&
1446 aPresContext
->HasPendingInterrupt();
1448 rv
= nsHTMLScrollFrame::DidReflow(aPresContext
, aReflowState
, aStatus
);
1450 if (mNeedToReset
&& !wasInterrupted
) {
1451 mNeedToReset
= false;
1452 // Suppress scrolling to the selected element if we restored
1453 // scroll history state AND the list contents have not changed
1454 // since we loaded all the children AND nothing else forced us
1455 // to scroll by calling ResetList(true). The latter two conditions
1456 // are folded into mPostChildrenLoadedReset.
1458 // The idea is that we want scroll history restoration to trump ResetList
1459 // scrolling to the selected element, when the ResetList was probably only
1460 // caused by content loading normally.
1461 ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset
);
1464 mHasPendingInterruptAtStartOfReflow
= false;
1469 nsListControlFrame::GetType() const
1471 return nsGkAtoms::listControlFrame
;
1474 #ifdef DEBUG_FRAME_DUMP
1476 nsListControlFrame::GetFrameName(nsAString
& aResult
) const
1478 return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult
);
1483 nsListControlFrame::GetHeightOfARow()
1485 return HeightOfARow();
1489 nsListControlFrame::IsOptionDisabled(int32_t anIndex
, bool &aIsDisabled
)
1491 nsRefPtr
<dom::HTMLSelectElement
> sel
=
1492 dom::HTMLSelectElement::FromContent(mContent
);
1494 sel
->IsOptionDisabled(anIndex
, &aIsDisabled
);
1497 return NS_ERROR_FAILURE
;
1500 //----------------------------------------------------------------------
1502 //----------------------------------------------------------------------
1504 nsListControlFrame::IsLeftButton(nsIDOMEvent
* aMouseEvent
)
1506 // only allow selection with the left button
1507 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1509 uint16_t whichButton
;
1510 if (NS_SUCCEEDED(mouseEvent
->GetButton(&whichButton
))) {
1511 return whichButton
!= 0?false:true;
1518 nsListControlFrame::CalcFallbackRowHeight(float aFontSizeInflation
)
1520 nscoord rowHeight
= 0;
1522 nsRefPtr
<nsFontMetrics
> fontMet
;
1523 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet
),
1524 aFontSizeInflation
);
1526 rowHeight
= fontMet
->MaxHeight();
1533 nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow
,
1534 int32_t aNumberOfOptions
)
1536 NS_PRECONDITION(!IsInDropDownMode(),
1537 "Shouldn't be in dropdown mode when we call this");
1539 dom::HTMLSelectElement
* select
=
1540 dom::HTMLSelectElement::FromContentOrNull(mContent
);
1542 mNumDisplayRows
= select
->Size();
1544 mNumDisplayRows
= 1;
1547 if (mNumDisplayRows
< 1) {
1548 mNumDisplayRows
= 4;
1551 return mNumDisplayRows
* aHeightOfARow
;
1554 //----------------------------------------------------------------------
1555 // nsIDOMMouseListener
1556 //----------------------------------------------------------------------
1558 nsListControlFrame::MouseUp(nsIDOMEvent
* aMouseEvent
)
1560 NS_ASSERTION(aMouseEvent
!= nullptr, "aMouseEvent is null.");
1562 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1563 NS_ENSURE_TRUE(mouseEvent
, NS_ERROR_FAILURE
);
1565 UpdateInListState(aMouseEvent
);
1567 mButtonDown
= false;
1569 nsEventStates eventStates
= mContent
->AsElement()->State();
1570 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
1574 // only allow selection with the left button
1575 // if a right button click is on the combobox itself
1576 // or on the select when in listbox mode, then let the click through
1577 if (!IsLeftButton(aMouseEvent
)) {
1578 if (IsInDropDownMode()) {
1579 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
1580 aMouseEvent
->PreventDefault();
1581 aMouseEvent
->StopPropagation();
1583 CaptureMouseEvents(false);
1586 CaptureMouseEvents(false);
1587 return NS_ERROR_FAILURE
; // means consume event
1589 CaptureMouseEvents(false);
1594 const nsStyleVisibility
* vis
= StyleVisibility();
1596 if (!vis
->IsVisible()) {
1600 if (IsInDropDownMode()) {
1601 // XXX This is a bit of a hack, but.....
1602 // But the idea here is to make sure you get an "onclick" event when you mouse
1603 // down on the select and the drag over an option and let go
1604 // And then NOT get an "onclick" event when when you click down on the select
1605 // and then up outside of the select
1606 // the EventStateManager tracks the content of the mouse down and the mouse up
1607 // to make sure they are the same, and the onclick is sent in the PostHandleEvent
1608 // depeneding on whether the clickCount is non-zero.
1609 // So we cheat here by either setting or unsetting the clcikCount in the native event
1610 // so the right thing happens for the onclick event
1611 WidgetMouseEvent
* mouseEvent
=
1612 aMouseEvent
->GetInternalNSEvent()->AsMouseEvent();
1614 int32_t selectedIndex
;
1615 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
1616 // If it's disabled, disallow the click and leave.
1617 bool isDisabled
= false;
1618 IsOptionDisabled(selectedIndex
, isDisabled
);
1620 aMouseEvent
->PreventDefault();
1621 aMouseEvent
->StopPropagation();
1622 CaptureMouseEvents(false);
1623 return NS_ERROR_FAILURE
;
1626 if (kNothingSelected
!= selectedIndex
) {
1627 nsWeakFrame
weakFrame(this);
1628 ComboboxFinish(selectedIndex
);
1629 if (!weakFrame
.IsAlive())
1634 mouseEvent
->clickCount
= 1;
1636 // the click was out side of the select or its dropdown
1637 mouseEvent
->clickCount
= IgnoreMouseEventForSelection(aMouseEvent
) ? 1 : 0;
1640 CaptureMouseEvents(false);
1642 if (mChangesSinceDragStart
) {
1643 // reset this so that future MouseUps without a prior MouseDown
1644 // won't fire onchange
1645 mChangesSinceDragStart
= false;
1654 nsListControlFrame::UpdateInListState(nsIDOMEvent
* aEvent
)
1656 if (!mComboboxFrame
|| !mComboboxFrame
->IsDroppedDown())
1659 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent
, this);
1660 nsRect borderInnerEdge
= GetScrollPortRect();
1661 if (pt
.y
>= borderInnerEdge
.y
&& pt
.y
< borderInnerEdge
.YMost()) {
1662 mItemSelectionStarted
= true;
1666 bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent
* aEvent
)
1668 if (!mComboboxFrame
)
1671 // Our DOM listener does get called when the dropdown is not
1672 // showing, because it listens to events on the SELECT element
1673 if (!mComboboxFrame
->IsDroppedDown())
1676 return !mItemSelectionStarted
;
1679 #ifdef ACCESSIBILITY
1681 nsListControlFrame::FireMenuItemActiveEvent()
1683 if (mFocused
!= this && !IsInDropDownMode()) {
1687 nsCOMPtr
<nsIContent
> optionContent
= GetCurrentOption();
1688 if (!optionContent
) {
1692 FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent
);
1697 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent
* aMouseEvent
,
1700 if (IgnoreMouseEventForSelection(aMouseEvent
))
1701 return NS_ERROR_FAILURE
;
1703 if (nsIPresShell::GetCapturingContent() != mContent
) {
1704 // If we're not capturing, then ignore movement in the border
1705 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
1706 nsRect borderInnerEdge
= GetScrollPortRect();
1707 if (!borderInnerEdge
.Contains(pt
)) {
1708 return NS_ERROR_FAILURE
;
1712 nsRefPtr
<dom::HTMLOptionElement
> option
;
1713 for (nsCOMPtr
<nsIContent
> content
=
1714 PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
1716 content
= content
->GetParent()) {
1717 option
= dom::HTMLOptionElement::FromContent(content
);
1721 aCurIndex
= option
->Index();
1722 MOZ_ASSERT(aCurIndex
>= 0);
1726 int32_t numOptions
= GetNumberOfOptions();
1728 return NS_ERROR_FAILURE
;
1730 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
1732 // If the event coordinate is above the first option frame, then target the
1733 // first option frame
1734 nsRefPtr
<dom::HTMLOptionElement
> firstOption
= GetOption(0);
1735 NS_ASSERTION(firstOption
, "Can't find first option that's supposed to be there");
1736 nsIFrame
* optionFrame
= firstOption
->GetPrimaryFrame();
1738 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
1739 if (ptInOptionFrame
.y
< 0 && ptInOptionFrame
.x
>= 0 &&
1740 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
1746 nsRefPtr
<dom::HTMLOptionElement
> lastOption
= GetOption(numOptions
- 1);
1747 // If the event coordinate is below the last option frame, then target the
1748 // last option frame
1749 NS_ASSERTION(lastOption
, "Can't find last option that's supposed to be there");
1750 optionFrame
= lastOption
->GetPrimaryFrame();
1752 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
1753 if (ptInOptionFrame
.y
>= optionFrame
->GetSize().height
&& ptInOptionFrame
.x
>= 0 &&
1754 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
1755 aCurIndex
= numOptions
- 1;
1760 return NS_ERROR_FAILURE
;
1764 nsListControlFrame::MouseDown(nsIDOMEvent
* aMouseEvent
)
1766 NS_ASSERTION(aMouseEvent
!= nullptr, "aMouseEvent is null.");
1768 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1769 NS_ENSURE_TRUE(mouseEvent
, NS_ERROR_FAILURE
);
1771 UpdateInListState(aMouseEvent
);
1773 nsEventStates eventStates
= mContent
->AsElement()->State();
1774 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
1778 // only allow selection with the left button
1779 // if a right button click is on the combobox itself
1780 // or on the select when in listbox mode, then let the click through
1781 if (!IsLeftButton(aMouseEvent
)) {
1782 if (IsInDropDownMode()) {
1783 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
1784 aMouseEvent
->PreventDefault();
1785 aMouseEvent
->StopPropagation();
1789 return NS_ERROR_FAILURE
; // means consume event
1795 int32_t selectedIndex
;
1796 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
1799 CaptureMouseEvents(true);
1800 nsWeakFrame
weakFrame(this);
1802 HandleListSelection(aMouseEvent
, selectedIndex
); // might destroy us
1803 if (!weakFrame
.IsAlive()) {
1806 mChangesSinceDragStart
= change
;
1808 // NOTE: the combo box is responsible for dropping it down
1809 if (mComboboxFrame
) {
1810 if (XRE_GetProcessType() == GeckoProcessType_Content
&& BrowserTabsRemote()) {
1811 nsContentUtils::DispatchChromeEvent(mContent
->OwnerDoc(), mContent
,
1812 NS_LITERAL_STRING("mozshowdropdown"), true,
1817 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
1821 if (!nsComboboxControlFrame::ToolkitHasNativePopup())
1823 bool isDroppedDown
= mComboboxFrame
->IsDroppedDown();
1824 nsIFrame
* comboFrame
= do_QueryFrame(mComboboxFrame
);
1825 nsWeakFrame
weakFrame(comboFrame
);
1826 mComboboxFrame
->ShowDropDown(!isDroppedDown
);
1827 if (!weakFrame
.IsAlive())
1829 if (isDroppedDown
) {
1830 CaptureMouseEvents(false);
1839 //----------------------------------------------------------------------
1840 // nsIDOMMouseMotionListener
1841 //----------------------------------------------------------------------
1843 nsListControlFrame::MouseMove(nsIDOMEvent
* aMouseEvent
)
1845 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
1846 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1847 NS_ENSURE_TRUE(mouseEvent
, NS_ERROR_FAILURE
);
1849 UpdateInListState(aMouseEvent
);
1851 if (IsInDropDownMode()) {
1852 if (mComboboxFrame
->IsDroppedDown()) {
1853 int32_t selectedIndex
;
1854 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
1855 PerformSelection(selectedIndex
, false, false); // might destroy us
1858 } else {// XXX - temporary until we get drag events
1860 return DragMove(aMouseEvent
); // might destroy us
1867 nsListControlFrame::DragMove(nsIDOMEvent
* aMouseEvent
)
1869 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
1871 UpdateInListState(aMouseEvent
);
1873 if (!IsInDropDownMode()) {
1874 int32_t selectedIndex
;
1875 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
1876 // Don't waste cycles if we already dragged over this item
1877 if (selectedIndex
== mEndSelectionIndex
) {
1880 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1881 NS_ASSERTION(mouseEvent
, "aMouseEvent is not an nsIDOMMouseEvent!");
1884 mouseEvent
->GetMetaKey(&isControl
);
1886 mouseEvent
->GetCtrlKey(&isControl
);
1888 nsWeakFrame
weakFrame(this);
1889 // Turn SHIFT on when you are dragging, unless control is on.
1890 bool wasChanged
= PerformSelection(selectedIndex
,
1891 !isControl
, isControl
);
1892 if (!weakFrame
.IsAlive()) {
1895 mChangesSinceDragStart
= mChangesSinceDragStart
|| wasChanged
;
1901 //----------------------------------------------------------------------
1903 //----------------------------------------------------------------------
1905 nsListControlFrame::ScrollToIndex(int32_t aIndex
)
1908 // XXX shouldn't we just do nothing if we're asked to scroll to
1909 // kNothingSelected?
1910 ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT
);
1912 nsRefPtr
<dom::HTMLOptionElement
> option
=
1913 GetOption(SafeCast
<uint32_t>(aIndex
));
1915 ScrollToFrame(*option
);
1921 nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement
& aOptElement
)
1923 // otherwise we find the content's frame and scroll to it
1924 nsIFrame
* childFrame
= aOptElement
.GetPrimaryFrame();
1926 PresContext()->PresShell()->
1927 ScrollFrameRectIntoView(childFrame
,
1928 nsRect(nsPoint(0, 0), childFrame
->GetSize()),
1929 nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
1930 nsIPresShell::SCROLL_OVERFLOW_HIDDEN
|
1931 nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY
);
1935 //---------------------------------------------------------------------
1936 // Ok, the entire idea of this routine is to move to the next item that
1937 // is suppose to be selected. If the item is disabled then we search in
1938 // the same direction looking for the next item to select. If we run off
1939 // the end of the list then we start at the end of the list and search
1940 // backwards until we get back to the original item or an enabled option
1942 // aStartIndex - the index to start searching from
1943 // aNewIndex - will get set to the new index if it finds one
1944 // aNumOptions - the total number of options in the list
1945 // aDoAdjustInc - the initial increment 1-n
1946 // aDoAdjustIncNext - the increment used to search for the next enabled option
1948 // the aDoAdjustInc could be a "1" for a single item or
1949 // any number greater representing a page of items
1952 nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex
,
1954 int32_t aNumOptions
,
1955 int32_t aDoAdjustInc
,
1956 int32_t aDoAdjustIncNext
)
1958 // Cannot select anything if there is nothing to select
1959 if (aNumOptions
== 0) {
1960 aNewIndex
= kNothingSelected
;
1964 // means we reached the end of the list and now we are searching backwards
1965 bool doingReverse
= false;
1966 // lowest index in the search range
1968 // highest index in the search range
1969 int32_t top
= aNumOptions
;
1971 // Start off keyboard options at selectedIndex if nothing else is defaulted to
1973 // XXX Perhaps this should happen for mouse too, to start off shift click
1974 // automatically in multiple ... to do this, we'd need to override
1975 // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
1976 // sure of the effects, though, so I'm not doing it just yet.
1977 int32_t startIndex
= aStartIndex
;
1978 if (startIndex
< bottom
) {
1979 startIndex
= GetSelectedIndex();
1981 int32_t newIndex
= startIndex
+ aDoAdjustInc
;
1983 // make sure we start off in the range
1984 if (newIndex
< bottom
) {
1986 } else if (newIndex
>= top
) {
1987 newIndex
= aNumOptions
-1;
1991 // if the newIndex isn't disabled, we are golden, bail out
1992 bool isDisabled
= true;
1993 if (NS_SUCCEEDED(IsOptionDisabled(newIndex
, isDisabled
)) && !isDisabled
) {
1997 // it WAS disabled, so sart looking ahead for the next enabled option
1998 newIndex
+= aDoAdjustIncNext
;
2000 // well, if we reach end reverse the search
2001 if (newIndex
< bottom
) {
2003 return; // if we are in reverse mode and reach the end bail out
2005 // reset the newIndex to the end of the list we hit
2006 // reverse the incrementer
2007 // set the other end of the list to our original starting index
2009 aDoAdjustIncNext
= 1;
2010 doingReverse
= true;
2013 } else if (newIndex
>= top
) {
2015 return; // if we are in reverse mode and reach the end bail out
2017 // reset the newIndex to the end of the list we hit
2018 // reverse the incrementer
2019 // set the other end of the list to our original starting index
2021 aDoAdjustIncNext
= -1;
2022 doingReverse
= true;
2023 bottom
= startIndex
;
2028 // Looks like we found one
2029 aNewIndex
= newIndex
;
2033 nsListControlFrame::GetIncrementalString()
2035 if (sIncrementalString
== nullptr)
2036 sIncrementalString
= new nsString();
2038 return *sIncrementalString
;
2042 nsListControlFrame::Shutdown()
2044 delete sIncrementalString
;
2045 sIncrementalString
= nullptr;
2049 nsListControlFrame::DropDownToggleKey(nsIDOMEvent
* aKeyEvent
)
2051 // Cocoa widgets do native popups, so don't try to show
2053 if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2054 aKeyEvent
->PreventDefault();
2055 if (!mComboboxFrame
->IsDroppedDown()) {
2056 mComboboxFrame
->ShowDropDown(true);
2058 nsWeakFrame
weakFrame(this);
2059 // mEndSelectionIndex is the last item that got selected.
2060 ComboboxFinish(mEndSelectionIndex
);
2061 if (weakFrame
.IsAlive()) {
2069 nsListControlFrame::KeyDown(nsIDOMEvent
* aKeyEvent
)
2071 MOZ_ASSERT(aKeyEvent
, "aKeyEvent is null.");
2073 nsEventStates eventStates
= mContent
->AsElement()->State();
2074 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
2078 // Don't check defaultPrevented value because other browsers don't prevent
2079 // the key navigation of list control even if preventDefault() is called.
2081 const WidgetKeyboardEvent
* keyEvent
=
2082 aKeyEvent
->GetInternalNSEvent()->AsKeyboardEvent();
2083 MOZ_ASSERT(keyEvent
,
2084 "DOM event must have WidgetKeyboardEvent for its internal event");
2086 if (keyEvent
->IsAlt()) {
2087 if (keyEvent
->keyCode
== NS_VK_UP
|| keyEvent
->keyCode
== NS_VK_DOWN
) {
2088 DropDownToggleKey(aKeyEvent
);
2093 // now make sure there are options or we are wasting our time
2094 nsRefPtr
<dom::HTMLOptionsCollection
> options
= GetOptions();
2095 NS_ENSURE_TRUE(options
, NS_ERROR_FAILURE
);
2097 uint32_t numOptions
= options
->Length();
2099 // this is the new index to set
2100 int32_t newIndex
= kNothingSelected
;
2102 bool isControlOrMeta
= (keyEvent
->IsControl() || keyEvent
->IsMeta());
2103 if (isControlOrMeta
&& (keyEvent
->keyCode
== NS_VK_UP
||
2104 keyEvent
->keyCode
== NS_VK_LEFT
||
2105 keyEvent
->keyCode
== NS_VK_DOWN
||
2106 keyEvent
->keyCode
== NS_VK_RIGHT
)) {
2107 // Don't go into multiple select mode unless this list can handle it
2108 isControlOrMeta
= mControlSelectMode
= GetMultiple();
2109 } else if (keyEvent
->keyCode
!= NS_VK_SPACE
) {
2110 mControlSelectMode
= false;
2113 switch (keyEvent
->keyCode
) {
2116 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2117 static_cast<int32_t>(numOptions
),
2122 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2123 static_cast<int32_t>(numOptions
),
2127 if (mComboboxFrame
) {
2128 nsWeakFrame
weakFrame(this);
2129 if (mComboboxFrame
->IsDroppedDown()) {
2130 // At closing dropdown, users may not expect there is additional
2131 // behavior for this key event. Therefore, let's consume the event.
2132 aKeyEvent
->PreventDefault();
2133 ComboboxFinish(mEndSelectionIndex
);
2134 if (!weakFrame
.IsAlive()) {
2139 if (!weakFrame
.IsAlive()) {
2140 // If the keydown event causes destroying this, fired keypress on
2141 // another element may cause another action which may not be
2142 // expected by the user.
2143 aKeyEvent
->PreventDefault();
2147 newIndex
= mEndSelectionIndex
;
2149 case NS_VK_ESCAPE
: {
2150 nsWeakFrame
weakFrame(this);
2151 // XXX When the Escape keydown causes closing dropdown, it shouldn't
2152 // cause any additonal actions. We should call preventDefault() here.
2154 if (!weakFrame
.IsAlive()) {
2155 // If the keydown event causes destroying this, fired keypress on
2156 // another element may cause another action which may not be
2157 // expected by the user.
2158 aKeyEvent
->PreventDefault();
2163 case NS_VK_PAGE_UP
: {
2164 int32_t itemsPerPage
=
2165 std::max(1, static_cast<int32_t>(mNumDisplayRows
- 1));
2166 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2167 static_cast<int32_t>(numOptions
),
2171 case NS_VK_PAGE_DOWN
: {
2172 int32_t itemsPerPage
=
2173 std::max(1, static_cast<int32_t>(mNumDisplayRows
- 1));
2174 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2175 static_cast<int32_t>(numOptions
),
2180 AdjustIndexForDisabledOpt(0, newIndex
,
2181 static_cast<int32_t>(numOptions
),
2185 AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions
) - 1, newIndex
,
2186 static_cast<int32_t>(numOptions
),
2190 #if defined(XP_WIN) || defined(XP_OS2)
2192 DropDownToggleKey(aKeyEvent
);
2196 default: // printable key will be handled by keypress event.
2200 aKeyEvent
->PreventDefault();
2202 // Cancel incremental search if it's being performed.
2203 GetIncrementalString().Truncate();
2205 // Actually process the new index and let the selection code
2206 // do the scrolling for us
2207 PostHandleKeyEvent(newIndex
, 0, keyEvent
->IsShift(), isControlOrMeta
);
2212 nsListControlFrame::KeyPress(nsIDOMEvent
* aKeyEvent
)
2214 MOZ_ASSERT(aKeyEvent
, "aKeyEvent is null.");
2216 nsEventStates eventStates
= mContent
->AsElement()->State();
2217 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
2221 const WidgetKeyboardEvent
* keyEvent
=
2222 aKeyEvent
->GetInternalNSEvent()->AsKeyboardEvent();
2223 MOZ_ASSERT(keyEvent
,
2224 "DOM event must have WidgetKeyboardEvent for its internal event");
2226 // Select option with this as the first character
2227 // XXX Not I18N compliant
2229 // Don't do incremental search if the key event has already consumed.
2230 if (keyEvent
->mFlags
.mDefaultPrevented
) {
2234 if (keyEvent
->IsAlt()) {
2238 // With some keyboard layout, space key causes non-ASCII space.
2239 // So, the check in keydown event handler isn't enough, we need to check it
2240 // again with keypress event.
2241 if (keyEvent
->charCode
!= ' ') {
2242 mControlSelectMode
= false;
2245 bool isControlOrMeta
= (keyEvent
->IsControl() || keyEvent
->IsMeta());
2246 if (isControlOrMeta
&& keyEvent
->charCode
!= ' ') {
2250 // NOTE: If keyCode of keypress event is not 0, charCode is always 0.
2251 // Therefore, all non-printable keys are not handled after this block.
2252 if (!keyEvent
->charCode
) {
2253 // Backspace key will delete the last char in the string
2254 // XXX Backspace key causes "go back the history" on Windows. Shouldn't we
2255 // prevent its default action if incremental search is used since
2256 // getting focus? When I tested this, it worked accidentally.
2257 if (keyEvent
->keyCode
== NS_VK_BACK
&& !GetIncrementalString().IsEmpty()) {
2258 GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
2259 aKeyEvent
->PreventDefault();
2264 // We ate the key if we got this far.
2265 aKeyEvent
->PreventDefault();
2267 // XXX Why don't we check/modify timestamp first?
2269 // Incremental Search: if time elapsed is below
2270 // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
2271 // string we will use to find options and start searching at the current
2272 // keystroke. Otherwise, Truncate the string if it's been a long time
2273 // since our last keypress.
2274 if (keyEvent
->time
- gLastKeyTime
> INCREMENTAL_SEARCH_KEYPRESS_TIME
) {
2275 // If this is ' ' and we are at the beginning of the string, treat it as
2276 // "select this option" (bug 191543)
2277 if (keyEvent
->charCode
== ' ') {
2278 // Actually process the new index and let the selection code
2279 // do the scrolling for us
2280 PostHandleKeyEvent(mEndSelectionIndex
, keyEvent
->charCode
,
2281 keyEvent
->IsShift(), isControlOrMeta
);
2286 GetIncrementalString().Truncate();
2289 gLastKeyTime
= keyEvent
->time
;
2291 // Append this keystroke to the search string.
2292 char16_t uniChar
= ToLowerCase(static_cast<char16_t
>(keyEvent
->charCode
));
2293 GetIncrementalString().Append(uniChar
);
2295 // See bug 188199, if all letters in incremental string are same, just try to
2296 // match the first one
2297 nsAutoString
incrementalString(GetIncrementalString());
2298 uint32_t charIndex
= 1, stringLength
= incrementalString
.Length();
2299 while (charIndex
< stringLength
&&
2300 incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
2303 if (charIndex
== stringLength
) {
2304 incrementalString
.Truncate(1);
2308 // Determine where we're going to start reading the string
2309 // If we have multiple characters to look for, we start looking *at* the
2310 // current option. If we have only one character to look for, we start
2311 // looking *after* the current option.
2312 // Exception: if there is no option selected to start at, we always start
2314 int32_t startIndex
= GetSelectedIndex();
2315 if (startIndex
== kNothingSelected
) {
2317 } else if (stringLength
== 1) {
2321 // now make sure there are options or we are wasting our time
2322 nsRefPtr
<dom::HTMLOptionsCollection
> options
= GetOptions();
2323 NS_ENSURE_TRUE(options
, NS_ERROR_FAILURE
);
2325 uint32_t numOptions
= options
->Length();
2327 nsWeakFrame
weakFrame(this);
2328 for (uint32_t i
= 0; i
< numOptions
; ++i
) {
2329 uint32_t index
= (i
+ startIndex
) % numOptions
;
2330 nsRefPtr
<dom::HTMLOptionElement
> optionElement
=
2331 options
->ItemAsOption(index
);
2332 if (!optionElement
|| !optionElement
->GetPrimaryFrame()) {
2337 if (NS_FAILED(optionElement
->GetText(text
)) ||
2339 nsContentUtils::TrimWhitespace
<
2340 nsContentUtils::IsHTMLWhitespaceOrNBSP
>(text
, false),
2341 incrementalString
, nsCaseInsensitiveStringComparator())) {
2345 bool wasChanged
= PerformSelection(index
, keyEvent
->IsShift(), isControlOrMeta
);
2346 if (!weakFrame
.IsAlive()) {
2353 // If UpdateSelection() returns false, that means the frame is no longer
2354 // alive. We should stop doing anything.
2355 if (!UpdateSelection()) {
2365 nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex
,
2368 bool aIsControlOrMeta
)
2370 if (aNewIndex
== kNothingSelected
) {
2374 // If you hold control, but not shift, no key will actually do anything
2376 nsWeakFrame
weakFrame(this);
2377 bool wasChanged
= false;
2378 if (aIsControlOrMeta
&& !aIsShift
&& aCharCode
!= ' ') {
2379 mStartSelectionIndex
= aNewIndex
;
2380 mEndSelectionIndex
= aNewIndex
;
2382 ScrollToIndex(aNewIndex
);
2383 if (!weakFrame
.IsAlive()) {
2387 #ifdef ACCESSIBILITY
2388 FireMenuItemActiveEvent();
2390 } else if (mControlSelectMode
&& aCharCode
== ' ') {
2391 wasChanged
= SingleSelection(aNewIndex
, true);
2393 wasChanged
= PerformSelection(aNewIndex
, aIsShift
, aIsControlOrMeta
);
2395 if (wasChanged
&& weakFrame
.IsAlive()) {
2396 // dispatch event, update combobox, etc.
2402 /******************************************************************************
2403 * nsListEventListener
2404 *****************************************************************************/
2406 NS_IMPL_ISUPPORTS1(nsListEventListener
, nsIDOMEventListener
)
2409 nsListEventListener::HandleEvent(nsIDOMEvent
* aEvent
)
2414 nsAutoString eventType
;
2415 aEvent
->GetType(eventType
);
2416 if (eventType
.EqualsLiteral("keydown"))
2417 return mFrame
->nsListControlFrame::KeyDown(aEvent
);
2418 if (eventType
.EqualsLiteral("keypress"))
2419 return mFrame
->nsListControlFrame::KeyPress(aEvent
);
2420 if (eventType
.EqualsLiteral("mousedown"))
2421 return mFrame
->nsListControlFrame::MouseDown(aEvent
);
2422 if (eventType
.EqualsLiteral("mouseup"))
2423 return mFrame
->nsListControlFrame::MouseUp(aEvent
);
2424 if (eventType
.EqualsLiteral("mousemove"))
2425 return mFrame
->nsListControlFrame::MouseMove(aEvent
);