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