Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nscore.h"
7 : #include "nsCOMPtr.h"
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 "nsIDOMMouseEvent.h"
18 : #include "nsIXULRuntime.h"
19 : #include "nsFontMetrics.h"
20 : #include "nsIScrollableFrame.h"
21 : #include "nsCSSRendering.h"
22 : #include "nsIDOMEventListener.h"
23 : #include "nsLayoutUtils.h"
24 : #include "nsDisplayList.h"
25 : #include "nsContentUtils.h"
26 : #include "mozilla/Attributes.h"
27 : #include "mozilla/dom/HTMLOptionsCollection.h"
28 : #include "mozilla/dom/HTMLSelectElement.h"
29 : #include "mozilla/EventStateManager.h"
30 : #include "mozilla/EventStates.h"
31 : #include "mozilla/LookAndFeel.h"
32 : #include "mozilla/MouseEvents.h"
33 : #include "mozilla/Preferences.h"
34 : #include "mozilla/TextEvents.h"
35 : #include <algorithm>
36 :
37 : using namespace mozilla;
38 :
39 : // Constants
40 : const uint32_t kMaxDropDownRows = 20; // This matches the setting for 4.x browsers
41 : const int32_t kNothingSelected = -1;
42 :
43 : // Static members
44 : nsListControlFrame * nsListControlFrame::mFocused = nullptr;
45 : nsString * nsListControlFrame::sIncrementalString = nullptr;
46 :
47 : // Using for incremental typing navigation
48 : #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
49 : // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
50 : // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
51 : // need to find a good place to put them together.
52 : // if someone changes one, please also change the other.
53 :
54 : DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
55 :
56 : /******************************************************************************
57 : * nsListEventListener
58 : * This class is responsible for propagating events to the nsListControlFrame.
59 : * Frames are not refcounted so they can't be used as event listeners.
60 : *****************************************************************************/
61 :
62 : class nsListEventListener final : public nsIDOMEventListener
63 : {
64 : public:
65 0 : explicit nsListEventListener(nsListControlFrame *aFrame)
66 0 : : mFrame(aFrame) { }
67 :
68 0 : void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
69 :
70 : NS_DECL_ISUPPORTS
71 : NS_DECL_NSIDOMEVENTLISTENER
72 :
73 : private:
74 0 : ~nsListEventListener() {}
75 :
76 : nsListControlFrame *mFrame;
77 : };
78 :
79 : //---------------------------------------------------------
80 : nsContainerFrame*
81 0 : NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
82 : {
83 : nsListControlFrame* it =
84 0 : new (aPresShell) nsListControlFrame(aContext);
85 :
86 0 : it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
87 :
88 0 : return it;
89 : }
90 :
91 0 : NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
92 :
93 : //---------------------------------------------------------
94 0 : nsListControlFrame::nsListControlFrame(nsStyleContext* aContext)
95 : : nsHTMLScrollFrame(aContext, kClassID, false)
96 : , mView(nullptr)
97 : , mMightNeedSecondPass(false)
98 : , mHasPendingInterruptAtStartOfReflow(false)
99 : , mDropdownCanGrow(false)
100 : , mForceSelection(false)
101 0 : , mLastDropdownComputedBSize(NS_UNCONSTRAINEDSIZE)
102 : {
103 0 : mComboboxFrame = nullptr;
104 0 : mChangesSinceDragStart = false;
105 0 : mButtonDown = false;
106 :
107 0 : mIsAllContentHere = false;
108 0 : mIsAllFramesHere = false;
109 0 : mHasBeenInitialized = false;
110 0 : mNeedToReset = true;
111 0 : mPostChildrenLoadedReset = false;
112 :
113 0 : mControlSelectMode = false;
114 0 : }
115 :
116 : //---------------------------------------------------------
117 0 : nsListControlFrame::~nsListControlFrame()
118 : {
119 0 : mComboboxFrame = nullptr;
120 0 : }
121 :
122 0 : static bool ShouldFireDropDownEvent() {
123 0 : return (XRE_IsContentProcess() &&
124 0 : Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) ||
125 0 : Preferences::GetBool("dom.select_popup_in_parent.enabled", false);
126 : }
127 :
128 : // for Bug 47302 (remove this comment later)
129 : void
130 0 : nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
131 : {
132 : // get the receiver interface from the browser button's content node
133 0 : ENSURE_TRUE(mContent);
134 :
135 : // Clear the frame pointer on our event listener, just in case the
136 : // event listener can outlive the frame.
137 :
138 0 : mEventListener->SetFrame(nullptr);
139 :
140 0 : mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
141 0 : mEventListener, false);
142 0 : mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
143 0 : mEventListener, false);
144 0 : mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
145 0 : mEventListener, false);
146 0 : mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
147 0 : mEventListener, false);
148 0 : mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
149 0 : mEventListener, false);
150 :
151 0 : if (ShouldFireDropDownEvent()) {
152 0 : nsContentUtils::AddScriptRunner(
153 0 : new AsyncEventDispatcher(mContent,
154 0 : NS_LITERAL_STRING("mozhidedropdown"), true,
155 0 : true));
156 : }
157 :
158 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
159 0 : nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
160 : }
161 :
162 : void
163 0 : nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
164 : const nsRect& aDirtyRect,
165 : const nsDisplayListSet& aLists)
166 : {
167 : // We allow visibility:hidden <select>s to contain visible options.
168 :
169 : // Don't allow painting of list controls when painting is suppressed.
170 : // XXX why do we need this here? we should never reach this. Maybe
171 : // because these can have widgets? Hmm
172 0 : if (aBuilder->IsBackgroundOnly())
173 0 : return;
174 :
175 0 : DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
176 :
177 0 : if (IsInDropDownMode()) {
178 0 : NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
179 : "need an opaque backstop color");
180 : // XXX Because we have an opaque widget and we get called to paint with
181 : // this frame as the root of a stacking context we need make sure to draw
182 : // some opaque color over the whole widget. (Bug 511323)
183 0 : aLists.BorderBackground()->AppendNewToBottom(
184 : new (aBuilder) nsDisplaySolidColor(aBuilder,
185 0 : this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
186 0 : mLastDropdownBackstopColor));
187 : }
188 :
189 0 : nsHTMLScrollFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
190 : }
191 :
192 : /**
193 : * This is called by the SelectsAreaFrame, which is the same
194 : * as the frame returned by GetOptionsContainer. It's the frame which is
195 : * scrolled by us.
196 : * @param aPt the offset of this frame, relative to the rendering reference
197 : * frame
198 : */
199 0 : void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt)
200 : {
201 0 : if (mFocused != this) return;
202 :
203 0 : nsPresContext* presContext = PresContext();
204 :
205 0 : nsIFrame* containerFrame = GetOptionsContainer();
206 0 : if (!containerFrame) return;
207 :
208 0 : nsIFrame* childframe = nullptr;
209 0 : nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
210 0 : if (focusedContent) {
211 0 : childframe = focusedContent->GetPrimaryFrame();
212 : }
213 :
214 0 : nsRect fRect;
215 0 : if (childframe) {
216 : // get the child rect
217 0 : fRect = childframe->GetRect();
218 : // get it into our coordinates
219 0 : fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
220 : } else {
221 0 : float inflation = nsLayoutUtils::FontSizeInflationFor(this);
222 0 : fRect.x = fRect.y = 0;
223 0 : if (GetWritingMode().IsVertical()) {
224 0 : fRect.width = GetScrollPortRect().width;
225 0 : fRect.height = CalcFallbackRowBSize(inflation);
226 : } else {
227 0 : fRect.width = CalcFallbackRowBSize(inflation);
228 0 : fRect.height = GetScrollPortRect().height;
229 : }
230 0 : fRect.MoveBy(containerFrame->GetOffsetTo(this));
231 : }
232 0 : fRect += aPt;
233 :
234 0 : bool lastItemIsSelected = false;
235 0 : if (focusedContent) {
236 : nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
237 0 : do_QueryInterface(focusedContent);
238 0 : if (domOpt) {
239 0 : domOpt->GetSelected(&lastItemIsSelected);
240 : }
241 : }
242 :
243 : // set up back stop colors and then ask L&F service for the real colors
244 : nscolor color =
245 0 : LookAndFeel::GetColor(lastItemIsSelected ?
246 : LookAndFeel::eColorID_WidgetSelectForeground :
247 0 : LookAndFeel::eColorID_WidgetSelectBackground);
248 :
249 0 : nsCSSRendering::PaintFocus(presContext, aDrawTarget, fRect, color);
250 : }
251 :
252 : void
253 0 : nsListControlFrame::InvalidateFocus()
254 : {
255 0 : if (mFocused != this)
256 0 : return;
257 :
258 0 : nsIFrame* containerFrame = GetOptionsContainer();
259 0 : if (containerFrame) {
260 0 : containerFrame->InvalidateFrame();
261 : }
262 : }
263 :
264 0 : NS_QUERYFRAME_HEAD(nsListControlFrame)
265 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
266 0 : NS_QUERYFRAME_ENTRY(nsIListControlFrame)
267 0 : NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
268 0 : NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
269 :
270 : #ifdef ACCESSIBILITY
271 : a11y::AccType
272 0 : nsListControlFrame::AccessibleType()
273 : {
274 0 : return a11y::eHTMLSelectListType;
275 : }
276 : #endif
277 :
278 : static nscoord
279 0 : GetMaxOptionBSize(nsIFrame* aContainer, WritingMode aWM)
280 : {
281 0 : nscoord result = 0;
282 0 : for (nsIFrame* option : aContainer->PrincipalChildList()) {
283 : nscoord optionBSize;
284 0 : if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
285 0 : (do_QueryInterface(option->GetContent()))) {
286 : // An optgroup; drill through any scroll frame and recurse. |frame| might
287 : // be null here though if |option| is an anonymous leaf frame of some sort.
288 0 : auto frame = option->GetContentInsertionFrame();
289 0 : optionBSize = frame ? GetMaxOptionBSize(frame, aWM) : 0;
290 : } else {
291 : // an option
292 0 : optionBSize = option->BSize(aWM);
293 : }
294 0 : if (result < optionBSize)
295 0 : result = optionBSize;
296 : }
297 0 : return result;
298 : }
299 :
300 : //-----------------------------------------------------------------
301 : // Main Reflow for ListBox/Dropdown
302 : //-----------------------------------------------------------------
303 :
304 : nscoord
305 0 : nsListControlFrame::CalcBSizeOfARow()
306 : {
307 : // Calculate the block size in our writing mode of a single row in the
308 : // listbox or dropdown list by using the tallest thing in the subtree,
309 : // since there may be option groups in addition to option elements,
310 : // either of which may be visible or invisible, may use different
311 : // fonts, etc.
312 0 : int32_t blockSizeOfARow = GetMaxOptionBSize(GetOptionsContainer(),
313 0 : GetWritingMode());
314 :
315 : // Check to see if we have zero items (and optimize by checking
316 : // blockSizeOfARow first)
317 0 : if (blockSizeOfARow == 0 && GetNumberOfOptions() == 0) {
318 0 : float inflation = nsLayoutUtils::FontSizeInflationFor(this);
319 0 : blockSizeOfARow = CalcFallbackRowBSize(inflation);
320 : }
321 :
322 0 : return blockSizeOfARow;
323 : }
324 :
325 : nscoord
326 0 : nsListControlFrame::GetPrefISize(gfxContext *aRenderingContext)
327 : {
328 : nscoord result;
329 0 : DISPLAY_PREF_WIDTH(this, result);
330 :
331 : // Always add scrollbar inline sizes to the pref-inline-size of the
332 : // scrolled content. Combobox frames depend on this happening in the
333 : // dropdown, and standalone listboxes are overflow:scroll so they need
334 : // it too.
335 0 : WritingMode wm = GetWritingMode();
336 0 : result = GetScrolledFrame()->GetPrefISize(aRenderingContext);
337 0 : LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
338 0 : aRenderingContext));
339 0 : result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm));
340 0 : return result;
341 : }
342 :
343 : nscoord
344 0 : nsListControlFrame::GetMinISize(gfxContext *aRenderingContext)
345 : {
346 : nscoord result;
347 0 : DISPLAY_MIN_WIDTH(this, result);
348 :
349 : // Always add scrollbar inline sizes to the min-inline-size of the
350 : // scrolled content. Combobox frames depend on this happening in the
351 : // dropdown, and standalone listboxes are overflow:scroll so they need
352 : // it too.
353 0 : WritingMode wm = GetWritingMode();
354 0 : result = GetScrolledFrame()->GetMinISize(aRenderingContext);
355 0 : LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
356 0 : aRenderingContext));
357 0 : result += scrollbarSize.IStartEnd(wm);
358 :
359 0 : return result;
360 : }
361 :
362 : void
363 0 : nsListControlFrame::Reflow(nsPresContext* aPresContext,
364 : ReflowOutput& aDesiredSize,
365 : const ReflowInput& aReflowInput,
366 : nsReflowStatus& aStatus)
367 : {
368 0 : NS_PRECONDITION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
369 : "Must have a computed inline size");
370 :
371 0 : SchedulePaint();
372 :
373 0 : mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
374 :
375 : // If all the content and frames are here
376 : // then initialize it before reflow
377 0 : if (mIsAllContentHere && !mHasBeenInitialized) {
378 0 : if (false == mIsAllFramesHere) {
379 0 : CheckIfAllFramesHere();
380 : }
381 0 : if (mIsAllFramesHere && !mHasBeenInitialized) {
382 0 : mHasBeenInitialized = true;
383 : }
384 : }
385 :
386 0 : if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
387 0 : nsFormControlFrame::RegUnRegAccessKey(this, true);
388 : }
389 :
390 0 : if (IsInDropDownMode()) {
391 0 : ReflowAsDropdown(aPresContext, aDesiredSize, aReflowInput, aStatus);
392 0 : return;
393 : }
394 :
395 0 : MarkInReflow();
396 : /*
397 : * Due to the fact that our intrinsic block size depends on the block
398 : * sizes of our kids, we end up having to do two-pass reflow, in
399 : * general -- the first pass to find the intrinsic block size and a
400 : * second pass to reflow the scrollframe at that block size (which
401 : * will size the scrollbars correctly, etc).
402 : *
403 : * Naturally, we want to avoid doing the second reflow as much as
404 : * possible.
405 : * We can skip it in the following cases (in all of which the first
406 : * reflow is already happening at the right block size):
407 : *
408 : * - We're reflowing with a constrained computed block size -- just
409 : * use that block size.
410 : * - We're not dirty and have no dirty kids and shouldn't be reflowing
411 : * all kids. In this case, our cached max block size of a child is
412 : * not going to change.
413 : * - We do our first reflow using our cached max block size of a
414 : * child, then compute the new max block size and it's the same as
415 : * the old one.
416 : */
417 :
418 0 : bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
419 :
420 0 : mMightNeedSecondPass = autoBSize &&
421 0 : (NS_SUBTREE_DIRTY(this) || aReflowInput.ShouldReflowAllKids());
422 :
423 0 : ReflowInput state(aReflowInput);
424 0 : int32_t length = GetNumberOfRows();
425 :
426 0 : nscoord oldBSizeOfARow = BSizeOfARow();
427 :
428 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoBSize) {
429 : // When not doing an initial reflow, and when the block size is
430 : // auto, start off with our computed block size set to what we'd
431 : // expect our block size to be.
432 0 : nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
433 0 : computedBSize = state.ApplyMinMaxBSize(computedBSize);
434 0 : state.SetComputedBSize(computedBSize);
435 : }
436 :
437 0 : nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
438 :
439 0 : if (!mMightNeedSecondPass) {
440 0 : NS_ASSERTION(!autoBSize || BSizeOfARow() == oldBSizeOfARow,
441 : "How did our BSize of a row change if nothing was dirty?");
442 0 : NS_ASSERTION(!autoBSize ||
443 : !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
444 : "How do we not need a second pass during initial reflow at "
445 : "auto BSize?");
446 0 : NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
447 : "Shouldn't be suppressing if we don't need a second pass!");
448 0 : if (!autoBSize) {
449 : // Update our mNumDisplayRows based on our new row block size now
450 : // that we know it. Note that if autoBSize and we landed in this
451 : // code then we already set mNumDisplayRows in CalcIntrinsicBSize.
452 : // Also note that we can't use BSizeOfARow() here because that
453 : // just uses a cached value that we didn't compute.
454 0 : nscoord rowBSize = CalcBSizeOfARow();
455 0 : if (rowBSize == 0) {
456 : // Just pick something
457 0 : mNumDisplayRows = 1;
458 : } else {
459 0 : mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
460 : }
461 : }
462 :
463 0 : return;
464 : }
465 :
466 0 : mMightNeedSecondPass = false;
467 :
468 : // Now see whether we need a second pass. If we do, our
469 : // nsSelectsAreaFrame will have suppressed the scrollbar update.
470 0 : if (!IsScrollbarUpdateSuppressed()) {
471 : // All done. No need to do more reflow.
472 0 : return;
473 : }
474 :
475 0 : SetSuppressScrollbarUpdate(false);
476 :
477 : // Gotta reflow again.
478 : // XXXbz We're just changing the block size here; do we need to dirty
479 : // ourselves or anything like that? We might need to, per the letter
480 : // of the reflow protocol, but things seem to work fine without it...
481 : // Is that just an implementation detail of nsHTMLScrollFrame that
482 : // we're depending on?
483 0 : nsHTMLScrollFrame::DidReflow(aPresContext, &state,
484 0 : nsDidReflowStatus::FINISHED);
485 :
486 : // Now compute the block size we want to have
487 0 : nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
488 0 : computedBSize = state.ApplyMinMaxBSize(computedBSize);
489 0 : state.SetComputedBSize(computedBSize);
490 :
491 : // XXXbz to make the ascent really correct, we should add our
492 : // mComputedPadding.top to it (and subtract it from descent). Need that
493 : // because nsGfxScrollFrame just adds in the border....
494 0 : nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
495 : }
496 :
497 : void
498 0 : nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext,
499 : ReflowOutput& aDesiredSize,
500 : const ReflowInput& aReflowInput,
501 : nsReflowStatus& aStatus)
502 : {
503 0 : NS_PRECONDITION(aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE,
504 : "We should not have a computed block size here!");
505 :
506 0 : mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) ||
507 0 : aReflowInput.ShouldReflowAllKids();
508 :
509 0 : WritingMode wm = aReflowInput.GetWritingMode();
510 : #ifdef DEBUG
511 0 : nscoord oldBSizeOfARow = BSizeOfARow();
512 0 : nscoord oldVisibleBSize = (GetStateBits() & NS_FRAME_FIRST_REFLOW) ?
513 0 : NS_UNCONSTRAINEDSIZE : GetScrolledFrame()->BSize(wm);
514 : #endif
515 :
516 0 : ReflowInput state(aReflowInput);
517 :
518 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
519 : // When not doing an initial reflow, and when the block size is
520 : // auto, start off with our computed block size set to what we'd
521 : // expect our block size to be.
522 : // Note: At this point, mLastDropdownComputedBSize can be
523 : // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to
524 : // constrain the block size. That's fine; just do the same thing as
525 : // last time.
526 0 : state.SetComputedBSize(mLastDropdownComputedBSize);
527 : }
528 :
529 0 : nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
530 :
531 0 : if (!mMightNeedSecondPass) {
532 0 : NS_ASSERTION(oldVisibleBSize == GetScrolledFrame()->BSize(wm),
533 : "How did our kid's BSize change if nothing was dirty?");
534 0 : NS_ASSERTION(BSizeOfARow() == oldBSizeOfARow,
535 : "How did our BSize of a row change if nothing was dirty?");
536 0 : NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
537 : "Shouldn't be suppressing if we don't need a second pass!");
538 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
539 : "How can we avoid a second pass during first reflow?");
540 0 : return;
541 : }
542 :
543 0 : mMightNeedSecondPass = false;
544 :
545 : // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
546 : // will have suppressed the scrollbar update.
547 0 : if (!IsScrollbarUpdateSuppressed()) {
548 : // All done. No need to do more reflow.
549 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
550 : "How can we avoid a second pass during first reflow?");
551 0 : return;
552 : }
553 :
554 0 : SetSuppressScrollbarUpdate(false);
555 :
556 0 : nscoord visibleBSize = GetScrolledFrame()->BSize(wm);
557 0 : nscoord blockSizeOfARow = BSizeOfARow();
558 :
559 : // Gotta reflow again.
560 : // XXXbz We're just changing the block size here; do we need to dirty
561 : // ourselves or anything like that? We might need to, per the letter
562 : // of the reflow protocol, but things seem to work fine without it...
563 : // Is that just an implementation detail of nsHTMLScrollFrame that
564 : // we're depending on?
565 0 : nsHTMLScrollFrame::DidReflow(aPresContext, &state,
566 0 : nsDidReflowStatus::FINISHED);
567 :
568 : // Now compute the block size we want to have.
569 : // Note: no need to apply min/max constraints, since we have no such
570 : // rules applied to the combobox dropdown.
571 :
572 0 : mDropdownCanGrow = false;
573 0 : if (visibleBSize <= 0 || blockSizeOfARow <= 0 || XRE_IsContentProcess()) {
574 : // Looks like we have no options. Just size us to a single row
575 : // block size.
576 0 : state.SetComputedBSize(blockSizeOfARow);
577 0 : mNumDisplayRows = 1;
578 : } else {
579 : nsComboboxControlFrame* combobox =
580 0 : static_cast<nsComboboxControlFrame*>(mComboboxFrame);
581 0 : LogicalPoint translation(wm);
582 : nscoord before, after;
583 0 : combobox->GetAvailableDropdownSpace(wm, &before, &after, &translation);
584 0 : if (before <= 0 && after <= 0) {
585 0 : state.SetComputedBSize(blockSizeOfARow);
586 0 : mNumDisplayRows = 1;
587 0 : mDropdownCanGrow = GetNumberOfRows() > 1;
588 : } else {
589 0 : nscoord bp = aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
590 0 : nscoord availableBSize = std::max(before, after) - bp;
591 : nscoord newBSize;
592 : uint32_t rows;
593 0 : if (visibleBSize <= availableBSize) {
594 : // The dropdown fits in the available block size.
595 0 : rows = GetNumberOfRows();
596 0 : mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
597 0 : if (mNumDisplayRows == rows) {
598 0 : newBSize = visibleBSize; // use the exact block size
599 : } else {
600 0 : newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
601 : // The approximation here might actually be too big (bug 1208978);
602 : // don't let it exceed the actual block-size of the list.
603 0 : newBSize = std::min(newBSize, visibleBSize);
604 : }
605 : } else {
606 0 : rows = availableBSize / blockSizeOfARow;
607 0 : mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
608 0 : newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
609 : }
610 0 : state.SetComputedBSize(newBSize);
611 0 : mDropdownCanGrow = visibleBSize - newBSize >= blockSizeOfARow &&
612 0 : mNumDisplayRows != kMaxDropDownRows;
613 : }
614 : }
615 :
616 0 : mLastDropdownComputedBSize = state.ComputedBSize();
617 :
618 0 : nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
619 : }
620 :
621 : ScrollbarStyles
622 0 : nsListControlFrame::GetScrollbarStyles() const
623 : {
624 : // We can't express this in the style system yet; when we can, this can go away
625 : // and GetScrollbarStyles can be devirtualized
626 0 : int32_t style = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
627 0 : : NS_STYLE_OVERFLOW_SCROLL;
628 0 : if (GetWritingMode().IsVertical()) {
629 0 : return ScrollbarStyles(style, NS_STYLE_OVERFLOW_HIDDEN);
630 : } else {
631 0 : return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, style);
632 : }
633 : }
634 :
635 : bool
636 0 : nsListControlFrame::ShouldPropagateComputedBSizeToScrolledContent() const
637 : {
638 0 : return !IsInDropDownMode();
639 : }
640 :
641 : //---------------------------------------------------------
642 : nsContainerFrame*
643 0 : nsListControlFrame::GetContentInsertionFrame() {
644 0 : return GetOptionsContainer()->GetContentInsertionFrame();
645 : }
646 :
647 : //---------------------------------------------------------
648 : bool
649 0 : nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
650 : int32_t aEndIndex,
651 : bool aClearAll)
652 : {
653 0 : return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
654 0 : true, aClearAll);
655 : }
656 :
657 : //---------------------------------------------------------
658 : bool
659 0 : nsListControlFrame::SingleSelection(int32_t aClickedIndex, bool aDoToggle)
660 : {
661 0 : if (mComboboxFrame) {
662 0 : mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
663 : }
664 :
665 0 : bool wasChanged = false;
666 : // Get Current selection
667 0 : if (aDoToggle) {
668 0 : wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
669 : } else {
670 : wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
671 0 : true, true);
672 : }
673 0 : AutoWeakFrame weakFrame(this);
674 0 : ScrollToIndex(aClickedIndex);
675 0 : if (!weakFrame.IsAlive()) {
676 0 : return wasChanged;
677 : }
678 :
679 : #ifdef ACCESSIBILITY
680 0 : bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
681 : #endif
682 0 : mStartSelectionIndex = aClickedIndex;
683 0 : mEndSelectionIndex = aClickedIndex;
684 0 : InvalidateFocus();
685 :
686 : #ifdef ACCESSIBILITY
687 0 : if (isCurrentOptionChanged) {
688 0 : FireMenuItemActiveEvent();
689 : }
690 : #endif
691 :
692 0 : return wasChanged;
693 : }
694 :
695 : void
696 0 : nsListControlFrame::InitSelectionRange(int32_t aClickedIndex)
697 : {
698 : //
699 : // If nothing is selected, set the start selection depending on where
700 : // the user clicked and what the initial selection is:
701 : // - if the user clicked *before* selectedIndex, set the start index to
702 : // the end of the first contiguous selection.
703 : // - if the user clicked *after* the end of the first contiguous
704 : // selection, set the start index to selectedIndex.
705 : // - if the user clicked *within* the first contiguous selection, set the
706 : // start index to selectedIndex.
707 : // The last two rules, of course, boil down to the same thing: if the user
708 : // clicked >= selectedIndex, return selectedIndex.
709 : //
710 : // This makes it so that shift click works properly when you first click
711 : // in a multiple select.
712 : //
713 0 : int32_t selectedIndex = GetSelectedIndex();
714 0 : if (selectedIndex >= 0) {
715 : // Get the end of the contiguous selection
716 0 : RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
717 0 : NS_ASSERTION(options, "Collection of options is null!");
718 0 : uint32_t numOptions = options->Length();
719 : // Push i to one past the last selected index in the group.
720 : uint32_t i;
721 0 : for (i = selectedIndex + 1; i < numOptions; i++) {
722 0 : if (!options->ItemAsOption(i)->Selected()) {
723 0 : break;
724 : }
725 : }
726 :
727 0 : if (aClickedIndex < selectedIndex) {
728 : // User clicked before selection, so start selection at end of
729 : // contiguous selection
730 0 : mStartSelectionIndex = i-1;
731 0 : mEndSelectionIndex = selectedIndex;
732 : } else {
733 : // User clicked after selection, so start selection at start of
734 : // contiguous selection
735 0 : mStartSelectionIndex = selectedIndex;
736 0 : mEndSelectionIndex = i-1;
737 : }
738 : }
739 0 : }
740 :
741 : static uint32_t
742 0 : CountOptionsAndOptgroups(nsIFrame* aFrame)
743 : {
744 0 : uint32_t count = 0;
745 0 : nsFrameList::Enumerator e(aFrame->PrincipalChildList());
746 0 : for (; !e.AtEnd(); e.Next()) {
747 0 : nsIFrame* child = e.get();
748 0 : nsIContent* content = child->GetContent();
749 0 : if (content) {
750 0 : if (content->IsHTMLElement(nsGkAtoms::option)) {
751 0 : ++count;
752 : } else {
753 0 : nsCOMPtr<nsIDOMHTMLOptGroupElement> optgroup = do_QueryInterface(content);
754 0 : if (optgroup) {
755 0 : nsAutoString label;
756 0 : optgroup->GetLabel(label);
757 0 : if (label.Length() > 0) {
758 0 : ++count;
759 : }
760 0 : count += CountOptionsAndOptgroups(child);
761 : }
762 : }
763 : }
764 : }
765 0 : return count;
766 : }
767 :
768 : uint32_t
769 0 : nsListControlFrame::GetNumberOfRows()
770 : {
771 0 : return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
772 : }
773 :
774 : //---------------------------------------------------------
775 : bool
776 0 : nsListControlFrame::PerformSelection(int32_t aClickedIndex,
777 : bool aIsShift,
778 : bool aIsControl)
779 : {
780 0 : bool wasChanged = false;
781 :
782 0 : if (aClickedIndex == kNothingSelected && !mForceSelection) {
783 : // Ignore kNothingSelected unless the selection is forced
784 0 : } else if (GetMultiple()) {
785 0 : if (aIsShift) {
786 : // Make sure shift+click actually does something expected when
787 : // the user has never clicked on the select
788 0 : if (mStartSelectionIndex == kNothingSelected) {
789 0 : InitSelectionRange(aClickedIndex);
790 : }
791 :
792 : // Get the range from beginning (low) to end (high)
793 : // Shift *always* works, even if the current option is disabled
794 : int32_t startIndex;
795 : int32_t endIndex;
796 0 : if (mStartSelectionIndex == kNothingSelected) {
797 0 : startIndex = aClickedIndex;
798 0 : endIndex = aClickedIndex;
799 0 : } else if (mStartSelectionIndex <= aClickedIndex) {
800 0 : startIndex = mStartSelectionIndex;
801 0 : endIndex = aClickedIndex;
802 : } else {
803 0 : startIndex = aClickedIndex;
804 0 : endIndex = mStartSelectionIndex;
805 : }
806 :
807 : // Clear only if control was not pressed
808 0 : wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
809 0 : AutoWeakFrame weakFrame(this);
810 0 : ScrollToIndex(aClickedIndex);
811 0 : if (!weakFrame.IsAlive()) {
812 0 : return wasChanged;
813 : }
814 :
815 0 : if (mStartSelectionIndex == kNothingSelected) {
816 0 : mStartSelectionIndex = aClickedIndex;
817 : }
818 : #ifdef ACCESSIBILITY
819 0 : bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
820 : #endif
821 0 : mEndSelectionIndex = aClickedIndex;
822 0 : InvalidateFocus();
823 :
824 : #ifdef ACCESSIBILITY
825 0 : if (isCurrentOptionChanged) {
826 0 : FireMenuItemActiveEvent();
827 : }
828 : #endif
829 0 : } else if (aIsControl) {
830 0 : wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
831 : } else {
832 0 : wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
833 : }
834 : } else {
835 0 : wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
836 : }
837 :
838 0 : return wasChanged;
839 : }
840 :
841 : //---------------------------------------------------------
842 : bool
843 0 : nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
844 : int32_t aClickedIndex)
845 : {
846 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
847 : bool isShift;
848 : bool isControl;
849 : #ifdef XP_MACOSX
850 : mouseEvent->GetMetaKey(&isControl);
851 : #else
852 0 : mouseEvent->GetCtrlKey(&isControl);
853 : #endif
854 0 : mouseEvent->GetShiftKey(&isShift);
855 0 : return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us
856 : }
857 :
858 : //---------------------------------------------------------
859 : void
860 0 : nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents)
861 : {
862 : // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
863 : // so we never want to do mouse capturing. Note that we only bail if the list
864 : // is in drop-down mode, and the caller is requesting capture (we let release capture
865 : // requests go through to ensure that we can release capture requested via other
866 : // code paths, if any exist).
867 0 : if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
868 0 : return;
869 :
870 0 : if (aGrabMouseEvents) {
871 0 : nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
872 : } else {
873 0 : nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
874 :
875 0 : bool dropDownIsHidden = false;
876 0 : if (IsInDropDownMode()) {
877 0 : dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
878 : }
879 0 : if (capturingContent == mContent || dropDownIsHidden) {
880 : // only clear the capturing content if *we* are the ones doing the
881 : // capturing (or if the dropdown is hidden, in which case NO-ONE should
882 : // be capturing anything - it could be a scrollbar inside this listbox
883 : // which is actually grabbing
884 : // This shouldn't be necessary. We should simply ensure that events targeting
885 : // scrollbars are never visible to DOM consumers.
886 0 : nsIPresShell::SetCapturingContent(nullptr, 0);
887 : }
888 : }
889 : }
890 :
891 : //---------------------------------------------------------
892 : nsresult
893 0 : nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
894 : WidgetGUIEvent* aEvent,
895 : nsEventStatus* aEventStatus)
896 : {
897 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
898 :
899 : /*const char * desc[] = {"eMouseMove",
900 : "NS_MOUSE_LEFT_BUTTON_UP",
901 : "NS_MOUSE_LEFT_BUTTON_DOWN",
902 : "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
903 : "NS_MOUSE_MIDDLE_BUTTON_UP",
904 : "NS_MOUSE_MIDDLE_BUTTON_DOWN",
905 : "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
906 : "NS_MOUSE_RIGHT_BUTTON_UP",
907 : "NS_MOUSE_RIGHT_BUTTON_DOWN",
908 : "eMouseOver",
909 : "eMouseOut",
910 : "NS_MOUSE_LEFT_DOUBLECLICK",
911 : "NS_MOUSE_MIDDLE_DOUBLECLICK",
912 : "NS_MOUSE_RIGHT_DOUBLECLICK",
913 : "NS_MOUSE_LEFT_CLICK",
914 : "NS_MOUSE_MIDDLE_CLICK",
915 : "NS_MOUSE_RIGHT_CLICK"};
916 : int inx = aEvent->mMessage - eMouseEventFirst;
917 : if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK - eMouseEventFirst)) {
918 : printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->mMessage);
919 : } else {
920 : printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->mMessage);
921 : }*/
922 :
923 0 : if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
924 0 : return NS_OK;
925 :
926 : // do we have style that affects how we are selected?
927 : // do we have user-input style?
928 0 : const nsStyleUserInterface* uiStyle = StyleUserInterface();
929 0 : if (uiStyle->mUserInput == StyleUserInput::None ||
930 0 : uiStyle->mUserInput == StyleUserInput::Disabled) {
931 0 : return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
932 : }
933 0 : EventStates eventStates = mContent->AsElement()->State();
934 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
935 0 : return NS_OK;
936 :
937 0 : return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
938 : }
939 :
940 :
941 : //---------------------------------------------------------
942 : void
943 0 : nsListControlFrame::SetInitialChildList(ChildListID aListID,
944 : nsFrameList& aChildList)
945 : {
946 0 : if (aListID == kPrincipalList) {
947 : // First check to see if all the content has been added
948 0 : mIsAllContentHere = mContent->IsDoneAddingChildren();
949 0 : if (!mIsAllContentHere) {
950 0 : mIsAllFramesHere = false;
951 0 : mHasBeenInitialized = false;
952 : }
953 : }
954 0 : nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
955 :
956 : // If all the content is here now check
957 : // to see if all the frames have been created
958 : /*if (mIsAllContentHere) {
959 : // If all content and frames are here
960 : // the reset/initialize
961 : if (CheckIfAllFramesHere()) {
962 : ResetList(aPresContext);
963 : mHasBeenInitialized = true;
964 : }
965 : }*/
966 0 : }
967 :
968 : //---------------------------------------------------------
969 : void
970 0 : nsListControlFrame::Init(nsIContent* aContent,
971 : nsContainerFrame* aParent,
972 : nsIFrame* aPrevInFlow)
973 : {
974 0 : nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
975 :
976 0 : if (IsInDropDownMode()) {
977 0 : AddStateBits(NS_FRAME_IN_POPUP);
978 0 : CreateView();
979 : }
980 :
981 : // we shouldn't have to unregister this listener because when
982 : // our frame goes away all these content node go away as well
983 : // because our frame is the only one who references them.
984 : // we need to hook up our listeners before the editor is initialized
985 0 : mEventListener = new nsListEventListener(this);
986 :
987 0 : mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
988 0 : mEventListener, false, false);
989 0 : mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
990 0 : mEventListener, false, false);
991 0 : mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
992 0 : mEventListener, false, false);
993 0 : mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
994 0 : mEventListener, false, false);
995 0 : mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
996 0 : mEventListener, false, false);
997 :
998 0 : mStartSelectionIndex = kNothingSelected;
999 0 : mEndSelectionIndex = kNothingSelected;
1000 :
1001 0 : mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
1002 0 : }
1003 :
1004 : dom::HTMLOptionsCollection*
1005 0 : nsListControlFrame::GetOptions() const
1006 : {
1007 : dom::HTMLSelectElement* select =
1008 0 : dom::HTMLSelectElement::FromContentOrNull(mContent);
1009 0 : NS_ENSURE_TRUE(select, nullptr);
1010 :
1011 0 : return select->Options();
1012 : }
1013 :
1014 : dom::HTMLOptionElement*
1015 0 : nsListControlFrame::GetOption(uint32_t aIndex) const
1016 : {
1017 : dom::HTMLSelectElement* select =
1018 0 : dom::HTMLSelectElement::FromContentOrNull(mContent);
1019 0 : NS_ENSURE_TRUE(select, nullptr);
1020 :
1021 0 : return select->Item(aIndex);
1022 : }
1023 :
1024 : NS_IMETHODIMP
1025 0 : nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
1026 : {
1027 0 : if (aSelected) {
1028 0 : ScrollToIndex(aIndex);
1029 : }
1030 0 : return NS_OK;
1031 : }
1032 :
1033 : void
1034 0 : nsListControlFrame::OnContentReset()
1035 : {
1036 0 : ResetList(true);
1037 0 : }
1038 :
1039 : void
1040 0 : nsListControlFrame::ResetList(bool aAllowScrolling)
1041 : {
1042 : // if all the frames aren't here
1043 : // don't bother reseting
1044 0 : if (!mIsAllFramesHere) {
1045 0 : return;
1046 : }
1047 :
1048 0 : if (aAllowScrolling) {
1049 0 : mPostChildrenLoadedReset = true;
1050 :
1051 : // Scroll to the selected index
1052 0 : int32_t indexToSelect = kNothingSelected;
1053 :
1054 0 : nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
1055 0 : NS_ASSERTION(selectElement, "No select element!");
1056 0 : if (selectElement) {
1057 0 : selectElement->GetSelectedIndex(&indexToSelect);
1058 0 : AutoWeakFrame weakFrame(this);
1059 0 : ScrollToIndex(indexToSelect);
1060 0 : if (!weakFrame.IsAlive()) {
1061 0 : return;
1062 : }
1063 : }
1064 : }
1065 :
1066 0 : mStartSelectionIndex = kNothingSelected;
1067 0 : mEndSelectionIndex = kNothingSelected;
1068 0 : InvalidateFocus();
1069 : // Combobox will redisplay itself with the OnOptionSelected event
1070 : }
1071 :
1072 : void
1073 0 : nsListControlFrame::SetFocus(bool aOn, bool aRepaint)
1074 : {
1075 0 : InvalidateFocus();
1076 :
1077 0 : if (aOn) {
1078 0 : ComboboxFocusSet();
1079 0 : mFocused = this;
1080 : } else {
1081 0 : mFocused = nullptr;
1082 : }
1083 :
1084 0 : InvalidateFocus();
1085 0 : }
1086 :
1087 0 : void nsListControlFrame::ComboboxFocusSet()
1088 : {
1089 0 : gLastKeyTime = 0;
1090 0 : }
1091 :
1092 : void
1093 0 : nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
1094 : {
1095 0 : if (nullptr != aComboboxFrame) {
1096 0 : mComboboxFrame = do_QueryFrame(aComboboxFrame);
1097 : }
1098 0 : }
1099 :
1100 : void
1101 0 : nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr)
1102 : {
1103 0 : aStr.Truncate();
1104 0 : if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
1105 0 : optionElement->GetText(aStr);
1106 : }
1107 0 : }
1108 :
1109 : int32_t
1110 0 : nsListControlFrame::GetSelectedIndex()
1111 : {
1112 : dom::HTMLSelectElement* select =
1113 0 : dom::HTMLSelectElement::FromContentOrNull(mContent);
1114 0 : return select->SelectedIndex();
1115 : }
1116 :
1117 : dom::HTMLOptionElement*
1118 0 : nsListControlFrame::GetCurrentOption()
1119 : {
1120 : // The mEndSelectionIndex is what is currently being selected. Use
1121 : // the selected index if this is kNothingSelected.
1122 0 : int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
1123 0 : GetSelectedIndex() : mEndSelectionIndex;
1124 :
1125 0 : if (focusedIndex != kNothingSelected) {
1126 0 : return GetOption(AssertedCast<uint32_t>(focusedIndex));
1127 : }
1128 :
1129 : // There is no selected option. Return the first non-disabled option, if any.
1130 0 : return GetNonDisabledOptionFrom(0);
1131 : }
1132 :
1133 : HTMLOptionElement*
1134 0 : nsListControlFrame::GetNonDisabledOptionFrom(int32_t aFromIndex,
1135 : int32_t* aFoundIndex)
1136 : {
1137 : RefPtr<dom::HTMLSelectElement> selectElement =
1138 0 : dom::HTMLSelectElement::FromContent(mContent);
1139 :
1140 0 : const uint32_t length = selectElement->Length();
1141 0 : for (uint32_t i = std::max(aFromIndex, 0); i < length; ++i) {
1142 0 : HTMLOptionElement* node = selectElement->Item(i);
1143 0 : if (!node) {
1144 0 : break;
1145 : }
1146 0 : if (!selectElement->IsOptionDisabled(node)) {
1147 0 : if (aFoundIndex) {
1148 0 : *aFoundIndex = i;
1149 : }
1150 0 : return node;
1151 : }
1152 : }
1153 0 : return nullptr;
1154 : }
1155 :
1156 : bool
1157 0 : nsListControlFrame::IsInDropDownMode() const
1158 : {
1159 0 : return (mComboboxFrame != nullptr);
1160 : }
1161 :
1162 : uint32_t
1163 0 : nsListControlFrame::GetNumberOfOptions()
1164 : {
1165 0 : dom::HTMLOptionsCollection* options = GetOptions();
1166 0 : if (!options) {
1167 0 : return 0;
1168 : }
1169 :
1170 0 : return options->Length();
1171 : }
1172 :
1173 : //----------------------------------------------------------------------
1174 : // nsISelectControlFrame
1175 : //----------------------------------------------------------------------
1176 0 : bool nsListControlFrame::CheckIfAllFramesHere()
1177 : {
1178 : // Get the number of optgroups and options
1179 : //int32_t numContentItems = 0;
1180 0 : nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
1181 0 : if (node) {
1182 : // XXX Need to find a fail proff way to determine that
1183 : // all the frames are there
1184 0 : mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems);
1185 : }
1186 : // now make sure we have a frame each piece of content
1187 :
1188 0 : return mIsAllFramesHere;
1189 : }
1190 :
1191 : NS_IMETHODIMP
1192 0 : nsListControlFrame::DoneAddingChildren(bool aIsDone)
1193 : {
1194 0 : mIsAllContentHere = aIsDone;
1195 0 : if (mIsAllContentHere) {
1196 : // Here we check to see if all the frames have been created
1197 : // for all the content.
1198 : // If so, then we can initialize;
1199 0 : if (!mIsAllFramesHere) {
1200 : // if all the frames are now present we can initialize
1201 0 : if (CheckIfAllFramesHere()) {
1202 0 : mHasBeenInitialized = true;
1203 0 : ResetList(true);
1204 : }
1205 : }
1206 : }
1207 0 : return NS_OK;
1208 : }
1209 :
1210 : NS_IMETHODIMP
1211 0 : nsListControlFrame::AddOption(int32_t aIndex)
1212 : {
1213 : #ifdef DO_REFLOW_DEBUG
1214 : printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
1215 : #endif
1216 :
1217 0 : if (!mIsAllContentHere) {
1218 0 : mIsAllContentHere = mContent->IsDoneAddingChildren();
1219 0 : if (!mIsAllContentHere) {
1220 0 : mIsAllFramesHere = false;
1221 0 : mHasBeenInitialized = false;
1222 : } else {
1223 0 : mIsAllFramesHere = (aIndex == static_cast<int32_t>(GetNumberOfOptions()-1));
1224 : }
1225 : }
1226 :
1227 : // Make sure we scroll to the selected option as needed
1228 0 : mNeedToReset = true;
1229 :
1230 0 : if (!mHasBeenInitialized) {
1231 0 : return NS_OK;
1232 : }
1233 :
1234 0 : mPostChildrenLoadedReset = mIsAllContentHere;
1235 0 : return NS_OK;
1236 : }
1237 :
1238 : static int32_t
1239 0 : DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength)
1240 : {
1241 0 : return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1);
1242 : }
1243 :
1244 : NS_IMETHODIMP
1245 0 : nsListControlFrame::RemoveOption(int32_t aIndex)
1246 : {
1247 0 : NS_PRECONDITION(aIndex >= 0, "negative <option> index");
1248 :
1249 : // Need to reset if we're a dropdown
1250 0 : if (IsInDropDownMode()) {
1251 0 : mNeedToReset = true;
1252 0 : mPostChildrenLoadedReset = mIsAllContentHere;
1253 : }
1254 :
1255 0 : if (mStartSelectionIndex != kNothingSelected) {
1256 0 : NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
1257 0 : int32_t numOptions = GetNumberOfOptions();
1258 : // NOTE: numOptions is the new number of options whereas aIndex is the
1259 : // unadjusted index of the removed option (hence the <= below).
1260 0 : NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
1261 :
1262 0 : int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
1263 0 : int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
1264 0 : int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
1265 0 : if (aIndex < *low)
1266 0 : *low = ::DecrementAndClamp(*low, numOptions);
1267 0 : if (aIndex <= *high)
1268 0 : *high = ::DecrementAndClamp(*high, numOptions);
1269 0 : if (forward == 0)
1270 0 : *low = *high;
1271 : }
1272 : else
1273 0 : NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
1274 :
1275 0 : InvalidateFocus();
1276 0 : return NS_OK;
1277 : }
1278 :
1279 : //---------------------------------------------------------
1280 : // Set the option selected in the DOM. This method is named
1281 : // as it is because it indicates that the frame is the source
1282 : // of this event rather than the receiver.
1283 : bool
1284 0 : nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
1285 : int32_t aEndIndex,
1286 : bool aValue,
1287 : bool aClearAll)
1288 : {
1289 : RefPtr<dom::HTMLSelectElement> selectElement =
1290 0 : dom::HTMLSelectElement::FromContent(mContent);
1291 :
1292 0 : uint32_t mask = dom::HTMLSelectElement::NOTIFY;
1293 0 : if (mForceSelection) {
1294 0 : mask |= dom::HTMLSelectElement::SET_DISABLED;
1295 : }
1296 0 : if (aValue) {
1297 0 : mask |= dom::HTMLSelectElement::IS_SELECTED;
1298 : }
1299 0 : if (aClearAll) {
1300 0 : mask |= dom::HTMLSelectElement::CLEAR_ALL;
1301 : }
1302 :
1303 0 : return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
1304 : }
1305 :
1306 : bool
1307 0 : nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex)
1308 : {
1309 : RefPtr<dom::HTMLOptionElement> option =
1310 0 : GetOption(static_cast<uint32_t>(aIndex));
1311 0 : NS_ENSURE_TRUE(option, false);
1312 :
1313 : RefPtr<dom::HTMLSelectElement> selectElement =
1314 0 : dom::HTMLSelectElement::FromContent(mContent);
1315 :
1316 0 : uint32_t mask = dom::HTMLSelectElement::NOTIFY;
1317 0 : if (!option->Selected()) {
1318 0 : mask |= dom::HTMLSelectElement::IS_SELECTED;
1319 : }
1320 :
1321 0 : return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
1322 : }
1323 :
1324 :
1325 : // Dispatch event and such
1326 : bool
1327 0 : nsListControlFrame::UpdateSelection()
1328 : {
1329 0 : if (mIsAllFramesHere) {
1330 : // if it's a combobox, display the new text
1331 0 : AutoWeakFrame weakFrame(this);
1332 0 : if (mComboboxFrame) {
1333 0 : mComboboxFrame->RedisplaySelectedText();
1334 :
1335 : // When dropdown list is open, onchange event will be fired when Enter key
1336 : // is hit or when dropdown list is dismissed.
1337 0 : if (mComboboxFrame->IsDroppedDown()) {
1338 0 : return weakFrame.IsAlive();
1339 : }
1340 : }
1341 0 : if (mIsAllContentHere) {
1342 0 : FireOnInputAndOnChange();
1343 : }
1344 0 : return weakFrame.IsAlive();
1345 : }
1346 0 : return true;
1347 : }
1348 :
1349 : void
1350 0 : nsListControlFrame::ComboboxFinish(int32_t aIndex)
1351 : {
1352 0 : gLastKeyTime = 0;
1353 :
1354 0 : if (mComboboxFrame) {
1355 0 : int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
1356 : // Make sure we can always reset to the displayed index
1357 0 : mForceSelection = displayIndex == aIndex;
1358 :
1359 0 : AutoWeakFrame weakFrame(this);
1360 0 : PerformSelection(aIndex, false, false); // might destroy us
1361 0 : if (!weakFrame.IsAlive() || !mComboboxFrame) {
1362 0 : return;
1363 : }
1364 :
1365 0 : if (displayIndex != aIndex) {
1366 0 : mComboboxFrame->RedisplaySelectedText(); // might destroy us
1367 : }
1368 :
1369 0 : if (weakFrame.IsAlive() && mComboboxFrame) {
1370 0 : mComboboxFrame->RollupFromList(); // might destroy us
1371 : }
1372 : }
1373 : }
1374 :
1375 : // Send out an onInput and onChange notification.
1376 : void
1377 0 : nsListControlFrame::FireOnInputAndOnChange()
1378 : {
1379 0 : if (mComboboxFrame) {
1380 : // Return hit without changing anything
1381 0 : int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1382 0 : if (index == NS_SKIP_NOTIFY_INDEX) {
1383 0 : return;
1384 : }
1385 :
1386 : // See if the selection actually changed
1387 0 : if (index == GetSelectedIndex()) {
1388 0 : return;
1389 : }
1390 : }
1391 :
1392 0 : nsCOMPtr<nsIContent> content = mContent;
1393 : // Dispatch the input event.
1394 0 : nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
1395 0 : NS_LITERAL_STRING("input"), true,
1396 0 : false);
1397 : // Dispatch the change event.
1398 0 : nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
1399 0 : NS_LITERAL_STRING("change"), true,
1400 0 : false);
1401 : }
1402 :
1403 : NS_IMETHODIMP
1404 0 : nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
1405 : {
1406 0 : if (mComboboxFrame) {
1407 : // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
1408 : // event for this setting of selectedIndex.
1409 0 : mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1410 : }
1411 :
1412 0 : AutoWeakFrame weakFrame(this);
1413 0 : ScrollToIndex(aNewIndex);
1414 0 : if (!weakFrame.IsAlive()) {
1415 0 : return NS_OK;
1416 : }
1417 0 : mStartSelectionIndex = aNewIndex;
1418 0 : mEndSelectionIndex = aNewIndex;
1419 0 : InvalidateFocus();
1420 :
1421 : #ifdef ACCESSIBILITY
1422 0 : FireMenuItemActiveEvent();
1423 : #endif
1424 :
1425 0 : return NS_OK;
1426 : }
1427 :
1428 : //----------------------------------------------------------------------
1429 : // End nsISelectControlFrame
1430 : //----------------------------------------------------------------------
1431 :
1432 : nsresult
1433 0 : nsListControlFrame::SetFormProperty(nsIAtom* aName,
1434 : const nsAString& aValue)
1435 : {
1436 0 : if (nsGkAtoms::selected == aName) {
1437 0 : return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
1438 0 : } else if (nsGkAtoms::selectedindex == aName) {
1439 : // You shouldn't be calling me for this!!!
1440 0 : return NS_ERROR_INVALID_ARG;
1441 : }
1442 :
1443 : // We should be told about selectedIndex by the DOM element through
1444 : // OnOptionSelected
1445 :
1446 0 : return NS_OK;
1447 : }
1448 :
1449 : void
1450 0 : nsListControlFrame::AboutToDropDown()
1451 : {
1452 0 : NS_ASSERTION(IsInDropDownMode(),
1453 : "AboutToDropDown called without being in dropdown mode");
1454 :
1455 : // Our widget doesn't get invalidated on changes to the rest of the document,
1456 : // so compute and store this color at the start of a dropdown so we don't
1457 : // get weird painting behaviour.
1458 : // We start looking for backgrounds above the combobox frame to avoid
1459 : // duplicating the combobox frame's background and compose each background
1460 : // color we find underneath until we have an opaque color, or run out of
1461 : // backgrounds. We compose with the PresContext default background color,
1462 : // which is always opaque, in case we don't end up with an opaque color.
1463 : // This gives us a very poor approximation of translucency.
1464 0 : nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
1465 0 : nsIFrame* ancestor = comboboxFrame->GetParent();
1466 0 : mLastDropdownBackstopColor = NS_RGBA(0,0,0,0);
1467 0 : while (NS_GET_A(mLastDropdownBackstopColor) < 255 && ancestor) {
1468 0 : nsStyleContext* context = ancestor->StyleContext();
1469 0 : mLastDropdownBackstopColor =
1470 0 : NS_ComposeColors(context->StyleBackground()->BackgroundColor(context),
1471 : mLastDropdownBackstopColor);
1472 0 : ancestor = ancestor->GetParent();
1473 : }
1474 0 : mLastDropdownBackstopColor =
1475 0 : NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
1476 : mLastDropdownBackstopColor);
1477 :
1478 0 : if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
1479 0 : AutoWeakFrame weakFrame(this);
1480 0 : ScrollToIndex(GetSelectedIndex());
1481 0 : if (!weakFrame.IsAlive()) {
1482 0 : return;
1483 : }
1484 : #ifdef ACCESSIBILITY
1485 0 : FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1486 : #endif
1487 : }
1488 0 : mItemSelectionStarted = false;
1489 0 : mForceSelection = false;
1490 : }
1491 :
1492 : // We are about to be rolledup from the outside (ComboboxFrame)
1493 : void
1494 0 : nsListControlFrame::AboutToRollup()
1495 : {
1496 : // We've been updating the combobox with the keyboard up until now, but not
1497 : // with the mouse. The problem is, even with mouse selection, we are
1498 : // updating the <select>. So if the mouse goes over an option just before
1499 : // he leaves the box and clicks, that's what the <select> will show.
1500 : //
1501 : // To deal with this we say "whatever is in the combobox is canonical."
1502 : // - IF the combobox is different from the current selected index, we
1503 : // reset the index.
1504 :
1505 0 : if (IsInDropDownMode()) {
1506 0 : ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
1507 : }
1508 0 : }
1509 :
1510 : void
1511 0 : nsListControlFrame::DidReflow(nsPresContext* aPresContext,
1512 : const ReflowInput* aReflowInput,
1513 : nsDidReflowStatus aStatus)
1514 : {
1515 0 : bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
1516 0 : aPresContext->HasPendingInterrupt();
1517 :
1518 0 : nsHTMLScrollFrame::DidReflow(aPresContext, aReflowInput, aStatus);
1519 :
1520 0 : if (mNeedToReset && !wasInterrupted) {
1521 0 : mNeedToReset = false;
1522 : // Suppress scrolling to the selected element if we restored
1523 : // scroll history state AND the list contents have not changed
1524 : // since we loaded all the children AND nothing else forced us
1525 : // to scroll by calling ResetList(true). The latter two conditions
1526 : // are folded into mPostChildrenLoadedReset.
1527 : //
1528 : // The idea is that we want scroll history restoration to trump ResetList
1529 : // scrolling to the selected element, when the ResetList was probably only
1530 : // caused by content loading normally.
1531 0 : ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
1532 : }
1533 :
1534 0 : mHasPendingInterruptAtStartOfReflow = false;
1535 0 : }
1536 :
1537 : #ifdef DEBUG_FRAME_DUMP
1538 : nsresult
1539 0 : nsListControlFrame::GetFrameName(nsAString& aResult) const
1540 : {
1541 0 : return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
1542 : }
1543 : #endif
1544 :
1545 : nscoord
1546 0 : nsListControlFrame::GetBSizeOfARow()
1547 : {
1548 0 : return BSizeOfARow();
1549 : }
1550 :
1551 : nsresult
1552 0 : nsListControlFrame::IsOptionDisabled(int32_t anIndex, bool &aIsDisabled)
1553 : {
1554 : RefPtr<dom::HTMLSelectElement> sel =
1555 0 : dom::HTMLSelectElement::FromContent(mContent);
1556 0 : if (sel) {
1557 0 : sel->IsOptionDisabled(anIndex, &aIsDisabled);
1558 0 : return NS_OK;
1559 : }
1560 0 : return NS_ERROR_FAILURE;
1561 : }
1562 :
1563 : //----------------------------------------------------------------------
1564 : // helper
1565 : //----------------------------------------------------------------------
1566 : bool
1567 0 : nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
1568 : {
1569 : // only allow selection with the left button
1570 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1571 0 : if (mouseEvent) {
1572 : int16_t whichButton;
1573 0 : if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
1574 0 : return whichButton != 0?false:true;
1575 : }
1576 : }
1577 0 : return false;
1578 : }
1579 :
1580 : nscoord
1581 0 : nsListControlFrame::CalcFallbackRowBSize(float aFontSizeInflation)
1582 : {
1583 : RefPtr<nsFontMetrics> fontMet =
1584 0 : nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
1585 0 : return fontMet->MaxHeight();
1586 : }
1587 :
1588 : nscoord
1589 0 : nsListControlFrame::CalcIntrinsicBSize(nscoord aBSizeOfARow,
1590 : int32_t aNumberOfOptions)
1591 : {
1592 0 : NS_PRECONDITION(!IsInDropDownMode(),
1593 : "Shouldn't be in dropdown mode when we call this");
1594 :
1595 : dom::HTMLSelectElement* select =
1596 0 : dom::HTMLSelectElement::FromContentOrNull(mContent);
1597 0 : if (select) {
1598 0 : mNumDisplayRows = select->Size();
1599 : } else {
1600 0 : mNumDisplayRows = 1;
1601 : }
1602 :
1603 0 : if (mNumDisplayRows < 1) {
1604 0 : mNumDisplayRows = 4;
1605 : }
1606 :
1607 0 : return mNumDisplayRows * aBSizeOfARow;
1608 : }
1609 :
1610 : //----------------------------------------------------------------------
1611 : // nsIDOMMouseListener
1612 : //----------------------------------------------------------------------
1613 : nsresult
1614 0 : nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
1615 : {
1616 0 : NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
1617 :
1618 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1619 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1620 :
1621 0 : UpdateInListState(aMouseEvent);
1622 :
1623 0 : mButtonDown = false;
1624 :
1625 0 : EventStates eventStates = mContent->AsElement()->State();
1626 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1627 0 : return NS_OK;
1628 : }
1629 :
1630 : // only allow selection with the left button
1631 : // if a right button click is on the combobox itself
1632 : // or on the select when in listbox mode, then let the click through
1633 0 : if (!IsLeftButton(aMouseEvent)) {
1634 0 : if (IsInDropDownMode()) {
1635 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1636 0 : aMouseEvent->PreventDefault();
1637 0 : aMouseEvent->StopPropagation();
1638 : } else {
1639 0 : CaptureMouseEvents(false);
1640 0 : return NS_OK;
1641 : }
1642 0 : CaptureMouseEvents(false);
1643 0 : return NS_ERROR_FAILURE; // means consume event
1644 : } else {
1645 0 : CaptureMouseEvents(false);
1646 0 : return NS_OK;
1647 : }
1648 : }
1649 :
1650 0 : const nsStyleVisibility* vis = StyleVisibility();
1651 :
1652 0 : if (!vis->IsVisible()) {
1653 0 : return NS_OK;
1654 : }
1655 :
1656 0 : if (IsInDropDownMode()) {
1657 : // XXX This is a bit of a hack, but.....
1658 : // But the idea here is to make sure you get an "onclick" event when you mouse
1659 : // down on the select and the drag over an option and let go
1660 : // And then NOT get an "onclick" event when when you click down on the select
1661 : // and then up outside of the select
1662 : // the EventStateManager tracks the content of the mouse down and the mouse up
1663 : // to make sure they are the same, and the onclick is sent in the PostHandleEvent
1664 : // depeneding on whether the clickCount is non-zero.
1665 : // So we cheat here by either setting or unsetting the clcikCount in the native event
1666 : // so the right thing happens for the onclick event
1667 : WidgetMouseEvent* mouseEvent =
1668 0 : aMouseEvent->WidgetEventPtr()->AsMouseEvent();
1669 :
1670 : int32_t selectedIndex;
1671 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1672 : // If it's disabled, disallow the click and leave.
1673 0 : bool isDisabled = false;
1674 0 : IsOptionDisabled(selectedIndex, isDisabled);
1675 0 : if (isDisabled) {
1676 0 : aMouseEvent->PreventDefault();
1677 0 : aMouseEvent->StopPropagation();
1678 0 : CaptureMouseEvents(false);
1679 0 : return NS_ERROR_FAILURE;
1680 : }
1681 :
1682 0 : if (kNothingSelected != selectedIndex) {
1683 0 : AutoWeakFrame weakFrame(this);
1684 0 : ComboboxFinish(selectedIndex);
1685 0 : if (!weakFrame.IsAlive()) {
1686 0 : return NS_OK;
1687 : }
1688 :
1689 0 : FireOnInputAndOnChange();
1690 : }
1691 :
1692 0 : mouseEvent->mClickCount = 1;
1693 : } else {
1694 : // the click was out side of the select or its dropdown
1695 0 : mouseEvent->mClickCount =
1696 0 : IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
1697 : }
1698 : } else {
1699 0 : CaptureMouseEvents(false);
1700 : // Notify
1701 0 : if (mChangesSinceDragStart) {
1702 : // reset this so that future MouseUps without a prior MouseDown
1703 : // won't fire onchange
1704 0 : mChangesSinceDragStart = false;
1705 0 : FireOnInputAndOnChange();
1706 : }
1707 : }
1708 :
1709 0 : return NS_OK;
1710 : }
1711 :
1712 : void
1713 0 : nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
1714 : {
1715 0 : if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
1716 0 : return;
1717 :
1718 0 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
1719 0 : nsRect borderInnerEdge = GetScrollPortRect();
1720 0 : if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
1721 0 : mItemSelectionStarted = true;
1722 : }
1723 : }
1724 :
1725 0 : bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
1726 : {
1727 0 : if (!mComboboxFrame)
1728 0 : return false;
1729 :
1730 : // Our DOM listener does get called when the dropdown is not
1731 : // showing, because it listens to events on the SELECT element
1732 0 : if (!mComboboxFrame->IsDroppedDown())
1733 0 : return true;
1734 :
1735 0 : return !mItemSelectionStarted;
1736 : }
1737 :
1738 : #ifdef ACCESSIBILITY
1739 : void
1740 0 : nsListControlFrame::FireMenuItemActiveEvent()
1741 : {
1742 0 : if (mFocused != this && !IsInDropDownMode()) {
1743 0 : return;
1744 : }
1745 :
1746 0 : nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
1747 0 : if (!optionContent) {
1748 0 : return;
1749 : }
1750 :
1751 0 : FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
1752 : }
1753 : #endif
1754 :
1755 : nsresult
1756 0 : nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent,
1757 : int32_t& aCurIndex)
1758 : {
1759 0 : if (IgnoreMouseEventForSelection(aMouseEvent))
1760 0 : return NS_ERROR_FAILURE;
1761 :
1762 0 : if (nsIPresShell::GetCapturingContent() != mContent) {
1763 : // If we're not capturing, then ignore movement in the border
1764 0 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
1765 0 : nsRect borderInnerEdge = GetScrollPortRect();
1766 0 : if (!borderInnerEdge.Contains(pt)) {
1767 0 : return NS_ERROR_FAILURE;
1768 : }
1769 : }
1770 :
1771 0 : RefPtr<dom::HTMLOptionElement> option;
1772 0 : for (nsCOMPtr<nsIContent> content =
1773 0 : PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
1774 0 : content && !option;
1775 0 : content = content->GetParent()) {
1776 0 : option = dom::HTMLOptionElement::FromContent(content);
1777 : }
1778 :
1779 0 : if (option) {
1780 0 : aCurIndex = option->Index();
1781 0 : MOZ_ASSERT(aCurIndex >= 0);
1782 0 : return NS_OK;
1783 : }
1784 :
1785 0 : return NS_ERROR_FAILURE;
1786 : }
1787 :
1788 : static bool
1789 0 : FireShowDropDownEvent(nsIContent* aContent, bool aShow, bool aIsSourceTouchEvent)
1790 : {
1791 0 : if (ShouldFireDropDownEvent()) {
1792 0 : nsString eventName;
1793 0 : if (aShow) {
1794 0 : eventName = aIsSourceTouchEvent ? NS_LITERAL_STRING("mozshowdropdown-sourcetouch") :
1795 0 : NS_LITERAL_STRING("mozshowdropdown");
1796 : } else {
1797 0 : eventName = NS_LITERAL_STRING("mozhidedropdown");
1798 : }
1799 0 : nsContentUtils::DispatchChromeEvent(aContent->OwnerDoc(), aContent,
1800 0 : eventName, true, false);
1801 0 : return true;
1802 : }
1803 :
1804 0 : return false;
1805 : }
1806 :
1807 : nsresult
1808 0 : nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
1809 : {
1810 0 : NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
1811 :
1812 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1813 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1814 :
1815 0 : UpdateInListState(aMouseEvent);
1816 :
1817 0 : EventStates eventStates = mContent->AsElement()->State();
1818 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1819 0 : return NS_OK;
1820 : }
1821 :
1822 : // only allow selection with the left button
1823 : // if a right button click is on the combobox itself
1824 : // or on the select when in listbox mode, then let the click through
1825 0 : if (!IsLeftButton(aMouseEvent)) {
1826 0 : if (IsInDropDownMode()) {
1827 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1828 0 : aMouseEvent->PreventDefault();
1829 0 : aMouseEvent->StopPropagation();
1830 : } else {
1831 0 : return NS_OK;
1832 : }
1833 0 : return NS_ERROR_FAILURE; // means consume event
1834 : } else {
1835 0 : return NS_OK;
1836 : }
1837 : }
1838 :
1839 : int32_t selectedIndex;
1840 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1841 : // Handle Like List
1842 0 : mButtonDown = true;
1843 0 : CaptureMouseEvents(true);
1844 0 : AutoWeakFrame weakFrame(this);
1845 : bool change =
1846 0 : HandleListSelection(aMouseEvent, selectedIndex); // might destroy us
1847 0 : if (!weakFrame.IsAlive()) {
1848 0 : return NS_OK;
1849 : }
1850 0 : mChangesSinceDragStart = change;
1851 : } else {
1852 : // NOTE: the combo box is responsible for dropping it down
1853 0 : if (mComboboxFrame) {
1854 : // Ignore the click that occurs on the option element when one is
1855 : // selected from the parent process popup.
1856 0 : if (mComboboxFrame->IsOpenInParentProcess()) {
1857 0 : nsCOMPtr<nsIDOMEventTarget> etarget;
1858 0 : aMouseEvent->GetTarget(getter_AddRefs(etarget));
1859 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(etarget);
1860 0 : if (option) {
1861 0 : return NS_OK;
1862 : }
1863 : }
1864 :
1865 0 : uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
1866 0 : if (NS_FAILED(mouseEvent->GetMozInputSource(&inputSource))) {
1867 0 : return NS_ERROR_FAILURE;
1868 : }
1869 0 : bool isSourceTouchEvent = inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
1870 0 : if (FireShowDropDownEvent(mContent, !mComboboxFrame->IsDroppedDownOrHasParentPopup(),
1871 : isSourceTouchEvent)) {
1872 0 : return NS_OK;
1873 : }
1874 :
1875 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1876 0 : return NS_OK;
1877 : }
1878 :
1879 0 : if (!nsComboboxControlFrame::ToolkitHasNativePopup())
1880 : {
1881 0 : bool isDroppedDown = mComboboxFrame->IsDroppedDown();
1882 0 : nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
1883 0 : AutoWeakFrame weakFrame(comboFrame);
1884 0 : mComboboxFrame->ShowDropDown(!isDroppedDown);
1885 0 : if (!weakFrame.IsAlive())
1886 0 : return NS_OK;
1887 0 : if (isDroppedDown) {
1888 0 : CaptureMouseEvents(false);
1889 : }
1890 : }
1891 : }
1892 : }
1893 :
1894 0 : return NS_OK;
1895 : }
1896 :
1897 : //----------------------------------------------------------------------
1898 : // nsIDOMMouseMotionListener
1899 : //----------------------------------------------------------------------
1900 : nsresult
1901 0 : nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
1902 : {
1903 0 : NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
1904 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1905 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1906 :
1907 0 : UpdateInListState(aMouseEvent);
1908 :
1909 0 : if (IsInDropDownMode()) {
1910 0 : if (mComboboxFrame->IsDroppedDown()) {
1911 : int32_t selectedIndex;
1912 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1913 0 : PerformSelection(selectedIndex, false, false); // might destroy us
1914 : }
1915 : }
1916 : } else {// XXX - temporary until we get drag events
1917 0 : if (mButtonDown) {
1918 0 : return DragMove(aMouseEvent); // might destroy us
1919 : }
1920 : }
1921 0 : return NS_OK;
1922 : }
1923 :
1924 : nsresult
1925 0 : nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
1926 : {
1927 0 : NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
1928 :
1929 0 : UpdateInListState(aMouseEvent);
1930 :
1931 0 : if (!IsInDropDownMode()) {
1932 : int32_t selectedIndex;
1933 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1934 : // Don't waste cycles if we already dragged over this item
1935 0 : if (selectedIndex == mEndSelectionIndex) {
1936 0 : return NS_OK;
1937 : }
1938 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1939 0 : NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
1940 : bool isControl;
1941 : #ifdef XP_MACOSX
1942 : mouseEvent->GetMetaKey(&isControl);
1943 : #else
1944 0 : mouseEvent->GetCtrlKey(&isControl);
1945 : #endif
1946 0 : AutoWeakFrame weakFrame(this);
1947 : // Turn SHIFT on when you are dragging, unless control is on.
1948 0 : bool wasChanged = PerformSelection(selectedIndex,
1949 0 : !isControl, isControl);
1950 0 : if (!weakFrame.IsAlive()) {
1951 0 : return NS_OK;
1952 : }
1953 0 : mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
1954 : }
1955 : }
1956 0 : return NS_OK;
1957 : }
1958 :
1959 : //----------------------------------------------------------------------
1960 : // Scroll helpers.
1961 : //----------------------------------------------------------------------
1962 : void
1963 0 : nsListControlFrame::ScrollToIndex(int32_t aIndex)
1964 : {
1965 0 : if (aIndex < 0) {
1966 : // XXX shouldn't we just do nothing if we're asked to scroll to
1967 : // kNothingSelected?
1968 0 : ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
1969 : } else {
1970 : RefPtr<dom::HTMLOptionElement> option =
1971 0 : GetOption(AssertedCast<uint32_t>(aIndex));
1972 0 : if (option) {
1973 0 : ScrollToFrame(*option);
1974 : }
1975 : }
1976 0 : }
1977 :
1978 : void
1979 0 : nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement)
1980 : {
1981 : // otherwise we find the content's frame and scroll to it
1982 0 : nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
1983 0 : if (childFrame) {
1984 0 : PresContext()->PresShell()->
1985 0 : ScrollFrameRectIntoView(childFrame,
1986 0 : nsRect(nsPoint(0, 0), childFrame->GetSize()),
1987 : nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
1988 : nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
1989 0 : nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
1990 : }
1991 0 : }
1992 :
1993 : //---------------------------------------------------------------------
1994 : // Ok, the entire idea of this routine is to move to the next item that
1995 : // is suppose to be selected. If the item is disabled then we search in
1996 : // the same direction looking for the next item to select. If we run off
1997 : // the end of the list then we start at the end of the list and search
1998 : // backwards until we get back to the original item or an enabled option
1999 : //
2000 : // aStartIndex - the index to start searching from
2001 : // aNewIndex - will get set to the new index if it finds one
2002 : // aNumOptions - the total number of options in the list
2003 : // aDoAdjustInc - the initial increment 1-n
2004 : // aDoAdjustIncNext - the increment used to search for the next enabled option
2005 : //
2006 : // the aDoAdjustInc could be a "1" for a single item or
2007 : // any number greater representing a page of items
2008 : //
2009 : void
2010 0 : nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex,
2011 : int32_t &aNewIndex,
2012 : int32_t aNumOptions,
2013 : int32_t aDoAdjustInc,
2014 : int32_t aDoAdjustIncNext)
2015 : {
2016 : // Cannot select anything if there is nothing to select
2017 0 : if (aNumOptions == 0) {
2018 0 : aNewIndex = kNothingSelected;
2019 0 : return;
2020 : }
2021 :
2022 : // means we reached the end of the list and now we are searching backwards
2023 0 : bool doingReverse = false;
2024 : // lowest index in the search range
2025 0 : int32_t bottom = 0;
2026 : // highest index in the search range
2027 0 : int32_t top = aNumOptions;
2028 :
2029 : // Start off keyboard options at selectedIndex if nothing else is defaulted to
2030 : //
2031 : // XXX Perhaps this should happen for mouse too, to start off shift click
2032 : // automatically in multiple ... to do this, we'd need to override
2033 : // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
2034 : // sure of the effects, though, so I'm not doing it just yet.
2035 0 : int32_t startIndex = aStartIndex;
2036 0 : if (startIndex < bottom) {
2037 0 : startIndex = GetSelectedIndex();
2038 : }
2039 0 : int32_t newIndex = startIndex + aDoAdjustInc;
2040 :
2041 : // make sure we start off in the range
2042 0 : if (newIndex < bottom) {
2043 0 : newIndex = 0;
2044 0 : } else if (newIndex >= top) {
2045 0 : newIndex = aNumOptions-1;
2046 : }
2047 :
2048 : while (1) {
2049 : // if the newIndex isn't disabled, we are golden, bail out
2050 0 : bool isDisabled = true;
2051 0 : if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
2052 0 : break;
2053 : }
2054 :
2055 : // it WAS disabled, so sart looking ahead for the next enabled option
2056 0 : newIndex += aDoAdjustIncNext;
2057 :
2058 : // well, if we reach end reverse the search
2059 0 : if (newIndex < bottom) {
2060 0 : if (doingReverse) {
2061 0 : return; // if we are in reverse mode and reach the end bail out
2062 : } else {
2063 : // reset the newIndex to the end of the list we hit
2064 : // reverse the incrementer
2065 : // set the other end of the list to our original starting index
2066 0 : newIndex = bottom;
2067 0 : aDoAdjustIncNext = 1;
2068 0 : doingReverse = true;
2069 0 : top = startIndex;
2070 : }
2071 0 : } else if (newIndex >= top) {
2072 0 : if (doingReverse) {
2073 0 : return; // if we are in reverse mode and reach the end bail out
2074 : } else {
2075 : // reset the newIndex to the end of the list we hit
2076 : // reverse the incrementer
2077 : // set the other end of the list to our original starting index
2078 0 : newIndex = top - 1;
2079 0 : aDoAdjustIncNext = -1;
2080 0 : doingReverse = true;
2081 0 : bottom = startIndex;
2082 : }
2083 : }
2084 0 : }
2085 :
2086 : // Looks like we found one
2087 0 : aNewIndex = newIndex;
2088 : }
2089 :
2090 : nsAString&
2091 0 : nsListControlFrame::GetIncrementalString()
2092 : {
2093 0 : if (sIncrementalString == nullptr)
2094 0 : sIncrementalString = new nsString();
2095 :
2096 0 : return *sIncrementalString;
2097 : }
2098 :
2099 : void
2100 0 : nsListControlFrame::Shutdown()
2101 : {
2102 0 : delete sIncrementalString;
2103 0 : sIncrementalString = nullptr;
2104 0 : }
2105 :
2106 : void
2107 0 : nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
2108 : {
2109 : // Cocoa widgets do native popups, so don't try to show
2110 : // dropdowns there.
2111 0 : if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2112 0 : aKeyEvent->PreventDefault();
2113 0 : if (!mComboboxFrame->IsDroppedDown()) {
2114 0 : if (!FireShowDropDownEvent(mContent, true, false)) {
2115 0 : mComboboxFrame->ShowDropDown(true);
2116 : }
2117 : } else {
2118 0 : AutoWeakFrame weakFrame(this);
2119 : // mEndSelectionIndex is the last item that got selected.
2120 0 : ComboboxFinish(mEndSelectionIndex);
2121 0 : if (weakFrame.IsAlive()) {
2122 0 : FireOnInputAndOnChange();
2123 : }
2124 : }
2125 : }
2126 0 : }
2127 :
2128 : nsresult
2129 0 : nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent)
2130 : {
2131 0 : MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
2132 :
2133 0 : EventStates eventStates = mContent->AsElement()->State();
2134 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
2135 0 : return NS_OK;
2136 : }
2137 :
2138 0 : AutoIncrementalSearchResetter incrementalSearchResetter;
2139 :
2140 : // Don't check defaultPrevented value because other browsers don't prevent
2141 : // the key navigation of list control even if preventDefault() is called.
2142 : // XXXmats 2015-04-16: the above is not true anymore, Chrome prevents all
2143 : // XXXmats keyboard events, even tabbing, when preventDefault() is called
2144 : // XXXmats in onkeydown. That seems sub-optimal though.
2145 :
2146 : const WidgetKeyboardEvent* keyEvent =
2147 0 : aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
2148 0 : MOZ_ASSERT(keyEvent,
2149 : "DOM event must have WidgetKeyboardEvent for its internal event");
2150 :
2151 : bool dropDownMenuOnUpDown;
2152 : bool dropDownMenuOnSpace;
2153 : #ifdef XP_MACOSX
2154 : dropDownMenuOnUpDown = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
2155 : dropDownMenuOnSpace = !keyEvent->IsAlt() && !keyEvent->IsControl() &&
2156 : !keyEvent->IsMeta();
2157 : #else
2158 0 : dropDownMenuOnUpDown = keyEvent->IsAlt();
2159 0 : dropDownMenuOnSpace = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
2160 : #endif
2161 : bool withinIncrementalSearchTime =
2162 0 : keyEvent->mTime - gLastKeyTime <= INCREMENTAL_SEARCH_KEYPRESS_TIME;
2163 0 : if ((dropDownMenuOnUpDown &&
2164 0 : (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN)) ||
2165 0 : (dropDownMenuOnSpace && keyEvent->mKeyCode == NS_VK_SPACE &&
2166 0 : !withinIncrementalSearchTime)) {
2167 0 : DropDownToggleKey(aKeyEvent);
2168 0 : if (keyEvent->DefaultPrevented()) {
2169 0 : return NS_OK;
2170 : }
2171 : }
2172 0 : if (keyEvent->IsAlt()) {
2173 0 : return NS_OK;
2174 : }
2175 :
2176 : // now make sure there are options or we are wasting our time
2177 0 : RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
2178 0 : NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
2179 :
2180 0 : uint32_t numOptions = options->Length();
2181 :
2182 : // this is the new index to set
2183 0 : int32_t newIndex = kNothingSelected;
2184 :
2185 0 : bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
2186 : // Don't try to handle multiple-select pgUp/pgDown in single-select lists.
2187 0 : if (isControlOrMeta && !GetMultiple() &&
2188 0 : (keyEvent->mKeyCode == NS_VK_PAGE_UP ||
2189 0 : keyEvent->mKeyCode == NS_VK_PAGE_DOWN)) {
2190 0 : return NS_OK;
2191 : }
2192 0 : if (isControlOrMeta && (keyEvent->mKeyCode == NS_VK_UP ||
2193 0 : keyEvent->mKeyCode == NS_VK_LEFT ||
2194 0 : keyEvent->mKeyCode == NS_VK_DOWN ||
2195 0 : keyEvent->mKeyCode == NS_VK_RIGHT ||
2196 0 : keyEvent->mKeyCode == NS_VK_HOME ||
2197 0 : keyEvent->mKeyCode == NS_VK_END)) {
2198 : // Don't go into multiple-select mode unless this list can handle it.
2199 0 : isControlOrMeta = mControlSelectMode = GetMultiple();
2200 0 : } else if (keyEvent->mKeyCode != NS_VK_SPACE) {
2201 0 : mControlSelectMode = false;
2202 : }
2203 :
2204 : // We should not change the selection if the popup is "opened
2205 : // in the parent process" (even when we're in single-process mode).
2206 0 : bool shouldSelectByKey = !mComboboxFrame ||
2207 0 : !mComboboxFrame->IsOpenInParentProcess();
2208 :
2209 0 : switch (keyEvent->mKeyCode) {
2210 : case NS_VK_UP:
2211 : case NS_VK_LEFT:
2212 0 : if (shouldSelectByKey) {
2213 0 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2214 : static_cast<int32_t>(numOptions),
2215 0 : -1, -1);
2216 : }
2217 0 : break;
2218 : case NS_VK_DOWN:
2219 : case NS_VK_RIGHT:
2220 0 : if (shouldSelectByKey) {
2221 0 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2222 : static_cast<int32_t>(numOptions),
2223 0 : 1, 1);
2224 : }
2225 0 : break;
2226 : case NS_VK_RETURN:
2227 0 : if (IsInDropDownMode()) {
2228 0 : if (mComboboxFrame->IsDroppedDown()) {
2229 : // If the select element is a dropdown style, Enter key should be
2230 : // consumed while the dropdown is open for security.
2231 0 : aKeyEvent->PreventDefault();
2232 :
2233 0 : AutoWeakFrame weakFrame(this);
2234 0 : ComboboxFinish(mEndSelectionIndex);
2235 0 : if (!weakFrame.IsAlive()) {
2236 0 : return NS_OK;
2237 : }
2238 : }
2239 0 : FireOnInputAndOnChange();
2240 0 : return NS_OK;
2241 : }
2242 :
2243 : // If this is single select listbox, Enter key doesn't cause anything.
2244 0 : if (!GetMultiple()) {
2245 0 : return NS_OK;
2246 : }
2247 :
2248 0 : newIndex = mEndSelectionIndex;
2249 0 : break;
2250 : case NS_VK_ESCAPE: {
2251 : // If the select element is a listbox style, Escape key causes nothing.
2252 0 : if (!IsInDropDownMode()) {
2253 0 : return NS_OK;
2254 : }
2255 :
2256 0 : AboutToRollup();
2257 : // If the select element is a dropdown style, Enter key should be
2258 : // consumed everytime since Escape key may be pressed accidentally after
2259 : // the dropdown is closed by Escepe key.
2260 0 : aKeyEvent->PreventDefault();
2261 0 : return NS_OK;
2262 : }
2263 : case NS_VK_PAGE_UP: {
2264 0 : if (shouldSelectByKey) {
2265 : int32_t itemsPerPage =
2266 0 : std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
2267 0 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2268 : static_cast<int32_t>(numOptions),
2269 0 : -itemsPerPage, -1);
2270 : }
2271 0 : break;
2272 : }
2273 : case NS_VK_PAGE_DOWN: {
2274 0 : if (shouldSelectByKey) {
2275 : int32_t itemsPerPage =
2276 0 : std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
2277 0 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2278 : static_cast<int32_t>(numOptions),
2279 0 : itemsPerPage, 1);
2280 : }
2281 0 : break;
2282 : }
2283 : case NS_VK_HOME:
2284 0 : if (shouldSelectByKey) {
2285 0 : AdjustIndexForDisabledOpt(0, newIndex,
2286 : static_cast<int32_t>(numOptions),
2287 0 : 0, 1);
2288 : }
2289 0 : break;
2290 : case NS_VK_END:
2291 0 : if (shouldSelectByKey) {
2292 0 : AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions) - 1, newIndex,
2293 : static_cast<int32_t>(numOptions),
2294 0 : 0, -1);
2295 : }
2296 0 : break;
2297 :
2298 : #if defined(XP_WIN)
2299 : case NS_VK_F4:
2300 : if (!isControlOrMeta) {
2301 : DropDownToggleKey(aKeyEvent);
2302 : }
2303 : return NS_OK;
2304 : #endif
2305 :
2306 : default: // printable key will be handled by keypress event.
2307 0 : incrementalSearchResetter.Cancel();
2308 0 : return NS_OK;
2309 : }
2310 :
2311 0 : aKeyEvent->PreventDefault();
2312 :
2313 : // Actually process the new index and let the selection code
2314 : // do the scrolling for us
2315 0 : PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta);
2316 0 : return NS_OK;
2317 : }
2318 :
2319 : nsresult
2320 0 : nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
2321 : {
2322 0 : MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
2323 :
2324 0 : EventStates eventStates = mContent->AsElement()->State();
2325 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
2326 0 : return NS_OK;
2327 : }
2328 :
2329 0 : AutoIncrementalSearchResetter incrementalSearchResetter;
2330 :
2331 : const WidgetKeyboardEvent* keyEvent =
2332 0 : aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
2333 0 : MOZ_ASSERT(keyEvent,
2334 : "DOM event must have WidgetKeyboardEvent for its internal event");
2335 :
2336 : // Select option with this as the first character
2337 : // XXX Not I18N compliant
2338 :
2339 : // Don't do incremental search if the key event has already consumed.
2340 0 : if (keyEvent->DefaultPrevented()) {
2341 0 : return NS_OK;
2342 : }
2343 :
2344 0 : if (keyEvent->IsAlt()) {
2345 0 : return NS_OK;
2346 : }
2347 :
2348 : // With some keyboard layout, space key causes non-ASCII space.
2349 : // So, the check in keydown event handler isn't enough, we need to check it
2350 : // again with keypress event.
2351 0 : if (keyEvent->mCharCode != ' ') {
2352 0 : mControlSelectMode = false;
2353 : }
2354 :
2355 0 : bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
2356 0 : if (isControlOrMeta && keyEvent->mCharCode != ' ') {
2357 0 : return NS_OK;
2358 : }
2359 :
2360 : // NOTE: If mKeyCode of keypress event is not 0, mCharCode is always 0.
2361 : // Therefore, all non-printable keys are not handled after this block.
2362 0 : if (!keyEvent->mCharCode) {
2363 : // Backspace key will delete the last char in the string. Otherwise,
2364 : // non-printable keypress should reset incremental search.
2365 0 : if (keyEvent->mKeyCode == NS_VK_BACK) {
2366 0 : incrementalSearchResetter.Cancel();
2367 0 : if (!GetIncrementalString().IsEmpty()) {
2368 0 : GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
2369 : }
2370 0 : aKeyEvent->PreventDefault();
2371 : } else {
2372 : // XXX When a select element has focus, even if the key causes nothing,
2373 : // it might be better to call preventDefault() here because nobody
2374 : // should expect one of other elements including chrome handles the
2375 : // key event.
2376 : }
2377 0 : return NS_OK;
2378 : }
2379 :
2380 0 : incrementalSearchResetter.Cancel();
2381 :
2382 : // We ate the key if we got this far.
2383 0 : aKeyEvent->PreventDefault();
2384 :
2385 : // XXX Why don't we check/modify timestamp first?
2386 :
2387 : // Incremental Search: if time elapsed is below
2388 : // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
2389 : // string we will use to find options and start searching at the current
2390 : // keystroke. Otherwise, Truncate the string if it's been a long time
2391 : // since our last keypress.
2392 0 : if (keyEvent->mTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
2393 : // If this is ' ' and we are at the beginning of the string, treat it as
2394 : // "select this option" (bug 191543)
2395 0 : if (keyEvent->mCharCode == ' ') {
2396 : // Actually process the new index and let the selection code
2397 : // do the scrolling for us
2398 0 : PostHandleKeyEvent(mEndSelectionIndex, keyEvent->mCharCode,
2399 0 : keyEvent->IsShift(), isControlOrMeta);
2400 :
2401 0 : return NS_OK;
2402 : }
2403 :
2404 0 : GetIncrementalString().Truncate();
2405 : }
2406 :
2407 0 : gLastKeyTime = keyEvent->mTime;
2408 :
2409 : // Append this keystroke to the search string.
2410 0 : char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->mCharCode));
2411 0 : GetIncrementalString().Append(uniChar);
2412 :
2413 : // See bug 188199, if all letters in incremental string are same, just try to
2414 : // match the first one
2415 0 : nsAutoString incrementalString(GetIncrementalString());
2416 0 : uint32_t charIndex = 1, stringLength = incrementalString.Length();
2417 0 : while (charIndex < stringLength &&
2418 0 : incrementalString[charIndex] == incrementalString[charIndex - 1]) {
2419 0 : charIndex++;
2420 : }
2421 0 : if (charIndex == stringLength) {
2422 0 : incrementalString.Truncate(1);
2423 0 : stringLength = 1;
2424 : }
2425 :
2426 : // Determine where we're going to start reading the string
2427 : // If we have multiple characters to look for, we start looking *at* the
2428 : // current option. If we have only one character to look for, we start
2429 : // looking *after* the current option.
2430 : // Exception: if there is no option selected to start at, we always start
2431 : // *at* 0.
2432 0 : int32_t startIndex = GetSelectedIndex();
2433 0 : if (startIndex == kNothingSelected) {
2434 0 : startIndex = 0;
2435 0 : } else if (stringLength == 1) {
2436 0 : startIndex++;
2437 : }
2438 :
2439 : // now make sure there are options or we are wasting our time
2440 0 : RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
2441 0 : NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
2442 :
2443 0 : uint32_t numOptions = options->Length();
2444 :
2445 0 : AutoWeakFrame weakFrame(this);
2446 0 : for (uint32_t i = 0; i < numOptions; ++i) {
2447 0 : uint32_t index = (i + startIndex) % numOptions;
2448 : RefPtr<dom::HTMLOptionElement> optionElement =
2449 0 : options->ItemAsOption(index);
2450 0 : if (!optionElement || !optionElement->GetPrimaryFrame()) {
2451 0 : continue;
2452 : }
2453 :
2454 0 : nsAutoString text;
2455 0 : if (NS_FAILED(optionElement->GetText(text)) ||
2456 0 : !StringBeginsWith(
2457 : nsContentUtils::TrimWhitespace<
2458 0 : nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
2459 0 : incrementalString, nsCaseInsensitiveStringComparator())) {
2460 0 : continue;
2461 : }
2462 :
2463 0 : bool wasChanged = PerformSelection(index, keyEvent->IsShift(), isControlOrMeta);
2464 0 : if (!weakFrame.IsAlive()) {
2465 0 : return NS_OK;
2466 : }
2467 0 : if (!wasChanged) {
2468 0 : break;
2469 : }
2470 :
2471 : // If UpdateSelection() returns false, that means the frame is no longer
2472 : // alive. We should stop doing anything.
2473 0 : if (!UpdateSelection()) {
2474 0 : return NS_OK;
2475 : }
2476 0 : break;
2477 : }
2478 :
2479 0 : return NS_OK;
2480 : }
2481 :
2482 : void
2483 0 : nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
2484 : uint32_t aCharCode,
2485 : bool aIsShift,
2486 : bool aIsControlOrMeta)
2487 : {
2488 0 : if (aNewIndex == kNothingSelected) {
2489 0 : int32_t focusedIndex = mEndSelectionIndex == kNothingSelected ?
2490 0 : GetSelectedIndex() : mEndSelectionIndex;
2491 0 : if (focusedIndex != kNothingSelected) {
2492 0 : return;
2493 : }
2494 : // No options are selected. In this case the focus ring is on the first
2495 : // non-disabled option (if any), so we should behave as if that's the option
2496 : // the user acted on.
2497 0 : if (!GetNonDisabledOptionFrom(0, &aNewIndex)) {
2498 0 : return;
2499 : }
2500 : }
2501 :
2502 : // If you hold control, but not shift, no key will actually do anything
2503 : // except space.
2504 0 : AutoWeakFrame weakFrame(this);
2505 0 : bool wasChanged = false;
2506 0 : if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
2507 0 : mStartSelectionIndex = aNewIndex;
2508 0 : mEndSelectionIndex = aNewIndex;
2509 0 : InvalidateFocus();
2510 0 : ScrollToIndex(aNewIndex);
2511 0 : if (!weakFrame.IsAlive()) {
2512 0 : return;
2513 : }
2514 :
2515 : #ifdef ACCESSIBILITY
2516 0 : FireMenuItemActiveEvent();
2517 : #endif
2518 0 : } else if (mControlSelectMode && aCharCode == ' ') {
2519 0 : wasChanged = SingleSelection(aNewIndex, true);
2520 : } else {
2521 0 : wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta);
2522 : }
2523 0 : if (wasChanged && weakFrame.IsAlive()) {
2524 : // dispatch event, update combobox, etc.
2525 0 : UpdateSelection();
2526 : }
2527 : }
2528 :
2529 :
2530 : /******************************************************************************
2531 : * nsListEventListener
2532 : *****************************************************************************/
2533 :
2534 0 : NS_IMPL_ISUPPORTS(nsListEventListener, nsIDOMEventListener)
2535 :
2536 : NS_IMETHODIMP
2537 0 : nsListEventListener::HandleEvent(nsIDOMEvent* aEvent)
2538 : {
2539 0 : if (!mFrame)
2540 0 : return NS_OK;
2541 :
2542 0 : nsAutoString eventType;
2543 0 : aEvent->GetType(eventType);
2544 0 : if (eventType.EqualsLiteral("keydown")) {
2545 0 : return mFrame->nsListControlFrame::KeyDown(aEvent);
2546 : }
2547 0 : if (eventType.EqualsLiteral("keypress")) {
2548 0 : return mFrame->nsListControlFrame::KeyPress(aEvent);
2549 : }
2550 0 : if (eventType.EqualsLiteral("mousedown")) {
2551 0 : bool defaultPrevented = false;
2552 0 : aEvent->GetDefaultPrevented(&defaultPrevented);
2553 0 : if (defaultPrevented) {
2554 0 : return NS_OK;
2555 : }
2556 0 : return mFrame->nsListControlFrame::MouseDown(aEvent);
2557 : }
2558 0 : if (eventType.EqualsLiteral("mouseup")) {
2559 : // Don't try to honor defaultPrevented here - it's not web compatible.
2560 : // (bug 1194733)
2561 0 : return mFrame->nsListControlFrame::MouseUp(aEvent);
2562 : }
2563 0 : if (eventType.EqualsLiteral("mousemove")) {
2564 : // I don't think we want to honor defaultPrevented on mousemove
2565 : // in general, and it would only prevent highlighting here.
2566 0 : return mFrame->nsListControlFrame::MouseMove(aEvent);
2567 : }
2568 :
2569 0 : NS_ABORT();
2570 0 : return NS_OK;
2571 : }
|