Coverage Report

Created: 2018-09-25 14:53

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