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