1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * The XUL Popup Manager keeps track of all open popups. 9 */ 10 11 #ifndef nsXULPopupManager_h__ 12 #define nsXULPopupManager_h__ 13 14 #include "mozilla/Logging.h" 15 #include "nsHashtablesFwd.h" 16 #include "nsIContent.h" 17 #include "nsIRollupListener.h" 18 #include "nsIDOMEventListener.h" 19 #include "nsPoint.h" 20 #include "nsCOMPtr.h" 21 #include "nsTArray.h" 22 #include "nsIObserver.h" 23 #include "nsITimer.h" 24 #include "nsIReflowCallback.h" 25 #include "nsThreadUtils.h" 26 #include "nsPresContext.h" 27 #include "nsStyleConsts.h" 28 #include "nsWidgetInitData.h" 29 #include "mozilla/Attributes.h" 30 #include "mozilla/widget/NativeMenu.h" 31 #include "Units.h" 32 33 // XXX Avoid including this here by moving function bodies to the cpp file. 34 #include "mozilla/dom/Element.h" 35 36 // X.h defines KeyPress 37 #ifdef KeyPress 38 # undef KeyPress 39 #endif 40 41 /** 42 * There are two types that are used: 43 * - dismissable popups such as menus, which should close up when there is a 44 * click outside the popup. In this situation, the entire chain of menus 45 * above should also be closed. 46 * - panels, which stay open until a request is made to close them. This 47 * type is used by tooltips. 48 * 49 * When a new popup is opened, it is appended to the popup chain, stored in a 50 * linked list in mPopups. 51 * Popups are stored in this list linked from newest to oldest. When a click 52 * occurs outside one of the open dismissable popups, the chain is closed by 53 * calling Rollup. 54 */ 55 56 class nsContainerFrame; 57 class nsMenuFrame; 58 class nsMenuPopupFrame; 59 class nsMenuBarFrame; 60 class nsMenuParent; 61 class nsIDocShellTreeItem; 62 class nsPIDOMWindowOuter; 63 class nsRefreshDriver; 64 65 namespace mozilla { 66 class PresShell; 67 namespace dom { 68 class Event; 69 class KeyboardEvent; 70 } // namespace dom 71 } // namespace mozilla 72 73 // XUL popups can be in several different states. When opening a popup, the 74 // state changes as follows: 75 // ePopupClosed - initial state 76 // ePopupShowing - during the period when the popupshowing event fires 77 // ePopupOpening - between the popupshowing event and being visible. Creation 78 // of the child frames, layout and reflow occurs in this 79 // state. The popup is stored in the popup manager's list of 80 // open popups during this state. 81 // ePopupVisible - layout is done and the popup's view and widget are made 82 // visible. The popup is visible on screen but may be 83 // transitioning. The popupshown event has not yet fired. 84 // ePopupShown - the popup has been shown and is fully ready. This state is 85 // assigned just before the popupshown event fires. 86 // When closing a popup: 87 // ePopupHidden - during the period when the popuphiding event fires and 88 // the popup is removed. 89 // ePopupClosed - the popup's widget is made invisible. 90 enum nsPopupState { 91 // state when a popup is not open 92 ePopupClosed, 93 // state from when a popup is requested to be shown to after the 94 // popupshowing event has been fired. 95 ePopupShowing, 96 // state while a popup is waiting to be laid out and positioned 97 ePopupPositioning, 98 // state while a popup is open but the widget is not yet visible 99 ePopupOpening, 100 // state while a popup is visible and waiting for the popupshown event 101 ePopupVisible, 102 // state while a popup is open and visible on screen 103 ePopupShown, 104 // state from when a popup is requested to be hidden to when it is closed. 105 ePopupHiding, 106 // state which indicates that the popup was hidden without firing the 107 // popuphiding or popuphidden events. It is used when executing a menu 108 // command because the menu needs to be hidden before the command event 109 // fires, yet the popuphiding and popuphidden events are fired after. This 110 // state can also occur when the popup is removed because the document is 111 // unloaded. 112 ePopupInvisible 113 }; 114 115 // when a menu command is executed, the closemenu attribute may be used 116 // to define how the menu should be closed up 117 enum CloseMenuMode { 118 CloseMenuMode_Auto, // close up the chain of menus, default value 119 CloseMenuMode_None, // don't close up any menus 120 CloseMenuMode_Single // close up only the menu the command is inside 121 }; 122 123 /** 124 * nsNavigationDirection: an enum expressing navigation through the menus in 125 * terms which are independent of the directionality of the chrome. The 126 * terminology, derived from XSL-FO and CSS3 (e.g. 127 * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start, 128 * End), with the addition of First and Last (mapped to Home and End 129 * respectively). 130 * 131 * In languages such as English where the inline progression is left-to-right 132 * and the block progression is top-to-bottom (lr-tb), these terms will map out 133 * as in the following diagram 134 * 135 * --- inline progression ---> 136 * 137 * First | 138 * ... | 139 * Before | 140 * +--------+ block 141 * Start | | End progression 142 * +--------+ | 143 * After | 144 * ... | 145 * Last V 146 * 147 */ 148 149 enum nsNavigationDirection { 150 eNavigationDirection_Last, 151 eNavigationDirection_First, 152 eNavigationDirection_Start, 153 eNavigationDirection_Before, 154 eNavigationDirection_End, 155 eNavigationDirection_After 156 }; 157 158 enum nsIgnoreKeys { 159 eIgnoreKeys_False, 160 eIgnoreKeys_True, 161 eIgnoreKeys_Shortcuts, 162 }; 163 164 #define NS_DIRECTION_IS_INLINE(dir) \ 165 (dir == eNavigationDirection_Start || dir == eNavigationDirection_End) 166 #define NS_DIRECTION_IS_BLOCK(dir) \ 167 (dir == eNavigationDirection_Before || dir == eNavigationDirection_After) 168 #define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) \ 169 (dir == eNavigationDirection_First || dir == eNavigationDirection_Last) 170 171 static_assert(static_cast<uint8_t>(mozilla::StyleDirection::Ltr) == 0 && 172 static_cast<uint8_t>(mozilla::StyleDirection::Rtl) == 1, 173 "Left to Right should be 0 and Right to Left should be 1"); 174 175 /** 176 * DirectionFromKeyCodeTable: two arrays, the first for left-to-right and the 177 * other for right-to-left, that map keycodes to values of 178 * nsNavigationDirection. 179 */ 180 extern const nsNavigationDirection DirectionFromKeyCodeTable[2][6]; 181 182 #define NS_DIRECTION_FROM_KEY_CODE(frame, keycode) \ 183 (DirectionFromKeyCodeTable[static_cast<uint8_t>( \ 184 (frame)->StyleVisibility()->mDirection)][( \ 185 keycode)-mozilla::dom::KeyboardEvent_Binding::DOM_VK_END]) 186 187 // nsMenuChainItem holds info about an open popup. Items are stored in a 188 // doubly linked list. Note that the linked list is stored beginning from 189 // the lowest child in a chain of menus, as this is the active submenu. 190 class nsMenuChainItem { 191 private: 192 nsMenuPopupFrame* mFrame; // the popup frame 193 nsPopupType mPopupType; // the popup type of the frame 194 bool mNoAutoHide; // true for noautohide panels 195 bool mIsContext; // true for context menus 196 bool mOnMenuBar; // true if the menu is on a menu bar 197 nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used 198 199 // True if the popup should maintain its position relative to the anchor when 200 // the anchor moves. 201 bool mFollowAnchor; 202 203 // The last seen position of the anchor, relative to the screen. 204 nsRect mCurrentRect; 205 206 nsMenuChainItem* mParent; 207 nsMenuChainItem* mChild; 208 209 public: nsMenuChainItem(nsMenuPopupFrame * aFrame,bool aNoAutoHide,bool aIsContext,nsPopupType aPopupType)210 nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aNoAutoHide, bool aIsContext, 211 nsPopupType aPopupType) 212 : mFrame(aFrame), 213 mPopupType(aPopupType), 214 mNoAutoHide(aNoAutoHide), 215 mIsContext(aIsContext), 216 mOnMenuBar(false), 217 mIgnoreKeys(eIgnoreKeys_False), 218 mFollowAnchor(false), 219 mParent(nullptr), 220 mChild(nullptr) { 221 NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); 222 MOZ_COUNT_CTOR(nsMenuChainItem); 223 } 224 225 MOZ_COUNTED_DTOR(nsMenuChainItem) 226 227 nsIContent* Content(); Frame()228 nsMenuPopupFrame* Frame() { return mFrame; } PopupType()229 nsPopupType PopupType() { return mPopupType; } IsNoAutoHide()230 bool IsNoAutoHide() { return mNoAutoHide; } SetNoAutoHide(bool aNoAutoHide)231 void SetNoAutoHide(bool aNoAutoHide) { mNoAutoHide = aNoAutoHide; } IsMenu()232 bool IsMenu() { return mPopupType == ePopupTypeMenu; } IsContextMenu()233 bool IsContextMenu() { return mIsContext; } IgnoreKeys()234 nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; } SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys)235 void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } IsOnMenuBar()236 bool IsOnMenuBar() { return mOnMenuBar; } SetOnMenuBar(bool aOnMenuBar)237 void SetOnMenuBar(bool aOnMenuBar) { mOnMenuBar = aOnMenuBar; } GetParent()238 nsMenuChainItem* GetParent() { return mParent; } GetChild()239 nsMenuChainItem* GetChild() { return mChild; } FollowsAnchor()240 bool FollowsAnchor() { return mFollowAnchor; } 241 void UpdateFollowAnchor(); 242 void CheckForAnchorChange(); 243 244 // set the parent of this item to aParent, also changing the parent 245 // to have this as a child. 246 void SetParent(nsMenuChainItem* aParent); 247 248 // Removes an item from the chain. The root pointer must be supplied in case 249 // the item is the first item in the chain in which case the pointer will be 250 // set to the next item, or null if there isn't another item. After detaching, 251 // this item will not have a parent or a child. 252 void Detach(nsMenuChainItem** aRoot); 253 }; 254 255 // this class is used for dispatching popuphiding events asynchronously. 256 class nsXULPopupHidingEvent : public mozilla::Runnable { 257 public: nsXULPopupHidingEvent(nsIContent * aPopup,nsIContent * aNextPopup,nsIContent * aLastPopup,nsPopupType aPopupType,bool aDeselectMenu,bool aIsCancel)258 nsXULPopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup, 259 nsIContent* aLastPopup, nsPopupType aPopupType, 260 bool aDeselectMenu, bool aIsCancel) 261 : mozilla::Runnable("nsXULPopupHidingEvent"), 262 mPopup(aPopup), 263 mNextPopup(aNextPopup), 264 mLastPopup(aLastPopup), 265 mPopupType(aPopupType), 266 mDeselectMenu(aDeselectMenu), 267 mIsRollup(aIsCancel) { 268 NS_ASSERTION(aPopup, 269 "null popup supplied to nsXULPopupHidingEvent constructor"); 270 // aNextPopup and aLastPopup may be null 271 } 272 273 NS_IMETHOD Run() override; 274 275 private: 276 nsCOMPtr<nsIContent> mPopup; 277 nsCOMPtr<nsIContent> mNextPopup; 278 nsCOMPtr<nsIContent> mLastPopup; 279 nsPopupType mPopupType; 280 bool mDeselectMenu; 281 bool mIsRollup; 282 }; 283 284 // this class is used for dispatching popuppositioned events asynchronously. 285 class nsXULPopupPositionedEvent : public mozilla::Runnable { 286 public: nsXULPopupPositionedEvent(nsIContent * aPopup)287 explicit nsXULPopupPositionedEvent(nsIContent* aPopup) 288 : mozilla::Runnable("nsXULPopupPositionedEvent"), mPopup(aPopup) { 289 NS_ASSERTION( 290 aPopup, "null popup supplied to nsXULPopupPositionedEvent constructor"); 291 } 292 293 NS_IMETHOD Run() override; 294 295 // Asynchronously dispatch a popuppositioned event at aPopup if this is a 296 // panel that should receieve such events. Return true if the event was sent. 297 static bool DispatchIfNeeded(nsIContent* aPopup); 298 299 private: 300 nsCOMPtr<nsIContent> mPopup; 301 }; 302 303 // this class is used for dispatching menu command events asynchronously. 304 class nsXULMenuCommandEvent : public mozilla::Runnable { 305 public: nsXULMenuCommandEvent(mozilla::dom::Element * aMenu,bool aIsTrusted,mozilla::Modifiers aModifiers,bool aUserInput,bool aFlipChecked,int16_t aButton)306 nsXULMenuCommandEvent(mozilla::dom::Element* aMenu, bool aIsTrusted, 307 mozilla::Modifiers aModifiers, bool aUserInput, 308 bool aFlipChecked, int16_t aButton) 309 : mozilla::Runnable("nsXULMenuCommandEvent"), 310 mMenu(aMenu), 311 mModifiers(aModifiers), 312 mButton(aButton), 313 mIsTrusted(aIsTrusted), 314 mUserInput(aUserInput), 315 mFlipChecked(aFlipChecked), 316 mCloseMenuMode(CloseMenuMode_Auto) { 317 NS_ASSERTION(aMenu, 318 "null menu supplied to nsXULMenuCommandEvent constructor"); 319 } 320 321 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; 322 SetCloseMenuMode(CloseMenuMode aCloseMenuMode)323 void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { 324 mCloseMenuMode = aCloseMenuMode; 325 } 326 327 private: 328 RefPtr<mozilla::dom::Element> mMenu; 329 330 mozilla::Modifiers mModifiers; 331 int16_t mButton; 332 bool mIsTrusted; 333 bool mUserInput; 334 bool mFlipChecked; 335 CloseMenuMode mCloseMenuMode; 336 }; 337 338 class nsXULPopupManager final : public nsIDOMEventListener, 339 public nsIRollupListener, 340 public nsIObserver, 341 public mozilla::widget::NativeMenu::Observer { 342 public: 343 friend class nsXULPopupHidingEvent; 344 friend class nsXULPopupPositionedEvent; 345 friend class nsXULMenuCommandEvent; 346 friend class TransitionEnder; 347 348 NS_DECL_ISUPPORTS 349 NS_DECL_NSIOBSERVER 350 NS_DECL_NSIDOMEVENTLISTENER 351 352 // nsIRollupListener 353 MOZ_CAN_RUN_SCRIPT_BOUNDARY 354 virtual bool Rollup(uint32_t aCount, bool aFlush, 355 const mozilla::LayoutDeviceIntPoint* pos, 356 nsIContent** aLastRolledUp) override; 357 virtual bool ShouldRollupOnMouseWheelEvent() override; 358 virtual bool ShouldConsumeOnMouseWheelEvent() override; 359 virtual bool ShouldRollupOnMouseActivate() override; 360 virtual uint32_t GetSubmenuWidgetChain( 361 nsTArray<nsIWidget*>* aWidgetChain) override; NotifyGeometryChange()362 virtual void NotifyGeometryChange() override {} 363 virtual nsIWidget* GetRollupWidget() override; 364 virtual bool RollupNativeMenu() override; 365 366 // NativeMenu::Observer 367 void OnNativeMenuOpened() override; 368 void OnNativeMenuClosed() override; 369 void OnNativeSubMenuWillOpen(mozilla::dom::Element* aPopupElement) override; 370 void OnNativeSubMenuDidOpen(mozilla::dom::Element* aPopupElement) override; 371 void OnNativeSubMenuClosed(mozilla::dom::Element* aPopupElement) override; 372 void OnNativeMenuWillActivateItem( 373 mozilla::dom::Element* aMenuItemElement) override; 374 375 static nsXULPopupManager* sInstance; 376 377 // initialize and shutdown methods called by nsLayoutStatics 378 static nsresult Init(); 379 static void Shutdown(); 380 381 // returns a weak reference to the popup manager instance, could return null 382 // if a popup manager could not be allocated 383 static nsXULPopupManager* GetInstance(); 384 385 // Returns the immediate parent frame of inserted children of aFrame's 386 // content. 387 // 388 // FIXME(emilio): Or something like that, because this is kind of broken in a 389 // variety of situations like multiple insertion points. 390 static nsContainerFrame* ImmediateParentFrame(nsContainerFrame* aFrame); 391 392 // This should be called when a window is moved or resized to adjust the 393 // popups accordingly. 394 void AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow); 395 void AdjustPopupsOnWindowChange(mozilla::PresShell* aPresShell); 396 397 // given a menu frame, find the prevous or next menu frame. If aPopup is 398 // true then navigate a menupopup, from one item on the menu to the previous 399 // or next one. This is used for cursor navigation between items in a popup 400 // menu. If aIsPopup is false, the navigation is on a menubar, so navigate 401 // between menus on the menubar. This is used for left/right cursor 402 // navigation. 403 // 404 // Items that are not valid, such as non-menu or non-menuitem elements are 405 // skipped, and the next or previous item after that is checked. 406 // 407 // If aStart is null, the first valid item is retrieved by GetNextMenuItem 408 // and the last valid item is retrieved by GetPreviousMenuItem. 409 // 410 // Both methods will loop around the beginning or end if needed. 411 // 412 // aParent - the parent menubar or menupopup 413 // aStart - the menu/menuitem to start navigation from. GetPreviousMenuItem 414 // returns the item before it, while GetNextMenuItem returns the 415 // item after it. 416 // aIsPopup - true for menupopups, false for menubars 417 // aWrap - true to wrap around to the beginning and continue searching if not 418 // found. False to end at the beginning or end of the menu. 419 static nsMenuFrame* GetPreviousMenuItem(nsContainerFrame* aParent, 420 nsMenuFrame* aStart, bool aIsPopup, 421 bool aWrap); 422 static nsMenuFrame* GetNextMenuItem(nsContainerFrame* aParent, 423 nsMenuFrame* aStart, bool aIsPopup, 424 bool aWrap); 425 426 // returns true if the menu item aContent is a valid menuitem which may 427 // be navigated to. aIsPopup should be true for items on a popup, or false 428 // for items on a menubar. 429 static bool IsValidMenuItem(nsIContent* aContent, bool aOnPopup); 430 431 // inform the popup manager that a menu bar has been activated or deactivated, 432 // either because one of its menus has opened or closed, or that the menubar 433 // has been focused such that its menus may be navigated with the keyboard. 434 // aActivate should be true when the menubar should be focused, and false 435 // when the active menu bar should be defocused. In the latter case, if 436 // aMenuBar isn't currently active, yet another menu bar is, that menu bar 437 // will remain active. 438 void SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate); 439 440 // retrieve the node and offset of the last mouse event used to open a 441 // context menu. This information is determined from the rangeParent and 442 // the rangeOffset of the event supplied to ShowPopup or ShowPopupAtScreen. 443 // This is used by the implementation of Document::GetPopupRangeParent 444 // and Document::GetPopupRangeOffset. GetMouseLocationParent()445 nsIContent* GetMouseLocationParent() const { return mRangeParentContent; } MouseLocationOffset()446 int32_t MouseLocationOffset() const { return mRangeOffset; } 447 448 /** 449 * Open a <menu> given its content node. If aSelectFirstItem is 450 * set to true, the first item on the menu will automatically be 451 * selected. If aAsynchronous is true, the event will be dispatched 452 * asynchronously. This should be true when called from frame code. 453 */ 454 void ShowMenu(nsIContent* aMenu, bool aSelectFirstItem, bool aAsynchronous); 455 456 /** 457 * Open a popup, either anchored or unanchored. If aSelectFirstItem is 458 * true, then the first item in the menu is selected. The arguments are 459 * similar to those for XULPopupElement::OpenPopup. 460 * 461 * aTriggerEvent should be the event that triggered the event. This is used 462 * to determine the coordinates and trigger node for the popup. This may be 463 * null if the popup was not triggered by an event. 464 * 465 * This fires the popupshowing event synchronously. 466 */ 467 void ShowPopup(nsIContent* aPopup, nsIContent* aAnchorContent, 468 const nsAString& aPosition, int32_t aXPos, int32_t aYPos, 469 bool aIsContextMenu, bool aAttributesOverride, 470 bool aSelectFirstItem, mozilla::dom::Event* aTriggerEvent); 471 472 /** 473 * Open a popup at a specific screen position specified by aXPos and aYPos, 474 * measured in CSS pixels. 475 * 476 * This fires the popupshowing event synchronously. 477 * 478 * If aIsContextMenu is true, the popup is positioned at a slight 479 * offset from aXPos/aYPos to ensure that it is not under the mouse 480 * cursor. 481 */ 482 void ShowPopupAtScreen(nsIContent* aPopup, int32_t aXPos, int32_t aYPos, 483 bool aIsContextMenu, 484 mozilla::dom::Event* aTriggerEvent); 485 486 /* Open a popup anchored at a screen rectangle specified by aRect. 487 * The remaining arguments are similar to ShowPopup. 488 */ 489 void ShowPopupAtScreenRect(nsIContent* aPopup, const nsAString& aPosition, 490 const nsIntRect& aRect, bool aIsContextMenu, 491 bool aAttributesOverride, 492 mozilla::dom::Event* aTriggerEvent); 493 494 /** 495 * Open a popup as a native menu, at a specific screen position specified by 496 * aXPos and aYPos, measured in CSS pixels. 497 * 498 * This fires the popupshowing event synchronously. 499 * 500 * Returns whether native menus are supported for aPopup on this platform. 501 */ 502 bool ShowPopupAsNativeMenu(nsIContent* aPopup, int32_t aXPos, int32_t aYPos, 503 bool aIsContextMenu, 504 mozilla::dom::Event* aTriggerEvent); 505 506 /** 507 * Open a tooltip at a specific screen position specified by aXPos and aYPos, 508 * measured in CSS pixels. 509 * 510 * This fires the popupshowing event synchronously. 511 */ 512 void ShowTooltipAtScreen(nsIContent* aPopup, nsIContent* aTriggerContent, 513 int32_t aXPos, int32_t aYPos); 514 515 /* 516 * Hide a popup aPopup. If the popup is in a <menu>, then also inform the 517 * menu that the popup is being hidden. 518 * 519 * aHideChain - true if the entire chain of menus should be closed. If false, 520 * only this popup is closed. 521 * aDeselectMenu - true if the parent <menu> of the popup should be 522 * deselected. This will be false when the menu is closed by 523 * pressing the Escape key. 524 * aAsynchronous - true if the first popuphiding event should be sent 525 * asynchrously. This should be true if HidePopup is called 526 * from a frame. 527 * aIsCancel - true if this popup is hiding due to being cancelled. 528 * aLastPopup - optional popup to close last when hiding a chain of menus. 529 * If null, then all popups will be closed. 530 */ 531 void HidePopup(nsIContent* aPopup, bool aHideChain, bool aDeselectMenu, 532 bool aAsynchronous, bool aIsCancel, 533 nsIContent* aLastPopup = nullptr); 534 535 /* 536 * Hide the popup of a <menu>. 537 */ 538 void HideMenu(nsIContent* aMenu); 539 540 /** 541 * Hide a popup after a short delay. This is used when rolling over menu 542 * items. This timer is stored in mCloseTimer. The timer may be cancelled and 543 * the popup closed by calling KillMenuTimer. 544 */ 545 void HidePopupAfterDelay(nsMenuPopupFrame* aPopup); 546 547 /** 548 * Hide all of the popups from a given docshell. This should be called when 549 * the document is hidden. 550 */ 551 void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide); 552 553 /** 554 * Enable or disable the dynamic noautohide state of a panel. 555 * 556 * aPanel - the panel whose state is to change 557 * aShouldRollup - whether the panel is no longer noautohide 558 */ 559 void EnableRollup(nsIContent* aPopup, bool aShouldRollup); 560 561 /** 562 * Check if any popups need to be repositioned or hidden after a style or 563 * layout change. This will update, for example, any arrow type panels when 564 * the anchor that is is pointing to has moved, resized or gone away. 565 * Only those popups that pertain to the supplied aRefreshDriver are updated. 566 */ 567 void UpdatePopupPositions(nsRefreshDriver* aRefreshDriver); 568 569 /** 570 * Enable or disable anchor following on the popup if needed. 571 */ 572 void UpdateFollowAnchor(nsMenuPopupFrame* aPopup); 573 574 /** 575 * Execute a menu command from the triggering event aEvent. 576 * 577 * aMenu - a menuitem to execute 578 * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse 579 * event which triggered the menu to be executed, may not be null 580 */ 581 void ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent); 582 583 /** 584 * If a native menu is open, and aItem is an item in the menu's subtree, 585 * execute the item with the help of the native menu and close the menu. 586 * Returns true if a native menu was open. 587 */ 588 bool ActivateNativeMenuItem(nsIContent* aItem, mozilla::Modifiers aModifiers, 589 int16_t aButton, mozilla::ErrorResult& aRv); 590 591 /** 592 * Return true if the popup for the supplied content node is open. 593 */ 594 bool IsPopupOpen(nsIContent* aPopup); 595 596 /** 597 * Return true if the popup for the supplied menu parent is open. 598 */ 599 bool IsPopupOpenForMenuParent(nsMenuParent* aMenuParent); 600 601 /** 602 * Return the frame for the topmost open popup of a given type, or null if 603 * no popup of that type is open. If aType is ePopupTypeAny, a menu of any 604 * type is returned. 605 */ 606 nsIFrame* GetTopPopup(nsPopupType aType); 607 608 /** 609 * Return an array of all the open and visible popup frames for 610 * menus, in order from top to bottom. 611 */ 612 void GetVisiblePopups(nsTArray<nsIFrame*>& aPopups); 613 614 /** 615 * Get the node that last triggered a popup or tooltip in the document 616 * aDocument. aDocument must be non-null and be a document contained within 617 * the same window hierarchy as the popup to retrieve. 618 */ GetLastTriggerPopupNode(mozilla::dom::Document * aDocument)619 already_AddRefed<nsINode> GetLastTriggerPopupNode( 620 mozilla::dom::Document* aDocument) { 621 return GetLastTriggerNode(aDocument, false); 622 } 623 GetLastTriggerTooltipNode(mozilla::dom::Document * aDocument)624 already_AddRefed<nsINode> GetLastTriggerTooltipNode( 625 mozilla::dom::Document* aDocument) { 626 return GetLastTriggerNode(aDocument, true); 627 } 628 629 /** 630 * Return false if a popup may not be opened. This will return false if the 631 * popup is already open, if the popup is in a content shell that is not 632 * focused, or if it is a submenu of another menu that isn't open. 633 */ 634 bool MayShowPopup(nsMenuPopupFrame* aFrame); 635 636 /** 637 * Indicate that the popup associated with aView has been moved to the 638 * specified screen coordiates. 639 */ 640 void PopupMoved(nsIFrame* aFrame, nsIntPoint aPoint); 641 642 /** 643 * Indicate that the popup associated with aView has been resized to the 644 * given device pixel size aSize. 645 */ 646 void PopupResized(nsIFrame* aFrame, mozilla::LayoutDeviceIntSize aSize); 647 648 /** 649 * Called when a popup frame is destroyed. In this case, just remove the 650 * item and later popups from the list. No point going through HidePopup as 651 * the frames have gone away. 652 */ 653 void PopupDestroyed(nsMenuPopupFrame* aFrame); 654 655 /** 656 * Returns true if there is a context menu open. If aPopup is specified, 657 * then the context menu must be later in the chain than aPopup. If aPopup 658 * is null, returns true if any context menu at all is open. 659 */ 660 bool HasContextMenu(nsMenuPopupFrame* aPopup); 661 662 /** 663 * Update the commands for the menus within the menu popup for a given 664 * content node. aPopup should be a XUL menupopup element. This method 665 * changes attributes on the children of aPopup, and deals only with the 666 * content of the popup, not the frames. 667 */ 668 void UpdateMenuItems(nsIContent* aPopup); 669 670 /** 671 * Stop the timer which hides a popup after a delay, started by a previous 672 * call to HidePopupAfterDelay. In addition, the popup awaiting to be hidden 673 * is closed asynchronously. 674 */ 675 void KillMenuTimer(); 676 677 /** 678 * Cancel the timer which closes menus after delay, but only if the menu to 679 * close is aMenuParent. When a submenu is opened, the user might move the 680 * mouse over a sibling menuitem which would normally close the menu. This 681 * menu is closed via a timer. However, if the user moves the mouse over the 682 * submenu before the timer fires, we should instead cancel the timer. This 683 * ensures that the user can move the mouse diagonally over a menu. 684 */ 685 void CancelMenuTimer(nsMenuParent* aMenuParent); 686 687 /** 688 * Handles navigation for menu accelkeys. If aFrame is specified, then the 689 * key is handled by that popup, otherwise if aFrame is null, the key is 690 * handled by the active popup or menubar. 691 */ 692 bool HandleShortcutNavigation(mozilla::dom::KeyboardEvent* aKeyEvent, 693 nsMenuPopupFrame* aFrame); 694 695 /** 696 * Handles cursor navigation within a menu. Returns true if the key has 697 * been handled. 698 */ 699 bool HandleKeyboardNavigation(uint32_t aKeyCode); 700 701 /** 702 * Handle keyboard navigation within a menu popup specified by aFrame. 703 * Returns true if the key was handled and other default handling 704 * should not occur. 705 */ HandleKeyboardNavigationInPopup(nsMenuPopupFrame * aFrame,nsNavigationDirection aDir)706 bool HandleKeyboardNavigationInPopup(nsMenuPopupFrame* aFrame, 707 nsNavigationDirection aDir) { 708 return HandleKeyboardNavigationInPopup(nullptr, aFrame, aDir); 709 } 710 711 /** 712 * Handles the keyboard event with keyCode value. Returns true if the event 713 * has been handled. 714 */ 715 bool HandleKeyboardEventWithKeyCode(mozilla::dom::KeyboardEvent* aKeyEvent, 716 nsMenuChainItem* aTopVisibleMenuItem); 717 718 // Sets mIgnoreKeys of the Top Visible Menu Item 719 nsresult UpdateIgnoreKeys(bool aIgnoreKeys); 720 721 nsPopupState GetPopupState(mozilla::dom::Element* aPopupElement); 722 723 nsresult KeyUp(mozilla::dom::KeyboardEvent* aKeyEvent); 724 nsresult KeyDown(mozilla::dom::KeyboardEvent* aKeyEvent); 725 nsresult KeyPress(mozilla::dom::KeyboardEvent* aKeyEvent); 726 727 protected: 728 nsXULPopupManager(); 729 ~nsXULPopupManager(); 730 731 // get the nsMenuPopupFrame, if any, for the given content node 732 MOZ_CAN_RUN_SCRIPT_BOUNDARY 733 nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, 734 bool aShouldFlush); 735 736 // return the topmost menu, skipping over invisible popups 737 nsMenuChainItem* GetTopVisibleMenu(); 738 739 // Hide all of the visible popups from the given list. This function can 740 // cause style changes and frame destruction. 741 void HidePopupsInList(const nsTArray<nsMenuPopupFrame*>& aFrames); 742 743 // Hide, but don't close, visible menus. Called before executing a menu item. 744 // The caller promises to close the menus properly (with a call to HidePopup) 745 // once the item has been executed. 746 void HideOpenMenusBeforeExecutingMenu(CloseMenuMode aMode); 747 748 // set the event that was used to trigger the popup, or null to clear the 749 // event details. aTriggerContent will be set to the target of the event. 750 MOZ_CAN_RUN_SCRIPT_BOUNDARY 751 void InitTriggerEvent(mozilla::dom::Event* aEvent, nsIContent* aPopup, 752 nsIContent** aTriggerContent); 753 754 // callbacks for ShowPopup and HidePopup as events may be done asynchronously 755 void ShowPopupCallback(nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, 756 bool aIsContextMenu, bool aSelectFirstItem); 757 void HidePopupCallback(nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, 758 nsIContent* aNextPopup, nsIContent* aLastPopup, 759 nsPopupType aPopupType, bool aDeselectMenu); 760 761 /** 762 * Trigger frame construction and reflow in the popup, fire a popupshowing 763 * event on the popup and then open the popup. 764 * 765 * aPopup - the popup to open 766 * aIsContextMenu - true for context menus 767 * aSelectFirstItem - true to select the first item in the menu 768 * aTriggerEvent - the event that triggered the showing event. 769 * This is currently used to propagate the 770 * inputSource attribute. May be null. 771 */ 772 void BeginShowingPopup(nsIContent* aPopup, bool aIsContextMenu, 773 bool aSelectFirstItem, 774 mozilla::dom::Event* aTriggerEvent); 775 776 /** 777 * Fire a popuphiding event and then hide the popup. This will be called 778 * recursively if aNextPopup and aLastPopup are set in order to hide a chain 779 * of open menus. If these are not set, only one popup is closed. However, 780 * if the popup type indicates a menu, yet the next popup is not a menu, 781 * then this ends the closing of popups. This allows a menulist inside a 782 * non-menu to close up the menu but not close up the panel it is contained 783 * within. 784 * 785 * The caller must keep a strong reference to aPopup, aNextPopup and 786 * aLastPopup. 787 * 788 * aPopup - the popup to hide 789 * aNextPopup - the next popup to hide 790 * aLastPopup - the last popup in the chain to hide 791 * aPresContext - nsPresContext for the popup's frame 792 * aPopupType - the PopupType of the frame. 793 * aDeselectMenu - true to unhighlight the menu when hiding it 794 * aIsCancel - true if this popup is hiding due to being cancelled. 795 */ 796 MOZ_CAN_RUN_SCRIPT_BOUNDARY 797 void FirePopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup, 798 nsIContent* aLastPopup, nsPresContext* aPresContext, 799 nsPopupType aPopupType, bool aDeselectMenu, 800 bool aIsCancel); 801 802 /** 803 * Handle keyboard navigation within a menu popup specified by aItem. 804 */ HandleKeyboardNavigationInPopup(nsMenuChainItem * aItem,nsNavigationDirection aDir)805 bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, 806 nsNavigationDirection aDir) { 807 return HandleKeyboardNavigationInPopup(aItem, aItem->Frame(), aDir); 808 } 809 810 private: 811 /** 812 * Handle keyboard navigation within a menu popup aFrame. If aItem is 813 * supplied, then it is expected to have a frame equal to aFrame. 814 * If aItem is non-null, then the navigation may be redirected to 815 * an open submenu if one exists. Returns true if the key was 816 * handled and other default handling should not occur. 817 */ 818 bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, 819 nsMenuPopupFrame* aFrame, 820 nsNavigationDirection aDir); 821 822 protected: 823 already_AddRefed<nsINode> GetLastTriggerNode( 824 mozilla::dom::Document* aDocument, bool aIsTooltip); 825 826 /** 827 * Fire a popupshowing event for aPopup. 828 */ 829 nsEventStatus FirePopupShowingEvent(nsIContent* aPopup, 830 nsPresContext* aPresContext, 831 mozilla::dom::Event* aTriggerEvent); 832 833 /** 834 * Set mouse capturing for the current popup. This traps mouse clicks that 835 * occur outside the popup so that it can be closed up. aOldPopup should be 836 * set to the popup that was previously the current popup. 837 */ 838 void SetCaptureState(nsIContent* aOldPopup); 839 840 /** 841 * Key event listeners are attached to the document containing the current 842 * menu for menu and shortcut navigation. Only one listener is needed at a 843 * time, stored in mKeyListener, so switch it only if the document changes. 844 * Having menus in different documents is very rare, so the listeners will 845 * usually only be attached when the first menu opens and removed when all 846 * menus have closed. 847 * 848 * This is also used when only a menubar is active without any open menus, 849 * so that keyboard navigation between menus on the menubar may be done. 850 */ 851 void UpdateKeyboardListeners(); 852 853 /* 854 * Returns true if the docshell for aDoc is aExpected or a child of aExpected. 855 */ 856 bool IsChildOfDocShell(mozilla::dom::Document* aDoc, 857 nsIDocShellTreeItem* aExpected); 858 859 // the document the key event listener is attached to 860 nsCOMPtr<mozilla::dom::EventTarget> mKeyListener; 861 862 // widget that is currently listening to rollup events 863 nsCOMPtr<nsIWidget> mWidget; 864 865 // range parent and offset set in SetTriggerEvent 866 nsCOMPtr<nsIContent> mRangeParentContent; 867 int32_t mRangeOffset; 868 // Device pixels relative to the showing popup's presshell's 869 // root prescontext's root frame. 870 mozilla::LayoutDeviceIntPoint mCachedMousePoint; 871 872 // cached modifiers 873 mozilla::Modifiers mCachedModifiers; 874 875 // set to the currently active menu bar, if any 876 nsMenuBarFrame* mActiveMenuBar; 877 878 // linked list of normal menus and panels. 879 nsMenuChainItem* mPopups; 880 881 // timer used for HidePopupAfterDelay 882 nsCOMPtr<nsITimer> mCloseTimer; 883 884 // a popup that is waiting on the timer 885 nsMenuPopupFrame* mTimerMenu; 886 887 // the popup that is currently being opened, stored only during the 888 // popupshowing event 889 nsCOMPtr<nsIContent> mOpeningPopup; 890 891 // If a popup is displayed as a native menu, this is non-null while the 892 // native menu is open. 893 // mNativeMenu has a strong reference to the menupopup nsIContent. 894 RefPtr<mozilla::widget::NativeMenu> mNativeMenu; 895 896 // If the currently open native menu activated an item, this is the item's 897 // close menu mode. Nothing() if mNativeMenu is null or if no item was 898 // activated. 899 mozilla::Maybe<CloseMenuMode> mNativeMenuActivatedItemCloseMenuMode; 900 901 // If a popup is displayed as a native menu, this map contains the popup state 902 // for any of its non-closed submenus. This state cannot be stored on the 903 // submenus' nsMenuPopupFrames, because we usually don't generate frames for 904 // the contents of native menus. 905 // If a submenu is not present in this map, it means it's closed. 906 // This map is empty if mNativeMenu is null. 907 nsTHashMap<RefPtr<mozilla::dom::Element>, nsPopupState> 908 mNativeMenuSubmenuStates; 909 }; 910 911 #endif 912