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