summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsMenuX.h
blob: ff78196ea724df474a3acc59b8ad42270b04e054 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsMenuX_h_
#define nsMenuX_h_

#import <Cocoa/Cocoa.h>

#include "mozilla/EventForwards.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Variant.h"
#include "nsISupports.h"
#include "nsMenuParentX.h"
#include "nsMenuBarX.h"
#include "nsMenuGroupOwnerX.h"
#include "nsMenuItemIconX.h"
#include "nsCOMPtr.h"
#include "nsChangeObserver.h"
#include "nsThreadUtils.h"

class nsMenuX;
class nsMenuItemX;
class nsIWidget;

// MenuDelegate is used to receive Cocoa notifications for setting
// up carbon events. Protocol is defined as of 10.6 SDK.
@interface MenuDelegate : NSObject <NSMenuDelegate> {
  nsMenuX* mGeckoMenu;  // weak ref
  NSMutableArray* mBlocksToRunWhenOpen;
}
- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu;
- (void)runBlockWhenOpen:(void (^)())block;
- (void)menu:(NSMenu*)menu willActivateItem:(NSMenuItem*)item;
@property BOOL menuIsInMenubar;
@end

class nsMenuXObserver {
 public:
  // Called when a menu in this menu subtree opens, before popupshowing.
  // No strong reference is held to the observer during the call.
  virtual void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) = 0;

  // Called when a menu in this menu subtree opened, after popupshown.
  // No strong reference is held to the observer during the call.
  virtual void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) = 0;

  // Called before a menu item is activated.
  virtual void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement,
                                      mozilla::dom::Element* aMenuItemElement) = 0;

  // Called when a menu in this menu subtree closed, after popuphidden.
  // No strong reference is held to the observer during the call.
  virtual void OnMenuClosed(mozilla::dom::Element* aPopupElement) = 0;
};

// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
class nsMenuX final : public nsMenuParentX,
                      public nsChangeObserver,
                      public nsMenuItemIconX::Listener,
                      public nsMenuXObserver {
 public:
  using Observer = nsMenuXObserver;

  // aParent is optional.
  nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aContent);

  NS_INLINE_DECL_REFCOUNTING(nsMenuX)

  // If > 0, the OS is indexing all the app's menus (triggered by opening
  // Help menu on Leopard and higher).  There are some things that are
  // unsafe to do while this is happening.
  static int32_t sIndexingMenuLevel;

  NS_DECL_CHANGEOBSERVER

  // nsMenuItemIconX::Listener
  void IconUpdated() override;

  // nsMenuXObserver, to forward notifications from our children to our observer.
  void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) override;
  void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) override;
  void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement,
                              mozilla::dom::Element* aMenuItemElement) override;
  void OnMenuClosed(mozilla::dom::Element* aPopupElement) override;

  bool IsVisible() const { return mVisible; }

  // Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group owner pointer, on this
  // nsMenuX and also all nested nsMenuX and nsMenuItemX objects.
  // This is needed because nsMenuX is reference-counted and can outlive its owner, and the menu
  // group owner asserts that everything has been unregistered when it is destroyed.
  void DetachFromGroupOwnerRecursive();

  // Nulls out our reference to the parent.
  // This is needed because nsMenuX is reference-counted and can outlive its parent.
  void DetachFromParent() { mParent = nullptr; }

  mozilla::Maybe<MenuChild> GetItemAt(uint32_t aPos);
  uint32_t GetItemCount();

  mozilla::Maybe<MenuChild> GetVisibleItemAt(uint32_t aPos);
  nsresult GetVisibleItemCount(uint32_t& aCount);

  mozilla::Maybe<MenuChild> GetItemForElement(mozilla::dom::Element* aMenuChildElement);

  // Asynchronously runs the command event on aItem, after the root menu has closed.
  void ActivateItemAfterClosing(RefPtr<nsMenuItemX>&& aItem, NSEventModifierFlags aModifiers,
                                int16_t aButton);

  bool IsOpenForGecko() const { return mIsOpenForGecko; }

  // Fires the popupshowing event and returns whether the handler allows the popup to open.
  // When calling this method, the caller must hold a strong reference to this object, because other
  // references to this object can be dropped during the handling of the DOM event.
  MOZ_CAN_RUN_SCRIPT bool OnOpen();

  void PopupShowingEventWasSentAndApprovedExternally() { DidFirePopupShowing(); }

  // Called from the menu delegate during menuWillOpen, or to simulate opening.
  // Ignored if the menu is already considered open.
  // When calling this method, the caller must hold a strong reference to this object, because other
  // references to this object can be dropped during the handling of the DOM event.
  // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void MenuOpened();

  // Called from the menu delegate during menuDidClose, or to simulate closing.
  // Ignored if the menu is already considered closed.
  // When calling this method, the caller must hold a strong reference to this object, because other
  // references to this object can be dropped during the handling of the DOM event.
  void MenuClosed();

  // Close the menu if it's open, and flush any pending popuphiding / popuphidden events.
  bool Close();

  // Called from the menu delegate during menu:willHighlightItem:.
  // If called with Nothing(), it means that no item is highlighted.
  // The index only accounts for visible items, i.e. items for which there exists an NSMenuItem* in
  // mNativeMenu.
  void OnHighlightedItemChanged(const mozilla::Maybe<uint32_t>& aNewHighlightedIndex);

  // Called from the menu delegate before an item anywhere in this menu is activated.
  // Called after MenuClosed().
  void OnWillActivateItem(NSMenuItem* aItem);

  void SetRebuild(bool aMenuEvent);
  void SetupIcon();
  nsIContent* Content() { return mContent; }
  NSMenuItem* NativeNSMenuItem() { return mNativeMenuItem; }
  GeckoNSMenu* NativeNSMenu() { return mNativeMenu; }

  void SetIconListener(nsMenuItemIconX::Listener* aListener) { mIconListener = aListener; }
  void ClearIconListener() { mIconListener = nullptr; }

  // nsMenuParentX
  void MenuChildChangedVisibility(const MenuChild& aChild, bool aIsVisible) override;

  void Dump(uint32_t aIndent) const;

  static bool IsXULHelpMenu(nsIContent* aMenuContent);
  static bool IsXULWindowMenu(nsIContent* aMenuContent);

  // Set an observer that gets notified of menu opening and closing.
  // The menu does not keep a strong reference the observer. The observer must
  // remove itself before it is destroyed.
  void SetObserver(Observer* aObserver) { mObserver = aObserver; }

  // Stop observing.
  void ClearObserver() { mObserver = nullptr; }

 protected:
  virtual ~nsMenuX();

  void RebuildMenu();
  nsresult RemoveAll();
  nsresult SetEnabled(bool aIsEnabled);
  nsresult GetEnabled(bool* aIsEnabled);
  already_AddRefed<nsIContent> GetMenuPopupContent();
  void WillInsertChild(const MenuChild& aChild);
  void WillRemoveChild(const MenuChild& aChild);
  void AddMenuChild(MenuChild&& aChild);
  void InsertMenuChild(MenuChild&& aChild);
  void RemoveMenuChild(const MenuChild& aChild);
  mozilla::Maybe<MenuChild> CreateMenuChild(nsIContent* aContent);
  RefPtr<nsMenuItemX> CreateMenuItem(nsIContent* aMenuItemContent);
  GeckoNSMenu* CreateMenuWithGeckoString(nsString& aMenuTitle);
  void DidFirePopupShowing();

  // Find the index at which aChild needs to be inserted into mMenuChildren such that mMenuChildren
  // remains in correct content order, i.e. the order in mMenuChildren is the same as the order of
  // the DOM children of our <menupopup>.
  size_t FindInsertionIndex(const MenuChild& aChild);

  // Calculates the index at which aChild's NSMenuItem should be inserted into our NSMenu.
  // The order of NSMenuItems in the NSMenu is the same as the order of menu children in
  // mMenuChildren; the only difference is that mMenuChildren contains both visible and invisible
  // children, and the NSMenu only contains visible items. So the insertion index is equal to the
  // number of visible previous siblings of aChild in mMenuChildren.
  NSInteger CalculateNativeInsertionPoint(const MenuChild& aChild);

  // Fires the popupshown event.
  MOZ_CAN_RUN_SCRIPT void MenuOpenedAsync();

  // Called from mPendingAsyncMenuCloseRunnable asynchronously after MenuClosed(), so that it runs
  // after any potential menuItemHit calls for clicked menu items.
  // Fires popuphiding and popuphidden events.
  // When calling this method, the caller must hold a strong reference to this object, because other
  // references to this object can be dropped during the handling of the DOM event.
  MOZ_CAN_RUN_SCRIPT void MenuClosedAsync();

  // If mPendingAsyncMenuOpenRunnable is non-null, call MenuOpenedAsync() to send out the pending
  // popupshown event.
  // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void FlushMenuOpenedRunnable();

  // If mPendingAsyncMenuCloseRunnable is non-null, call MenuClosedAsync() to send out pending
  // popuphiding/popuphidden events.
  // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void FlushMenuClosedRunnable();

  // Make sure the NSMenu contains at least one item, even if mVisibleItemsCount is zero.
  // Otherwise it won't open.
  void InsertPlaceholderIfNeeded();
  // Remove the placeholder before adding an item to mNativeNSMenu.
  void RemovePlaceholderIfPresent();

  nsCOMPtr<nsIContent> mContent;  // XUL <menu> or <menupopup>

  // Contains nsMenuX and nsMenuItemX objects
  nsTArray<MenuChild> mMenuChildren;

  nsString mLabel;
  uint32_t mVisibleItemsCount = 0;                     // cache
  nsMenuParentX* mParent = nullptr;                    // [weak]
  nsMenuGroupOwnerX* mMenuGroupOwner = nullptr;        // [weak]
  nsMenuItemIconX::Listener* mIconListener = nullptr;  // [weak]
  mozilla::UniquePtr<nsMenuItemIconX> mIcon;

  Observer* mObserver = nullptr;  // non-owning pointer to our observer

  // Non-null between a call to MenuOpened() and MenuOpenedAsync().
  RefPtr<mozilla::CancelableRunnable> mPendingAsyncMenuOpenRunnable;

  // Non-null between a call to MenuClosed() and MenuClosedAsync().
  // This is asynchronous so that, if a menu item is clicked, we can fire popuphiding *after* we
  // execute the menu item command. The macOS menu system calls menuWillClose *before* it calls
  // menuItemHit.
  RefPtr<mozilla::CancelableRunnable> mPendingAsyncMenuCloseRunnable;

  struct PendingCommandEvent {
    RefPtr<nsMenuItemX> mMenuItem;
    NSEventModifierFlags mModifiers;
    int16_t mButton;
  };

  // Any pending command events.
  // These are queued by ActivateItemAfterClosing and run by MenuClosedAsync.
  nsTArray<PendingCommandEvent> mPendingCommandEvents;

  GeckoNSMenu* mNativeMenu = nil;     // [strong]
  MenuDelegate* mMenuDelegate = nil;  // [strong]
  // nsMenuX objects should always have a valid native menu item.
  NSMenuItem* mNativeMenuItem = nil;  // [strong]

  // Nothing() if no item is highlighted. The index only accounts for visible items.
  mozilla::Maybe<uint32_t> mHighlightedItemIndex;

  bool mIsEnabled = true;
  bool mNeedsRebuild = true;

  // Whether the native NSMenu is considered open.
  // Also affected by MenuOpened() / MenuClosed() calls for simulated opening / closing.
  bool mIsOpen = false;

  // Whether the popup is open from Gecko's perspective, based on popupshowing / popuphiding events.
  bool mIsOpenForGecko = false;

  bool mVisible = true;

  // true between an OnOpen() call that returned true, and the subsequent call
  // to MenuOpened().
  bool mDidFirePopupshowingAndIsApprovedToOpen = false;
};

#endif  // nsMenuX_h_