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