nsXULPopupManager.h (34097B)
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 "Units.h" 15 #include "mozilla/Attributes.h" 16 #include "mozilla/FunctionRef.h" 17 #include "mozilla/Logging.h" 18 #include "mozilla/widget/InitData.h" 19 #include "mozilla/widget/NativeMenu.h" 20 #include "nsCOMPtr.h" 21 #include "nsHashtablesFwd.h" 22 #include "nsIContent.h" 23 #include "nsIDOMEventListener.h" 24 #include "nsIObserver.h" 25 #include "nsIRollupListener.h" 26 #include "nsPoint.h" 27 #include "nsTArray.h" 28 #include "nsThreadUtils.h" 29 30 // XXX Avoid including this here by moving function bodies to the cpp file. 31 #include "mozilla/dom/Element.h" 32 33 // X.h defines KeyPress 34 #ifdef KeyPress 35 # undef KeyPress 36 #endif 37 38 /** 39 * There are two types that are used: 40 * - dismissable popups such as menus, which should close up when there is a 41 * click outside the popup. In this situation, the entire chain of menus 42 * above should also be closed. 43 * - panels, which stay open until a request is made to close them. This 44 * type is used by tooltips. 45 * 46 * When a new popup is opened, it is appended to the popup chain, stored in a 47 * linked list in mPopups. 48 * Popups are stored in this list linked from newest to oldest. When a click 49 * occurs outside one of the open dismissable popups, the chain is closed by 50 * calling Rollup. 51 */ 52 53 class nsContainerFrame; 54 class nsITimer; 55 class nsIDocShellTreeItem; 56 class nsMenuPopupFrame; 57 class nsPIDOMWindowOuter; 58 class nsRefreshDriver; 59 class PopupQueue; 60 61 namespace mozilla { 62 class PresShell; 63 namespace dom { 64 class Event; 65 class KeyboardEvent; 66 class UIEvent; 67 class XULButtonElement; 68 class XULMenuBarElement; 69 class XULPopupElement; 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 enum class HidePopupOption : uint8_t { 165 // If the entire chain of menus should be closed. 166 HideChain, 167 // If the parent <menu> of the popup should not be deselected. This will not 168 // be set when the menu is closed by pressing the Escape key. 169 DeselectMenu, 170 // If the first popuphiding event should be sent asynchrously. This should 171 // be set if HidePopup is called from a frame. 172 Async, 173 // If this popup is hiding due to being cancelled. 174 IsRollup, 175 // Whether animations should be disabled for rolled-up popups. 176 DisableAnimations, 177 }; 178 179 using HidePopupOptions = mozilla::EnumSet<HidePopupOption>; 180 181 /** 182 * DirectionFromKeyCodeTable: two arrays, the first for left-to-right and the 183 * other for right-to-left, that map keycodes to values of 184 * nsNavigationDirection. 185 */ 186 extern const nsNavigationDirection DirectionFromKeyCodeTable[2][6]; 187 188 #define NS_DIRECTION_FROM_KEY_CODE(frame, keycode) \ 189 (DirectionFromKeyCodeTable \ 190 [static_cast<uint8_t>((frame)->StyleVisibility()->mDirection)] \ 191 [(keycode) - mozilla::dom::KeyboardEvent_Binding::DOM_VK_END]) 192 193 // Used to hold information about a popup that is about to be opened. 194 struct PendingPopup { 195 using Element = mozilla::dom::Element; 196 using Event = mozilla::dom::Event; 197 198 PendingPopup(Element* aPopup, Event* aEvent); 199 200 const RefPtr<Element> mPopup; 201 const RefPtr<Event> mEvent; 202 203 // Device pixels relative to the showing popup's presshell's 204 // root prescontext's root frame. 205 mozilla::LayoutDeviceIntPoint mMousePoint; 206 207 // Cached modifiers used to trigger the popup. 208 mozilla::Modifiers mModifiers; 209 210 already_AddRefed<nsIContent> GetTriggerContent() const; 211 212 void InitMousePoint(); 213 214 void SetMousePoint(mozilla::LayoutDeviceIntPoint aMousePoint) { 215 mMousePoint = aMousePoint; 216 } 217 218 uint16_t MouseInputSource() const; 219 }; 220 221 // nsMenuChainItem holds info about an open popup. Items are stored in a 222 // doubly linked list. Note that the linked list is stored beginning from 223 // the lowest child in a chain of menus, as this is the active submenu. 224 class nsMenuChainItem { 225 using PopupType = mozilla::widget::PopupType; 226 227 nsMenuPopupFrame* mFrame; // the popup frame 228 PopupType mPopupType; // the popup type of the frame 229 bool mNoAutoHide; // true for noautohide panels 230 bool mIsContext; // true for context menus 231 bool mOnMenuBar; // true if the menu is on a menu bar 232 nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used 233 234 // True if the popup should maintain its position relative to the anchor when 235 // the anchor moves. 236 bool mFollowAnchor; 237 238 // The last seen position of the anchor, relative to the screen. 239 nsRect mCurrentRect; 240 241 mozilla::UniquePtr<nsMenuChainItem> mParent; 242 // Back pointer, safe because mChild keeps us alive. 243 nsMenuChainItem* mChild = nullptr; 244 245 public: 246 nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aNoAutoHide, bool aIsContext, 247 PopupType aPopupType) 248 : mFrame(aFrame), 249 mPopupType(aPopupType), 250 mNoAutoHide(aNoAutoHide), 251 mIsContext(aIsContext), 252 mOnMenuBar(false), 253 mIgnoreKeys(eIgnoreKeys_False), 254 mFollowAnchor(false) { 255 NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); 256 MOZ_COUNT_CTOR(nsMenuChainItem); 257 } 258 259 MOZ_COUNTED_DTOR(nsMenuChainItem) 260 261 mozilla::dom::XULPopupElement* Element(); 262 nsMenuPopupFrame* Frame() { return mFrame; } 263 PopupType GetPopupType() { return mPopupType; } 264 bool IsNoAutoHide() { return mNoAutoHide; } 265 void SetNoAutoHide(bool aNoAutoHide) { mNoAutoHide = aNoAutoHide; } 266 bool IsMenu() { return mPopupType == PopupType::Menu; } 267 bool IsContextMenu() { return mIsContext; } 268 nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; } 269 void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } 270 bool IsOnMenuBar() { return mOnMenuBar; } 271 void SetOnMenuBar(bool aOnMenuBar) { mOnMenuBar = aOnMenuBar; } 272 nsMenuChainItem* GetParent() { return mParent.get(); } 273 nsMenuChainItem* GetChild() { return mChild; } 274 bool FollowsAnchor() { return mFollowAnchor; } 275 void UpdateFollowAnchor(); 276 void CheckForAnchorChange(); 277 278 // set the parent of this item to aParent, also changing the parent 279 // to have this as a child. 280 void SetParent(mozilla::UniquePtr<nsMenuChainItem> aParent); 281 // Removes the parent pointer and returns it. 282 mozilla::UniquePtr<nsMenuChainItem> Detach(); 283 }; 284 285 // this class is used for dispatching popuphiding events asynchronously. 286 class nsXULPopupHidingEvent : public mozilla::Runnable { 287 using PopupType = mozilla::widget::PopupType; 288 using Element = mozilla::dom::Element; 289 290 public: 291 nsXULPopupHidingEvent(Element* aPopup, Element* aNextPopup, 292 Element* aLastPopup, PopupType aPopupType, 293 HidePopupOptions aOptions) 294 : mozilla::Runnable("nsXULPopupHidingEvent"), 295 mPopup(aPopup), 296 mNextPopup(aNextPopup), 297 mLastPopup(aLastPopup), 298 mPopupType(aPopupType), 299 mOptions(aOptions) { 300 NS_ASSERTION(aPopup, 301 "null popup supplied to nsXULPopupHidingEvent constructor"); 302 // aNextPopup and aLastPopup may be null 303 } 304 305 NS_IMETHOD Run() override; 306 307 private: 308 nsCOMPtr<Element> mPopup; 309 nsCOMPtr<Element> mNextPopup; 310 nsCOMPtr<Element> mLastPopup; 311 PopupType mPopupType; 312 HidePopupOptions mOptions; 313 }; 314 315 // this class is used for dispatching popuppositioned events asynchronously. 316 class nsXULPopupPositionedEvent : public mozilla::Runnable { 317 using Element = mozilla::dom::Element; 318 319 public: 320 explicit nsXULPopupPositionedEvent(Element* aPopup) 321 : mozilla::Runnable("nsXULPopupPositionedEvent"), mPopup(aPopup) { 322 MOZ_ASSERT(aPopup); 323 } 324 325 NS_IMETHOD Run() override; 326 327 // Asynchronously dispatch a popuppositioned event at aPopup if this is a 328 // panel that should receieve such events. Return true if the event was sent. 329 static bool DispatchIfNeeded(Element* aPopup); 330 331 private: 332 const nsCOMPtr<Element> mPopup; 333 }; 334 335 // this class is used for dispatching menu command events asynchronously. 336 class nsXULMenuCommandEvent : public mozilla::Runnable { 337 using Element = mozilla::dom::Element; 338 339 public: 340 nsXULMenuCommandEvent(Element* aMenu, bool aIsTrusted, 341 mozilla::Modifiers aModifiers, bool aUserInput, 342 bool aFlipChecked, int16_t aButton) 343 : mozilla::Runnable("nsXULMenuCommandEvent"), 344 mMenu(aMenu), 345 mModifiers(aModifiers), 346 mButton(aButton), 347 mIsTrusted(aIsTrusted), 348 mUserInput(aUserInput), 349 mFlipChecked(aFlipChecked), 350 mCloseMenuMode(CloseMenuMode_Auto) { 351 NS_ASSERTION(aMenu, 352 "null menu supplied to nsXULMenuCommandEvent constructor"); 353 } 354 355 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; 356 357 void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { 358 mCloseMenuMode = aCloseMenuMode; 359 } 360 361 private: 362 RefPtr<Element> mMenu; 363 364 mozilla::Modifiers mModifiers; 365 int16_t mButton; 366 bool mIsTrusted; 367 bool mUserInput; 368 bool mFlipChecked; 369 CloseMenuMode mCloseMenuMode; 370 }; 371 372 class nsXULPopupManager final : public nsIDOMEventListener, 373 public nsIRollupListener, 374 public nsIObserver, 375 public mozilla::widget::NativeMenu::Observer { 376 public: 377 friend class nsXULPopupHidingEvent; 378 friend class nsXULPopupPositionedEvent; 379 friend class nsXULMenuCommandEvent; 380 friend class TransitionEnder; 381 382 using PopupType = mozilla::widget::PopupType; 383 using Element = mozilla::dom::Element; 384 385 NS_DECL_ISUPPORTS 386 NS_DECL_NSIOBSERVER 387 NS_DECL_NSIDOMEVENTLISTENER 388 389 // nsIRollupListener 390 MOZ_CAN_RUN_SCRIPT_BOUNDARY 391 bool Rollup(const RollupOptions&, 392 nsIContent** aLastRolledUp = nullptr) override; 393 bool ShouldRollupOnMouseWheelEvent() override; 394 bool ShouldConsumeOnMouseWheelEvent() override; 395 bool ShouldRollupOnMouseActivate() override; 396 uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*>* aWidgetChain) override; 397 nsIWidget* GetRollupWidget() override; 398 bool RollupNativeMenu() override; 399 400 MOZ_CAN_RUN_SCRIPT_BOUNDARY bool RollupTooltips(); 401 402 enum class RollupKind { Tooltip, Menu }; 403 MOZ_CAN_RUN_SCRIPT 404 bool RollupInternal(RollupKind, const RollupOptions&, 405 nsIContent** aLastRolledUp); 406 407 // NativeMenu::Observer 408 void OnNativeMenuOpened() override; 409 void OnNativeMenuClosed() override; 410 void OnNativeSubMenuWillOpen(mozilla::dom::Element* aPopupElement) override; 411 void OnNativeSubMenuDidOpen(mozilla::dom::Element* aPopupElement) override; 412 void OnNativeSubMenuClosed(mozilla::dom::Element* aPopupElement) override; 413 MOZ_CAN_RUN_SCRIPT_BOUNDARY void OnNativeMenuWillActivateItem( 414 mozilla::dom::Element* aMenuItemElement) override; 415 416 static nsXULPopupManager* sInstance; 417 418 // initialize and shutdown methods called by nsLayoutStatics 419 static nsresult Init(); 420 static void Shutdown(); 421 422 // returns a weak reference to the popup manager instance, could return null 423 // if a popup manager could not be allocated 424 static nsXULPopupManager* GetInstance(); 425 426 // This should be called when a window is moved or resized to adjust the 427 // popups accordingly. 428 void AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow); 429 void AdjustPopupsOnWindowChange(mozilla::PresShell* aPresShell); 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(mozilla::dom::XULMenuBarElement* aMenuBar, 439 bool aActivate); 440 441 struct MayShowMenuResult { 442 const bool mIsNative = false; 443 mozilla::dom::XULButtonElement* const mMenuButton = nullptr; 444 nsMenuPopupFrame* const mMenuPopupFrame = nullptr; 445 446 explicit operator bool() const { 447 MOZ_ASSERT(!!mMenuButton == !!mMenuPopupFrame); 448 return mIsNative || mMenuButton; 449 } 450 }; 451 452 MayShowMenuResult MayShowMenu(nsIContent* aMenu); 453 454 /** 455 * Open a <menu> given its content node. If aSelectFirstItem is 456 * set to true, the first item on the menu will automatically be 457 * selected. 458 */ 459 void ShowMenu(nsIContent* aMenu, bool aSelectFirstItem); 460 461 /** 462 * Open a popup, either anchored or unanchored. If aSelectFirstItem is 463 * true, then the first item in the menu is selected. The arguments are 464 * similar to those for XULPopupElement::OpenPopup. 465 * 466 * aTriggerEvent should be the event that triggered the event. This is used 467 * to determine the coordinates and trigger node for the popup. This may be 468 * null if the popup was not triggered by an event. 469 * 470 * This fires the popupshowing event synchronously. 471 */ 472 void ShowPopup(Element* aPopup, nsIContent* aAnchorContent, 473 const nsAString& aPosition, int32_t aXPos, int32_t aYPos, 474 bool aIsContextMenu, bool aAttributesOverride, 475 bool aSelectFirstItem, mozilla::dom::Event* aTriggerEvent); 476 477 /** 478 * Open a popup at a specific screen position specified by aXPos and aYPos, 479 * measured in CSS pixels. 480 * 481 * This fires the popupshowing event synchronously. 482 * 483 * If aIsContextMenu is true, the popup is positioned at a slight 484 * offset from aXPos/aYPos to ensure that it is not under the mouse 485 * cursor. 486 */ 487 void ShowPopupAtScreen(Element* aPopup, int32_t aXPos, int32_t aYPos, 488 bool aIsContextMenu, 489 mozilla::dom::Event* aTriggerEvent); 490 491 /* Open a popup anchored at a screen rectangle specified by aRect. 492 * The remaining arguments are similar to ShowPopup. 493 */ 494 void ShowPopupAtScreenRect(Element* aPopup, const nsAString& aPosition, 495 const nsIntRect& aRect, bool aIsContextMenu, 496 bool aAttributesOverride, 497 mozilla::dom::Event* aTriggerEvent); 498 499 /** 500 * Open a popup as a native menu, at a specific screen position specified by 501 * aXPos and aYPos, measured in CSS pixels. 502 * 503 * This fires the popupshowing event synchronously. 504 * 505 * Returns whether native menus are supported for aPopup on this platform. 506 * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) 507 */ 508 MOZ_CAN_RUN_SCRIPT_BOUNDARY bool ShowPopupAsNativeMenu( 509 Element* aPopup, int32_t aXPos, int32_t aYPos, bool aIsContextMenu, 510 mozilla::dom::Event* aTriggerEvent); 511 512 /** 513 * Open a tooltip at a specific screen position specified by aXPos and aYPos, 514 * measured in device pixels. This fires the popupshowing event synchronously. 515 */ 516 void ShowTooltipAtScreen(Element* aPopup, nsIContent* aTriggerContent, 517 const mozilla::LayoutDeviceIntPoint&); 518 519 /* 520 * Hide a popup aPopup. If the popup is in a <menu>, then also inform the 521 * menu that the popup is being hidden. 522 * aLastPopup - optional popup to close last when hiding a chain of menus. 523 * If null, then all popups will be closed. 524 */ 525 void HidePopup(Element* aPopup, HidePopupOptions, 526 Element* aLastPopup = nullptr); 527 528 /* 529 * Hide the popup of a <menu>. 530 */ 531 void HideMenu(nsIContent* aMenu); 532 533 /** 534 * Hide a popup after a short delay. This is used when rolling over menu 535 * items. This timer is stored in mCloseTimer. The timer may be cancelled and 536 * the popup closed by calling KillMenuTimer. 537 */ 538 void HidePopupAfterDelay(nsMenuPopupFrame* aPopup, int32_t aDelay); 539 540 /** 541 * Hide all of the popups from a given docshell. This should be called when 542 * the document is hidden. 543 */ 544 MOZ_CAN_RUN_SCRIPT_BOUNDARY 545 void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide); 546 547 /** 548 * Check if any popups need to be repositioned or hidden after a style or 549 * layout change. This will update, for example, any arrow type panels when 550 * the anchor that is is pointing to has moved, resized or gone away. 551 * Only those popups that pertain to the supplied aRefreshDriver are updated. 552 */ 553 void UpdatePopupPositions(nsRefreshDriver* aRefreshDriver); 554 MOZ_CAN_RUN_SCRIPT_BOUNDARY 555 void PaintPopups(nsRefreshDriver* aRefreshDriver); 556 557 /** 558 * Get the first nsMenuChainItem that is matched by the matching callback 559 * function provided. 560 */ 561 nsMenuChainItem* FirstMatchingPopup( 562 mozilla::FunctionRef<bool(nsMenuChainItem*)> aMatcher) const; 563 564 /** 565 * Enable or disable anchor following on the popup if needed. 566 */ 567 void UpdateFollowAnchor(nsMenuPopupFrame* aPopup); 568 569 /** 570 * Execute a menu command from the triggering event aEvent. 571 * 572 * aMenu - a menuitem to execute 573 * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse 574 * event which triggered the menu to be executed, may not be null 575 */ 576 MOZ_CAN_RUN_SCRIPT void ExecuteMenu(nsIContent* aMenu, 577 nsXULMenuCommandEvent* aEvent); 578 579 /** 580 * If a native menu is open, and aItem is an item in the menu's subtree, 581 * execute the item with the help of the native menu and close the menu. 582 * Returns true if a native menu was open. 583 */ 584 bool ActivateNativeMenuItem(nsIContent* aItem, mozilla::Modifiers aModifiers, 585 int16_t aButton, mozilla::ErrorResult& aRv); 586 587 /** 588 * Return true if the popup for the supplied content node is open. 589 */ 590 bool IsPopupOpen(Element* aPopup); 591 592 /** 593 * Return the frame for the topmost open popup of a given type, or null if 594 * no popup of that type is open. If aType is PopupType::Any, a menu of any 595 * type is returned. 596 */ 597 nsIFrame* GetTopPopup(PopupType aType); 598 599 /** 600 * Returns the topmost active menuitem that's currently visible, if any. 601 */ 602 nsIContent* GetTopActiveMenuItemContent(); 603 604 /** 605 * Return an array of all the open and visible popup frames for 606 * menus, in order from top to bottom. 607 * XXX should we always include native menu? 608 */ 609 void GetVisiblePopups(nsTArray<nsMenuPopupFrame*>& aPopups, 610 bool aIncludeNativeMenu = false); 611 612 /** 613 * Get the node that last triggered a popup or tooltip in the document 614 * aDocument. aDocument must be non-null and be a document contained within 615 * the same window hierarchy as the popup to retrieve. 616 */ 617 already_AddRefed<nsINode> GetLastTriggerPopupNode( 618 mozilla::dom::Document* aDocument) { 619 return GetLastTriggerNode(aDocument, false); 620 } 621 622 already_AddRefed<nsINode> GetLastTriggerTooltipNode( 623 mozilla::dom::Document* aDocument) { 624 return GetLastTriggerNode(aDocument, true); 625 } 626 627 /** 628 * Return false if a popup may not be opened. This will return false if the 629 * popup is already open, if the popup is in a content shell that is not 630 * focused, or if it is a submenu of another menu that isn't open. 631 */ 632 bool MayShowPopup(nsMenuPopupFrame* aFrame); 633 634 /** 635 * Called when a popup frame is destroyed. In this case, just remove the 636 * item and later popups from the list. No point going through HidePopup as 637 * the frames have gone away. 638 */ 639 MOZ_CAN_RUN_SCRIPT void PopupDestroyed(nsMenuPopupFrame* aFrame); 640 641 /** 642 * Returns true if there is a context menu open. If aPopup is specified, 643 * then the context menu must be later in the chain than aPopup. If aPopup 644 * is null, returns true if any context menu at all is open. 645 */ 646 bool HasContextMenu(nsMenuPopupFrame* aPopup); 647 648 /** 649 * Update the commands for the menus within the menu popup for a given 650 * content node. aPopup should be a XUL menupopup element. This method 651 * changes attributes on the children of aPopup, and deals only with the 652 * content of the popup, not the frames. 653 */ 654 void UpdateMenuItems(Element* aPopup); 655 656 /** 657 * Stop the timer which hides a popup after a delay, started by a previous 658 * call to HidePopupAfterDelay. In addition, the popup awaiting to be hidden 659 * is closed asynchronously. 660 */ 661 void KillMenuTimer(); 662 663 /** 664 * Cancel the timer which closes menus after delay, but only if the menu to 665 * close is aMenuParent. When a submenu is opened, the user might move the 666 * mouse over a sibling menuitem which would normally close the menu. This 667 * menu is closed via a timer. However, if the user moves the mouse over the 668 * submenu before the timer fires, we should instead cancel the timer. This 669 * ensures that the user can move the mouse diagonally over a menu. 670 */ 671 void CancelMenuTimer(nsMenuPopupFrame*); 672 673 /** 674 * Handles navigation for menu accelkeys. If aFrame is specified, then the 675 * key is handled by that popup, otherwise if aFrame is null, the key is 676 * handled by the active popup or menubar. 677 */ 678 MOZ_CAN_RUN_SCRIPT bool HandleShortcutNavigation( 679 mozilla::dom::KeyboardEvent& aKeyEvent, nsMenuPopupFrame* aFrame); 680 681 /** 682 * Handles cursor navigation within a menu. Returns true if the key has 683 * been handled. 684 */ 685 MOZ_CAN_RUN_SCRIPT bool HandleKeyboardNavigation(uint32_t aKeyCode); 686 687 /** 688 * Handle keyboard navigation within a menu popup specified by aFrame. 689 * Returns true if the key was handled and other default handling 690 * should not occur. 691 */ 692 MOZ_CAN_RUN_SCRIPT bool HandleKeyboardNavigationInPopup( 693 nsMenuPopupFrame* aFrame, nsNavigationDirection aDir) { 694 return HandleKeyboardNavigationInPopup(nullptr, aFrame, aDir); 695 } 696 697 /** 698 * Handles the keyboard event with keyCode value. Returns true if the event 699 * has been handled. 700 */ 701 MOZ_CAN_RUN_SCRIPT bool HandleKeyboardEventWithKeyCode( 702 mozilla::dom::KeyboardEvent* aKeyEvent, 703 nsMenuChainItem* aTopVisibleMenuItem); 704 705 // Sets mIgnoreKeys of the Top Visible Menu Item 706 nsresult UpdateIgnoreKeys(bool aIgnoreKeys); 707 708 nsPopupState GetPopupState(mozilla::dom::Element* aPopupElement); 709 710 mozilla::dom::Event* GetOpeningPopupEvent() const { 711 return mPendingPopup->mEvent.get(); 712 } 713 714 MOZ_CAN_RUN_SCRIPT nsresult KeyUp(mozilla::dom::KeyboardEvent* aKeyEvent); 715 MOZ_CAN_RUN_SCRIPT nsresult KeyDown(mozilla::dom::KeyboardEvent* aKeyEvent); 716 MOZ_CAN_RUN_SCRIPT nsresult KeyPress(mozilla::dom::KeyboardEvent* aKeyEvent); 717 718 protected: 719 nsXULPopupManager(); 720 ~nsXULPopupManager(); 721 722 // get the nsMenuPopupFrame, if any, for the given content node 723 MOZ_CAN_RUN_SCRIPT_BOUNDARY 724 nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, 725 bool aShouldFlush); 726 727 // Get the menu to start rolling up. 728 nsMenuChainItem* GetRollupItem(RollupKind); 729 730 // Return the topmost menu, skipping over invisible popups 731 nsMenuChainItem* GetTopVisibleMenu() { 732 return GetRollupItem(RollupKind::Menu); 733 } 734 735 // Add the chain item to the chain and update mPopups to point to it. 736 void AddMenuChainItem(mozilla::UniquePtr<nsMenuChainItem>); 737 738 // Removes the chain item from the chain and deletes it. 739 void RemoveMenuChainItem(nsMenuChainItem*); 740 741 // Hide all of the visible popups from the given list. This function can 742 // cause style changes and frame destruction. 743 MOZ_CAN_RUN_SCRIPT void HidePopupsInList( 744 const nsTArray<nsMenuPopupFrame*>& aFrames); 745 746 // Hide, but don't close, visible menus. Called before executing a menu item. 747 // The caller promises to close the menus properly (with a call to HidePopup) 748 // once the item has been executed. 749 MOZ_CAN_RUN_SCRIPT void HideOpenMenusBeforeExecutingMenu(CloseMenuMode aMode); 750 751 // callbacks for ShowPopup and HidePopup as events may be done asynchronously 752 MOZ_CAN_RUN_SCRIPT void ShowPopupCallback(Element* aPopup, 753 nsMenuPopupFrame* aPopupFrame, 754 bool aIsContextMenu, 755 bool aSelectFirstItem); 756 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HidePopupCallback( 757 Element* aPopup, nsMenuPopupFrame* aPopupFrame, Element* aNextPopup, 758 Element* aLastPopup, PopupType aPopupType, HidePopupOptions); 759 760 /** 761 * Trigger frame construction and reflow in the popup, fire a popupshowing 762 * event on the popup and then open the popup. 763 * 764 * aPendingPopup - information about the popup to open 765 * aIsContextMenu - true for context menus 766 * aSelectFirstItem - true to select the first item in the menu 767 * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) 768 * 769 * Return false if the popup is not going to be shown. This is mainly used for 770 * the queue popup logic. 771 */ 772 MOZ_CAN_RUN_SCRIPT_BOUNDARY bool BeginShowingPopup( 773 const PendingPopup& aPendingPopup, bool aIsContextMenu, 774 bool aSelectFirstItem); 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 * aOptions - the relevant options to hide the popup. Only a subset is looked 794 * at. 795 */ 796 MOZ_CAN_RUN_SCRIPT_BOUNDARY 797 void FirePopupHidingEvent(Element* aPopup, Element* aNextPopup, 798 Element* aLastPopup, nsPresContext* aPresContext, 799 PopupType aPopupType, HidePopupOptions aOptions); 800 801 /** 802 * Handle keyboard navigation within a menu popup specified by aItem. 803 */ 804 MOZ_CAN_RUN_SCRIPT 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 MOZ_CAN_RUN_SCRIPT bool HandleKeyboardNavigationInPopup( 819 nsMenuChainItem* aItem, 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 MOZ_CAN_RUN_SCRIPT nsEventStatus FirePopupShowingEvent( 830 const PendingPopup& aPendingPopup, nsPresContext* aPresContext); 831 832 /** 833 * Set mouse capturing for the current popup. This traps mouse clicks that 834 * occur outside the popup so that it can be closed up. aOldPopup should be 835 * set to the popup that was previously the current popup. 836 */ 837 void SetCaptureState(nsIContent* aOldPopup); 838 839 /** 840 * Key event listeners are attached to the document containing the current 841 * menu for menu and shortcut navigation. Only one listener is needed at a 842 * time, stored in mKeyListener, so switch it only if the document changes. 843 * Having menus in different documents is very rare, so the listeners will 844 * usually only be attached when the first menu opens and removed when all 845 * menus have closed. 846 * 847 * This is also used when only a menubar is active without any open menus, 848 * so that keyboard navigation between menus on the menubar may be done. 849 */ 850 // TODO: Convert UpdateKeyboardListeners() to MOZ_CAN_RUN_SCRIPT and get rid 851 // of the kungFuDeathGrip in it. 852 MOZ_CAN_RUN_SCRIPT_BOUNDARY void UpdateKeyboardListeners(); 853 854 /* 855 * Returns true if the docshell for aDoc is aExpected or a child of aExpected. 856 */ 857 bool IsChildOfDocShell(mozilla::dom::Document* aDoc, 858 nsIDocShellTreeItem* aExpected); 859 860 // Finds a chain item in mPopups. 861 nsMenuChainItem* FindPopup(Element* aPopup) const; 862 863 // Dimiss existing queueable shown popups before showing a non-queueable one. 864 void DismissQueueableShownPopups(); 865 866 // the document the key event listener is attached to 867 nsCOMPtr<mozilla::dom::EventTarget> mKeyListener; 868 869 // widget that is currently listening to rollup events 870 nsCOMPtr<nsIWidget> mWidget; 871 872 // set to the currently active menu bar, if any 873 mozilla::dom::XULMenuBarElement* mActiveMenuBar; 874 875 // linked list of normal menus and panels. mPopups points to the innermost 876 // popup, which keeps alive all their parents. 877 mozilla::UniquePtr<nsMenuChainItem> mPopups; 878 879 // timer used for HidePopupAfterDelay 880 nsCOMPtr<nsITimer> mCloseTimer; 881 nsMenuPopupFrame* mTimerMenu = nullptr; 882 883 // Information about the popup that is currently firing a popupshowing event. 884 const PendingPopup* mPendingPopup; 885 886 // If a popup is displayed as a native menu, this is non-null while the 887 // native menu is open. 888 // mNativeMenu has a strong reference to the menupopup nsIContent. 889 RefPtr<mozilla::widget::NativeMenu> mNativeMenu; 890 891 // If the currently open native menu activated an item, this is the item's 892 // close menu mode. Nothing() if mNativeMenu is null or if no item was 893 // activated. 894 mozilla::Maybe<CloseMenuMode> mNativeMenuActivatedItemCloseMenuMode; 895 896 // If a popup is displayed as a native menu, this map contains the popup state 897 // for any of its non-closed submenus. This state cannot be stored on the 898 // submenus' nsMenuPopupFrames, because we usually don't generate frames for 899 // the contents of native menus. 900 // If a submenu is not present in this map, it means it's closed. 901 // This map is empty if mNativeMenu is null. 902 nsTHashMap<RefPtr<mozilla::dom::Element>, nsPopupState> 903 mNativeMenuSubmenuStates; 904 905 // A queue for "queuable" popups. 906 RefPtr<PopupQueue> mPopupQueue; 907 }; 908 909 #endif