1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 #ifndef nsListControlFrame_h___ 7 #define nsListControlFrame_h___ 8 9 #ifdef DEBUG_evaughan 10 //#define DEBUG_rods 11 #endif 12 13 #ifdef DEBUG_rods 14 //#define DO_REFLOW_DEBUG 15 //#define DO_REFLOW_COUNTER 16 //#define DO_UNCONSTRAINED_CHECK 17 //#define DO_PIXELS 18 #endif 19 20 #include "mozilla/Attributes.h" 21 #include "nsGfxScrollFrame.h" 22 #include "nsIFormControlFrame.h" 23 #include "nsISelectControlFrame.h" 24 #include "nsSelectsAreaFrame.h" 25 26 // X.h defines KeyPress 27 #ifdef KeyPress 28 # undef KeyPress 29 #endif 30 31 class nsComboboxControlFrame; 32 class nsPresContext; 33 class nsListEventListener; 34 35 namespace mozilla { 36 class PresShell; 37 namespace dom { 38 class Event; 39 class HTMLOptionElement; 40 class HTMLSelectElement; 41 class HTMLOptionsCollection; 42 } // namespace dom 43 } // namespace mozilla 44 45 /** 46 * Frame-based listbox. 47 */ 48 49 class nsListControlFrame final : public nsHTMLScrollFrame, 50 public nsIFormControlFrame, 51 public nsISelectControlFrame { 52 public: 53 typedef mozilla::dom::HTMLOptionElement HTMLOptionElement; 54 55 friend nsListControlFrame* NS_NewListControlFrame( 56 mozilla::PresShell* aPresShell, ComputedStyle* aStyle); 57 58 NS_DECL_QUERYFRAME 59 NS_DECL_FRAMEARENA_HELPERS(nsListControlFrame) 60 61 // nsIFrame 62 nsresult HandleEvent(nsPresContext* aPresContext, 63 mozilla::WidgetGUIEvent* aEvent, 64 nsEventStatus* aEventStatus) final; 65 66 void SetInitialChildList(ChildListID aListID, nsFrameList& aChildList) final; 67 68 nscoord GetPrefISize(gfxContext* aRenderingContext) final; 69 nscoord GetMinISize(gfxContext* aRenderingContext) final; 70 71 void Reflow(nsPresContext* aCX, ReflowOutput& aDesiredSize, 72 const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final; 73 74 void Init(nsIContent* aContent, nsContainerFrame* aParent, 75 nsIFrame* aPrevInFlow) final; 76 77 MOZ_CAN_RUN_SCRIPT_BOUNDARY 78 void DidReflow(nsPresContext* aPresContext, 79 const ReflowInput* aReflowInput) final; 80 void DestroyFrom(nsIFrame* aDestructRoot, 81 PostDestroyData& aPostDestroyData) final; 82 83 void BuildDisplayList(nsDisplayListBuilder* aBuilder, 84 const nsDisplayListSet& aLists) final; 85 86 nsContainerFrame* GetContentInsertionFrame() final; 87 IsFrameOfType(uint32_t aFlags)88 bool IsFrameOfType(uint32_t aFlags) const final { 89 return nsHTMLScrollFrame::IsFrameOfType( 90 aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock)); 91 } 92 93 #ifdef DEBUG_FRAME_DUMP 94 nsresult GetFrameName(nsAString& aResult) const final; 95 #endif 96 97 // nsIFormControlFrame 98 nsresult SetFormProperty(nsAtom* aName, const nsAString& aValue) final; 99 MOZ_CAN_RUN_SCRIPT_BOUNDARY 100 void SetFocus(bool aOn = true, bool aRepaint = false) final; 101 102 mozilla::ScrollStyles GetScrollStyles() const final; 103 bool ShouldPropagateComputedBSizeToScrolledContent() const final; 104 105 // for accessibility purposes 106 #ifdef ACCESSIBILITY 107 mozilla::a11y::AccType AccessibleType() final; 108 #endif 109 110 void SetComboboxFrame(nsIFrame* aComboboxFrame); 111 int32_t GetSelectedIndex(); 112 HTMLOptionElement* GetCurrentOption(); 113 114 /** 115 * Gets the text of the currently selected item. 116 * If the there are zero items then an empty string is returned 117 * If there is nothing selected, then the 0th item's text is returned. 118 */ 119 void GetOptionText(uint32_t aIndex, nsAString& aStr); 120 121 void CaptureMouseEvents(bool aGrabMouseEvents); 122 nscoord GetBSizeOfARow(); 123 uint32_t GetNumberOfOptions(); 124 MOZ_CAN_RUN_SCRIPT_BOUNDARY void AboutToDropDown(); 125 126 /** 127 * @note This method might destroy the frame, pres shell and other objects. 128 */ 129 void AboutToRollup(); 130 131 /** 132 * Dispatch a DOM oninput and onchange event synchroniously. 133 * @note This method might destroy the frame, pres shell and other objects. 134 */ 135 MOZ_CAN_RUN_SCRIPT 136 void FireOnInputAndOnChange(); 137 138 /** 139 * Makes aIndex the selected option of a combobox list. 140 * @note This method might destroy the frame, pres shell and other objects. 141 */ 142 MOZ_CAN_RUN_SCRIPT_BOUNDARY void ComboboxFinish(int32_t aIndex); 143 MOZ_CAN_RUN_SCRIPT_BOUNDARY void OnContentReset(); 144 145 // nsISelectControlFrame 146 NS_IMETHOD AddOption(int32_t index) final; 147 NS_IMETHOD RemoveOption(int32_t index) final; 148 MOZ_CAN_RUN_SCRIPT_BOUNDARY 149 NS_IMETHOD DoneAddingChildren(bool aIsDone) final; 150 151 /** 152 * Gets the content (an option) by index and then set it as 153 * being selected or not selected. 154 */ 155 MOZ_CAN_RUN_SCRIPT_BOUNDARY 156 NS_IMETHOD OnOptionSelected(int32_t aIndex, bool aSelected) final; 157 MOZ_CAN_RUN_SCRIPT_BOUNDARY 158 NS_IMETHOD_(void) 159 OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) final; 160 161 /** 162 * Mouse event listeners. 163 * @note These methods might destroy the frame, pres shell and other objects. 164 */ 165 MOZ_CAN_RUN_SCRIPT 166 nsresult MouseDown(mozilla::dom::Event* aMouseEvent); 167 MOZ_CAN_RUN_SCRIPT 168 nsresult MouseUp(mozilla::dom::Event* aMouseEvent); 169 MOZ_CAN_RUN_SCRIPT 170 nsresult MouseMove(mozilla::dom::Event* aMouseEvent); 171 MOZ_CAN_RUN_SCRIPT 172 nsresult DragMove(mozilla::dom::Event* aMouseEvent); 173 MOZ_CAN_RUN_SCRIPT 174 nsresult KeyDown(mozilla::dom::Event* aKeyEvent); 175 MOZ_CAN_RUN_SCRIPT 176 nsresult KeyPress(mozilla::dom::Event* aKeyEvent); 177 178 /** 179 * Returns the options collection for mContent, if any. 180 */ 181 mozilla::dom::HTMLOptionsCollection* GetOptions() const; 182 /** 183 * Returns the HTMLOptionElement for a given index in mContent's collection. 184 */ 185 HTMLOptionElement* GetOption(uint32_t aIndex) const; 186 187 static void ComboboxFocusSet(); 188 189 // Helper IsFocused()190 bool IsFocused() { return this == mFocused; } 191 192 /** 193 * Function to paint the focus rect when our nsSelectsAreaFrame is painting. 194 * @param aPt the offset of this frame, relative to the rendering reference 195 * frame 196 */ 197 void PaintFocus(mozilla::gfx::DrawTarget* aDrawTarget, nsPoint aPt); 198 199 /** 200 * If this frame IsFocused(), invalidates an area that includes anything 201 * that PaintFocus will or could have painted --- basically the whole 202 * GetOptionsContainer, plus some extra stuff if there are no options. This 203 * must be called every time mEndSelectionIndex changes. 204 */ 205 void InvalidateFocus(); 206 207 /** 208 * Function to calculate the block size of a row, for use with the 209 * "size" attribute. 210 * Can't be const because GetNumberOfOptions() isn't const. 211 */ 212 nscoord CalcBSizeOfARow(); 213 214 /** 215 * Function to ask whether we're currently in what might be the 216 * first pass of a two-pass reflow. 217 */ MightNeedSecondPass()218 bool MightNeedSecondPass() const { return mMightNeedSecondPass; } 219 SetSuppressScrollbarUpdate(bool aSuppress)220 void SetSuppressScrollbarUpdate(bool aSuppress) { 221 nsHTMLScrollFrame::SetSuppressScrollbarUpdate(aSuppress); 222 } 223 224 /** 225 * Return whether the list is in dropdown mode. 226 */ 227 bool IsInDropDownMode() const; 228 229 /** 230 * Return the number of displayed rows in the list. 231 */ GetNumDisplayRows()232 uint32_t GetNumDisplayRows() const { return mNumDisplayRows; } 233 234 /** 235 * Return true if the drop-down list can display more rows. 236 * (always false if not in drop-down mode) 237 */ GetDropdownCanGrow()238 bool GetDropdownCanGrow() const { return mDropdownCanGrow; } 239 240 /** 241 * Frees statics owned by this class. 242 */ 243 static void Shutdown(); 244 245 #ifdef ACCESSIBILITY 246 /** 247 * Post a custom DOM event for the change, so that accessibility can 248 * fire a native focus event for accessibility 249 * (Some 3rd party products need to track our focus) 250 */ 251 void FireMenuItemActiveEvent(); // Inform assistive tech what got focused 252 #endif 253 254 protected: 255 /** 256 * Return the first non-disabled option starting at aFromIndex (inclusive). 257 * @param aFoundIndex if non-null, set to the index of the returned option 258 */ 259 HTMLOptionElement* GetNonDisabledOptionFrom(int32_t aFromIndex, 260 int32_t* aFoundIndex = nullptr); 261 262 /** 263 * Updates the selected text in a combobox and then calls FireOnChange(). 264 * @note This method might destroy the frame, pres shell and other objects. 265 * Returns false if calling it destroyed |this|. 266 */ 267 MOZ_CAN_RUN_SCRIPT 268 bool UpdateSelection(); 269 270 /** 271 * Returns whether mContent supports multiple selection. 272 */ GetMultiple()273 bool GetMultiple() const { 274 return mContent->AsElement()->HasAttr(kNameSpaceID_None, 275 nsGkAtoms::multiple); 276 } 277 278 /** 279 * Toggles (show/hide) the combobox dropdown menu. 280 * @note This method might destroy the frame, pres shell and other objects. 281 */ 282 MOZ_CAN_RUN_SCRIPT 283 void DropDownToggleKey(mozilla::dom::Event* aKeyEvent); 284 285 /** 286 * @return true if the <option> at aIndex is selectable by the user. 287 */ 288 bool IsOptionInteractivelySelectable(int32_t aIndex) const; 289 /** 290 * @return true if aOption in aSelect is selectable by the user. 291 */ 292 static bool IsOptionInteractivelySelectable( 293 mozilla::dom::HTMLSelectElement* aSelect, 294 mozilla::dom::HTMLOptionElement* aOption); 295 296 MOZ_CAN_RUN_SCRIPT void ScrollToFrame(HTMLOptionElement& aOptElement); 297 298 MOZ_CAN_RUN_SCRIPT void ScrollToIndex(int32_t anIndex); 299 300 /** 301 * When the user clicks on the comboboxframe to show the dropdown 302 * listbox, they then have to move the mouse into the list. We don't 303 * want to process those mouse events as selection events (i.e., to 304 * scroll list items into view). So we ignore the events until 305 * the mouse moves below our border-inner-edge, when 306 * mItemSelectionStarted is set. 307 * 308 * @param aPoint relative to this frame 309 */ 310 bool IgnoreMouseEventForSelection(mozilla::dom::Event* aEvent); 311 312 /** 313 * If the dropdown is showing and the mouse has moved below our 314 * border-inner-edge, then set mItemSelectionStarted. 315 */ 316 void UpdateInListState(mozilla::dom::Event* aEvent); 317 void AdjustIndexForDisabledOpt(int32_t aStartIndex, int32_t& anNewIndex, 318 int32_t aNumOptions, int32_t aDoAdjustInc, 319 int32_t aDoAdjustIncNext); 320 321 /** 322 * Resets the select back to it's original default values; 323 * those values as determined by the original HTML 324 */ 325 MOZ_CAN_RUN_SCRIPT void ResetList(bool aAllowScrolling); 326 327 explicit nsListControlFrame(ComputedStyle* aStyle, 328 nsPresContext* aPresContext); 329 virtual ~nsListControlFrame(); 330 331 /** 332 * Sets the mSelectedIndex and mOldSelectedIndex from figuring out what 333 * item was selected using content 334 * @param aPoint the event point, in listcontrolframe coordinates 335 * @return NS_OK if it successfully found the selection 336 */ 337 nsresult GetIndexFromDOMEvent(mozilla::dom::Event* aMouseEvent, 338 int32_t& aCurIndex); 339 340 bool CheckIfAllFramesHere(); 341 bool IsLeftButton(mozilla::dom::Event* aMouseEvent); 342 343 // guess at a row block size based on our own style. 344 nscoord CalcFallbackRowBSize(float aFontSizeInflation); 345 346 // CalcIntrinsicBSize computes our intrinsic block size (taking the 347 // "size" attribute into account). This should only be called in 348 // non-dropdown mode. 349 nscoord CalcIntrinsicBSize(nscoord aBSizeOfARow, int32_t aNumberOfOptions); 350 351 // Dropped down stuff 352 void SetComboboxItem(int32_t aIndex); 353 354 /** 355 * Method to reflow ourselves as a dropdown list. This differs from 356 * reflow as a listbox because the criteria for needing a second 357 * pass are different. This will be called from Reflow() as needed. 358 */ 359 void ReflowAsDropdown(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, 360 const ReflowInput& aReflowInput, 361 nsReflowStatus& aStatus); 362 363 // Selection 364 bool SetOptionsSelectedFromFrame(int32_t aStartIndex, int32_t aEndIndex, 365 bool aValue, bool aClearAll); 366 bool ToggleOptionSelectedFromFrame(int32_t aIndex); 367 368 MOZ_CAN_RUN_SCRIPT 369 bool SingleSelection(int32_t aClickedIndex, bool aDoToggle); 370 bool ExtendedSelection(int32_t aStartIndex, int32_t aEndIndex, 371 bool aClearAll); 372 MOZ_CAN_RUN_SCRIPT 373 bool PerformSelection(int32_t aClickedIndex, bool aIsShift, bool aIsControl); 374 MOZ_CAN_RUN_SCRIPT 375 bool HandleListSelection(mozilla::dom::Event* aDOMEvent, 376 int32_t selectedIndex); 377 void InitSelectionRange(int32_t aClickedIndex); 378 MOZ_CAN_RUN_SCRIPT 379 void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode, bool aIsShift, 380 bool aIsControlOrMeta); 381 382 public: GetOptionsContainer()383 nsSelectsAreaFrame* GetOptionsContainer() const { 384 return static_cast<nsSelectsAreaFrame*>(GetScrolledFrame()); 385 } 386 387 protected: BSizeOfARow()388 nscoord BSizeOfARow() { return GetOptionsContainer()->BSizeOfARow(); } 389 390 /** 391 * @return how many displayable options/optgroups this frame has. 392 */ 393 uint32_t GetNumberOfRows(); 394 GetViewInternal()395 nsView* GetViewInternal() const final { return mView; } SetViewInternal(nsView * aView)396 void SetViewInternal(nsView* aView) final { mView = aView; } 397 398 // Data Members 399 int32_t mStartSelectionIndex; 400 int32_t mEndSelectionIndex; 401 402 nsComboboxControlFrame* mComboboxFrame; 403 404 // The view is only created (& non-null) if IsInDropDownMode() is true. 405 nsView* mView; 406 407 uint32_t mNumDisplayRows; 408 bool mChangesSinceDragStart : 1; 409 bool mButtonDown : 1; 410 411 // Has the user selected a visible item since we showed the dropdown? 412 bool mItemSelectionStarted : 1; 413 414 bool mIsAllContentHere : 1; 415 bool mIsAllFramesHere : 1; 416 bool mHasBeenInitialized : 1; 417 bool mNeedToReset : 1; 418 bool mPostChildrenLoadedReset : 1; 419 420 // bool value for multiple discontiguous selection 421 bool mControlSelectMode : 1; 422 423 // True if we're in the middle of a reflow and might need a second 424 // pass. This only happens for auto heights. 425 bool mMightNeedSecondPass : 1; 426 427 /** 428 * Set to aPresContext->HasPendingInterrupt() at the start of Reflow. 429 * Set to false at the end of DidReflow. 430 */ 431 bool mHasPendingInterruptAtStartOfReflow : 1; 432 433 // True if the drop-down can show more rows. Always false if this list 434 // is not in drop-down mode. 435 bool mDropdownCanGrow : 1; 436 437 // True if the selection can be set to nothing or disabled options. 438 bool mForceSelection : 1; 439 440 // The last computed block size we reflowed at if we're a combobox 441 // dropdown. 442 // XXXbz should we be using a subclass here? Or just not worry 443 // about the extra member on listboxes? 444 nscoord mLastDropdownComputedBSize; 445 446 // At the time of our last dropdown, the backstop color to draw in case we 447 // are translucent. 448 nscolor mLastDropdownBackstopColor; 449 450 RefPtr<nsListEventListener> mEventListener; 451 452 static nsListControlFrame* mFocused; 453 static nsString* sIncrementalString; 454 455 #ifdef DO_REFLOW_COUNTER 456 int32_t mReflowId; 457 #endif 458 459 private: 460 // for incremental typing navigation 461 static nsAString& GetIncrementalString(); 462 static DOMTimeStamp gLastKeyTime; 463 464 class MOZ_RAII AutoIncrementalSearchResetter { 465 public: AutoIncrementalSearchResetter()466 AutoIncrementalSearchResetter() : mCancelled(false) {} ~AutoIncrementalSearchResetter()467 ~AutoIncrementalSearchResetter() { 468 if (!mCancelled) { 469 nsListControlFrame::GetIncrementalString().Truncate(); 470 } 471 } Cancel()472 void Cancel() { mCancelled = true; } 473 474 private: 475 bool mCancelled; 476 }; 477 }; 478 479 #endif /* nsListControlFrame_h___ */ 480