Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nscore.h"
8
#include "nsCOMPtr.h"
9
#include "nsUnicharUtils.h"
10
#include "nsListControlFrame.h"
11
#include "nsCheckboxRadioFrame.h" // for COMPARE macro
12
#include "nsGkAtoms.h"
13
#include "nsComboboxControlFrame.h"
14
#include "nsFontMetrics.h"
15
#include "nsIScrollableFrame.h"
16
#include "nsCSSRendering.h"
17
#include "nsIDOMEventListener.h"
18
#include "nsLayoutUtils.h"
19
#include "nsDisplayList.h"
20
#include "nsContentUtils.h"
21
#include "mozilla/Attributes.h"
22
#include "mozilla/dom/Event.h"
23
#include "mozilla/dom/HTMLOptGroupElement.h"
24
#include "mozilla/dom/HTMLOptionsCollection.h"
25
#include "mozilla/dom/HTMLSelectElement.h"
26
#include "mozilla/dom/MouseEvent.h"
27
#include "mozilla/dom/MouseEventBinding.h"
28
#include "mozilla/EventStateManager.h"
29
#include "mozilla/EventStates.h"
30
#include "mozilla/LookAndFeel.h"
31
#include "mozilla/MouseEvents.h"
32
#include "mozilla/Preferences.h"
33
#include "mozilla/PresShell.h"
34
#include "mozilla/StaticPrefs_browser.h"
35
#include "mozilla/TextEvents.h"
36
#include <algorithm>
37
38
using namespace mozilla;
39
using namespace mozilla::dom;
40
41
// Constants
42
const uint32_t kMaxDropDownRows = 20; // matches the setting for 4.x browsers
43
const int32_t kNothingSelected = -1;
44
45
// Static members
46
nsListControlFrame* nsListControlFrame::mFocused = nullptr;
47
nsString* nsListControlFrame::sIncrementalString = nullptr;
48
49
// Using for incremental typing navigation
50
#define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
51
// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
52
// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
53
// need to find a good place to put them together.
54
// if someone changes one, please also change the other.
55
56
DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
57
58
/******************************************************************************
59
* nsListEventListener
60
* This class is responsible for propagating events to the nsListControlFrame.
61
* Frames are not refcounted so they can't be used as event listeners.
62
*****************************************************************************/
63
64
class nsListEventListener final : public nsIDOMEventListener {
65
public:
66
explicit nsListEventListener(nsListControlFrame* aFrame) : mFrame(aFrame) {}
67
68
void SetFrame(nsListControlFrame* aFrame) { mFrame = aFrame; }
69
70
NS_DECL_ISUPPORTS
71
72
// nsIDOMEventListener
73
MOZ_CAN_RUN_SCRIPT_BOUNDARY
74
NS_IMETHOD HandleEvent(Event* aEvent) override;
75
76
private:
77
~nsListEventListener() {}
78
79
nsListControlFrame* mFrame;
80
};
81
82
//---------------------------------------------------------
83
nsContainerFrame* NS_NewListControlFrame(PresShell* aPresShell,
84
ComputedStyle* aStyle) {
85
nsListControlFrame* it =
86
new (aPresShell) nsListControlFrame(aStyle, aPresShell->GetPresContext());
87
88
it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
89
90
return it;
91
}
92
93
NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
94
95
//---------------------------------------------------------
96
nsListControlFrame::nsListControlFrame(ComputedStyle* aStyle,
97
nsPresContext* aPresContext)
98
: nsHTMLScrollFrame(aStyle, aPresContext, kClassID, false),
99
mView(nullptr),
100
mMightNeedSecondPass(false),
101
mHasPendingInterruptAtStartOfReflow(false),
102
mDropdownCanGrow(false),
103
mForceSelection(false),
104
mLastDropdownComputedBSize(NS_UNCONSTRAINEDSIZE) {
105
mComboboxFrame = nullptr;
106
mChangesSinceDragStart = false;
107
mButtonDown = false;
108
109
mIsAllContentHere = false;
110
mIsAllFramesHere = false;
111
mHasBeenInitialized = false;
112
mNeedToReset = true;
113
mPostChildrenLoadedReset = false;
114
115
mControlSelectMode = false;
116
}
117
118
//---------------------------------------------------------
119
nsListControlFrame::~nsListControlFrame() { mComboboxFrame = nullptr; }
120
121
static bool ShouldFireDropDownEvent() {
122
return (XRE_IsContentProcess() &&
123
StaticPrefs::browser_tabs_remote_desktopbehavior()) ||
124
Preferences::GetBool("dom.select_popup_in_parent.enabled", false);
125
}
126
127
// for Bug 47302 (remove this comment later)
128
void nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
129
PostDestroyData& aPostDestroyData) {
130
// get the receiver interface from the browser button's content node
131
NS_ENSURE_TRUE_VOID(mContent);
132
133
// Clear the frame pointer on our event listener, just in case the
134
// event listener can outlive the frame.
135
136
mEventListener->SetFrame(nullptr);
137
138
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
139
mEventListener, false);
140
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
141
mEventListener, false);
142
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
143
mEventListener, false);
144
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
145
mEventListener, false);
146
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
147
mEventListener, false);
148
149
if (ShouldFireDropDownEvent()) {
150
nsContentUtils::AddScriptRunner(
151
new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("mozhidedropdown"),
152
CanBubble::eYes, ChromeOnlyDispatch::eYes));
153
}
154
155
nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
156
nsHTMLScrollFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
157
}
158
159
void nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
160
const nsDisplayListSet& aLists) {
161
// We allow visibility:hidden <select>s to contain visible options.
162
163
// Don't allow painting of list controls when painting is suppressed.
164
// XXX why do we need this here? we should never reach this. Maybe
165
// because these can have widgets? Hmm
166
if (aBuilder->IsBackgroundOnly()) return;
167
168
DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
169
170
if (IsInDropDownMode()) {
171
NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
172
"need an opaque backstop color");
173
// XXX Because we have an opaque widget and we get called to paint with
174
// this frame as the root of a stacking context we need make sure to draw
175
// some opaque color over the whole widget. (Bug 511323)
176
aLists.BorderBackground()->AppendNewToBottom<nsDisplaySolidColor>(
177
aBuilder, this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
178
mLastDropdownBackstopColor);
179
}
180
181
nsHTMLScrollFrame::BuildDisplayList(aBuilder, aLists);
182
}
183
184
/**
185
* This is called by the SelectsAreaFrame, which is the same
186
* as the frame returned by GetOptionsContainer. It's the frame which is
187
* scrolled by us.
188
* @param aPt the offset of this frame, relative to the rendering reference
189
* frame
190
*/
191
void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt) {
192
if (mFocused != this) return;
193
194
nsPresContext* presContext = PresContext();
195
196
nsIFrame* containerFrame = GetOptionsContainer();
197
if (!containerFrame) return;
198
199
nsIFrame* childframe = nullptr;
200
nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
201
if (focusedContent) {
202
childframe = focusedContent->GetPrimaryFrame();
203
}
204
205
nsRect fRect;
206
if (childframe) {
207
// get the child rect
208
fRect = childframe->GetRect();
209
// get it into our coordinates
210
fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
211
} else {
212
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
213
fRect.x = fRect.y = 0;
214
if (GetWritingMode().IsVertical()) {
215
fRect.width = GetScrollPortRect().width;
216
fRect.height = CalcFallbackRowBSize(inflation);
217
} else {
218
fRect.width = CalcFallbackRowBSize(inflation);
219
fRect.height = GetScrollPortRect().height;
220
}
221
fRect.MoveBy(containerFrame->GetOffsetTo(this));
222
}
223
fRect += aPt;
224
225
bool lastItemIsSelected = false;
226
HTMLOptionElement* domOpt = HTMLOptionElement::FromNodeOrNull(focusedContent);
227
if (domOpt) {
228
lastItemIsSelected = domOpt->Selected();
229
}
230
231
// set up back stop colors and then ask L&F service for the real colors
232
nscolor color = LookAndFeel::GetColor(
233
lastItemIsSelected ? LookAndFeel::ColorID::WidgetSelectForeground
234
: LookAndFeel::ColorID::WidgetSelectBackground);
235
236
nsCSSRendering::PaintFocus(presContext, aDrawTarget, fRect, color);
237
}
238
239
void nsListControlFrame::InvalidateFocus() {
240
if (mFocused != this) return;
241
242
nsIFrame* containerFrame = GetOptionsContainer();
243
if (containerFrame) {
244
containerFrame->InvalidateFrame();
245
}
246
}
247
248
NS_QUERYFRAME_HEAD(nsListControlFrame)
249
NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
250
NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
251
NS_QUERYFRAME_ENTRY(nsListControlFrame)
252
NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
253
254
#ifdef ACCESSIBILITY
255
a11y::AccType nsListControlFrame::AccessibleType() {
256
return a11y::eHTMLSelectListType;
257
}
258
#endif
259
260
// Return true if we found at least one <option> or non-empty <optgroup> label
261
// that has a frame. aResult will be the maximum BSize of those.
262
static bool GetMaxRowBSize(nsIFrame* aContainer, WritingMode aWM,
263
nscoord* aResult) {
264
bool found = false;
265
for (nsIFrame* child : aContainer->PrincipalChildList()) {
266
if (child->GetContent()->IsHTMLElement(nsGkAtoms::optgroup)) {
267
// An optgroup; drill through any scroll frame and recurse. |inner| might
268
// be null here though if |inner| is an anonymous leaf frame of some sort.
269
auto inner = child->GetContentInsertionFrame();
270
if (inner && GetMaxRowBSize(inner, aWM, aResult)) {
271
found = true;
272
}
273
} else {
274
// an option or optgroup label
275
bool isOptGroupLabel =
276
child->Style()->IsPseudoElement() &&
277
aContainer->GetContent()->IsHTMLElement(nsGkAtoms::optgroup);
278
nscoord childBSize = child->BSize(aWM);
279
// XXX bug 1499176: skip empty <optgroup> labels (zero bsize) for now
280
if (!isOptGroupLabel || childBSize > nscoord(0)) {
281
found = true;
282
*aResult = std::max(childBSize, *aResult);
283
}
284
}
285
}
286
return found;
287
}
288
289
//-----------------------------------------------------------------
290
// Main Reflow for ListBox/Dropdown
291
//-----------------------------------------------------------------
292
293
nscoord nsListControlFrame::CalcBSizeOfARow() {
294
// Calculate the block size in our writing mode of a single row in the
295
// listbox or dropdown list by using the tallest thing in the subtree,
296
// since there may be option groups in addition to option elements,
297
// either of which may be visible or invisible, may use different
298
// fonts, etc.
299
nscoord rowBSize(0);
300
if (StyleDisplay()->IsContainSize() ||
301
!GetMaxRowBSize(GetOptionsContainer(), GetWritingMode(), &rowBSize)) {
302
// We don't have any <option>s or <optgroup> labels with a frame.
303
// (Or we're size-contained, which has the same outcome for our sizing.)
304
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
305
rowBSize = CalcFallbackRowBSize(inflation);
306
}
307
return rowBSize;
308
}
309
310
nscoord nsListControlFrame::GetPrefISize(gfxContext* aRenderingContext) {
311
nscoord result;
312
DISPLAY_PREF_INLINE_SIZE(this, result);
313
314
// Always add scrollbar inline sizes to the pref-inline-size of the
315
// scrolled content. Combobox frames depend on this happening in the
316
// dropdown, and standalone listboxes are overflow:scroll so they need
317
// it too.
318
WritingMode wm = GetWritingMode();
319
result = StyleDisplay()->IsContainSize()
320
? 0
321
: GetScrolledFrame()->GetPrefISize(aRenderingContext);
322
LogicalMargin scrollbarSize(
323
wm, GetDesiredScrollbarSizes(PresContext(), aRenderingContext));
324
result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm));
325
return result;
326
}
327
328
nscoord nsListControlFrame::GetMinISize(gfxContext* aRenderingContext) {
329
nscoord result;
330
DISPLAY_MIN_INLINE_SIZE(this, result);
331
332
// Always add scrollbar inline sizes to the min-inline-size of the
333
// scrolled content. Combobox frames depend on this happening in the
334
// dropdown, and standalone listboxes are overflow:scroll so they need
335
// it too.
336
WritingMode wm = GetWritingMode();
337
result = StyleDisplay()->IsContainSize()
338
? 0
339
: GetScrolledFrame()->GetMinISize(aRenderingContext);
340
LogicalMargin scrollbarSize(
341
wm, GetDesiredScrollbarSizes(PresContext(), aRenderingContext));
342
result += scrollbarSize.IStartEnd(wm);
343
344
return result;
345
}
346
347
void nsListControlFrame::Reflow(nsPresContext* aPresContext,
348
ReflowOutput& aDesiredSize,
349
const ReflowInput& aReflowInput,
350
nsReflowStatus& aStatus) {
351
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
352
NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
353
"Must have a computed inline size");
354
355
SchedulePaint();
356
357
mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
358
359
// If all the content and frames are here
360
// then initialize it before reflow
361
if (mIsAllContentHere && !mHasBeenInitialized) {
362
if (false == mIsAllFramesHere) {
363
CheckIfAllFramesHere();
364
}
365
if (mIsAllFramesHere && !mHasBeenInitialized) {
366
mHasBeenInitialized = true;
367
}
368
}
369
370
if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
371
nsCheckboxRadioFrame::RegUnRegAccessKey(this, true);
372
}
373
374
if (IsInDropDownMode()) {
375
ReflowAsDropdown(aPresContext, aDesiredSize, aReflowInput, aStatus);
376
return;
377
}
378
379
MarkInReflow();
380
/*
381
* Due to the fact that our intrinsic block size depends on the block
382
* sizes of our kids, we end up having to do two-pass reflow, in
383
* general -- the first pass to find the intrinsic block size and a
384
* second pass to reflow the scrollframe at that block size (which
385
* will size the scrollbars correctly, etc).
386
*
387
* Naturally, we want to avoid doing the second reflow as much as
388
* possible.
389
* We can skip it in the following cases (in all of which the first
390
* reflow is already happening at the right block size):
391
*
392
* - We're reflowing with a constrained computed block size -- just
393
* use that block size.
394
* - We're not dirty and have no dirty kids and shouldn't be reflowing
395
* all kids. In this case, our cached max block size of a child is
396
* not going to change.
397
* - We do our first reflow using our cached max block size of a
398
* child, then compute the new max block size and it's the same as
399
* the old one.
400
*/
401
402
bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
403
404
mMightNeedSecondPass = autoBSize && (NS_SUBTREE_DIRTY(this) ||
405
aReflowInput.ShouldReflowAllKids());
406
407
ReflowInput state(aReflowInput);
408
int32_t length = GetNumberOfRows();
409
410
nscoord oldBSizeOfARow = BSizeOfARow();
411
412
if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoBSize) {
413
// When not doing an initial reflow, and when the block size is
414
// auto, start off with our computed block size set to what we'd
415
// expect our block size to be.
416
nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
417
computedBSize = state.ApplyMinMaxBSize(computedBSize);
418
state.SetComputedBSize(computedBSize);
419
}
420
421
nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
422
423
if (!mMightNeedSecondPass) {
424
NS_ASSERTION(!autoBSize || BSizeOfARow() == oldBSizeOfARow,
425
"How did our BSize of a row change if nothing was dirty?");
426
NS_ASSERTION(!autoBSize || !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
427
"How do we not need a second pass during initial reflow at "
428
"auto BSize?");
429
NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
430
"Shouldn't be suppressing if we don't need a second pass!");
431
if (!autoBSize) {
432
// Update our mNumDisplayRows based on our new row block size now
433
// that we know it. Note that if autoBSize and we landed in this
434
// code then we already set mNumDisplayRows in CalcIntrinsicBSize.
435
// Also note that we can't use BSizeOfARow() here because that
436
// just uses a cached value that we didn't compute.
437
nscoord rowBSize = CalcBSizeOfARow();
438
if (rowBSize == 0) {
439
// Just pick something
440
mNumDisplayRows = 1;
441
} else {
442
mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
443
}
444
}
445
446
return;
447
}
448
449
mMightNeedSecondPass = false;
450
451
// Now see whether we need a second pass. If we do, our
452
// nsSelectsAreaFrame will have suppressed the scrollbar update.
453
if (!IsScrollbarUpdateSuppressed()) {
454
// All done. No need to do more reflow.
455
return;
456
}
457
458
SetSuppressScrollbarUpdate(false);
459
460
// Gotta reflow again.
461
// XXXbz We're just changing the block size here; do we need to dirty
462
// ourselves or anything like that? We might need to, per the letter
463
// of the reflow protocol, but things seem to work fine without it...
464
// Is that just an implementation detail of nsHTMLScrollFrame that
465
// we're depending on?
466
nsHTMLScrollFrame::DidReflow(aPresContext, &state);
467
468
// Now compute the block size we want to have
469
nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
470
computedBSize = state.ApplyMinMaxBSize(computedBSize);
471
state.SetComputedBSize(computedBSize);
472
473
// XXXbz to make the ascent really correct, we should add our
474
// mComputedPadding.top to it (and subtract it from descent). Need that
475
// because nsGfxScrollFrame just adds in the border....
476
aStatus.Reset();
477
nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
478
}
479
480
void nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext,
481
ReflowOutput& aDesiredSize,
482
const ReflowInput& aReflowInput,
483
nsReflowStatus& aStatus) {
484
MOZ_ASSERT(aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE,
485
"We should not have a computed block size here!");
486
487
mMightNeedSecondPass =
488
NS_SUBTREE_DIRTY(this) || aReflowInput.ShouldReflowAllKids();
489
490
WritingMode wm = aReflowInput.GetWritingMode();
491
#ifdef DEBUG
492
nscoord oldBSizeOfARow = BSizeOfARow();
493
nscoord oldVisibleBSize = (GetStateBits() & NS_FRAME_FIRST_REFLOW)
494
? NS_UNCONSTRAINEDSIZE
495
: GetScrolledFrame()->BSize(wm);
496
#endif
497
498
ReflowInput state(aReflowInput);
499
500
if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
501
// When not doing an initial reflow, and when the block size is
502
// auto, start off with our computed block size set to what we'd
503
// expect our block size to be.
504
// Note: At this point, mLastDropdownComputedBSize can be
505
// NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to
506
// constrain the block size. That's fine; just do the same thing as
507
// last time.
508
state.SetComputedBSize(mLastDropdownComputedBSize);
509
}
510
511
nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
512
513
if (!mMightNeedSecondPass) {
514
NS_ASSERTION(oldVisibleBSize == GetScrolledFrame()->BSize(wm),
515
"How did our kid's BSize change if nothing was dirty?");
516
NS_ASSERTION(BSizeOfARow() == oldBSizeOfARow,
517
"How did our BSize of a row change if nothing was dirty?");
518
NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
519
"Shouldn't be suppressing if we don't need a second pass!");
520
NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
521
"How can we avoid a second pass during first reflow?");
522
return;
523
}
524
525
mMightNeedSecondPass = false;
526
527
// Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
528
// will have suppressed the scrollbar update.
529
if (!IsScrollbarUpdateSuppressed()) {
530
// All done. No need to do more reflow.
531
NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
532
"How can we avoid a second pass during first reflow?");
533
return;
534
}
535
536
SetSuppressScrollbarUpdate(false);
537
538
nscoord visibleBSize = GetScrolledFrame()->BSize(wm);
539
nscoord blockSizeOfARow = BSizeOfARow();
540
541
// Gotta reflow again.
542
// XXXbz We're just changing the block size here; do we need to dirty
543
// ourselves or anything like that? We might need to, per the letter
544
// of the reflow protocol, but things seem to work fine without it...
545
// Is that just an implementation detail of nsHTMLScrollFrame that
546
// we're depending on?
547
nsHTMLScrollFrame::DidReflow(aPresContext, &state);
548
549
// Now compute the block size we want to have.
550
// Note: no need to apply min/max constraints, since we have no such
551
// rules applied to the combobox dropdown.
552
553
mDropdownCanGrow = false;
554
if (visibleBSize <= 0 || blockSizeOfARow <= 0 || XRE_IsContentProcess()) {
555
// Looks like we have no options. Just size us to a single row
556
// block size.
557
state.SetComputedBSize(blockSizeOfARow);
558
// mNumDisplayRows is used as the number of options to move for the page
559
// up/down keys. If we're in a content process, we can't calculate
560
// mNumDisplayRows properly, but the maximum number of rows is a lot more
561
// uesful for page up/down than 1.
562
mNumDisplayRows = XRE_IsContentProcess() ? kMaxDropDownRows : 1;
563
} else {
564
nsComboboxControlFrame* combobox =
565
static_cast<nsComboboxControlFrame*>(mComboboxFrame);
566
LogicalPoint translation(wm);
567
nscoord before, after;
568
combobox->GetAvailableDropdownSpace(wm, &before, &after, &translation);
569
if (before <= 0 && after <= 0) {
570
state.SetComputedBSize(blockSizeOfARow);
571
mNumDisplayRows = 1;
572
mDropdownCanGrow = GetNumberOfRows() > 1;
573
} else {
574
nscoord bp = aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
575
nscoord availableBSize = std::max(before, after) - bp;
576
nscoord newBSize;
577
uint32_t rows;
578
if (visibleBSize <= availableBSize) {
579
// The dropdown fits in the available block size.
580
rows = GetNumberOfRows();
581
mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
582
if (mNumDisplayRows == rows) {
583
newBSize = visibleBSize; // use the exact block size
584
} else {
585
newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
586
// The approximation here might actually be too big (bug 1208978);
587
// don't let it exceed the actual block-size of the list.
588
newBSize = std::min(newBSize, visibleBSize);
589
}
590
} else {
591
rows = availableBSize / blockSizeOfARow;
592
mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
593
newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
594
}
595
state.SetComputedBSize(newBSize);
596
mDropdownCanGrow = visibleBSize - newBSize >= blockSizeOfARow &&
597
mNumDisplayRows != kMaxDropDownRows;
598
}
599
}
600
601
mLastDropdownComputedBSize = state.ComputedBSize();
602
603
aStatus.Reset();
604
nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
605
}
606
607
ScrollStyles nsListControlFrame::GetScrollStyles() const {
608
// We can't express this in the style system yet; when we can, this can go
609
// away and GetScrollStyles can be devirtualized
610
auto style = IsInDropDownMode() ? StyleOverflow::Auto : StyleOverflow::Scroll;
611
if (GetWritingMode().IsVertical()) {
612
return ScrollStyles(style, StyleOverflow::Hidden);
613
} else {
614
return ScrollStyles(StyleOverflow::Hidden, style);
615
}
616
}
617
618
bool nsListControlFrame::ShouldPropagateComputedBSizeToScrolledContent() const {
619
return !IsInDropDownMode();
620
}
621
622
//---------------------------------------------------------
623
nsContainerFrame* nsListControlFrame::GetContentInsertionFrame() {
624
return GetOptionsContainer()->GetContentInsertionFrame();
625
}
626
627
//---------------------------------------------------------
628
bool nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
629
int32_t aEndIndex, bool aClearAll) {
630
return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex, true, aClearAll);
631
}
632
633
//---------------------------------------------------------
634
bool nsListControlFrame::SingleSelection(int32_t aClickedIndex,
635
bool aDoToggle) {
636
if (mComboboxFrame) {
637
mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
638
}
639
640
bool wasChanged = false;
641
// Get Current selection
642
if (aDoToggle) {
643
wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
644
} else {
645
wasChanged =
646
SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex, true, true);
647
}
648
AutoWeakFrame weakFrame(this);
649
ScrollToIndex(aClickedIndex);
650
if (!weakFrame.IsAlive()) {
651
return wasChanged;
652
}
653
654
#ifdef ACCESSIBILITY
655
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
656
#endif
657
mStartSelectionIndex = aClickedIndex;
658
mEndSelectionIndex = aClickedIndex;
659
InvalidateFocus();
660
661
#ifdef ACCESSIBILITY
662
if (isCurrentOptionChanged) {
663
FireMenuItemActiveEvent();
664
}
665
#endif
666
667
return wasChanged;
668
}
669
670
void nsListControlFrame::InitSelectionRange(int32_t aClickedIndex) {
671
//
672
// If nothing is selected, set the start selection depending on where
673
// the user clicked and what the initial selection is:
674
// - if the user clicked *before* selectedIndex, set the start index to
675
// the end of the first contiguous selection.
676
// - if the user clicked *after* the end of the first contiguous
677
// selection, set the start index to selectedIndex.
678
// - if the user clicked *within* the first contiguous selection, set the
679
// start index to selectedIndex.
680
// The last two rules, of course, boil down to the same thing: if the user
681
// clicked >= selectedIndex, return selectedIndex.
682
//
683
// This makes it so that shift click works properly when you first click
684
// in a multiple select.
685
//
686
int32_t selectedIndex = GetSelectedIndex();
687
if (selectedIndex >= 0) {
688
// Get the end of the contiguous selection
689
RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
690
NS_ASSERTION(options, "Collection of options is null!");
691
uint32_t numOptions = options->Length();
692
// Push i to one past the last selected index in the group.
693
uint32_t i;
694
for (i = selectedIndex + 1; i < numOptions; i++) {
695
if (!options->ItemAsOption(i)->Selected()) {
696
break;
697
}
698
}
699
700
if (aClickedIndex < selectedIndex) {
701
// User clicked before selection, so start selection at end of
702
// contiguous selection
703
mStartSelectionIndex = i - 1;
704
mEndSelectionIndex = selectedIndex;
705
} else {
706
// User clicked after selection, so start selection at start of
707
// contiguous selection
708
mStartSelectionIndex = selectedIndex;
709
mEndSelectionIndex = i - 1;
710
}
711
}
712
}
713
714
static uint32_t CountOptionsAndOptgroups(nsIFrame* aFrame) {
715
uint32_t count = 0;
716
nsFrameList::Enumerator e(aFrame->PrincipalChildList());
717
for (; !e.AtEnd(); e.Next()) {
718
nsIFrame* child = e.get();
719
nsIContent* content = child->GetContent();
720
if (content) {
721
if (content->IsHTMLElement(nsGkAtoms::option)) {
722
++count;
723
} else {
724
RefPtr<HTMLOptGroupElement> optgroup =
725
HTMLOptGroupElement::FromNode(content);
726
if (optgroup) {
727
nsAutoString label;
728
optgroup->GetLabel(label);
729
if (label.Length() > 0) {
730
++count;
731
}
732
count += CountOptionsAndOptgroups(child);
733
}
734
}
735
}
736
}
737
return count;
738
}
739
740
uint32_t nsListControlFrame::GetNumberOfRows() {
741
return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
742
}
743
744
//---------------------------------------------------------
745
bool nsListControlFrame::PerformSelection(int32_t aClickedIndex, bool aIsShift,
746
bool aIsControl) {
747
bool wasChanged = false;
748
749
if (aClickedIndex == kNothingSelected && !mForceSelection) {
750
// Ignore kNothingSelected unless the selection is forced
751
} else if (GetMultiple()) {
752
if (aIsShift) {
753
// Make sure shift+click actually does something expected when
754
// the user has never clicked on the select
755
if (mStartSelectionIndex == kNothingSelected) {
756
InitSelectionRange(aClickedIndex);
757
}
758
759
// Get the range from beginning (low) to end (high)
760
// Shift *always* works, even if the current option is disabled
761
int32_t startIndex;
762
int32_t endIndex;
763
if (mStartSelectionIndex == kNothingSelected) {
764
startIndex = aClickedIndex;
765
endIndex = aClickedIndex;
766
} else if (mStartSelectionIndex <= aClickedIndex) {
767
startIndex = mStartSelectionIndex;
768
endIndex = aClickedIndex;
769
} else {
770
startIndex = aClickedIndex;
771
endIndex = mStartSelectionIndex;
772
}
773
774
// Clear only if control was not pressed
775
wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
776
AutoWeakFrame weakFrame(this);
777
ScrollToIndex(aClickedIndex);
778
if (!weakFrame.IsAlive()) {
779
return wasChanged;
780
}
781
782
if (mStartSelectionIndex == kNothingSelected) {
783
mStartSelectionIndex = aClickedIndex;
784
}
785
#ifdef ACCESSIBILITY
786
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
787
#endif
788
mEndSelectionIndex = aClickedIndex;
789
InvalidateFocus();
790
791
#ifdef ACCESSIBILITY
792
if (isCurrentOptionChanged) {
793
FireMenuItemActiveEvent();
794
}
795
#endif
796
} else if (aIsControl) {
797
wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
798
} else {
799
wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
800
}
801
} else {
802
wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
803
}
804
805
return wasChanged;
806
}
807
808
//---------------------------------------------------------
809
bool nsListControlFrame::HandleListSelection(dom::Event* aEvent,
810
int32_t aClickedIndex) {
811
MouseEvent* mouseEvent = aEvent->AsMouseEvent();
812
bool isControl;
813
#ifdef XP_MACOSX
814
isControl = mouseEvent->MetaKey();
815
#else
816
isControl = mouseEvent->CtrlKey();
817
#endif
818
bool isShift = mouseEvent->ShiftKey();
819
return PerformSelection(aClickedIndex, isShift,
820
isControl); // might destroy us
821
}
822
823
//---------------------------------------------------------
824
void nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents) {
825
// Currently cocoa widgets use a native popup widget which tracks clicks
826
// synchronously, so we never want to do mouse capturing. Note that we only
827
// bail if the list is in drop-down mode, and the caller is requesting capture
828
// (we let release capture requests go through to ensure that we can release
829
// capture requested via other code paths, if any exist).
830
if (aGrabMouseEvents && IsInDropDownMode() &&
831
nsComboboxControlFrame::ToolkitHasNativePopup())
832
return;
833
834
if (aGrabMouseEvents) {
835
PresShell::SetCapturingContent(mContent, CaptureFlags::IgnoreAllowedState);
836
} else {
837
nsIContent* capturingContent = PresShell::GetCapturingContent();
838
839
bool dropDownIsHidden = false;
840
if (IsInDropDownMode()) {
841
dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
842
}
843
if (capturingContent == mContent || dropDownIsHidden) {
844
// only clear the capturing content if *we* are the ones doing the
845
// capturing (or if the dropdown is hidden, in which case NO-ONE should
846
// be capturing anything - it could be a scrollbar inside this listbox
847
// which is actually grabbing
848
// This shouldn't be necessary. We should simply ensure that events
849
// targeting scrollbars are never visible to DOM consumers.
850
PresShell::ReleaseCapturingContent();
851
}
852
}
853
}
854
855
//---------------------------------------------------------
856
nsresult nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
857
WidgetGUIEvent* aEvent,
858
nsEventStatus* aEventStatus) {
859
NS_ENSURE_ARG_POINTER(aEventStatus);
860
861
/*const char * desc[] = {"eMouseMove",
862
"NS_MOUSE_LEFT_BUTTON_UP",
863
"NS_MOUSE_LEFT_BUTTON_DOWN",
864
"<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
865
"NS_MOUSE_MIDDLE_BUTTON_UP",
866
"NS_MOUSE_MIDDLE_BUTTON_DOWN",
867
"<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
868
"NS_MOUSE_RIGHT_BUTTON_UP",
869
"NS_MOUSE_RIGHT_BUTTON_DOWN",
870
"eMouseOver",
871
"eMouseOut",
872
"NS_MOUSE_LEFT_DOUBLECLICK",
873
"NS_MOUSE_MIDDLE_DOUBLECLICK",
874
"NS_MOUSE_RIGHT_DOUBLECLICK",
875
"NS_MOUSE_LEFT_CLICK",
876
"NS_MOUSE_MIDDLE_CLICK",
877
"NS_MOUSE_RIGHT_CLICK"};
878
int inx = aEvent->mMessage - eMouseEventFirst;
879
if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK - eMouseEventFirst)) {
880
printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->mMessage);
881
} else {
882
printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->mMessage);
883
}*/
884
885
if (nsEventStatus_eConsumeNoDefault == *aEventStatus) return NS_OK;
886
887
// disabled state affects how we're selected, but we don't want to go through
888
// nsHTMLScrollFrame if we're disabled.
889
if (IsContentDisabled()) {
890
return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
891
}
892
893
return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
894
}
895
896
//---------------------------------------------------------
897
void nsListControlFrame::SetInitialChildList(ChildListID aListID,
898
nsFrameList& aChildList) {
899
if (aListID == kPrincipalList) {
900
// First check to see if all the content has been added
901
mIsAllContentHere = mContent->IsDoneAddingChildren();
902
if (!mIsAllContentHere) {
903
mIsAllFramesHere = false;
904
mHasBeenInitialized = false;
905
}
906
}
907
nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
908
909
// If all the content is here now check
910
// to see if all the frames have been created
911
/*if (mIsAllContentHere) {
912
// If all content and frames are here
913
// the reset/initialize
914
if (CheckIfAllFramesHere()) {
915
ResetList(aPresContext);
916
mHasBeenInitialized = true;
917
}
918
}*/
919
}
920
921
//---------------------------------------------------------
922
void nsListControlFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
923
nsIFrame* aPrevInFlow) {
924
nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
925
926
if (IsInDropDownMode()) {
927
AddStateBits(NS_FRAME_IN_POPUP);
928
CreateView();
929
}
930
931
// we shouldn't have to unregister this listener because when
932
// our frame goes away all these content node go away as well
933
// because our frame is the only one who references them.
934
// we need to hook up our listeners before the editor is initialized
935
mEventListener = new nsListEventListener(this);
936
937
mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mEventListener,
938
false, false);
939
mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
940
mEventListener, false, false);
941
mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
942
mEventListener, false, false);
943
mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), mEventListener,
944
false, false);
945
mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
946
mEventListener, false, false);
947
948
mStartSelectionIndex = kNothingSelected;
949
mEndSelectionIndex = kNothingSelected;
950
951
mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
952
}
953
954
dom::HTMLOptionsCollection* nsListControlFrame::GetOptions() const {
955
dom::HTMLSelectElement* select =
956
dom::HTMLSelectElement::FromNodeOrNull(mContent);
957
NS_ENSURE_TRUE(select, nullptr);
958
959
return select->Options();
960
}
961
962
dom::HTMLOptionElement* nsListControlFrame::GetOption(uint32_t aIndex) const {
963
dom::HTMLSelectElement* select =
964
dom::HTMLSelectElement::FromNodeOrNull(mContent);
965
NS_ENSURE_TRUE(select, nullptr);
966
967
return select->Item(aIndex);
968
}
969
970
NS_IMETHODIMP
971
nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) {
972
if (aSelected) {
973
ScrollToIndex(aIndex);
974
}
975
return NS_OK;
976
}
977
978
void nsListControlFrame::OnContentReset() { ResetList(true); }
979
980
void nsListControlFrame::ResetList(bool aAllowScrolling) {
981
// if all the frames aren't here
982
// don't bother reseting
983
if (!mIsAllFramesHere) {
984
return;
985
}
986
987
if (aAllowScrolling) {
988
mPostChildrenLoadedReset = true;
989
990
// Scroll to the selected index
991
int32_t indexToSelect = kNothingSelected;
992
993
HTMLSelectElement* selectElement = HTMLSelectElement::FromNode(mContent);
994
if (selectElement) {
995
indexToSelect = selectElement->SelectedIndex();
996
AutoWeakFrame weakFrame(this);
997
ScrollToIndex(indexToSelect);
998
if (!weakFrame.IsAlive()) {
999
return;
1000
}
1001
}
1002
}
1003
1004
mStartSelectionIndex = kNothingSelected;
1005
mEndSelectionIndex = kNothingSelected;
1006
InvalidateFocus();
1007
// Combobox will redisplay itself with the OnOptionSelected event
1008
}
1009
1010
void nsListControlFrame::SetFocus(bool aOn, bool aRepaint) {
1011
InvalidateFocus();
1012
1013
if (aOn) {
1014
ComboboxFocusSet();
1015
mFocused = this;
1016
} else {
1017
mFocused = nullptr;
1018
}
1019
1020
InvalidateFocus();
1021
}
1022
1023
void nsListControlFrame::ComboboxFocusSet() { gLastKeyTime = 0; }
1024
1025
void nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame) {
1026
if (nullptr != aComboboxFrame) {
1027
mComboboxFrame = do_QueryFrame(aComboboxFrame);
1028
}
1029
}
1030
1031
void nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr) {
1032
aStr.Truncate();
1033
if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
1034
optionElement->GetText(aStr);
1035
}
1036
}
1037
1038
int32_t nsListControlFrame::GetSelectedIndex() {
1039
dom::HTMLSelectElement* select =
1040
dom::HTMLSelectElement::FromNodeOrNull(mContent);
1041
return select->SelectedIndex();
1042
}
1043
1044
dom::HTMLOptionElement* nsListControlFrame::GetCurrentOption() {
1045
// The mEndSelectionIndex is what is currently being selected. Use
1046
// the selected index if this is kNothingSelected.
1047
int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected)
1048
? GetSelectedIndex()
1049
: mEndSelectionIndex;
1050
1051
if (focusedIndex != kNothingSelected) {
1052
return GetOption(AssertedCast<uint32_t>(focusedIndex));
1053
}
1054
1055
// There is no selected option. Return the first non-disabled option, if any.
1056
return GetNonDisabledOptionFrom(0);
1057
}
1058
1059
HTMLOptionElement* nsListControlFrame::GetNonDisabledOptionFrom(
1060
int32_t aFromIndex, int32_t* aFoundIndex) {
1061
RefPtr<dom::HTMLSelectElement> selectElement =
1062
dom::HTMLSelectElement::FromNode(mContent);
1063
1064
const uint32_t length = selectElement->Length();
1065
for (uint32_t i = std::max(aFromIndex, 0); i < length; ++i) {
1066
HTMLOptionElement* node = selectElement->Item(i);
1067
if (!node) {
1068
break;
1069
}
1070
if (IsOptionInteractivelySelectable(selectElement, node)) {
1071
if (aFoundIndex) {
1072
*aFoundIndex = i;
1073
}
1074
return node;
1075
}
1076
}
1077
return nullptr;
1078
}
1079
1080
bool nsListControlFrame::IsInDropDownMode() const {
1081
return (mComboboxFrame != nullptr);
1082
}
1083
1084
uint32_t nsListControlFrame::GetNumberOfOptions() {
1085
dom::HTMLOptionsCollection* options = GetOptions();
1086
if (!options) {
1087
return 0;
1088
}
1089
1090
return options->Length();
1091
}
1092
1093
//----------------------------------------------------------------------
1094
// nsISelectControlFrame
1095
//----------------------------------------------------------------------
1096
bool nsListControlFrame::CheckIfAllFramesHere() {
1097
// XXX Need to find a fail proof way to determine that
1098
// all the frames are there
1099
mIsAllFramesHere = true;
1100
1101
// now make sure we have a frame each piece of content
1102
1103
return mIsAllFramesHere;
1104
}
1105
1106
NS_IMETHODIMP
1107
nsListControlFrame::DoneAddingChildren(bool aIsDone) {
1108
mIsAllContentHere = aIsDone;
1109
if (mIsAllContentHere) {
1110
// Here we check to see if all the frames have been created
1111
// for all the content.
1112
// If so, then we can initialize;
1113
if (!mIsAllFramesHere) {
1114
// if all the frames are now present we can initialize
1115
if (CheckIfAllFramesHere()) {
1116
mHasBeenInitialized = true;
1117
ResetList(true);
1118
}
1119
}
1120
}
1121
return NS_OK;
1122
}
1123
1124
NS_IMETHODIMP
1125
nsListControlFrame::AddOption(int32_t aIndex) {
1126
#ifdef DO_REFLOW_DEBUG
1127
printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
1128
#endif
1129
1130
if (!mIsAllContentHere) {
1131
mIsAllContentHere = mContent->IsDoneAddingChildren();
1132
if (!mIsAllContentHere) {
1133
mIsAllFramesHere = false;
1134
mHasBeenInitialized = false;
1135
} else {
1136
mIsAllFramesHere =
1137
(aIndex == static_cast<int32_t>(GetNumberOfOptions() - 1));
1138
}
1139
}
1140
1141
// Make sure we scroll to the selected option as needed
1142
mNeedToReset = true;
1143
1144
if (!mHasBeenInitialized) {
1145
return NS_OK;
1146
}
1147
1148
mPostChildrenLoadedReset = mIsAllContentHere;
1149
return NS_OK;
1150
}
1151
1152
static int32_t DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength) {
1153
return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1);
1154
}
1155
1156
NS_IMETHODIMP
1157
nsListControlFrame::RemoveOption(int32_t aIndex) {
1158
MOZ_ASSERT(aIndex >= 0, "negative <option> index");
1159
1160
// Need to reset if we're a dropdown
1161
if (IsInDropDownMode()) {
1162
mNeedToReset = true;
1163
mPostChildrenLoadedReset = mIsAllContentHere;
1164
}
1165
1166
if (mStartSelectionIndex != kNothingSelected) {
1167
NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
1168
int32_t numOptions = GetNumberOfOptions();
1169
// NOTE: numOptions is the new number of options whereas aIndex is the
1170
// unadjusted index of the removed option (hence the <= below).
1171
NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
1172
1173
int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
1174
int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
1175
int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
1176
if (aIndex < *low) *low = ::DecrementAndClamp(*low, numOptions);
1177
if (aIndex <= *high) *high = ::DecrementAndClamp(*high, numOptions);
1178
if (forward == 0) *low = *high;
1179
} else
1180
NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
1181
1182
InvalidateFocus();
1183
return NS_OK;
1184
}
1185
1186
//---------------------------------------------------------
1187
// Set the option selected in the DOM. This method is named
1188
// as it is because it indicates that the frame is the source
1189
// of this event rather than the receiver.
1190
bool nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
1191
int32_t aEndIndex,
1192
bool aValue,
1193
bool aClearAll) {
1194
RefPtr<dom::HTMLSelectElement> selectElement =
1195
dom::HTMLSelectElement::FromNode(mContent);
1196
1197
uint32_t mask = dom::HTMLSelectElement::NOTIFY;
1198
if (mForceSelection) {
1199
mask |= dom::HTMLSelectElement::SET_DISABLED;
1200
}
1201
if (aValue) {
1202
mask |= dom::HTMLSelectElement::IS_SELECTED;
1203
}
1204
if (aClearAll) {
1205
mask |= dom::HTMLSelectElement::CLEAR_ALL;
1206
}
1207
1208
return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
1209
}
1210
1211
bool nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex) {
1212
RefPtr<dom::HTMLOptionElement> option =
1213
GetOption(static_cast<uint32_t>(aIndex));
1214
NS_ENSURE_TRUE(option, false);
1215
1216
RefPtr<dom::HTMLSelectElement> selectElement =
1217
dom::HTMLSelectElement::FromNode(mContent);
1218
1219
uint32_t mask = dom::HTMLSelectElement::NOTIFY;
1220
if (!option->Selected()) {
1221
mask |= dom::HTMLSelectElement::IS_SELECTED;
1222
}
1223
1224
return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
1225
}
1226
1227
// Dispatch event and such
1228
bool nsListControlFrame::UpdateSelection() {
1229
if (mIsAllFramesHere) {
1230
// if it's a combobox, display the new text
1231
AutoWeakFrame weakFrame(this);
1232
if (mComboboxFrame) {
1233
mComboboxFrame->RedisplaySelectedText();
1234
1235
// When dropdown list is open, onchange event will be fired when Enter key
1236
// is hit or when dropdown list is dismissed.
1237
if (mComboboxFrame->IsDroppedDown()) {
1238
return weakFrame.IsAlive();
1239
}
1240
}
1241
if (mIsAllContentHere) {
1242
FireOnInputAndOnChange();
1243
}
1244
return weakFrame.IsAlive();
1245
}
1246
return true;
1247
}
1248
1249
void nsListControlFrame::ComboboxFinish(int32_t aIndex) {
1250
gLastKeyTime = 0;
1251
1252
if (mComboboxFrame) {
1253
int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
1254
// Make sure we can always reset to the displayed index
1255
mForceSelection = displayIndex == aIndex;
1256
1257
AutoWeakFrame weakFrame(this);
1258
PerformSelection(aIndex, false, false); // might destroy us
1259
if (!weakFrame.IsAlive() || !mComboboxFrame) {
1260
return;
1261
}
1262
1263
if (displayIndex != aIndex) {
1264
mComboboxFrame->RedisplaySelectedText(); // might destroy us
1265
}
1266
1267
if (weakFrame.IsAlive() && mComboboxFrame) {
1268
mComboboxFrame->RollupFromList(); // might destroy us
1269
}
1270
}
1271
}
1272
1273
// Send out an onInput and onChange notification.
1274
void nsListControlFrame::FireOnInputAndOnChange() {
1275
if (mComboboxFrame) {
1276
// Return hit without changing anything
1277
int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1278
if (index == NS_SKIP_NOTIFY_INDEX) {
1279
return;
1280
}
1281
1282
// See if the selection actually changed
1283
if (index == GetSelectedIndex()) {
1284
return;
1285
}
1286
}
1287
1288
RefPtr<Element> element = Element::FromNodeOrNull(mContent);
1289
if (NS_WARN_IF(!element)) {
1290
return;
1291
}
1292
// Dispatch the input event.
1293
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(element);
1294
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1295
"Failed to dispatch input event");
1296
1297
// Dispatch the change event.
1298
nsContentUtils::DispatchTrustedEvent(element->OwnerDoc(), element,
1299
NS_LITERAL_STRING("change"),
1300
CanBubble::eYes, Cancelable::eNo);
1301
}
1302
1303
NS_IMETHODIMP_(void)
1304
nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) {
1305
if (mComboboxFrame) {
1306
// UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an
1307
// onchange event for this setting of selectedIndex.
1308
mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1309
}
1310
1311
AutoWeakFrame weakFrame(this);
1312
ScrollToIndex(aNewIndex);
1313
if (!weakFrame.IsAlive()) {
1314
return;
1315
}
1316
mStartSelectionIndex = aNewIndex;
1317
mEndSelectionIndex = aNewIndex;
1318
InvalidateFocus();
1319
1320
#ifdef ACCESSIBILITY
1321
FireMenuItemActiveEvent();
1322
#endif
1323
}
1324
1325
//----------------------------------------------------------------------
1326
// End nsISelectControlFrame
1327
//----------------------------------------------------------------------
1328
1329
nsresult nsListControlFrame::SetFormProperty(nsAtom* aName,
1330
const nsAString& aValue) {
1331
if (nsGkAtoms::selected == aName) {
1332
return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
1333
} else if (nsGkAtoms::selectedindex == aName) {
1334
// You shouldn't be calling me for this!!!
1335
return NS_ERROR_INVALID_ARG;
1336
}
1337
1338
// We should be told about selectedIndex by the DOM element through
1339
// OnOptionSelected
1340
1341
return NS_OK;
1342
}
1343
1344
void nsListControlFrame::AboutToDropDown() {
1345
NS_ASSERTION(IsInDropDownMode(),
1346
"AboutToDropDown called without being in dropdown mode");
1347
1348
// Our widget doesn't get invalidated on changes to the rest of the document,
1349
// so compute and store this color at the start of a dropdown so we don't
1350
// get weird painting behaviour.
1351
// We start looking for backgrounds above the combobox frame to avoid
1352
// duplicating the combobox frame's background and compose each background
1353
// color we find underneath until we have an opaque color, or run out of
1354
// backgrounds. We compose with the PresContext default background color,
1355
// which is always opaque, in case we don't end up with an opaque color.
1356
// This gives us a very poor approximation of translucency.
1357
nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
1358
nsIFrame* ancestor = comboboxFrame->GetParent();
1359
mLastDropdownBackstopColor = NS_RGBA(0, 0, 0, 0);
1360
while (NS_GET_A(mLastDropdownBackstopColor) < 255 && ancestor) {
1361
ComputedStyle* context = ancestor->Style();
1362
mLastDropdownBackstopColor =
1363
NS_ComposeColors(context->StyleBackground()->BackgroundColor(context),
1364
mLastDropdownBackstopColor);
1365
ancestor = ancestor->GetParent();
1366
}
1367
mLastDropdownBackstopColor = NS_ComposeColors(
1368
PresContext()->DefaultBackgroundColor(), mLastDropdownBackstopColor);
1369
1370
if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
1371
AutoWeakFrame weakFrame(this);
1372
ScrollToIndex(GetSelectedIndex());
1373
if (!weakFrame.IsAlive()) {
1374
return;
1375
}
1376
#ifdef ACCESSIBILITY
1377
FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1378
#endif
1379
}
1380
mItemSelectionStarted = false;
1381
mForceSelection = false;
1382
}
1383
1384
// We are about to be rolledup from the outside (ComboboxFrame)
1385
void nsListControlFrame::AboutToRollup() {
1386
// We've been updating the combobox with the keyboard up until now, but not
1387
// with the mouse. The problem is, even with mouse selection, we are
1388
// updating the <select>. So if the mouse goes over an option just before
1389
// he leaves the box and clicks, that's what the <select> will show.
1390
//
1391
// To deal with this we say "whatever is in the combobox is canonical."
1392
// - IF the combobox is different from the current selected index, we
1393
// reset the index.
1394
1395
if (IsInDropDownMode()) {
1396
ComboboxFinish(
1397
mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
1398
}
1399
}
1400
1401
void nsListControlFrame::DidReflow(nsPresContext* aPresContext,
1402
const ReflowInput* aReflowInput) {
1403
bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
1404
aPresContext->HasPendingInterrupt();
1405
1406
nsHTMLScrollFrame::DidReflow(aPresContext, aReflowInput);
1407
1408
if (mNeedToReset && !wasInterrupted) {
1409
mNeedToReset = false;
1410
// Suppress scrolling to the selected element if we restored
1411
// scroll history state AND the list contents have not changed
1412
// since we loaded all the children AND nothing else forced us
1413
// to scroll by calling ResetList(true). The latter two conditions
1414
// are folded into mPostChildrenLoadedReset.
1415
//
1416
// The idea is that we want scroll history restoration to trump ResetList
1417
// scrolling to the selected element, when the ResetList was probably only
1418
// caused by content loading normally.
1419
ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
1420
}
1421
1422
mHasPendingInterruptAtStartOfReflow = false;
1423
}
1424
1425
#ifdef DEBUG_FRAME_DUMP
1426
nsresult nsListControlFrame::GetFrameName(nsAString& aResult) const {
1427
return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
1428
}
1429
#endif
1430
1431
nscoord nsListControlFrame::GetBSizeOfARow() { return BSizeOfARow(); }
1432
1433
bool nsListControlFrame::IsOptionInteractivelySelectable(int32_t aIndex) const {
1434
if (HTMLSelectElement* sel = HTMLSelectElement::FromNode(mContent)) {
1435
if (HTMLOptionElement* item = sel->Item(aIndex)) {
1436
return IsOptionInteractivelySelectable(sel, item);
1437
}
1438
}
1439
return false;
1440
}
1441
1442
bool nsListControlFrame::IsOptionInteractivelySelectable(
1443
HTMLSelectElement* aSelect, HTMLOptionElement* aOption) {
1444
return !aSelect->IsOptionDisabled(aOption) && aOption->GetPrimaryFrame();
1445
}
1446
1447
//----------------------------------------------------------------------
1448
// helper
1449
//----------------------------------------------------------------------
1450
bool nsListControlFrame::IsLeftButton(dom::Event* aMouseEvent) {
1451
// only allow selection with the left button
1452
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
1453
return mouseEvent && mouseEvent->Button() == 0;
1454
}
1455
1456
nscoord nsListControlFrame::CalcFallbackRowBSize(float aFontSizeInflation) {
1457
RefPtr<nsFontMetrics> fontMet =
1458
nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
1459
return fontMet->MaxHeight();
1460
}
1461
1462
nscoord nsListControlFrame::CalcIntrinsicBSize(nscoord aBSizeOfARow,
1463
int32_t aNumberOfOptions) {
1464
MOZ_ASSERT(!IsInDropDownMode(),
1465
"Shouldn't be in dropdown mode when we call this");
1466
1467
dom::HTMLSelectElement* select =
1468
dom::HTMLSelectElement::FromNodeOrNull(mContent);
1469
if (select) {
1470
mNumDisplayRows = select->Size();
1471
} else {
1472
mNumDisplayRows = 1;
1473
}
1474
1475
if (mNumDisplayRows < 1) {
1476
mNumDisplayRows = 4;
1477
}
1478
1479
return mNumDisplayRows * aBSizeOfARow;
1480
}
1481
1482
//----------------------------------------------------------------------
1483
// nsIDOMMouseListener
1484
//----------------------------------------------------------------------
1485
nsresult nsListControlFrame::MouseUp(dom::Event* aMouseEvent) {
1486
NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
1487
1488
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
1489
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1490
1491
UpdateInListState(aMouseEvent);
1492
1493
mButtonDown = false;
1494
1495
EventStates eventStates = mContent->AsElement()->State();
1496
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1497
return NS_OK;
1498
}
1499
1500
// only allow selection with the left button
1501
// if a right button click is on the combobox itself
1502
// or on the select when in listbox mode, then let the click through
1503
if (!IsLeftButton(aMouseEvent)) {
1504
if (IsInDropDownMode()) {
1505
if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1506
aMouseEvent->PreventDefault();
1507
aMouseEvent->StopPropagation();
1508
} else {
1509
CaptureMouseEvents(false);
1510
return NS_OK;
1511
}
1512
CaptureMouseEvents(false);
1513
return NS_ERROR_FAILURE; // means consume event
1514
} else {
1515
CaptureMouseEvents(false);
1516
return NS_OK;
1517
}
1518
}
1519
1520
const nsStyleVisibility* vis = StyleVisibility();
1521
1522
if (!vis->IsVisible()) {
1523
return NS_OK;
1524
}
1525
1526
if (IsInDropDownMode()) {
1527
// XXX This is a bit of a hack, but.....
1528
// But the idea here is to make sure you get an "onclick" event when you
1529
// mouse down on the select and the drag over an option and let go And then
1530
// NOT get an "onclick" event when when you click down on the select and
1531
// then up outside of the select the EventStateManager tracks the content of
1532
// the mouse down and the mouse up to make sure they are the same, and the
1533
// onclick is sent in the PostHandleEvent depeneding on whether the
1534
// clickCount is non-zero. So we cheat here by either setting or unsetting
1535
// the clcikCount in the native event so the right thing happens for the
1536
// onclick event
1537
WidgetMouseEvent* mouseEvent =
1538
aMouseEvent->WidgetEventPtr()->AsMouseEvent();
1539
1540
int32_t selectedIndex;
1541
if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1542
// If it's not selectable, disallow the click and leave.
1543
if (!IsOptionInteractivelySelectable(selectedIndex)) {
1544
aMouseEvent->PreventDefault();
1545
aMouseEvent->StopPropagation();
1546
CaptureMouseEvents(false);
1547
return NS_ERROR_FAILURE;
1548
}
1549
1550
if (kNothingSelected != selectedIndex) {
1551
AutoWeakFrame weakFrame(this);
1552
ComboboxFinish(selectedIndex);
1553
if (!weakFrame.IsAlive()) {
1554
return NS_OK;
1555
}
1556
1557
FireOnInputAndOnChange();
1558
}
1559
1560
mouseEvent->mClickCount = 1;
1561
} else {
1562
// the click was out side of the select or its dropdown
1563
mouseEvent->mClickCount =
1564
IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
1565
}
1566
} else {
1567
CaptureMouseEvents(false);
1568
// Notify
1569
if (mChangesSinceDragStart) {
1570
// reset this so that future MouseUps without a prior MouseDown
1571
// won't fire onchange
1572
mChangesSinceDragStart = false;
1573
FireOnInputAndOnChange();
1574
}
1575
}
1576
1577
return NS_OK;
1578
}
1579
1580
void nsListControlFrame::UpdateInListState(dom::Event* aEvent) {
1581
if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown()) return;
1582
1583
nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
1584
nsRect borderInnerEdge = GetScrollPortRect();
1585
if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
1586
mItemSelectionStarted = true;
1587
}
1588
}
1589
1590
bool nsListControlFrame::IgnoreMouseEventForSelection(dom::Event* aEvent) {
1591
if (!mComboboxFrame) return false;
1592
1593
// Our DOM listener does get called when the dropdown is not
1594
// showing, because it listens to events on the SELECT element
1595
if (!mComboboxFrame->IsDroppedDown()) return true;
1596
1597
return !mItemSelectionStarted;
1598
}
1599
1600
#ifdef ACCESSIBILITY
1601
void nsListControlFrame::FireMenuItemActiveEvent() {
1602
if (mFocused != this && !IsInDropDownMode()) {
1603
return;
1604
}
1605
1606
nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
1607
if (!optionContent) {
1608
return;
1609
}
1610
1611
FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
1612
}
1613
#endif
1614
1615
nsresult nsListControlFrame::GetIndexFromDOMEvent(dom::Event* aMouseEvent,
1616
int32_t& aCurIndex) {
1617
if (IgnoreMouseEventForSelection(aMouseEvent)) return NS_ERROR_FAILURE;
1618
1619
if (PresShell::GetCapturingContent() != mContent) {
1620
// If we're not capturing, then ignore movement in the border
1621
nsPoint pt =
1622
nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
1623
nsRect borderInnerEdge = GetScrollPortRect();
1624
if (!borderInnerEdge.Contains(pt)) {
1625
return NS_ERROR_FAILURE;
1626
}
1627
}
1628
1629
RefPtr<dom::HTMLOptionElement> option;
1630
for (nsCOMPtr<nsIContent> content =
1631
PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
1632
content && !option; content = content->GetParent()) {
1633
option = dom::HTMLOptionElement::FromNode(content);
1634
}
1635
1636
if (option) {
1637
aCurIndex = option->Index();
1638
MOZ_ASSERT(aCurIndex >= 0);
1639
return NS_OK;
1640
}
1641
1642
return NS_ERROR_FAILURE;
1643
}
1644
1645
static bool FireShowDropDownEvent(nsIContent* aContent, bool aShow,
1646
bool aIsSourceTouchEvent) {
1647
if (ShouldFireDropDownEvent()) {
1648
nsString eventName;
1649
if (aShow) {
1650
eventName = aIsSourceTouchEvent
1651
? NS_LITERAL_STRING("mozshowdropdown-sourcetouch")
1652
: NS_LITERAL_STRING("mozshowdropdown");
1653
} else {
1654
eventName = NS_LITERAL_STRING("mozhidedropdown");
1655
}
1656
nsContentUtils::DispatchChromeEvent(aContent->OwnerDoc(), aContent,
1657
eventName, CanBubble::eYes,
1658
Cancelable::eNo);
1659
return true;
1660
}
1661
1662
return false;
1663
}
1664
1665
nsresult nsListControlFrame::MouseDown(dom::Event* aMouseEvent) {
1666
NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
1667
1668
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
1669
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1670
1671
UpdateInListState(aMouseEvent);
1672
1673
EventStates eventStates = mContent->AsElement()->State();
1674
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1675
return NS_OK;
1676
}
1677
1678
// only allow selection with the left button
1679
// if a right button click is on the combobox itself
1680
// or on the select when in listbox mode, then let the click through
1681
if (!IsLeftButton(aMouseEvent)) {
1682
if (IsInDropDownMode()) {
1683
if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1684
aMouseEvent->PreventDefault();
1685
aMouseEvent->StopPropagation();
1686
} else {
1687
return NS_OK;
1688
}
1689
return NS_ERROR_FAILURE; // means consume event
1690
} else {
1691
return NS_OK;
1692
}
1693
}
1694
1695
int32_t selectedIndex;
1696
if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1697
// Handle Like List
1698
mButtonDown = true;
1699
CaptureMouseEvents(true);
1700
AutoWeakFrame weakFrame(this);
1701
bool change =
1702
HandleListSelection(aMouseEvent, selectedIndex); // might destroy us
1703
if (!weakFrame.IsAlive()) {
1704
return NS_OK;
1705
}
1706
mChangesSinceDragStart = change;
1707
} else {
1708
// NOTE: the combo box is responsible for dropping it down
1709
if (mComboboxFrame) {
1710
// Ignore the click that occurs on the option element when one is
1711
// selected from the parent process popup.
1712
if (mComboboxFrame->IsOpenInParentProcess()) {
1713
nsCOMPtr<nsIContent> econtent =
1714
do_QueryInterface(aMouseEvent->GetTarget());
1715
HTMLOptionElement* option = HTMLOptionElement::FromNodeOrNull(econtent);
1716
if (option) {
1717
return NS_OK;
1718
}
1719
}
1720
1721
uint16_t inputSource = mouseEvent->MozInputSource();
1722
bool isSourceTouchEvent =
1723
inputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH;
1724
if (FireShowDropDownEvent(
1725
mContent, !mComboboxFrame->IsDroppedDownOrHasParentPopup(),
1726
isSourceTouchEvent)) {
1727
return NS_OK;
1728
}
1729
1730
if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1731
return NS_OK;
1732
}
1733
1734
if (!nsComboboxControlFrame::ToolkitHasNativePopup()) {
1735
bool isDroppedDown = mComboboxFrame->IsDroppedDown();
1736
nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
1737
AutoWeakFrame weakFrame(comboFrame);
1738
mComboboxFrame->ShowDropDown(!isDroppedDown);
1739
if (!weakFrame.IsAlive()) return NS_OK;
1740
if (isDroppedDown) {
1741
CaptureMouseEvents(false);
1742
}
1743
}
1744
}
1745
}
1746
1747
return NS_OK;
1748
}
1749
1750
nsresult nsListControlFrame::MouseMove(dom::Event* aMouseEvent) {
1751
NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
1752
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
1753
NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1754
1755
UpdateInListState(aMouseEvent);
1756
1757
if (IsInDropDownMode()) {
1758
if (mComboboxFrame->IsDroppedDown()) {
1759
int32_t selectedIndex;
1760
if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1761
PerformSelection(selectedIndex, false, false); // might destroy us
1762
}
1763
}
1764
} else { // XXX - temporary until we get drag events
1765
if (mButtonDown) {
1766
return DragMove(aMouseEvent); // might destroy us
1767
}
1768
}
1769
return NS_OK;
1770
}
1771
1772
nsresult nsListControlFrame::DragMove(dom::Event* aMouseEvent) {
1773
NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
1774
1775
UpdateInListState(aMouseEvent);
1776
1777
if (!IsInDropDownMode()) {
1778
int32_t selectedIndex;
1779
if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1780
// Don't waste cycles if we already dragged over this item
1781
if (selectedIndex == mEndSelectionIndex) {
1782
return NS_OK;
1783
}
1784
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
1785
NS_ASSERTION(mouseEvent, "aMouseEvent is not a MouseEvent!");
1786
bool isControl;
1787
#ifdef XP_MACOSX
1788
isControl = mouseEvent->MetaKey();
1789
#else
1790
isControl = mouseEvent->CtrlKey();
1791
#endif
1792
AutoWeakFrame weakFrame(this);
1793
// Turn SHIFT on when you are dragging, unless control is on.
1794
bool wasChanged = PerformSelection(selectedIndex, !isControl, isControl);
1795
if (!weakFrame.IsAlive()) {
1796
return NS_OK;
1797
}
1798
mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
1799
}
1800
}
1801
return NS_OK;
1802
}
1803
1804
//----------------------------------------------------------------------
1805
// Scroll helpers.
1806
//----------------------------------------------------------------------
1807
void nsListControlFrame::ScrollToIndex(int32_t aIndex) {
1808
if (aIndex < 0) {
1809
// XXX shouldn't we just do nothing if we're asked to scroll to
1810
// kNothingSelected?
1811
ScrollTo(nsPoint(0, 0), ScrollMode::Instant);
1812
} else {
1813
RefPtr<dom::HTMLOptionElement> option =
1814
GetOption(AssertedCast<uint32_t>(aIndex));
1815
if (option) {
1816
ScrollToFrame(*option);
1817
}
1818
}
1819
}
1820
1821
void nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement) {
1822
// otherwise we find the content's frame and scroll to it
1823
nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
1824
if (childFrame) {
1825
RefPtr<mozilla::PresShell> presShell = PresShell();
1826
presShell->ScrollFrameRectIntoView(
1827
childFrame, nsRect(nsPoint(0, 0), childFrame->GetSize()), ScrollAxis(),
1828
ScrollAxis(),
1829
ScrollFlags::ScrollOverflowHidden |
1830
ScrollFlags::ScrollFirstAncestorOnly |
1831
ScrollFlags::IgnoreMarginAndPadding);
1832
}
1833
}
1834
1835
//---------------------------------------------------------------------
1836
// Ok, the entire idea of this routine is to move to the next item that
1837
// is suppose to be selected. If the item is disabled then we search in
1838
// the same direction looking for the next item to select. If we run off
1839
// the end of the list then we start at the end of the list and search
1840
// backwards until we get back to the original item or an enabled option
1841
//
1842
// aStartIndex - the index to start searching from
1843
// aNewIndex - will get set to the new index if it finds one
1844
// aNumOptions - the total number of options in the list
1845
// aDoAdjustInc - the initial increment 1-n
1846
// aDoAdjustIncNext - the increment used to search for the next enabled option
1847
//
1848
// the aDoAdjustInc could be a "1" for a single item or
1849
// any number greater representing a page of items
1850
//
1851
void nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex,
1852
int32_t& aNewIndex,
1853
int32_t aNumOptions,
1854
int32_t aDoAdjustInc,
1855
int32_t aDoAdjustIncNext) {
1856
// Cannot select anything if there is nothing to select
1857
if (aNumOptions == 0) {
1858
aNewIndex = kNothingSelected;
1859
return;
1860
}
1861
1862
// means we reached the end of the list and now we are searching backwards
1863
bool doingReverse = false;
1864
// lowest index in the search range
1865
int32_t bottom = 0;
1866
// highest index in the search range
1867
int32_t top = aNumOptions;
1868
1869
// Start off keyboard options at selectedIndex if nothing else is defaulted to
1870
//
1871
// XXX Perhaps this should happen for mouse too, to start off shift click
1872
// automatically in multiple ... to do this, we'd need to override
1873
// OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
1874
// sure of the effects, though, so I'm not doing it just yet.
1875
int32_t startIndex = aStartIndex;
1876
if (startIndex < bottom) {
1877
startIndex = GetSelectedIndex();
1878
}
1879
int32_t newIndex = startIndex + aDoAdjustInc;
1880
1881
// make sure we start off in the range
1882
if (newIndex < bottom) {
1883
newIndex = 0;
1884
} else if (newIndex >= top) {
1885
newIndex = aNumOptions - 1;
1886
}
1887
1888
while (1) {
1889
// if the newIndex is selectable, we are golden, bail out
1890
if (IsOptionInteractivelySelectable(newIndex)) {
1891
break;
1892
}
1893
1894
// it WAS disabled, so sart looking ahead for the next enabled option
1895
newIndex += aDoAdjustIncNext;
1896
1897
// well, if we reach end reverse the search
1898
if (newIndex < bottom) {
1899
if (doingReverse) {
1900
return; // if we are in reverse mode and reach the end bail out
1901
} else {
1902
// reset the newIndex to the end of the list we hit
1903
// reverse the incrementer
1904
// set the other end of the list to our original starting index
1905
newIndex = bottom;
1906
aDoAdjustIncNext = 1;
1907
doingReverse = true;
1908
top = startIndex;
1909
}
1910
} else if (newIndex >= top) {
1911
if (doingReverse) {
1912
return; // if we are in reverse mode and reach the end bail out
1913
} else {
1914
// reset the newIndex to the end of the list we hit
1915
// reverse the incrementer
1916
// set the other end of the list to our original starting index
1917
newIndex = top - 1;
1918
aDoAdjustIncNext = -1;
1919
doingReverse = true;