1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/forms/nsListControlFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2453 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nscore.h" 1.10 +#include "nsCOMPtr.h" 1.11 +#include "nsUnicharUtils.h" 1.12 +#include "nsListControlFrame.h" 1.13 +#include "nsFormControlFrame.h" // for COMPARE macro 1.14 +#include "nsGkAtoms.h" 1.15 +#include "nsIDOMHTMLSelectElement.h" 1.16 +#include "nsIDOMHTMLOptionElement.h" 1.17 +#include "nsComboboxControlFrame.h" 1.18 +#include "nsIDOMHTMLOptGroupElement.h" 1.19 +#include "nsIPresShell.h" 1.20 +#include "nsIDOMMouseEvent.h" 1.21 +#include "nsIXULRuntime.h" 1.22 +#include "nsFontMetrics.h" 1.23 +#include "nsIScrollableFrame.h" 1.24 +#include "nsCSSRendering.h" 1.25 +#include "nsIDOMEventListener.h" 1.26 +#include "nsLayoutUtils.h" 1.27 +#include "nsDisplayList.h" 1.28 +#include "nsContentUtils.h" 1.29 +#include "mozilla/Attributes.h" 1.30 +#include "mozilla/dom/HTMLOptionsCollection.h" 1.31 +#include "mozilla/dom/HTMLSelectElement.h" 1.32 +#include "mozilla/EventStateManager.h" 1.33 +#include "mozilla/EventStates.h" 1.34 +#include "mozilla/LookAndFeel.h" 1.35 +#include "mozilla/MouseEvents.h" 1.36 +#include "mozilla/Preferences.h" 1.37 +#include "mozilla/TextEvents.h" 1.38 +#include <algorithm> 1.39 + 1.40 +using namespace mozilla; 1.41 + 1.42 +// Constants 1.43 +const uint32_t kMaxDropDownRows = 20; // This matches the setting for 4.x browsers 1.44 +const int32_t kNothingSelected = -1; 1.45 + 1.46 +// Static members 1.47 +nsListControlFrame * nsListControlFrame::mFocused = nullptr; 1.48 +nsString * nsListControlFrame::sIncrementalString = nullptr; 1.49 + 1.50 +// Using for incremental typing navigation 1.51 +#define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000 1.52 +// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: 1.53 +// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml 1.54 +// need to find a good place to put them together. 1.55 +// if someone changes one, please also change the other. 1.56 + 1.57 +DOMTimeStamp nsListControlFrame::gLastKeyTime = 0; 1.58 + 1.59 +/****************************************************************************** 1.60 + * nsListEventListener 1.61 + * This class is responsible for propagating events to the nsListControlFrame. 1.62 + * Frames are not refcounted so they can't be used as event listeners. 1.63 + *****************************************************************************/ 1.64 + 1.65 +class nsListEventListener MOZ_FINAL : public nsIDOMEventListener 1.66 +{ 1.67 +public: 1.68 + nsListEventListener(nsListControlFrame *aFrame) 1.69 + : mFrame(aFrame) { } 1.70 + 1.71 + void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; } 1.72 + 1.73 + NS_DECL_ISUPPORTS 1.74 + NS_DECL_NSIDOMEVENTLISTENER 1.75 + 1.76 +private: 1.77 + nsListControlFrame *mFrame; 1.78 +}; 1.79 + 1.80 +//--------------------------------------------------------- 1.81 +nsIFrame* 1.82 +NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.83 +{ 1.84 + nsListControlFrame* it = 1.85 + new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument(), aContext); 1.86 + 1.87 + it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION); 1.88 + 1.89 + return it; 1.90 +} 1.91 + 1.92 +NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame) 1.93 + 1.94 +//--------------------------------------------------------- 1.95 +nsListControlFrame::nsListControlFrame( 1.96 + nsIPresShell* aShell, nsIDocument* aDocument, nsStyleContext* aContext) 1.97 + : nsHTMLScrollFrame(aShell, aContext, false), 1.98 + mMightNeedSecondPass(false), 1.99 + mHasPendingInterruptAtStartOfReflow(false), 1.100 + mDropdownCanGrow(false), 1.101 + mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE) 1.102 +{ 1.103 + mComboboxFrame = nullptr; 1.104 + mChangesSinceDragStart = false; 1.105 + mButtonDown = false; 1.106 + 1.107 + mIsAllContentHere = false; 1.108 + mIsAllFramesHere = false; 1.109 + mHasBeenInitialized = false; 1.110 + mNeedToReset = true; 1.111 + mPostChildrenLoadedReset = false; 1.112 + 1.113 + mControlSelectMode = false; 1.114 +} 1.115 + 1.116 +//--------------------------------------------------------- 1.117 +nsListControlFrame::~nsListControlFrame() 1.118 +{ 1.119 + mComboboxFrame = nullptr; 1.120 +} 1.121 + 1.122 +// for Bug 47302 (remove this comment later) 1.123 +void 1.124 +nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.125 +{ 1.126 + // get the receiver interface from the browser button's content node 1.127 + ENSURE_TRUE(mContent); 1.128 + 1.129 + // Clear the frame pointer on our event listener, just in case the 1.130 + // event listener can outlive the frame. 1.131 + 1.132 + mEventListener->SetFrame(nullptr); 1.133 + 1.134 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), 1.135 + mEventListener, false); 1.136 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), 1.137 + mEventListener, false); 1.138 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), 1.139 + mEventListener, false); 1.140 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), 1.141 + mEventListener, false); 1.142 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"), 1.143 + mEventListener, false); 1.144 + 1.145 + nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); 1.146 + nsHTMLScrollFrame::DestroyFrom(aDestructRoot); 1.147 +} 1.148 + 1.149 +void 1.150 +nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.151 + const nsRect& aDirtyRect, 1.152 + const nsDisplayListSet& aLists) 1.153 +{ 1.154 + // We allow visibility:hidden <select>s to contain visible options. 1.155 + 1.156 + // Don't allow painting of list controls when painting is suppressed. 1.157 + // XXX why do we need this here? we should never reach this. Maybe 1.158 + // because these can have widgets? Hmm 1.159 + if (aBuilder->IsBackgroundOnly()) 1.160 + return; 1.161 + 1.162 + DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame"); 1.163 + 1.164 + if (IsInDropDownMode()) { 1.165 + NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255, 1.166 + "need an opaque backstop color"); 1.167 + // XXX Because we have an opaque widget and we get called to paint with 1.168 + // this frame as the root of a stacking context we need make sure to draw 1.169 + // some opaque color over the whole widget. (Bug 511323) 1.170 + aLists.BorderBackground()->AppendNewToBottom( 1.171 + new (aBuilder) nsDisplaySolidColor(aBuilder, 1.172 + this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()), 1.173 + mLastDropdownBackstopColor)); 1.174 + } 1.175 + 1.176 + nsHTMLScrollFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); 1.177 +} 1.178 + 1.179 +/** 1.180 + * This is called by the SelectsAreaFrame, which is the same 1.181 + * as the frame returned by GetOptionsContainer. It's the frame which is 1.182 + * scrolled by us. 1.183 + * @param aPt the offset of this frame, relative to the rendering reference 1.184 + * frame 1.185 + */ 1.186 +void nsListControlFrame::PaintFocus(nsRenderingContext& aRC, nsPoint aPt) 1.187 +{ 1.188 + if (mFocused != this) return; 1.189 + 1.190 + nsPresContext* presContext = PresContext(); 1.191 + 1.192 + nsIFrame* containerFrame = GetOptionsContainer(); 1.193 + if (!containerFrame) return; 1.194 + 1.195 + nsIFrame* childframe = nullptr; 1.196 + nsCOMPtr<nsIContent> focusedContent = GetCurrentOption(); 1.197 + if (focusedContent) { 1.198 + childframe = focusedContent->GetPrimaryFrame(); 1.199 + } 1.200 + 1.201 + nsRect fRect; 1.202 + if (childframe) { 1.203 + // get the child rect 1.204 + fRect = childframe->GetRect(); 1.205 + // get it into our coordinates 1.206 + fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this)); 1.207 + } else { 1.208 + float inflation = nsLayoutUtils::FontSizeInflationFor(this); 1.209 + fRect.x = fRect.y = 0; 1.210 + fRect.width = GetScrollPortRect().width; 1.211 + fRect.height = CalcFallbackRowHeight(inflation); 1.212 + fRect.MoveBy(containerFrame->GetOffsetTo(this)); 1.213 + } 1.214 + fRect += aPt; 1.215 + 1.216 + bool lastItemIsSelected = false; 1.217 + if (focusedContent) { 1.218 + nsCOMPtr<nsIDOMHTMLOptionElement> domOpt = 1.219 + do_QueryInterface(focusedContent); 1.220 + if (domOpt) { 1.221 + domOpt->GetSelected(&lastItemIsSelected); 1.222 + } 1.223 + } 1.224 + 1.225 + // set up back stop colors and then ask L&F service for the real colors 1.226 + nscolor color = 1.227 + LookAndFeel::GetColor(lastItemIsSelected ? 1.228 + LookAndFeel::eColorID_WidgetSelectForeground : 1.229 + LookAndFeel::eColorID_WidgetSelectBackground); 1.230 + 1.231 + nsCSSRendering::PaintFocus(presContext, aRC, fRect, color); 1.232 +} 1.233 + 1.234 +void 1.235 +nsListControlFrame::InvalidateFocus() 1.236 +{ 1.237 + if (mFocused != this) 1.238 + return; 1.239 + 1.240 + nsIFrame* containerFrame = GetOptionsContainer(); 1.241 + if (containerFrame) { 1.242 + containerFrame->InvalidateFrame(); 1.243 + } 1.244 +} 1.245 + 1.246 +NS_QUERYFRAME_HEAD(nsListControlFrame) 1.247 + NS_QUERYFRAME_ENTRY(nsIFormControlFrame) 1.248 + NS_QUERYFRAME_ENTRY(nsIListControlFrame) 1.249 + NS_QUERYFRAME_ENTRY(nsISelectControlFrame) 1.250 +NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame) 1.251 + 1.252 +#ifdef ACCESSIBILITY 1.253 +a11y::AccType 1.254 +nsListControlFrame::AccessibleType() 1.255 +{ 1.256 + return a11y::eHTMLSelectListType; 1.257 +} 1.258 +#endif 1.259 + 1.260 +static nscoord 1.261 +GetMaxOptionHeight(nsIFrame* aContainer) 1.262 +{ 1.263 + nscoord result = 0; 1.264 + for (nsIFrame* option = aContainer->GetFirstPrincipalChild(); 1.265 + option; option = option->GetNextSibling()) { 1.266 + nscoord optionHeight; 1.267 + if (nsCOMPtr<nsIDOMHTMLOptGroupElement> 1.268 + (do_QueryInterface(option->GetContent()))) { 1.269 + // an optgroup 1.270 + optionHeight = GetMaxOptionHeight(option); 1.271 + } else { 1.272 + // an option 1.273 + optionHeight = option->GetSize().height; 1.274 + } 1.275 + if (result < optionHeight) 1.276 + result = optionHeight; 1.277 + } 1.278 + return result; 1.279 +} 1.280 + 1.281 +//----------------------------------------------------------------- 1.282 +// Main Reflow for ListBox/Dropdown 1.283 +//----------------------------------------------------------------- 1.284 + 1.285 +nscoord 1.286 +nsListControlFrame::CalcHeightOfARow() 1.287 +{ 1.288 + // Calculate the height of a single row in the listbox or dropdown list by 1.289 + // using the tallest thing in the subtree, since there may be option groups 1.290 + // in addition to option elements, either of which may be visible or 1.291 + // invisible, may use different fonts, etc. 1.292 + int32_t heightOfARow = GetMaxOptionHeight(GetOptionsContainer()); 1.293 + 1.294 + // Check to see if we have zero items (and optimize by checking 1.295 + // heightOfARow first) 1.296 + if (heightOfARow == 0 && GetNumberOfOptions() == 0) { 1.297 + float inflation = nsLayoutUtils::FontSizeInflationFor(this); 1.298 + heightOfARow = CalcFallbackRowHeight(inflation); 1.299 + } 1.300 + 1.301 + return heightOfARow; 1.302 +} 1.303 + 1.304 +nscoord 1.305 +nsListControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) 1.306 +{ 1.307 + nscoord result; 1.308 + DISPLAY_PREF_WIDTH(this, result); 1.309 + 1.310 + // Always add scrollbar widths to the pref-width of the scrolled 1.311 + // content. Combobox frames depend on this happening in the dropdown, 1.312 + // and standalone listboxes are overflow:scroll so they need it too. 1.313 + result = GetScrolledFrame()->GetPrefWidth(aRenderingContext); 1.314 + result = NSCoordSaturatingAdd(result, 1.315 + GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight()); 1.316 + 1.317 + return result; 1.318 +} 1.319 + 1.320 +nscoord 1.321 +nsListControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext) 1.322 +{ 1.323 + nscoord result; 1.324 + DISPLAY_MIN_WIDTH(this, result); 1.325 + 1.326 + // Always add scrollbar widths to the min-width of the scrolled 1.327 + // content. Combobox frames depend on this happening in the dropdown, 1.328 + // and standalone listboxes are overflow:scroll so they need it too. 1.329 + result = GetScrolledFrame()->GetMinWidth(aRenderingContext); 1.330 + result += GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight(); 1.331 + 1.332 + return result; 1.333 +} 1.334 + 1.335 +nsresult 1.336 +nsListControlFrame::Reflow(nsPresContext* aPresContext, 1.337 + nsHTMLReflowMetrics& aDesiredSize, 1.338 + const nsHTMLReflowState& aReflowState, 1.339 + nsReflowStatus& aStatus) 1.340 +{ 1.341 + NS_PRECONDITION(aReflowState.ComputedWidth() != NS_UNCONSTRAINEDSIZE, 1.342 + "Must have a computed width"); 1.343 + 1.344 + SchedulePaint(); 1.345 + 1.346 + mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt(); 1.347 + 1.348 + // If all the content and frames are here 1.349 + // then initialize it before reflow 1.350 + if (mIsAllContentHere && !mHasBeenInitialized) { 1.351 + if (false == mIsAllFramesHere) { 1.352 + CheckIfAllFramesHere(); 1.353 + } 1.354 + if (mIsAllFramesHere && !mHasBeenInitialized) { 1.355 + mHasBeenInitialized = true; 1.356 + } 1.357 + } 1.358 + 1.359 + if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { 1.360 + nsFormControlFrame::RegUnRegAccessKey(this, true); 1.361 + } 1.362 + 1.363 + if (IsInDropDownMode()) { 1.364 + return ReflowAsDropdown(aPresContext, aDesiredSize, aReflowState, aStatus); 1.365 + } 1.366 + 1.367 + /* 1.368 + * Due to the fact that our intrinsic height depends on the heights of our 1.369 + * kids, we end up having to do two-pass reflow, in general -- the first pass 1.370 + * to find the intrinsic height and a second pass to reflow the scrollframe 1.371 + * at that height (which will size the scrollbars correctly, etc). 1.372 + * 1.373 + * Naturaly, we want to avoid doing the second reflow as much as possible. 1.374 + * We can skip it in the following cases (in all of which the first reflow is 1.375 + * already happening at the right height): 1.376 + * 1.377 + * - We're reflowing with a constrained computed height -- just use that 1.378 + * height. 1.379 + * - We're not dirty and have no dirty kids and shouldn't be reflowing all 1.380 + * kids. In this case, our cached max height of a child is not going to 1.381 + * change. 1.382 + * - We do our first reflow using our cached max height of a child, then 1.383 + * compute the new max height and it's the same as the old one. 1.384 + */ 1.385 + 1.386 + bool autoHeight = (aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE); 1.387 + 1.388 + mMightNeedSecondPass = autoHeight && 1.389 + (NS_SUBTREE_DIRTY(this) || aReflowState.ShouldReflowAllKids()); 1.390 + 1.391 + nsHTMLReflowState state(aReflowState); 1.392 + int32_t length = GetNumberOfRows(); 1.393 + 1.394 + nscoord oldHeightOfARow = HeightOfARow(); 1.395 + 1.396 + if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoHeight) { 1.397 + // When not doing an initial reflow, and when the height is auto, start off 1.398 + // with our computed height set to what we'd expect our height to be. 1.399 + nscoord computedHeight = CalcIntrinsicHeight(oldHeightOfARow, length); 1.400 + computedHeight = state.ApplyMinMaxHeight(computedHeight); 1.401 + state.SetComputedHeight(computedHeight); 1.402 + } 1.403 + 1.404 + nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, 1.405 + state, aStatus); 1.406 + NS_ENSURE_SUCCESS(rv, rv); 1.407 + 1.408 + if (!mMightNeedSecondPass) { 1.409 + NS_ASSERTION(!autoHeight || HeightOfARow() == oldHeightOfARow, 1.410 + "How did our height of a row change if nothing was dirty?"); 1.411 + NS_ASSERTION(!autoHeight || 1.412 + !(GetStateBits() & NS_FRAME_FIRST_REFLOW), 1.413 + "How do we not need a second pass during initial reflow at " 1.414 + "auto height?"); 1.415 + NS_ASSERTION(!IsScrollbarUpdateSuppressed(), 1.416 + "Shouldn't be suppressing if we don't need a second pass!"); 1.417 + if (!autoHeight) { 1.418 + // Update our mNumDisplayRows based on our new row height now that we 1.419 + // know it. Note that if autoHeight and we landed in this code then we 1.420 + // already set mNumDisplayRows in CalcIntrinsicHeight. Also note that we 1.421 + // can't use HeightOfARow() here because that just uses a cached value 1.422 + // that we didn't compute. 1.423 + nscoord rowHeight = CalcHeightOfARow(); 1.424 + if (rowHeight == 0) { 1.425 + // Just pick something 1.426 + mNumDisplayRows = 1; 1.427 + } else { 1.428 + mNumDisplayRows = std::max(1, state.ComputedHeight() / rowHeight); 1.429 + } 1.430 + } 1.431 + 1.432 + return rv; 1.433 + } 1.434 + 1.435 + mMightNeedSecondPass = false; 1.436 + 1.437 + // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame 1.438 + // will have suppressed the scrollbar update. 1.439 + if (!IsScrollbarUpdateSuppressed()) { 1.440 + // All done. No need to do more reflow. 1.441 + NS_ASSERTION(!IsScrollbarUpdateSuppressed(), 1.442 + "Shouldn't be suppressing if the height of a row has not " 1.443 + "changed!"); 1.444 + return rv; 1.445 + } 1.446 + 1.447 + SetSuppressScrollbarUpdate(false); 1.448 + 1.449 + // Gotta reflow again. 1.450 + // XXXbz We're just changing the height here; do we need to dirty ourselves 1.451 + // or anything like that? We might need to, per the letter of the reflow 1.452 + // protocol, but things seem to work fine without it... Is that just an 1.453 + // implementation detail of nsHTMLScrollFrame that we're depending on? 1.454 + nsHTMLScrollFrame::DidReflow(aPresContext, &state, 1.455 + nsDidReflowStatus::FINISHED); 1.456 + 1.457 + // Now compute the height we want to have 1.458 + nscoord computedHeight = CalcIntrinsicHeight(HeightOfARow(), length); 1.459 + computedHeight = state.ApplyMinMaxHeight(computedHeight); 1.460 + state.SetComputedHeight(computedHeight); 1.461 + 1.462 + nsHTMLScrollFrame::WillReflow(aPresContext); 1.463 + 1.464 + // XXXbz to make the ascent really correct, we should add our 1.465 + // mComputedPadding.top to it (and subtract it from descent). Need that 1.466 + // because nsGfxScrollFrame just adds in the border.... 1.467 + return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus); 1.468 +} 1.469 + 1.470 +nsresult 1.471 +nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext, 1.472 + nsHTMLReflowMetrics& aDesiredSize, 1.473 + const nsHTMLReflowState& aReflowState, 1.474 + nsReflowStatus& aStatus) 1.475 +{ 1.476 + NS_PRECONDITION(aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE, 1.477 + "We should not have a computed height here!"); 1.478 + 1.479 + mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) || 1.480 + aReflowState.ShouldReflowAllKids(); 1.481 + 1.482 +#ifdef DEBUG 1.483 + nscoord oldHeightOfARow = HeightOfARow(); 1.484 + nscoord oldVisibleHeight = (GetStateBits() & NS_FRAME_FIRST_REFLOW) ? 1.485 + NS_UNCONSTRAINEDSIZE : GetScrolledFrame()->GetSize().height; 1.486 +#endif 1.487 + 1.488 + nsHTMLReflowState state(aReflowState); 1.489 + 1.490 + if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { 1.491 + // When not doing an initial reflow, and when the height is auto, start off 1.492 + // with our computed height set to what we'd expect our height to be. 1.493 + // Note: At this point, mLastDropdownComputedHeight can be 1.494 + // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain 1.495 + // the height. That's fine; just do the same thing as last time. 1.496 + state.SetComputedHeight(mLastDropdownComputedHeight); 1.497 + } 1.498 + 1.499 + nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, 1.500 + state, aStatus); 1.501 + NS_ENSURE_SUCCESS(rv, rv); 1.502 + 1.503 + if (!mMightNeedSecondPass) { 1.504 + NS_ASSERTION(oldVisibleHeight == GetScrolledFrame()->GetSize().height, 1.505 + "How did our kid's height change if nothing was dirty?"); 1.506 + NS_ASSERTION(HeightOfARow() == oldHeightOfARow, 1.507 + "How did our height of a row change if nothing was dirty?"); 1.508 + NS_ASSERTION(!IsScrollbarUpdateSuppressed(), 1.509 + "Shouldn't be suppressing if we don't need a second pass!"); 1.510 + NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW), 1.511 + "How can we avoid a second pass during first reflow?"); 1.512 + return rv; 1.513 + } 1.514 + 1.515 + mMightNeedSecondPass = false; 1.516 + 1.517 + // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame 1.518 + // will have suppressed the scrollbar update. 1.519 + if (!IsScrollbarUpdateSuppressed()) { 1.520 + // All done. No need to do more reflow. 1.521 + NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW), 1.522 + "How can we avoid a second pass during first reflow?"); 1.523 + return rv; 1.524 + } 1.525 + 1.526 + SetSuppressScrollbarUpdate(false); 1.527 + 1.528 + nscoord visibleHeight = GetScrolledFrame()->GetSize().height; 1.529 + nscoord heightOfARow = HeightOfARow(); 1.530 + 1.531 + // Gotta reflow again. 1.532 + // XXXbz We're just changing the height here; do we need to dirty ourselves 1.533 + // or anything like that? We might need to, per the letter of the reflow 1.534 + // protocol, but things seem to work fine without it... Is that just an 1.535 + // implementation detail of nsHTMLScrollFrame that we're depending on? 1.536 + nsHTMLScrollFrame::DidReflow(aPresContext, &state, 1.537 + nsDidReflowStatus::FINISHED); 1.538 + 1.539 + // Now compute the height we want to have. 1.540 + // Note: no need to apply min/max constraints, since we have no such 1.541 + // rules applied to the combobox dropdown. 1.542 + 1.543 + mDropdownCanGrow = false; 1.544 + if (visibleHeight <= 0 || heightOfARow <= 0) { 1.545 + // Looks like we have no options. Just size us to a single row height. 1.546 + state.SetComputedHeight(heightOfARow); 1.547 + mNumDisplayRows = 1; 1.548 + } else { 1.549 + nsComboboxControlFrame* combobox = static_cast<nsComboboxControlFrame*>(mComboboxFrame); 1.550 + nsPoint translation; 1.551 + nscoord above, below; 1.552 + combobox->GetAvailableDropdownSpace(&above, &below, &translation); 1.553 + if (above <= 0 && below <= 0) { 1.554 + state.SetComputedHeight(heightOfARow); 1.555 + mNumDisplayRows = 1; 1.556 + mDropdownCanGrow = GetNumberOfRows() > 1; 1.557 + } else { 1.558 + nscoord bp = aReflowState.ComputedPhysicalBorderPadding().TopBottom(); 1.559 + nscoord availableHeight = std::max(above, below) - bp; 1.560 + nscoord newHeight; 1.561 + uint32_t rows; 1.562 + if (visibleHeight <= availableHeight) { 1.563 + // The dropdown fits in the available height. 1.564 + rows = GetNumberOfRows(); 1.565 + mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows); 1.566 + if (mNumDisplayRows == rows) { 1.567 + newHeight = visibleHeight; // use the exact height 1.568 + } else { 1.569 + newHeight = mNumDisplayRows * heightOfARow; // approximate 1.570 + } 1.571 + } else { 1.572 + rows = availableHeight / heightOfARow; 1.573 + mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows); 1.574 + newHeight = mNumDisplayRows * heightOfARow; // approximate 1.575 + } 1.576 + state.SetComputedHeight(newHeight); 1.577 + mDropdownCanGrow = visibleHeight - newHeight >= heightOfARow && 1.578 + mNumDisplayRows != kMaxDropDownRows; 1.579 + } 1.580 + } 1.581 + 1.582 + mLastDropdownComputedHeight = state.ComputedHeight(); 1.583 + 1.584 + nsHTMLScrollFrame::WillReflow(aPresContext); 1.585 + return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus); 1.586 +} 1.587 + 1.588 +ScrollbarStyles 1.589 +nsListControlFrame::GetScrollbarStyles() const 1.590 +{ 1.591 + // We can't express this in the style system yet; when we can, this can go away 1.592 + // and GetScrollbarStyles can be devirtualized 1.593 + int32_t verticalStyle = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO 1.594 + : NS_STYLE_OVERFLOW_SCROLL; 1.595 + return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, verticalStyle); 1.596 +} 1.597 + 1.598 +bool 1.599 +nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const 1.600 +{ 1.601 + return !IsInDropDownMode(); 1.602 +} 1.603 + 1.604 +//--------------------------------------------------------- 1.605 +nsIFrame* 1.606 +nsListControlFrame::GetContentInsertionFrame() { 1.607 + return GetOptionsContainer()->GetContentInsertionFrame(); 1.608 +} 1.609 + 1.610 +//--------------------------------------------------------- 1.611 +bool 1.612 +nsListControlFrame::ExtendedSelection(int32_t aStartIndex, 1.613 + int32_t aEndIndex, 1.614 + bool aClearAll) 1.615 +{ 1.616 + return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex, 1.617 + true, aClearAll); 1.618 +} 1.619 + 1.620 +//--------------------------------------------------------- 1.621 +bool 1.622 +nsListControlFrame::SingleSelection(int32_t aClickedIndex, bool aDoToggle) 1.623 +{ 1.624 + if (mComboboxFrame) { 1.625 + mComboboxFrame->UpdateRecentIndex(GetSelectedIndex()); 1.626 + } 1.627 + 1.628 + bool wasChanged = false; 1.629 + // Get Current selection 1.630 + if (aDoToggle) { 1.631 + wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex); 1.632 + } else { 1.633 + wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex, 1.634 + true, true); 1.635 + } 1.636 + nsWeakFrame weakFrame(this); 1.637 + ScrollToIndex(aClickedIndex); 1.638 + if (!weakFrame.IsAlive()) { 1.639 + return wasChanged; 1.640 + } 1.641 + 1.642 +#ifdef ACCESSIBILITY 1.643 + bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex; 1.644 +#endif 1.645 + mStartSelectionIndex = aClickedIndex; 1.646 + mEndSelectionIndex = aClickedIndex; 1.647 + InvalidateFocus(); 1.648 + 1.649 +#ifdef ACCESSIBILITY 1.650 + if (isCurrentOptionChanged) { 1.651 + FireMenuItemActiveEvent(); 1.652 + } 1.653 +#endif 1.654 + 1.655 + return wasChanged; 1.656 +} 1.657 + 1.658 +void 1.659 +nsListControlFrame::InitSelectionRange(int32_t aClickedIndex) 1.660 +{ 1.661 + // 1.662 + // If nothing is selected, set the start selection depending on where 1.663 + // the user clicked and what the initial selection is: 1.664 + // - if the user clicked *before* selectedIndex, set the start index to 1.665 + // the end of the first contiguous selection. 1.666 + // - if the user clicked *after* the end of the first contiguous 1.667 + // selection, set the start index to selectedIndex. 1.668 + // - if the user clicked *within* the first contiguous selection, set the 1.669 + // start index to selectedIndex. 1.670 + // The last two rules, of course, boil down to the same thing: if the user 1.671 + // clicked >= selectedIndex, return selectedIndex. 1.672 + // 1.673 + // This makes it so that shift click works properly when you first click 1.674 + // in a multiple select. 1.675 + // 1.676 + int32_t selectedIndex = GetSelectedIndex(); 1.677 + if (selectedIndex >= 0) { 1.678 + // Get the end of the contiguous selection 1.679 + nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions(); 1.680 + NS_ASSERTION(options, "Collection of options is null!"); 1.681 + uint32_t numOptions = options->Length(); 1.682 + // Push i to one past the last selected index in the group. 1.683 + uint32_t i; 1.684 + for (i = selectedIndex + 1; i < numOptions; i++) { 1.685 + if (!options->ItemAsOption(i)->Selected()) { 1.686 + break; 1.687 + } 1.688 + } 1.689 + 1.690 + if (aClickedIndex < selectedIndex) { 1.691 + // User clicked before selection, so start selection at end of 1.692 + // contiguous selection 1.693 + mStartSelectionIndex = i-1; 1.694 + mEndSelectionIndex = selectedIndex; 1.695 + } else { 1.696 + // User clicked after selection, so start selection at start of 1.697 + // contiguous selection 1.698 + mStartSelectionIndex = selectedIndex; 1.699 + mEndSelectionIndex = i-1; 1.700 + } 1.701 + } 1.702 +} 1.703 + 1.704 +static uint32_t 1.705 +CountOptionsAndOptgroups(nsIFrame* aFrame) 1.706 +{ 1.707 + uint32_t count = 0; 1.708 + nsFrameList::Enumerator e(aFrame->PrincipalChildList()); 1.709 + for (; !e.AtEnd(); e.Next()) { 1.710 + nsIFrame* child = e.get(); 1.711 + nsIContent* content = child->GetContent(); 1.712 + if (content) { 1.713 + if (content->IsHTML(nsGkAtoms::option)) { 1.714 + ++count; 1.715 + } else { 1.716 + nsCOMPtr<nsIDOMHTMLOptGroupElement> optgroup = do_QueryInterface(content); 1.717 + if (optgroup) { 1.718 + nsAutoString label; 1.719 + optgroup->GetLabel(label); 1.720 + if (label.Length() > 0) { 1.721 + ++count; 1.722 + } 1.723 + count += CountOptionsAndOptgroups(child); 1.724 + } 1.725 + } 1.726 + } 1.727 + } 1.728 + return count; 1.729 +} 1.730 + 1.731 +uint32_t 1.732 +nsListControlFrame::GetNumberOfRows() 1.733 +{ 1.734 + return ::CountOptionsAndOptgroups(GetContentInsertionFrame()); 1.735 +} 1.736 + 1.737 +//--------------------------------------------------------- 1.738 +bool 1.739 +nsListControlFrame::PerformSelection(int32_t aClickedIndex, 1.740 + bool aIsShift, 1.741 + bool aIsControl) 1.742 +{ 1.743 + bool wasChanged = false; 1.744 + 1.745 + if (aClickedIndex == kNothingSelected) { 1.746 + } 1.747 + else if (GetMultiple()) { 1.748 + if (aIsShift) { 1.749 + // Make sure shift+click actually does something expected when 1.750 + // the user has never clicked on the select 1.751 + if (mStartSelectionIndex == kNothingSelected) { 1.752 + InitSelectionRange(aClickedIndex); 1.753 + } 1.754 + 1.755 + // Get the range from beginning (low) to end (high) 1.756 + // Shift *always* works, even if the current option is disabled 1.757 + int32_t startIndex; 1.758 + int32_t endIndex; 1.759 + if (mStartSelectionIndex == kNothingSelected) { 1.760 + startIndex = aClickedIndex; 1.761 + endIndex = aClickedIndex; 1.762 + } else if (mStartSelectionIndex <= aClickedIndex) { 1.763 + startIndex = mStartSelectionIndex; 1.764 + endIndex = aClickedIndex; 1.765 + } else { 1.766 + startIndex = aClickedIndex; 1.767 + endIndex = mStartSelectionIndex; 1.768 + } 1.769 + 1.770 + // Clear only if control was not pressed 1.771 + wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl); 1.772 + nsWeakFrame weakFrame(this); 1.773 + ScrollToIndex(aClickedIndex); 1.774 + if (!weakFrame.IsAlive()) { 1.775 + return wasChanged; 1.776 + } 1.777 + 1.778 + if (mStartSelectionIndex == kNothingSelected) { 1.779 + mStartSelectionIndex = aClickedIndex; 1.780 + } 1.781 +#ifdef ACCESSIBILITY 1.782 + bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex; 1.783 +#endif 1.784 + mEndSelectionIndex = aClickedIndex; 1.785 + InvalidateFocus(); 1.786 + 1.787 +#ifdef ACCESSIBILITY 1.788 + if (isCurrentOptionChanged) { 1.789 + FireMenuItemActiveEvent(); 1.790 + } 1.791 +#endif 1.792 + } else if (aIsControl) { 1.793 + wasChanged = SingleSelection(aClickedIndex, true); // might destroy us 1.794 + } else { 1.795 + wasChanged = SingleSelection(aClickedIndex, false); // might destroy us 1.796 + } 1.797 + } else { 1.798 + wasChanged = SingleSelection(aClickedIndex, false); // might destroy us 1.799 + } 1.800 + 1.801 + return wasChanged; 1.802 +} 1.803 + 1.804 +//--------------------------------------------------------- 1.805 +bool 1.806 +nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent, 1.807 + int32_t aClickedIndex) 1.808 +{ 1.809 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); 1.810 + bool isShift; 1.811 + bool isControl; 1.812 +#ifdef XP_MACOSX 1.813 + mouseEvent->GetMetaKey(&isControl); 1.814 +#else 1.815 + mouseEvent->GetCtrlKey(&isControl); 1.816 +#endif 1.817 + mouseEvent->GetShiftKey(&isShift); 1.818 + return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us 1.819 +} 1.820 + 1.821 +//--------------------------------------------------------- 1.822 +void 1.823 +nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents) 1.824 +{ 1.825 + // Currently cocoa widgets use a native popup widget which tracks clicks synchronously, 1.826 + // so we never want to do mouse capturing. Note that we only bail if the list 1.827 + // is in drop-down mode, and the caller is requesting capture (we let release capture 1.828 + // requests go through to ensure that we can release capture requested via other 1.829 + // code paths, if any exist). 1.830 + if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup()) 1.831 + return; 1.832 + 1.833 + if (aGrabMouseEvents) { 1.834 + nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED); 1.835 + } else { 1.836 + nsIContent* capturingContent = nsIPresShell::GetCapturingContent(); 1.837 + 1.838 + bool dropDownIsHidden = false; 1.839 + if (IsInDropDownMode()) { 1.840 + dropDownIsHidden = !mComboboxFrame->IsDroppedDown(); 1.841 + } 1.842 + if (capturingContent == mContent || dropDownIsHidden) { 1.843 + // only clear the capturing content if *we* are the ones doing the 1.844 + // capturing (or if the dropdown is hidden, in which case NO-ONE should 1.845 + // be capturing anything - it could be a scrollbar inside this listbox 1.846 + // which is actually grabbing 1.847 + // This shouldn't be necessary. We should simply ensure that events targeting 1.848 + // scrollbars are never visible to DOM consumers. 1.849 + nsIPresShell::SetCapturingContent(nullptr, 0); 1.850 + } 1.851 + } 1.852 +} 1.853 + 1.854 +//--------------------------------------------------------- 1.855 +nsresult 1.856 +nsListControlFrame::HandleEvent(nsPresContext* aPresContext, 1.857 + WidgetGUIEvent* aEvent, 1.858 + nsEventStatus* aEventStatus) 1.859 +{ 1.860 + NS_ENSURE_ARG_POINTER(aEventStatus); 1.861 + 1.862 + /*const char * desc[] = {"NS_MOUSE_MOVE", 1.863 + "NS_MOUSE_LEFT_BUTTON_UP", 1.864 + "NS_MOUSE_LEFT_BUTTON_DOWN", 1.865 + "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>", 1.866 + "NS_MOUSE_MIDDLE_BUTTON_UP", 1.867 + "NS_MOUSE_MIDDLE_BUTTON_DOWN", 1.868 + "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>", 1.869 + "NS_MOUSE_RIGHT_BUTTON_UP", 1.870 + "NS_MOUSE_RIGHT_BUTTON_DOWN", 1.871 + "NS_MOUSE_ENTER_SYNTH", 1.872 + "NS_MOUSE_EXIT_SYNTH", 1.873 + "NS_MOUSE_LEFT_DOUBLECLICK", 1.874 + "NS_MOUSE_MIDDLE_DOUBLECLICK", 1.875 + "NS_MOUSE_RIGHT_DOUBLECLICK", 1.876 + "NS_MOUSE_LEFT_CLICK", 1.877 + "NS_MOUSE_MIDDLE_CLICK", 1.878 + "NS_MOUSE_RIGHT_CLICK"}; 1.879 + int inx = aEvent->message-NS_MOUSE_MESSAGE_START; 1.880 + if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) { 1.881 + printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message); 1.882 + } else { 1.883 + printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message); 1.884 + }*/ 1.885 + 1.886 + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) 1.887 + return NS_OK; 1.888 + 1.889 + // do we have style that affects how we are selected? 1.890 + // do we have user-input style? 1.891 + const nsStyleUserInterface* uiStyle = StyleUserInterface(); 1.892 + if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED) 1.893 + return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 1.894 + 1.895 + EventStates eventStates = mContent->AsElement()->State(); 1.896 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) 1.897 + return NS_OK; 1.898 + 1.899 + return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 1.900 +} 1.901 + 1.902 + 1.903 +//--------------------------------------------------------- 1.904 +nsresult 1.905 +nsListControlFrame::SetInitialChildList(ChildListID aListID, 1.906 + nsFrameList& aChildList) 1.907 +{ 1.908 + // First check to see if all the content has been added 1.909 + mIsAllContentHere = mContent->IsDoneAddingChildren(); 1.910 + if (!mIsAllContentHere) { 1.911 + mIsAllFramesHere = false; 1.912 + mHasBeenInitialized = false; 1.913 + } 1.914 + nsresult rv = nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList); 1.915 + 1.916 + // If all the content is here now check 1.917 + // to see if all the frames have been created 1.918 + /*if (mIsAllContentHere) { 1.919 + // If all content and frames are here 1.920 + // the reset/initialize 1.921 + if (CheckIfAllFramesHere()) { 1.922 + ResetList(aPresContext); 1.923 + mHasBeenInitialized = true; 1.924 + } 1.925 + }*/ 1.926 + 1.927 + return rv; 1.928 +} 1.929 + 1.930 +//--------------------------------------------------------- 1.931 +void 1.932 +nsListControlFrame::Init(nsIContent* aContent, 1.933 + nsIFrame* aParent, 1.934 + nsIFrame* aPrevInFlow) 1.935 +{ 1.936 + nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow); 1.937 + 1.938 + // we shouldn't have to unregister this listener because when 1.939 + // our frame goes away all these content node go away as well 1.940 + // because our frame is the only one who references them. 1.941 + // we need to hook up our listeners before the editor is initialized 1.942 + mEventListener = new nsListEventListener(this); 1.943 + 1.944 + mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"), 1.945 + mEventListener, false, false); 1.946 + mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"), 1.947 + mEventListener, false, false); 1.948 + mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), 1.949 + mEventListener, false, false); 1.950 + mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), 1.951 + mEventListener, false, false); 1.952 + mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), 1.953 + mEventListener, false, false); 1.954 + 1.955 + mStartSelectionIndex = kNothingSelected; 1.956 + mEndSelectionIndex = kNothingSelected; 1.957 + 1.958 + mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor(); 1.959 + 1.960 + if (IsInDropDownMode()) { 1.961 + AddStateBits(NS_FRAME_IN_POPUP); 1.962 + } 1.963 +} 1.964 + 1.965 +dom::HTMLOptionsCollection* 1.966 +nsListControlFrame::GetOptions() const 1.967 +{ 1.968 + dom::HTMLSelectElement* select = 1.969 + dom::HTMLSelectElement::FromContentOrNull(mContent); 1.970 + NS_ENSURE_TRUE(select, nullptr); 1.971 + 1.972 + return select->Options(); 1.973 +} 1.974 + 1.975 +dom::HTMLOptionElement* 1.976 +nsListControlFrame::GetOption(uint32_t aIndex) const 1.977 +{ 1.978 + dom::HTMLSelectElement* select = 1.979 + dom::HTMLSelectElement::FromContentOrNull(mContent); 1.980 + NS_ENSURE_TRUE(select, nullptr); 1.981 + 1.982 + return select->Item(aIndex); 1.983 +} 1.984 + 1.985 +NS_IMETHODIMP 1.986 +nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) 1.987 +{ 1.988 + if (aSelected) { 1.989 + ScrollToIndex(aIndex); 1.990 + } 1.991 + return NS_OK; 1.992 +} 1.993 + 1.994 +void 1.995 +nsListControlFrame::OnContentReset() 1.996 +{ 1.997 + ResetList(true); 1.998 +} 1.999 + 1.1000 +void 1.1001 +nsListControlFrame::ResetList(bool aAllowScrolling) 1.1002 +{ 1.1003 + // if all the frames aren't here 1.1004 + // don't bother reseting 1.1005 + if (!mIsAllFramesHere) { 1.1006 + return; 1.1007 + } 1.1008 + 1.1009 + if (aAllowScrolling) { 1.1010 + mPostChildrenLoadedReset = true; 1.1011 + 1.1012 + // Scroll to the selected index 1.1013 + int32_t indexToSelect = kNothingSelected; 1.1014 + 1.1015 + nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent)); 1.1016 + NS_ASSERTION(selectElement, "No select element!"); 1.1017 + if (selectElement) { 1.1018 + selectElement->GetSelectedIndex(&indexToSelect); 1.1019 + nsWeakFrame weakFrame(this); 1.1020 + ScrollToIndex(indexToSelect); 1.1021 + if (!weakFrame.IsAlive()) { 1.1022 + return; 1.1023 + } 1.1024 + } 1.1025 + } 1.1026 + 1.1027 + mStartSelectionIndex = kNothingSelected; 1.1028 + mEndSelectionIndex = kNothingSelected; 1.1029 + InvalidateFocus(); 1.1030 + // Combobox will redisplay itself with the OnOptionSelected event 1.1031 +} 1.1032 + 1.1033 +void 1.1034 +nsListControlFrame::SetFocus(bool aOn, bool aRepaint) 1.1035 +{ 1.1036 + InvalidateFocus(); 1.1037 + 1.1038 + if (aOn) { 1.1039 + ComboboxFocusSet(); 1.1040 + mFocused = this; 1.1041 + } else { 1.1042 + mFocused = nullptr; 1.1043 + } 1.1044 + 1.1045 + InvalidateFocus(); 1.1046 +} 1.1047 + 1.1048 +void nsListControlFrame::ComboboxFocusSet() 1.1049 +{ 1.1050 + gLastKeyTime = 0; 1.1051 +} 1.1052 + 1.1053 +void 1.1054 +nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame) 1.1055 +{ 1.1056 + if (nullptr != aComboboxFrame) { 1.1057 + mComboboxFrame = do_QueryFrame(aComboboxFrame); 1.1058 + } 1.1059 +} 1.1060 + 1.1061 +void 1.1062 +nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr) 1.1063 +{ 1.1064 + aStr.Truncate(); 1.1065 + if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) { 1.1066 + optionElement->GetText(aStr); 1.1067 + } 1.1068 +} 1.1069 + 1.1070 +int32_t 1.1071 +nsListControlFrame::GetSelectedIndex() 1.1072 +{ 1.1073 + dom::HTMLSelectElement* select = 1.1074 + dom::HTMLSelectElement::FromContentOrNull(mContent); 1.1075 + return select->SelectedIndex(); 1.1076 +} 1.1077 + 1.1078 +dom::HTMLOptionElement* 1.1079 +nsListControlFrame::GetCurrentOption() 1.1080 +{ 1.1081 + // The mEndSelectionIndex is what is currently being selected. Use 1.1082 + // the selected index if this is kNothingSelected. 1.1083 + int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected) ? 1.1084 + GetSelectedIndex() : mEndSelectionIndex; 1.1085 + 1.1086 + if (focusedIndex != kNothingSelected) { 1.1087 + return GetOption(SafeCast<uint32_t>(focusedIndex)); 1.1088 + } 1.1089 + 1.1090 + // There is no selected item. Return the first non-disabled item. 1.1091 + nsRefPtr<dom::HTMLSelectElement> selectElement = 1.1092 + dom::HTMLSelectElement::FromContent(mContent); 1.1093 + 1.1094 + for (uint32_t i = 0, length = selectElement->Length(); i < length; ++i) { 1.1095 + dom::HTMLOptionElement* node = selectElement->Item(i); 1.1096 + if (!node) { 1.1097 + return nullptr; 1.1098 + } 1.1099 + 1.1100 + if (!selectElement->IsOptionDisabled(node)) { 1.1101 + return node; 1.1102 + } 1.1103 + } 1.1104 + 1.1105 + return nullptr; 1.1106 +} 1.1107 + 1.1108 +bool 1.1109 +nsListControlFrame::IsInDropDownMode() const 1.1110 +{ 1.1111 + return (mComboboxFrame != nullptr); 1.1112 +} 1.1113 + 1.1114 +uint32_t 1.1115 +nsListControlFrame::GetNumberOfOptions() 1.1116 +{ 1.1117 + dom::HTMLOptionsCollection* options = GetOptions(); 1.1118 + if (!options) { 1.1119 + return 0; 1.1120 + } 1.1121 + 1.1122 + return options->Length(); 1.1123 +} 1.1124 + 1.1125 +//---------------------------------------------------------------------- 1.1126 +// nsISelectControlFrame 1.1127 +//---------------------------------------------------------------------- 1.1128 +bool nsListControlFrame::CheckIfAllFramesHere() 1.1129 +{ 1.1130 + // Get the number of optgroups and options 1.1131 + //int32_t numContentItems = 0; 1.1132 + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent)); 1.1133 + if (node) { 1.1134 + // XXX Need to find a fail proff way to determine that 1.1135 + // all the frames are there 1.1136 + mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems); 1.1137 + } 1.1138 + // now make sure we have a frame each piece of content 1.1139 + 1.1140 + return mIsAllFramesHere; 1.1141 +} 1.1142 + 1.1143 +NS_IMETHODIMP 1.1144 +nsListControlFrame::DoneAddingChildren(bool aIsDone) 1.1145 +{ 1.1146 + mIsAllContentHere = aIsDone; 1.1147 + if (mIsAllContentHere) { 1.1148 + // Here we check to see if all the frames have been created 1.1149 + // for all the content. 1.1150 + // If so, then we can initialize; 1.1151 + if (!mIsAllFramesHere) { 1.1152 + // if all the frames are now present we can initialize 1.1153 + if (CheckIfAllFramesHere()) { 1.1154 + mHasBeenInitialized = true; 1.1155 + ResetList(true); 1.1156 + } 1.1157 + } 1.1158 + } 1.1159 + return NS_OK; 1.1160 +} 1.1161 + 1.1162 +NS_IMETHODIMP 1.1163 +nsListControlFrame::AddOption(int32_t aIndex) 1.1164 +{ 1.1165 +#ifdef DO_REFLOW_DEBUG 1.1166 + printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex); 1.1167 +#endif 1.1168 + 1.1169 + if (!mIsAllContentHere) { 1.1170 + mIsAllContentHere = mContent->IsDoneAddingChildren(); 1.1171 + if (!mIsAllContentHere) { 1.1172 + mIsAllFramesHere = false; 1.1173 + mHasBeenInitialized = false; 1.1174 + } else { 1.1175 + mIsAllFramesHere = (aIndex == static_cast<int32_t>(GetNumberOfOptions()-1)); 1.1176 + } 1.1177 + } 1.1178 + 1.1179 + // Make sure we scroll to the selected option as needed 1.1180 + mNeedToReset = true; 1.1181 + 1.1182 + if (!mHasBeenInitialized) { 1.1183 + return NS_OK; 1.1184 + } 1.1185 + 1.1186 + mPostChildrenLoadedReset = mIsAllContentHere; 1.1187 + return NS_OK; 1.1188 +} 1.1189 + 1.1190 +static int32_t 1.1191 +DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength) 1.1192 +{ 1.1193 + return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1); 1.1194 +} 1.1195 + 1.1196 +NS_IMETHODIMP 1.1197 +nsListControlFrame::RemoveOption(int32_t aIndex) 1.1198 +{ 1.1199 + NS_PRECONDITION(aIndex >= 0, "negative <option> index"); 1.1200 + 1.1201 + // Need to reset if we're a dropdown 1.1202 + if (IsInDropDownMode()) { 1.1203 + mNeedToReset = true; 1.1204 + mPostChildrenLoadedReset = mIsAllContentHere; 1.1205 + } 1.1206 + 1.1207 + if (mStartSelectionIndex != kNothingSelected) { 1.1208 + NS_ASSERTION(mEndSelectionIndex != kNothingSelected, ""); 1.1209 + int32_t numOptions = GetNumberOfOptions(); 1.1210 + // NOTE: numOptions is the new number of options whereas aIndex is the 1.1211 + // unadjusted index of the removed option (hence the <= below). 1.1212 + NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index"); 1.1213 + 1.1214 + int32_t forward = mEndSelectionIndex - mStartSelectionIndex; 1.1215 + int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex; 1.1216 + int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex; 1.1217 + if (aIndex < *low) 1.1218 + *low = ::DecrementAndClamp(*low, numOptions); 1.1219 + if (aIndex <= *high) 1.1220 + *high = ::DecrementAndClamp(*high, numOptions); 1.1221 + if (forward == 0) 1.1222 + *low = *high; 1.1223 + } 1.1224 + else 1.1225 + NS_ASSERTION(mEndSelectionIndex == kNothingSelected, ""); 1.1226 + 1.1227 + InvalidateFocus(); 1.1228 + return NS_OK; 1.1229 +} 1.1230 + 1.1231 +//--------------------------------------------------------- 1.1232 +// Set the option selected in the DOM. This method is named 1.1233 +// as it is because it indicates that the frame is the source 1.1234 +// of this event rather than the receiver. 1.1235 +bool 1.1236 +nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex, 1.1237 + int32_t aEndIndex, 1.1238 + bool aValue, 1.1239 + bool aClearAll) 1.1240 +{ 1.1241 + nsRefPtr<dom::HTMLSelectElement> selectElement = 1.1242 + dom::HTMLSelectElement::FromContent(mContent); 1.1243 + 1.1244 + uint32_t mask = dom::HTMLSelectElement::NOTIFY; 1.1245 + if (aValue) { 1.1246 + mask |= dom::HTMLSelectElement::IS_SELECTED; 1.1247 + } 1.1248 + 1.1249 + if (aClearAll) { 1.1250 + mask |= dom::HTMLSelectElement::CLEAR_ALL; 1.1251 + } 1.1252 + 1.1253 + return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask); 1.1254 +} 1.1255 + 1.1256 +bool 1.1257 +nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex) 1.1258 +{ 1.1259 + nsRefPtr<dom::HTMLOptionElement> option = 1.1260 + GetOption(static_cast<uint32_t>(aIndex)); 1.1261 + NS_ENSURE_TRUE(option, false); 1.1262 + 1.1263 + nsRefPtr<dom::HTMLSelectElement> selectElement = 1.1264 + dom::HTMLSelectElement::FromContent(mContent); 1.1265 + 1.1266 + uint32_t mask = dom::HTMLSelectElement::NOTIFY; 1.1267 + if (!option->Selected()) { 1.1268 + mask |= dom::HTMLSelectElement::IS_SELECTED; 1.1269 + } 1.1270 + 1.1271 + return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask); 1.1272 +} 1.1273 + 1.1274 + 1.1275 +// Dispatch event and such 1.1276 +bool 1.1277 +nsListControlFrame::UpdateSelection() 1.1278 +{ 1.1279 + if (mIsAllFramesHere) { 1.1280 + // if it's a combobox, display the new text 1.1281 + nsWeakFrame weakFrame(this); 1.1282 + if (mComboboxFrame) { 1.1283 + mComboboxFrame->RedisplaySelectedText(); 1.1284 + } 1.1285 + // if it's a listbox, fire on change 1.1286 + else if (mIsAllContentHere) { 1.1287 + FireOnChange(); 1.1288 + } 1.1289 + return weakFrame.IsAlive(); 1.1290 + } 1.1291 + return true; 1.1292 +} 1.1293 + 1.1294 +void 1.1295 +nsListControlFrame::ComboboxFinish(int32_t aIndex) 1.1296 +{ 1.1297 + gLastKeyTime = 0; 1.1298 + 1.1299 + if (mComboboxFrame) { 1.1300 + nsWeakFrame weakFrame(this); 1.1301 + PerformSelection(aIndex, false, false); // might destroy us 1.1302 + if (!weakFrame.IsAlive() || !mComboboxFrame) { 1.1303 + return; 1.1304 + } 1.1305 + 1.1306 + int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea(); 1.1307 + if (displayIndex != aIndex) { 1.1308 + mComboboxFrame->RedisplaySelectedText(); // might destroy us 1.1309 + } 1.1310 + 1.1311 + if (weakFrame.IsAlive() && mComboboxFrame) { 1.1312 + mComboboxFrame->RollupFromList(); // might destroy us 1.1313 + } 1.1314 + } 1.1315 +} 1.1316 + 1.1317 +// Send out an onchange notification. 1.1318 +void 1.1319 +nsListControlFrame::FireOnChange() 1.1320 +{ 1.1321 + if (mComboboxFrame) { 1.1322 + // Return hit without changing anything 1.1323 + int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX); 1.1324 + if (index == NS_SKIP_NOTIFY_INDEX) 1.1325 + return; 1.1326 + 1.1327 + // See if the selection actually changed 1.1328 + if (index == GetSelectedIndex()) 1.1329 + return; 1.1330 + } 1.1331 + 1.1332 + // Dispatch the change event. 1.1333 + nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, 1.1334 + NS_LITERAL_STRING("change"), true, 1.1335 + false); 1.1336 +} 1.1337 + 1.1338 +NS_IMETHODIMP 1.1339 +nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) 1.1340 +{ 1.1341 + if (mComboboxFrame) { 1.1342 + // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange 1.1343 + // event for this setting of selectedIndex. 1.1344 + mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX); 1.1345 + } 1.1346 + 1.1347 + nsWeakFrame weakFrame(this); 1.1348 + ScrollToIndex(aNewIndex); 1.1349 + if (!weakFrame.IsAlive()) { 1.1350 + return NS_OK; 1.1351 + } 1.1352 + mStartSelectionIndex = aNewIndex; 1.1353 + mEndSelectionIndex = aNewIndex; 1.1354 + InvalidateFocus(); 1.1355 + 1.1356 +#ifdef ACCESSIBILITY 1.1357 + FireMenuItemActiveEvent(); 1.1358 +#endif 1.1359 + 1.1360 + return NS_OK; 1.1361 +} 1.1362 + 1.1363 +//---------------------------------------------------------------------- 1.1364 +// End nsISelectControlFrame 1.1365 +//---------------------------------------------------------------------- 1.1366 + 1.1367 +nsresult 1.1368 +nsListControlFrame::SetFormProperty(nsIAtom* aName, 1.1369 + const nsAString& aValue) 1.1370 +{ 1.1371 + if (nsGkAtoms::selected == aName) { 1.1372 + return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec. 1.1373 + } else if (nsGkAtoms::selectedindex == aName) { 1.1374 + // You shouldn't be calling me for this!!! 1.1375 + return NS_ERROR_INVALID_ARG; 1.1376 + } 1.1377 + 1.1378 + // We should be told about selectedIndex by the DOM element through 1.1379 + // OnOptionSelected 1.1380 + 1.1381 + return NS_OK; 1.1382 +} 1.1383 + 1.1384 +void 1.1385 +nsListControlFrame::AboutToDropDown() 1.1386 +{ 1.1387 + NS_ASSERTION(IsInDropDownMode(), 1.1388 + "AboutToDropDown called without being in dropdown mode"); 1.1389 + 1.1390 + // Our widget doesn't get invalidated on changes to the rest of the document, 1.1391 + // so compute and store this color at the start of a dropdown so we don't 1.1392 + // get weird painting behaviour. 1.1393 + // We start looking for backgrounds above the combobox frame to avoid 1.1394 + // duplicating the combobox frame's background and compose each background 1.1395 + // color we find underneath until we have an opaque color, or run out of 1.1396 + // backgrounds. We compose with the PresContext default background color, 1.1397 + // which is always opaque, in case we don't end up with an opaque color. 1.1398 + // This gives us a very poor approximation of translucency. 1.1399 + nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame); 1.1400 + nsStyleContext* context = comboboxFrame->StyleContext()->GetParent(); 1.1401 + mLastDropdownBackstopColor = NS_RGBA(0,0,0,0); 1.1402 + while (NS_GET_A(mLastDropdownBackstopColor) < 255 && context) { 1.1403 + mLastDropdownBackstopColor = 1.1404 + NS_ComposeColors(context->StyleBackground()->mBackgroundColor, 1.1405 + mLastDropdownBackstopColor); 1.1406 + context = context->GetParent(); 1.1407 + } 1.1408 + mLastDropdownBackstopColor = 1.1409 + NS_ComposeColors(PresContext()->DefaultBackgroundColor(), 1.1410 + mLastDropdownBackstopColor); 1.1411 + 1.1412 + if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) { 1.1413 + nsWeakFrame weakFrame(this); 1.1414 + ScrollToIndex(GetSelectedIndex()); 1.1415 + if (!weakFrame.IsAlive()) { 1.1416 + return; 1.1417 + } 1.1418 +#ifdef ACCESSIBILITY 1.1419 + FireMenuItemActiveEvent(); // Inform assistive tech what got focus 1.1420 +#endif 1.1421 + } 1.1422 + mItemSelectionStarted = false; 1.1423 +} 1.1424 + 1.1425 +// We are about to be rolledup from the outside (ComboboxFrame) 1.1426 +void 1.1427 +nsListControlFrame::AboutToRollup() 1.1428 +{ 1.1429 + // We've been updating the combobox with the keyboard up until now, but not 1.1430 + // with the mouse. The problem is, even with mouse selection, we are 1.1431 + // updating the <select>. So if the mouse goes over an option just before 1.1432 + // he leaves the box and clicks, that's what the <select> will show. 1.1433 + // 1.1434 + // To deal with this we say "whatever is in the combobox is canonical." 1.1435 + // - IF the combobox is different from the current selected index, we 1.1436 + // reset the index. 1.1437 + 1.1438 + if (IsInDropDownMode()) { 1.1439 + ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us 1.1440 + } 1.1441 +} 1.1442 + 1.1443 +nsresult 1.1444 +nsListControlFrame::DidReflow(nsPresContext* aPresContext, 1.1445 + const nsHTMLReflowState* aReflowState, 1.1446 + nsDidReflowStatus aStatus) 1.1447 +{ 1.1448 + nsresult rv; 1.1449 + bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow && 1.1450 + aPresContext->HasPendingInterrupt(); 1.1451 + 1.1452 + rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus); 1.1453 + 1.1454 + if (mNeedToReset && !wasInterrupted) { 1.1455 + mNeedToReset = false; 1.1456 + // Suppress scrolling to the selected element if we restored 1.1457 + // scroll history state AND the list contents have not changed 1.1458 + // since we loaded all the children AND nothing else forced us 1.1459 + // to scroll by calling ResetList(true). The latter two conditions 1.1460 + // are folded into mPostChildrenLoadedReset. 1.1461 + // 1.1462 + // The idea is that we want scroll history restoration to trump ResetList 1.1463 + // scrolling to the selected element, when the ResetList was probably only 1.1464 + // caused by content loading normally. 1.1465 + ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset); 1.1466 + } 1.1467 + 1.1468 + mHasPendingInterruptAtStartOfReflow = false; 1.1469 + return rv; 1.1470 +} 1.1471 + 1.1472 +nsIAtom* 1.1473 +nsListControlFrame::GetType() const 1.1474 +{ 1.1475 + return nsGkAtoms::listControlFrame; 1.1476 +} 1.1477 + 1.1478 +#ifdef DEBUG_FRAME_DUMP 1.1479 +nsresult 1.1480 +nsListControlFrame::GetFrameName(nsAString& aResult) const 1.1481 +{ 1.1482 + return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult); 1.1483 +} 1.1484 +#endif 1.1485 + 1.1486 +nscoord 1.1487 +nsListControlFrame::GetHeightOfARow() 1.1488 +{ 1.1489 + return HeightOfARow(); 1.1490 +} 1.1491 + 1.1492 +nsresult 1.1493 +nsListControlFrame::IsOptionDisabled(int32_t anIndex, bool &aIsDisabled) 1.1494 +{ 1.1495 + nsRefPtr<dom::HTMLSelectElement> sel = 1.1496 + dom::HTMLSelectElement::FromContent(mContent); 1.1497 + if (sel) { 1.1498 + sel->IsOptionDisabled(anIndex, &aIsDisabled); 1.1499 + return NS_OK; 1.1500 + } 1.1501 + return NS_ERROR_FAILURE; 1.1502 +} 1.1503 + 1.1504 +//---------------------------------------------------------------------- 1.1505 +// helper 1.1506 +//---------------------------------------------------------------------- 1.1507 +bool 1.1508 +nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent) 1.1509 +{ 1.1510 + // only allow selection with the left button 1.1511 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); 1.1512 + if (mouseEvent) { 1.1513 + int16_t whichButton; 1.1514 + if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) { 1.1515 + return whichButton != 0?false:true; 1.1516 + } 1.1517 + } 1.1518 + return false; 1.1519 +} 1.1520 + 1.1521 +nscoord 1.1522 +nsListControlFrame::CalcFallbackRowHeight(float aFontSizeInflation) 1.1523 +{ 1.1524 + nscoord rowHeight = 0; 1.1525 + 1.1526 + nsRefPtr<nsFontMetrics> fontMet; 1.1527 + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet), 1.1528 + aFontSizeInflation); 1.1529 + if (fontMet) { 1.1530 + rowHeight = fontMet->MaxHeight(); 1.1531 + } 1.1532 + 1.1533 + return rowHeight; 1.1534 +} 1.1535 + 1.1536 +nscoord 1.1537 +nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow, 1.1538 + int32_t aNumberOfOptions) 1.1539 +{ 1.1540 + NS_PRECONDITION(!IsInDropDownMode(), 1.1541 + "Shouldn't be in dropdown mode when we call this"); 1.1542 + 1.1543 + dom::HTMLSelectElement* select = 1.1544 + dom::HTMLSelectElement::FromContentOrNull(mContent); 1.1545 + if (select) { 1.1546 + mNumDisplayRows = select->Size(); 1.1547 + } else { 1.1548 + mNumDisplayRows = 1; 1.1549 + } 1.1550 + 1.1551 + if (mNumDisplayRows < 1) { 1.1552 + mNumDisplayRows = 4; 1.1553 + } 1.1554 + 1.1555 + return mNumDisplayRows * aHeightOfARow; 1.1556 +} 1.1557 + 1.1558 +//---------------------------------------------------------------------- 1.1559 +// nsIDOMMouseListener 1.1560 +//---------------------------------------------------------------------- 1.1561 +nsresult 1.1562 +nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent) 1.1563 +{ 1.1564 + NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null."); 1.1565 + 1.1566 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); 1.1567 + NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE); 1.1568 + 1.1569 + UpdateInListState(aMouseEvent); 1.1570 + 1.1571 + mButtonDown = false; 1.1572 + 1.1573 + EventStates eventStates = mContent->AsElement()->State(); 1.1574 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { 1.1575 + return NS_OK; 1.1576 + } 1.1577 + 1.1578 + // only allow selection with the left button 1.1579 + // if a right button click is on the combobox itself 1.1580 + // or on the select when in listbox mode, then let the click through 1.1581 + if (!IsLeftButton(aMouseEvent)) { 1.1582 + if (IsInDropDownMode()) { 1.1583 + if (!IgnoreMouseEventForSelection(aMouseEvent)) { 1.1584 + aMouseEvent->PreventDefault(); 1.1585 + aMouseEvent->StopPropagation(); 1.1586 + } else { 1.1587 + CaptureMouseEvents(false); 1.1588 + return NS_OK; 1.1589 + } 1.1590 + CaptureMouseEvents(false); 1.1591 + return NS_ERROR_FAILURE; // means consume event 1.1592 + } else { 1.1593 + CaptureMouseEvents(false); 1.1594 + return NS_OK; 1.1595 + } 1.1596 + } 1.1597 + 1.1598 + const nsStyleVisibility* vis = StyleVisibility(); 1.1599 + 1.1600 + if (!vis->IsVisible()) { 1.1601 + return NS_OK; 1.1602 + } 1.1603 + 1.1604 + if (IsInDropDownMode()) { 1.1605 + // XXX This is a bit of a hack, but..... 1.1606 + // But the idea here is to make sure you get an "onclick" event when you mouse 1.1607 + // down on the select and the drag over an option and let go 1.1608 + // And then NOT get an "onclick" event when when you click down on the select 1.1609 + // and then up outside of the select 1.1610 + // the EventStateManager tracks the content of the mouse down and the mouse up 1.1611 + // to make sure they are the same, and the onclick is sent in the PostHandleEvent 1.1612 + // depeneding on whether the clickCount is non-zero. 1.1613 + // So we cheat here by either setting or unsetting the clcikCount in the native event 1.1614 + // so the right thing happens for the onclick event 1.1615 + WidgetMouseEvent* mouseEvent = 1.1616 + aMouseEvent->GetInternalNSEvent()->AsMouseEvent(); 1.1617 + 1.1618 + int32_t selectedIndex; 1.1619 + if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { 1.1620 + // If it's disabled, disallow the click and leave. 1.1621 + bool isDisabled = false; 1.1622 + IsOptionDisabled(selectedIndex, isDisabled); 1.1623 + if (isDisabled) { 1.1624 + aMouseEvent->PreventDefault(); 1.1625 + aMouseEvent->StopPropagation(); 1.1626 + CaptureMouseEvents(false); 1.1627 + return NS_ERROR_FAILURE; 1.1628 + } 1.1629 + 1.1630 + if (kNothingSelected != selectedIndex) { 1.1631 + nsWeakFrame weakFrame(this); 1.1632 + ComboboxFinish(selectedIndex); 1.1633 + if (!weakFrame.IsAlive()) 1.1634 + return NS_OK; 1.1635 + FireOnChange(); 1.1636 + } 1.1637 + 1.1638 + mouseEvent->clickCount = 1; 1.1639 + } else { 1.1640 + // the click was out side of the select or its dropdown 1.1641 + mouseEvent->clickCount = IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0; 1.1642 + } 1.1643 + } else { 1.1644 + CaptureMouseEvents(false); 1.1645 + // Notify 1.1646 + if (mChangesSinceDragStart) { 1.1647 + // reset this so that future MouseUps without a prior MouseDown 1.1648 + // won't fire onchange 1.1649 + mChangesSinceDragStart = false; 1.1650 + FireOnChange(); 1.1651 + } 1.1652 + } 1.1653 + 1.1654 + return NS_OK; 1.1655 +} 1.1656 + 1.1657 +void 1.1658 +nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent) 1.1659 +{ 1.1660 + if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown()) 1.1661 + return; 1.1662 + 1.1663 + nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this); 1.1664 + nsRect borderInnerEdge = GetScrollPortRect(); 1.1665 + if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) { 1.1666 + mItemSelectionStarted = true; 1.1667 + } 1.1668 +} 1.1669 + 1.1670 +bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent) 1.1671 +{ 1.1672 + if (!mComboboxFrame) 1.1673 + return false; 1.1674 + 1.1675 + // Our DOM listener does get called when the dropdown is not 1.1676 + // showing, because it listens to events on the SELECT element 1.1677 + if (!mComboboxFrame->IsDroppedDown()) 1.1678 + return true; 1.1679 + 1.1680 + return !mItemSelectionStarted; 1.1681 +} 1.1682 + 1.1683 +#ifdef ACCESSIBILITY 1.1684 +void 1.1685 +nsListControlFrame::FireMenuItemActiveEvent() 1.1686 +{ 1.1687 + if (mFocused != this && !IsInDropDownMode()) { 1.1688 + return; 1.1689 + } 1.1690 + 1.1691 + nsCOMPtr<nsIContent> optionContent = GetCurrentOption(); 1.1692 + if (!optionContent) { 1.1693 + return; 1.1694 + } 1.1695 + 1.1696 + FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent); 1.1697 +} 1.1698 +#endif 1.1699 + 1.1700 +nsresult 1.1701 +nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent, 1.1702 + int32_t& aCurIndex) 1.1703 +{ 1.1704 + if (IgnoreMouseEventForSelection(aMouseEvent)) 1.1705 + return NS_ERROR_FAILURE; 1.1706 + 1.1707 + if (nsIPresShell::GetCapturingContent() != mContent) { 1.1708 + // If we're not capturing, then ignore movement in the border 1.1709 + nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this); 1.1710 + nsRect borderInnerEdge = GetScrollPortRect(); 1.1711 + if (!borderInnerEdge.Contains(pt)) { 1.1712 + return NS_ERROR_FAILURE; 1.1713 + } 1.1714 + } 1.1715 + 1.1716 + nsRefPtr<dom::HTMLOptionElement> option; 1.1717 + for (nsCOMPtr<nsIContent> content = 1.1718 + PresContext()->EventStateManager()->GetEventTargetContent(nullptr); 1.1719 + content && !option; 1.1720 + content = content->GetParent()) { 1.1721 + option = dom::HTMLOptionElement::FromContent(content); 1.1722 + } 1.1723 + 1.1724 + if (option) { 1.1725 + aCurIndex = option->Index(); 1.1726 + MOZ_ASSERT(aCurIndex >= 0); 1.1727 + return NS_OK; 1.1728 + } 1.1729 + 1.1730 + int32_t numOptions = GetNumberOfOptions(); 1.1731 + if (numOptions < 1) 1.1732 + return NS_ERROR_FAILURE; 1.1733 + 1.1734 + nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this); 1.1735 + 1.1736 + // If the event coordinate is above the first option frame, then target the 1.1737 + // first option frame 1.1738 + nsRefPtr<dom::HTMLOptionElement> firstOption = GetOption(0); 1.1739 + NS_ASSERTION(firstOption, "Can't find first option that's supposed to be there"); 1.1740 + nsIFrame* optionFrame = firstOption->GetPrimaryFrame(); 1.1741 + if (optionFrame) { 1.1742 + nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this); 1.1743 + if (ptInOptionFrame.y < 0 && ptInOptionFrame.x >= 0 && 1.1744 + ptInOptionFrame.x < optionFrame->GetSize().width) { 1.1745 + aCurIndex = 0; 1.1746 + return NS_OK; 1.1747 + } 1.1748 + } 1.1749 + 1.1750 + nsRefPtr<dom::HTMLOptionElement> lastOption = GetOption(numOptions - 1); 1.1751 + // If the event coordinate is below the last option frame, then target the 1.1752 + // last option frame 1.1753 + NS_ASSERTION(lastOption, "Can't find last option that's supposed to be there"); 1.1754 + optionFrame = lastOption->GetPrimaryFrame(); 1.1755 + if (optionFrame) { 1.1756 + nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this); 1.1757 + if (ptInOptionFrame.y >= optionFrame->GetSize().height && ptInOptionFrame.x >= 0 && 1.1758 + ptInOptionFrame.x < optionFrame->GetSize().width) { 1.1759 + aCurIndex = numOptions - 1; 1.1760 + return NS_OK; 1.1761 + } 1.1762 + } 1.1763 + 1.1764 + return NS_ERROR_FAILURE; 1.1765 +} 1.1766 + 1.1767 +nsresult 1.1768 +nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent) 1.1769 +{ 1.1770 + NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null."); 1.1771 + 1.1772 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); 1.1773 + NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE); 1.1774 + 1.1775 + UpdateInListState(aMouseEvent); 1.1776 + 1.1777 + EventStates eventStates = mContent->AsElement()->State(); 1.1778 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { 1.1779 + return NS_OK; 1.1780 + } 1.1781 + 1.1782 + // only allow selection with the left button 1.1783 + // if a right button click is on the combobox itself 1.1784 + // or on the select when in listbox mode, then let the click through 1.1785 + if (!IsLeftButton(aMouseEvent)) { 1.1786 + if (IsInDropDownMode()) { 1.1787 + if (!IgnoreMouseEventForSelection(aMouseEvent)) { 1.1788 + aMouseEvent->PreventDefault(); 1.1789 + aMouseEvent->StopPropagation(); 1.1790 + } else { 1.1791 + return NS_OK; 1.1792 + } 1.1793 + return NS_ERROR_FAILURE; // means consume event 1.1794 + } else { 1.1795 + return NS_OK; 1.1796 + } 1.1797 + } 1.1798 + 1.1799 + int32_t selectedIndex; 1.1800 + if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { 1.1801 + // Handle Like List 1.1802 + mButtonDown = true; 1.1803 + CaptureMouseEvents(true); 1.1804 + nsWeakFrame weakFrame(this); 1.1805 + bool change = 1.1806 + HandleListSelection(aMouseEvent, selectedIndex); // might destroy us 1.1807 + if (!weakFrame.IsAlive()) { 1.1808 + return NS_OK; 1.1809 + } 1.1810 + mChangesSinceDragStart = change; 1.1811 + } else { 1.1812 + // NOTE: the combo box is responsible for dropping it down 1.1813 + if (mComboboxFrame) { 1.1814 + if (XRE_GetProcessType() == GeckoProcessType_Content && BrowserTabsRemote()) { 1.1815 + nsContentUtils::DispatchChromeEvent(mContent->OwnerDoc(), mContent, 1.1816 + NS_LITERAL_STRING("mozshowdropdown"), true, 1.1817 + false); 1.1818 + return NS_OK; 1.1819 + } 1.1820 + 1.1821 + if (!IgnoreMouseEventForSelection(aMouseEvent)) { 1.1822 + return NS_OK; 1.1823 + } 1.1824 + 1.1825 + if (!nsComboboxControlFrame::ToolkitHasNativePopup()) 1.1826 + { 1.1827 + bool isDroppedDown = mComboboxFrame->IsDroppedDown(); 1.1828 + nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame); 1.1829 + nsWeakFrame weakFrame(comboFrame); 1.1830 + mComboboxFrame->ShowDropDown(!isDroppedDown); 1.1831 + if (!weakFrame.IsAlive()) 1.1832 + return NS_OK; 1.1833 + if (isDroppedDown) { 1.1834 + CaptureMouseEvents(false); 1.1835 + } 1.1836 + } 1.1837 + } 1.1838 + } 1.1839 + 1.1840 + return NS_OK; 1.1841 +} 1.1842 + 1.1843 +//---------------------------------------------------------------------- 1.1844 +// nsIDOMMouseMotionListener 1.1845 +//---------------------------------------------------------------------- 1.1846 +nsresult 1.1847 +nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent) 1.1848 +{ 1.1849 + NS_ASSERTION(aMouseEvent, "aMouseEvent is null."); 1.1850 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); 1.1851 + NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE); 1.1852 + 1.1853 + UpdateInListState(aMouseEvent); 1.1854 + 1.1855 + if (IsInDropDownMode()) { 1.1856 + if (mComboboxFrame->IsDroppedDown()) { 1.1857 + int32_t selectedIndex; 1.1858 + if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { 1.1859 + PerformSelection(selectedIndex, false, false); // might destroy us 1.1860 + } 1.1861 + } 1.1862 + } else {// XXX - temporary until we get drag events 1.1863 + if (mButtonDown) { 1.1864 + return DragMove(aMouseEvent); // might destroy us 1.1865 + } 1.1866 + } 1.1867 + return NS_OK; 1.1868 +} 1.1869 + 1.1870 +nsresult 1.1871 +nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent) 1.1872 +{ 1.1873 + NS_ASSERTION(aMouseEvent, "aMouseEvent is null."); 1.1874 + 1.1875 + UpdateInListState(aMouseEvent); 1.1876 + 1.1877 + if (!IsInDropDownMode()) { 1.1878 + int32_t selectedIndex; 1.1879 + if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) { 1.1880 + // Don't waste cycles if we already dragged over this item 1.1881 + if (selectedIndex == mEndSelectionIndex) { 1.1882 + return NS_OK; 1.1883 + } 1.1884 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); 1.1885 + NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!"); 1.1886 + bool isControl; 1.1887 +#ifdef XP_MACOSX 1.1888 + mouseEvent->GetMetaKey(&isControl); 1.1889 +#else 1.1890 + mouseEvent->GetCtrlKey(&isControl); 1.1891 +#endif 1.1892 + nsWeakFrame weakFrame(this); 1.1893 + // Turn SHIFT on when you are dragging, unless control is on. 1.1894 + bool wasChanged = PerformSelection(selectedIndex, 1.1895 + !isControl, isControl); 1.1896 + if (!weakFrame.IsAlive()) { 1.1897 + return NS_OK; 1.1898 + } 1.1899 + mChangesSinceDragStart = mChangesSinceDragStart || wasChanged; 1.1900 + } 1.1901 + } 1.1902 + return NS_OK; 1.1903 +} 1.1904 + 1.1905 +//---------------------------------------------------------------------- 1.1906 +// Scroll helpers. 1.1907 +//---------------------------------------------------------------------- 1.1908 +void 1.1909 +nsListControlFrame::ScrollToIndex(int32_t aIndex) 1.1910 +{ 1.1911 + if (aIndex < 0) { 1.1912 + // XXX shouldn't we just do nothing if we're asked to scroll to 1.1913 + // kNothingSelected? 1.1914 + ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT); 1.1915 + } else { 1.1916 + nsRefPtr<dom::HTMLOptionElement> option = 1.1917 + GetOption(SafeCast<uint32_t>(aIndex)); 1.1918 + if (option) { 1.1919 + ScrollToFrame(*option); 1.1920 + } 1.1921 + } 1.1922 +} 1.1923 + 1.1924 +void 1.1925 +nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement) 1.1926 +{ 1.1927 + // otherwise we find the content's frame and scroll to it 1.1928 + nsIFrame* childFrame = aOptElement.GetPrimaryFrame(); 1.1929 + if (childFrame) { 1.1930 + PresContext()->PresShell()-> 1.1931 + ScrollFrameRectIntoView(childFrame, 1.1932 + nsRect(nsPoint(0, 0), childFrame->GetSize()), 1.1933 + nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(), 1.1934 + nsIPresShell::SCROLL_OVERFLOW_HIDDEN | 1.1935 + nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY); 1.1936 + } 1.1937 +} 1.1938 + 1.1939 +//--------------------------------------------------------------------- 1.1940 +// Ok, the entire idea of this routine is to move to the next item that 1.1941 +// is suppose to be selected. If the item is disabled then we search in 1.1942 +// the same direction looking for the next item to select. If we run off 1.1943 +// the end of the list then we start at the end of the list and search 1.1944 +// backwards until we get back to the original item or an enabled option 1.1945 +// 1.1946 +// aStartIndex - the index to start searching from 1.1947 +// aNewIndex - will get set to the new index if it finds one 1.1948 +// aNumOptions - the total number of options in the list 1.1949 +// aDoAdjustInc - the initial increment 1-n 1.1950 +// aDoAdjustIncNext - the increment used to search for the next enabled option 1.1951 +// 1.1952 +// the aDoAdjustInc could be a "1" for a single item or 1.1953 +// any number greater representing a page of items 1.1954 +// 1.1955 +void 1.1956 +nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex, 1.1957 + int32_t &aNewIndex, 1.1958 + int32_t aNumOptions, 1.1959 + int32_t aDoAdjustInc, 1.1960 + int32_t aDoAdjustIncNext) 1.1961 +{ 1.1962 + // Cannot select anything if there is nothing to select 1.1963 + if (aNumOptions == 0) { 1.1964 + aNewIndex = kNothingSelected; 1.1965 + return; 1.1966 + } 1.1967 + 1.1968 + // means we reached the end of the list and now we are searching backwards 1.1969 + bool doingReverse = false; 1.1970 + // lowest index in the search range 1.1971 + int32_t bottom = 0; 1.1972 + // highest index in the search range 1.1973 + int32_t top = aNumOptions; 1.1974 + 1.1975 + // Start off keyboard options at selectedIndex if nothing else is defaulted to 1.1976 + // 1.1977 + // XXX Perhaps this should happen for mouse too, to start off shift click 1.1978 + // automatically in multiple ... to do this, we'd need to override 1.1979 + // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not 1.1980 + // sure of the effects, though, so I'm not doing it just yet. 1.1981 + int32_t startIndex = aStartIndex; 1.1982 + if (startIndex < bottom) { 1.1983 + startIndex = GetSelectedIndex(); 1.1984 + } 1.1985 + int32_t newIndex = startIndex + aDoAdjustInc; 1.1986 + 1.1987 + // make sure we start off in the range 1.1988 + if (newIndex < bottom) { 1.1989 + newIndex = 0; 1.1990 + } else if (newIndex >= top) { 1.1991 + newIndex = aNumOptions-1; 1.1992 + } 1.1993 + 1.1994 + while (1) { 1.1995 + // if the newIndex isn't disabled, we are golden, bail out 1.1996 + bool isDisabled = true; 1.1997 + if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) { 1.1998 + break; 1.1999 + } 1.2000 + 1.2001 + // it WAS disabled, so sart looking ahead for the next enabled option 1.2002 + newIndex += aDoAdjustIncNext; 1.2003 + 1.2004 + // well, if we reach end reverse the search 1.2005 + if (newIndex < bottom) { 1.2006 + if (doingReverse) { 1.2007 + return; // if we are in reverse mode and reach the end bail out 1.2008 + } else { 1.2009 + // reset the newIndex to the end of the list we hit 1.2010 + // reverse the incrementer 1.2011 + // set the other end of the list to our original starting index 1.2012 + newIndex = bottom; 1.2013 + aDoAdjustIncNext = 1; 1.2014 + doingReverse = true; 1.2015 + top = startIndex; 1.2016 + } 1.2017 + } else if (newIndex >= top) { 1.2018 + if (doingReverse) { 1.2019 + return; // if we are in reverse mode and reach the end bail out 1.2020 + } else { 1.2021 + // reset the newIndex to the end of the list we hit 1.2022 + // reverse the incrementer 1.2023 + // set the other end of the list to our original starting index 1.2024 + newIndex = top - 1; 1.2025 + aDoAdjustIncNext = -1; 1.2026 + doingReverse = true; 1.2027 + bottom = startIndex; 1.2028 + } 1.2029 + } 1.2030 + } 1.2031 + 1.2032 + // Looks like we found one 1.2033 + aNewIndex = newIndex; 1.2034 +} 1.2035 + 1.2036 +nsAString& 1.2037 +nsListControlFrame::GetIncrementalString() 1.2038 +{ 1.2039 + if (sIncrementalString == nullptr) 1.2040 + sIncrementalString = new nsString(); 1.2041 + 1.2042 + return *sIncrementalString; 1.2043 +} 1.2044 + 1.2045 +void 1.2046 +nsListControlFrame::Shutdown() 1.2047 +{ 1.2048 + delete sIncrementalString; 1.2049 + sIncrementalString = nullptr; 1.2050 +} 1.2051 + 1.2052 +void 1.2053 +nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent) 1.2054 +{ 1.2055 + // Cocoa widgets do native popups, so don't try to show 1.2056 + // dropdowns there. 1.2057 + if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) { 1.2058 + aKeyEvent->PreventDefault(); 1.2059 + if (!mComboboxFrame->IsDroppedDown()) { 1.2060 + mComboboxFrame->ShowDropDown(true); 1.2061 + } else { 1.2062 + nsWeakFrame weakFrame(this); 1.2063 + // mEndSelectionIndex is the last item that got selected. 1.2064 + ComboboxFinish(mEndSelectionIndex); 1.2065 + if (weakFrame.IsAlive()) { 1.2066 + FireOnChange(); 1.2067 + } 1.2068 + } 1.2069 + } 1.2070 +} 1.2071 + 1.2072 +nsresult 1.2073 +nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent) 1.2074 +{ 1.2075 + MOZ_ASSERT(aKeyEvent, "aKeyEvent is null."); 1.2076 + 1.2077 + EventStates eventStates = mContent->AsElement()->State(); 1.2078 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { 1.2079 + return NS_OK; 1.2080 + } 1.2081 + 1.2082 + AutoIncrementalSearchResetter incrementalSearchResetter; 1.2083 + 1.2084 + // Don't check defaultPrevented value because other browsers don't prevent 1.2085 + // the key navigation of list control even if preventDefault() is called. 1.2086 + 1.2087 + const WidgetKeyboardEvent* keyEvent = 1.2088 + aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); 1.2089 + MOZ_ASSERT(keyEvent, 1.2090 + "DOM event must have WidgetKeyboardEvent for its internal event"); 1.2091 + 1.2092 + if (keyEvent->IsAlt()) { 1.2093 + if (keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) { 1.2094 + DropDownToggleKey(aKeyEvent); 1.2095 + } 1.2096 + return NS_OK; 1.2097 + } 1.2098 + 1.2099 + // now make sure there are options or we are wasting our time 1.2100 + nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions(); 1.2101 + NS_ENSURE_TRUE(options, NS_ERROR_FAILURE); 1.2102 + 1.2103 + uint32_t numOptions = options->Length(); 1.2104 + 1.2105 + // this is the new index to set 1.2106 + int32_t newIndex = kNothingSelected; 1.2107 + 1.2108 + bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta()); 1.2109 + // Don't try to handle multiple-select pgUp/pgDown in single-select lists. 1.2110 + if (isControlOrMeta && !GetMultiple() && 1.2111 + (keyEvent->keyCode == NS_VK_PAGE_UP || 1.2112 + keyEvent->keyCode == NS_VK_PAGE_DOWN)) { 1.2113 + return NS_OK; 1.2114 + } 1.2115 + if (isControlOrMeta && (keyEvent->keyCode == NS_VK_UP || 1.2116 + keyEvent->keyCode == NS_VK_LEFT || 1.2117 + keyEvent->keyCode == NS_VK_DOWN || 1.2118 + keyEvent->keyCode == NS_VK_RIGHT || 1.2119 + keyEvent->keyCode == NS_VK_HOME || 1.2120 + keyEvent->keyCode == NS_VK_END)) { 1.2121 + // Don't go into multiple-select mode unless this list can handle it. 1.2122 + isControlOrMeta = mControlSelectMode = GetMultiple(); 1.2123 + } else if (keyEvent->keyCode != NS_VK_SPACE) { 1.2124 + mControlSelectMode = false; 1.2125 + } 1.2126 + 1.2127 + switch (keyEvent->keyCode) { 1.2128 + case NS_VK_UP: 1.2129 + case NS_VK_LEFT: 1.2130 + AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex, 1.2131 + static_cast<int32_t>(numOptions), 1.2132 + -1, -1); 1.2133 + break; 1.2134 + case NS_VK_DOWN: 1.2135 + case NS_VK_RIGHT: 1.2136 + AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex, 1.2137 + static_cast<int32_t>(numOptions), 1.2138 + 1, 1); 1.2139 + break; 1.2140 + case NS_VK_RETURN: 1.2141 + if (IsInDropDownMode()) { 1.2142 + if (mComboboxFrame->IsDroppedDown()) { 1.2143 + // If the select element is a dropdown style, Enter key should be 1.2144 + // consumed while the dropdown is open for security. 1.2145 + aKeyEvent->PreventDefault(); 1.2146 + 1.2147 + nsWeakFrame weakFrame(this); 1.2148 + ComboboxFinish(mEndSelectionIndex); 1.2149 + if (!weakFrame.IsAlive()) { 1.2150 + return NS_OK; 1.2151 + } 1.2152 + } 1.2153 + // XXX This is strange. On other browsers, "change" event is fired 1.2154 + // immediately after the selected item is changed rather than 1.2155 + // Enter key is pressed. 1.2156 + FireOnChange(); 1.2157 + return NS_OK; 1.2158 + } 1.2159 + 1.2160 + // If this is single select listbox, Enter key doesn't cause anything. 1.2161 + if (!GetMultiple()) { 1.2162 + return NS_OK; 1.2163 + } 1.2164 + 1.2165 + newIndex = mEndSelectionIndex; 1.2166 + break; 1.2167 + case NS_VK_ESCAPE: { 1.2168 + // If the select element is a listbox style, Escape key causes nothing. 1.2169 + if (!IsInDropDownMode()) { 1.2170 + return NS_OK; 1.2171 + } 1.2172 + 1.2173 + AboutToRollup(); 1.2174 + // If the select element is a dropdown style, Enter key should be 1.2175 + // consumed everytime since Escape key may be pressed accidentally after 1.2176 + // the dropdown is closed by Escepe key. 1.2177 + aKeyEvent->PreventDefault(); 1.2178 + return NS_OK; 1.2179 + } 1.2180 + case NS_VK_PAGE_UP: { 1.2181 + int32_t itemsPerPage = 1.2182 + std::max(1, static_cast<int32_t>(mNumDisplayRows - 1)); 1.2183 + AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex, 1.2184 + static_cast<int32_t>(numOptions), 1.2185 + -itemsPerPage, -1); 1.2186 + break; 1.2187 + } 1.2188 + case NS_VK_PAGE_DOWN: { 1.2189 + int32_t itemsPerPage = 1.2190 + std::max(1, static_cast<int32_t>(mNumDisplayRows - 1)); 1.2191 + AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex, 1.2192 + static_cast<int32_t>(numOptions), 1.2193 + itemsPerPage, 1); 1.2194 + break; 1.2195 + } 1.2196 + case NS_VK_HOME: 1.2197 + AdjustIndexForDisabledOpt(0, newIndex, 1.2198 + static_cast<int32_t>(numOptions), 1.2199 + 0, 1); 1.2200 + break; 1.2201 + case NS_VK_END: 1.2202 + AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions) - 1, newIndex, 1.2203 + static_cast<int32_t>(numOptions), 1.2204 + 0, -1); 1.2205 + break; 1.2206 + 1.2207 +#if defined(XP_WIN) 1.2208 + case NS_VK_F4: 1.2209 + if (!isControlOrMeta) { 1.2210 + DropDownToggleKey(aKeyEvent); 1.2211 + } 1.2212 + return NS_OK; 1.2213 +#endif 1.2214 + 1.2215 + default: // printable key will be handled by keypress event. 1.2216 + incrementalSearchResetter.Cancel(); 1.2217 + return NS_OK; 1.2218 + } 1.2219 + 1.2220 + aKeyEvent->PreventDefault(); 1.2221 + 1.2222 + // Actually process the new index and let the selection code 1.2223 + // do the scrolling for us 1.2224 + PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta); 1.2225 + return NS_OK; 1.2226 +} 1.2227 + 1.2228 +nsresult 1.2229 +nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent) 1.2230 +{ 1.2231 + MOZ_ASSERT(aKeyEvent, "aKeyEvent is null."); 1.2232 + 1.2233 + EventStates eventStates = mContent->AsElement()->State(); 1.2234 + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { 1.2235 + return NS_OK; 1.2236 + } 1.2237 + 1.2238 + AutoIncrementalSearchResetter incrementalSearchResetter; 1.2239 + 1.2240 + const WidgetKeyboardEvent* keyEvent = 1.2241 + aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); 1.2242 + MOZ_ASSERT(keyEvent, 1.2243 + "DOM event must have WidgetKeyboardEvent for its internal event"); 1.2244 + 1.2245 + // Select option with this as the first character 1.2246 + // XXX Not I18N compliant 1.2247 + 1.2248 + // Don't do incremental search if the key event has already consumed. 1.2249 + if (keyEvent->mFlags.mDefaultPrevented) { 1.2250 + return NS_OK; 1.2251 + } 1.2252 + 1.2253 + if (keyEvent->IsAlt()) { 1.2254 + return NS_OK; 1.2255 + } 1.2256 + 1.2257 + // With some keyboard layout, space key causes non-ASCII space. 1.2258 + // So, the check in keydown event handler isn't enough, we need to check it 1.2259 + // again with keypress event. 1.2260 + if (keyEvent->charCode != ' ') { 1.2261 + mControlSelectMode = false; 1.2262 + } 1.2263 + 1.2264 + bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta()); 1.2265 + if (isControlOrMeta && keyEvent->charCode != ' ') { 1.2266 + return NS_OK; 1.2267 + } 1.2268 + 1.2269 + // NOTE: If keyCode of keypress event is not 0, charCode is always 0. 1.2270 + // Therefore, all non-printable keys are not handled after this block. 1.2271 + if (!keyEvent->charCode) { 1.2272 + // Backspace key will delete the last char in the string. Otherwise, 1.2273 + // non-printable keypress should reset incremental search. 1.2274 + if (keyEvent->keyCode == NS_VK_BACK) { 1.2275 + incrementalSearchResetter.Cancel(); 1.2276 + if (!GetIncrementalString().IsEmpty()) { 1.2277 + GetIncrementalString().Truncate(GetIncrementalString().Length() - 1); 1.2278 + } 1.2279 + aKeyEvent->PreventDefault(); 1.2280 + } else { 1.2281 + // XXX When a select element has focus, even if the key causes nothing, 1.2282 + // it might be better to call preventDefault() here because nobody 1.2283 + // should expect one of other elements including chrome handles the 1.2284 + // key event. 1.2285 + } 1.2286 + return NS_OK; 1.2287 + } 1.2288 + 1.2289 + incrementalSearchResetter.Cancel(); 1.2290 + 1.2291 + // We ate the key if we got this far. 1.2292 + aKeyEvent->PreventDefault(); 1.2293 + 1.2294 + // XXX Why don't we check/modify timestamp first? 1.2295 + 1.2296 + // Incremental Search: if time elapsed is below 1.2297 + // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search 1.2298 + // string we will use to find options and start searching at the current 1.2299 + // keystroke. Otherwise, Truncate the string if it's been a long time 1.2300 + // since our last keypress. 1.2301 + if (keyEvent->time - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) { 1.2302 + // If this is ' ' and we are at the beginning of the string, treat it as 1.2303 + // "select this option" (bug 191543) 1.2304 + if (keyEvent->charCode == ' ') { 1.2305 + // Actually process the new index and let the selection code 1.2306 + // do the scrolling for us 1.2307 + PostHandleKeyEvent(mEndSelectionIndex, keyEvent->charCode, 1.2308 + keyEvent->IsShift(), isControlOrMeta); 1.2309 + 1.2310 + return NS_OK; 1.2311 + } 1.2312 + 1.2313 + GetIncrementalString().Truncate(); 1.2314 + } 1.2315 + 1.2316 + gLastKeyTime = keyEvent->time; 1.2317 + 1.2318 + // Append this keystroke to the search string. 1.2319 + char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->charCode)); 1.2320 + GetIncrementalString().Append(uniChar); 1.2321 + 1.2322 + // See bug 188199, if all letters in incremental string are same, just try to 1.2323 + // match the first one 1.2324 + nsAutoString incrementalString(GetIncrementalString()); 1.2325 + uint32_t charIndex = 1, stringLength = incrementalString.Length(); 1.2326 + while (charIndex < stringLength && 1.2327 + incrementalString[charIndex] == incrementalString[charIndex - 1]) { 1.2328 + charIndex++; 1.2329 + } 1.2330 + if (charIndex == stringLength) { 1.2331 + incrementalString.Truncate(1); 1.2332 + stringLength = 1; 1.2333 + } 1.2334 + 1.2335 + // Determine where we're going to start reading the string 1.2336 + // If we have multiple characters to look for, we start looking *at* the 1.2337 + // current option. If we have only one character to look for, we start 1.2338 + // looking *after* the current option. 1.2339 + // Exception: if there is no option selected to start at, we always start 1.2340 + // *at* 0. 1.2341 + int32_t startIndex = GetSelectedIndex(); 1.2342 + if (startIndex == kNothingSelected) { 1.2343 + startIndex = 0; 1.2344 + } else if (stringLength == 1) { 1.2345 + startIndex++; 1.2346 + } 1.2347 + 1.2348 + // now make sure there are options or we are wasting our time 1.2349 + nsRefPtr<dom::HTMLOptionsCollection> options = GetOptions(); 1.2350 + NS_ENSURE_TRUE(options, NS_ERROR_FAILURE); 1.2351 + 1.2352 + uint32_t numOptions = options->Length(); 1.2353 + 1.2354 + nsWeakFrame weakFrame(this); 1.2355 + for (uint32_t i = 0; i < numOptions; ++i) { 1.2356 + uint32_t index = (i + startIndex) % numOptions; 1.2357 + nsRefPtr<dom::HTMLOptionElement> optionElement = 1.2358 + options->ItemAsOption(index); 1.2359 + if (!optionElement || !optionElement->GetPrimaryFrame()) { 1.2360 + continue; 1.2361 + } 1.2362 + 1.2363 + nsAutoString text; 1.2364 + if (NS_FAILED(optionElement->GetText(text)) || 1.2365 + !StringBeginsWith( 1.2366 + nsContentUtils::TrimWhitespace< 1.2367 + nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false), 1.2368 + incrementalString, nsCaseInsensitiveStringComparator())) { 1.2369 + continue; 1.2370 + } 1.2371 + 1.2372 + bool wasChanged = PerformSelection(index, keyEvent->IsShift(), isControlOrMeta); 1.2373 + if (!weakFrame.IsAlive()) { 1.2374 + return NS_OK; 1.2375 + } 1.2376 + if (!wasChanged) { 1.2377 + break; 1.2378 + } 1.2379 + 1.2380 + // If UpdateSelection() returns false, that means the frame is no longer 1.2381 + // alive. We should stop doing anything. 1.2382 + if (!UpdateSelection()) { 1.2383 + return NS_OK; 1.2384 + } 1.2385 + break; 1.2386 + } 1.2387 + 1.2388 + return NS_OK; 1.2389 +} 1.2390 + 1.2391 +void 1.2392 +nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex, 1.2393 + uint32_t aCharCode, 1.2394 + bool aIsShift, 1.2395 + bool aIsControlOrMeta) 1.2396 +{ 1.2397 + if (aNewIndex == kNothingSelected) { 1.2398 + return; 1.2399 + } 1.2400 + 1.2401 + // If you hold control, but not shift, no key will actually do anything 1.2402 + // except space. 1.2403 + nsWeakFrame weakFrame(this); 1.2404 + bool wasChanged = false; 1.2405 + if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') { 1.2406 + mStartSelectionIndex = aNewIndex; 1.2407 + mEndSelectionIndex = aNewIndex; 1.2408 + InvalidateFocus(); 1.2409 + ScrollToIndex(aNewIndex); 1.2410 + if (!weakFrame.IsAlive()) { 1.2411 + return; 1.2412 + } 1.2413 + 1.2414 +#ifdef ACCESSIBILITY 1.2415 + FireMenuItemActiveEvent(); 1.2416 +#endif 1.2417 + } else if (mControlSelectMode && aCharCode == ' ') { 1.2418 + wasChanged = SingleSelection(aNewIndex, true); 1.2419 + } else { 1.2420 + wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta); 1.2421 + } 1.2422 + if (wasChanged && weakFrame.IsAlive()) { 1.2423 + // dispatch event, update combobox, etc. 1.2424 + UpdateSelection(); 1.2425 + } 1.2426 +} 1.2427 + 1.2428 + 1.2429 +/****************************************************************************** 1.2430 + * nsListEventListener 1.2431 + *****************************************************************************/ 1.2432 + 1.2433 +NS_IMPL_ISUPPORTS(nsListEventListener, nsIDOMEventListener) 1.2434 + 1.2435 +NS_IMETHODIMP 1.2436 +nsListEventListener::HandleEvent(nsIDOMEvent* aEvent) 1.2437 +{ 1.2438 + if (!mFrame) 1.2439 + return NS_OK; 1.2440 + 1.2441 + nsAutoString eventType; 1.2442 + aEvent->GetType(eventType); 1.2443 + if (eventType.EqualsLiteral("keydown")) 1.2444 + return mFrame->nsListControlFrame::KeyDown(aEvent); 1.2445 + if (eventType.EqualsLiteral("keypress")) 1.2446 + return mFrame->nsListControlFrame::KeyPress(aEvent); 1.2447 + if (eventType.EqualsLiteral("mousedown")) 1.2448 + return mFrame->nsListControlFrame::MouseDown(aEvent); 1.2449 + if (eventType.EqualsLiteral("mouseup")) 1.2450 + return mFrame->nsListControlFrame::MouseUp(aEvent); 1.2451 + if (eventType.EqualsLiteral("mousemove")) 1.2452 + return mFrame->nsListControlFrame::MouseMove(aEvent); 1.2453 + 1.2454 + NS_ABORT(); 1.2455 + return NS_OK; 1.2456 +}