layout/forms/nsListControlFrame.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial