1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #import <Cocoa/Cocoa.h>
11 #include "mozilla/EventForwards.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/Variant.h"
15 #include "nsISupports.h"
16 #include "nsMenuParentX.h"
17 #include "nsMenuBarX.h"
18 #include "nsMenuGroupOwnerX.h"
19 #include "nsMenuItemIconX.h"
21 #include "nsChangeObserver.h"
22 #include "nsThreadUtils.h"
28 // MenuDelegate is used to receive Cocoa notifications for setting
29 // up carbon events. Protocol is defined as of 10.6 SDK.
30 @interface MenuDelegate
: NSObject
<NSMenuDelegate
> {
31 nsMenuX
* mGeckoMenu
; // weak ref
32 NSMutableArray
* mBlocksToRunWhenOpen
;
34 - (id
)initWithGeckoMenu
:(nsMenuX
*)geckoMenu
;
35 - (void)runBlockWhenOpen
:(void (^)())block
;
36 - (void)menu
:(NSMenu
*)menu willActivateItem
:(NSMenuItem
*)item
;
39 class nsMenuXObserver
{
41 // Called when a menu in this menu subtree opens, before popupshowing.
42 // No strong reference is held to the observer during the call.
43 virtual void OnMenuWillOpen(mozilla::dom::Element
* aPopupElement
) = 0;
45 // Called when a menu in this menu subtree opened, after popupshown.
46 // No strong reference is held to the observer during the call.
47 virtual void OnMenuDidOpen(mozilla::dom::Element
* aPopupElement
) = 0;
49 // Called before a menu item is activated.
50 virtual void OnMenuWillActivateItem(
51 mozilla::dom::Element
* aPopupElement
,
52 mozilla::dom::Element
* aMenuItemElement
) = 0;
54 // Called when a menu in this menu subtree closed, after popuphidden.
55 // No strong reference is held to the observer during the call.
56 virtual void OnMenuClosed(mozilla::dom::Element
* aPopupElement
) = 0;
59 // Once instantiated, this object lives until its DOM node or its parent window
60 // is destroyed. Do not hold references to this, they can become invalid any
61 // time the DOM node can be destroyed.
62 class nsMenuX final
: public nsMenuParentX
,
63 public nsChangeObserver
,
64 public nsMenuItemIconX::Listener
,
65 public nsMenuXObserver
{
67 using Observer
= nsMenuXObserver
;
69 // aParent is optional.
70 nsMenuX(nsMenuParentX
* aParent
, nsMenuGroupOwnerX
* aMenuGroupOwner
,
71 nsIContent
* aContent
);
73 NS_INLINE_DECL_REFCOUNTING(nsMenuX
)
75 // If > 0, the OS is indexing all the app's menus (triggered by opening
76 // Help menu on Leopard and higher). There are some things that are
77 // unsafe to do while this is happening.
78 static int32_t sIndexingMenuLevel
;
80 NS_DECL_CHANGEOBSERVER
82 // nsMenuItemIconX::Listener
83 void IconUpdated() override
;
85 // nsMenuXObserver, to forward notifications from our children to our
87 void OnMenuWillOpen(mozilla::dom::Element
* aPopupElement
) override
;
88 void OnMenuDidOpen(mozilla::dom::Element
* aPopupElement
) override
;
89 void OnMenuWillActivateItem(mozilla::dom::Element
* aPopupElement
,
90 mozilla::dom::Element
* aMenuItemElement
) override
;
91 void OnMenuClosed(mozilla::dom::Element
* aPopupElement
) override
;
93 bool IsVisible() const { return mVisible
; }
95 // Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group
96 // owner pointer, on this nsMenuX and also all nested nsMenuX and nsMenuItemX
97 // objects. This is needed because nsMenuX is reference-counted and can
98 // outlive its owner, and the menu group owner asserts that everything has
99 // been unregistered when it is destroyed.
100 void DetachFromGroupOwnerRecursive();
102 // Nulls out our reference to the parent.
103 // This is needed because nsMenuX is reference-counted and can outlive its
105 void DetachFromParent() { mParent
= nullptr; }
107 mozilla::Maybe
<MenuChild
> GetItemAt(uint32_t aPos
);
108 uint32_t GetItemCount();
110 mozilla::Maybe
<MenuChild
> GetVisibleItemAt(uint32_t aPos
);
111 nsresult
GetVisibleItemCount(uint32_t& aCount
);
113 mozilla::Maybe
<MenuChild
> GetItemForElement(
114 mozilla::dom::Element
* aMenuChildElement
);
116 // Asynchronously runs the command event on aItem, after the root menu has
118 void ActivateItemAfterClosing(RefPtr
<nsMenuItemX
>&& aItem
,
119 NSEventModifierFlags aModifiers
,
122 bool IsOpenForGecko() const { return mIsOpenForGecko
; }
124 // Fires the popupshowing event and returns whether the handler allows the
125 // popup to open. When calling this method, the caller must hold a strong
126 // reference to this object, because other references to this object can be
127 // dropped during the handling of the DOM event.
128 MOZ_CAN_RUN_SCRIPT
bool OnOpen();
130 void PopupShowingEventWasSentAndApprovedExternally() {
131 DidFirePopupShowing();
134 // Called from the menu delegate during menuWillOpen, or to simulate opening.
135 // Ignored if the menu is already considered open.
136 // When calling this method, the caller must hold a strong reference to this
137 // object, because other references to this object can be dropped during the
138 // handling of the DOM event.
139 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
140 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void MenuOpened();
142 // Called from the menu delegate during menuDidClose, or to simulate closing.
143 // Ignored if the menu is already considered closed.
144 // When calling this method, the caller must hold a strong reference to this
145 // object, because other references to this object can be dropped during the
146 // handling of the DOM event.
149 // Close the menu if it's open, and flush any pending popuphiding /
150 // popuphidden events.
153 // Called from the menu delegate during menu:willHighlightItem:.
154 // If called with Nothing(), it means that no item is highlighted.
155 // The index only accounts for visible items, i.e. items for which there
156 // exists an NSMenuItem* in mNativeMenu.
157 void OnHighlightedItemChanged(
158 const mozilla::Maybe
<uint32_t>& aNewHighlightedIndex
);
160 // Called from the menu delegate before an item anywhere in this menu is
161 // activated. Called after MenuClosed().
162 void OnWillActivateItem(NSMenuItem
* aItem
);
164 void SetRebuild(bool aMenuEvent
);
166 nsIContent
* Content() { return mContent
; }
167 NSMenuItem
* NativeNSMenuItem() { return mNativeMenuItem
; }
168 GeckoNSMenu
* NativeNSMenu() { return mNativeMenu
; }
170 void SetIconListener(nsMenuItemIconX::Listener
* aListener
) {
171 mIconListener
= aListener
;
173 void ClearIconListener() { mIconListener
= nullptr; }
176 void MenuChildChangedVisibility(const MenuChild
& aChild
,
177 bool aIsVisible
) override
;
179 void Dump(uint32_t aIndent
) const;
181 static bool IsXULHelpMenu(nsIContent
* aMenuContent
);
182 static bool IsXULWindowMenu(nsIContent
* aMenuContent
);
184 // Set an observer that gets notified of menu opening and closing.
185 // The menu does not keep a strong reference the observer. The observer must
186 // remove itself before it is destroyed.
187 void SetObserver(Observer
* aObserver
) { mObserver
= aObserver
; }
190 void ClearObserver() { mObserver
= nullptr; }
196 nsresult
RemoveAll();
197 nsresult
SetEnabled(bool aIsEnabled
);
198 nsresult
GetEnabled(bool* aIsEnabled
);
199 already_AddRefed
<nsIContent
> GetMenuPopupContent();
200 void WillInsertChild(const MenuChild
& aChild
);
201 void WillRemoveChild(const MenuChild
& aChild
);
202 void AddMenuChild(MenuChild
&& aChild
);
203 void InsertMenuChild(MenuChild
&& aChild
);
204 void RemoveMenuChild(const MenuChild
& aChild
);
205 mozilla::Maybe
<MenuChild
> CreateMenuChild(nsIContent
* aContent
);
206 RefPtr
<nsMenuItemX
> CreateMenuItem(nsIContent
* aMenuItemContent
);
207 GeckoNSMenu
* CreateMenuWithGeckoString(nsString
& aMenuTitle
,
209 void DidFirePopupShowing();
211 // Find the index at which aChild needs to be inserted into mMenuChildren such
212 // that mMenuChildren remains in correct content order, i.e. the order in
213 // mMenuChildren is the same as the order of the DOM children of our
215 size_t FindInsertionIndex(const MenuChild
& aChild
);
217 // Calculates the index at which aChild's NSMenuItem should be inserted into
218 // our NSMenu. The order of NSMenuItems in the NSMenu is the same as the order
219 // of menu children in mMenuChildren; the only difference is that
220 // mMenuChildren contains both visible and invisible children, and the NSMenu
221 // only contains visible items. So the insertion index is equal to the number
222 // of visible previous siblings of aChild in mMenuChildren.
223 NSInteger
CalculateNativeInsertionPoint(const MenuChild
& aChild
);
225 // Fires the popupshown event.
226 MOZ_CAN_RUN_SCRIPT
void MenuOpenedAsync();
228 // Called from mPendingAsyncMenuCloseRunnable asynchronously after
229 // MenuClosed(), so that it runs after any potential menuItemHit calls for
230 // clicked menu items. Fires popuphiding and popuphidden events. When calling
231 // this method, the caller must hold a strong reference to this object,
232 // because other references to this object can be dropped during the handling
234 MOZ_CAN_RUN_SCRIPT
void MenuClosedAsync();
236 // If mPendingAsyncMenuOpenRunnable is non-null, call MenuOpenedAsync() to
237 // send out the pending popupshown event.
238 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
239 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void FlushMenuOpenedRunnable();
241 // If mPendingAsyncMenuCloseRunnable is non-null, call MenuClosedAsync() to
242 // send out pending popuphiding/popuphidden events.
243 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
244 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void FlushMenuClosedRunnable();
246 // Make sure the NSMenu contains at least one item, even if mVisibleItemsCount
247 // is zero. Otherwise it won't open.
248 void InsertPlaceholderIfNeeded();
249 // Remove the placeholder before adding an item to mNativeNSMenu.
250 void RemovePlaceholderIfPresent();
252 nsCOMPtr
<nsIContent
> mContent
; // XUL <menu> or <menupopup>
254 // Contains nsMenuX and nsMenuItemX objects
255 nsTArray
<MenuChild
> mMenuChildren
;
258 uint32_t mVisibleItemsCount
= 0; // cache
259 nsMenuParentX
* mParent
= nullptr; // [weak]
260 nsMenuGroupOwnerX
* mMenuGroupOwner
= nullptr; // [weak]
261 nsMenuItemIconX::Listener
* mIconListener
= nullptr; // [weak]
262 mozilla::UniquePtr
<nsMenuItemIconX
> mIcon
;
264 Observer
* mObserver
= nullptr; // non-owning pointer to our observer
266 // Non-null between a call to MenuOpened() and MenuOpenedAsync().
267 RefPtr
<mozilla::CancelableRunnable
> mPendingAsyncMenuOpenRunnable
;
269 // Non-null between a call to MenuClosed() and MenuClosedAsync().
270 // This is asynchronous so that, if a menu item is clicked, we can fire
271 // popuphiding *after* we execute the menu item command. The macOS menu system
272 // calls menuWillClose *before* it calls menuItemHit.
273 RefPtr
<mozilla::CancelableRunnable
> mPendingAsyncMenuCloseRunnable
;
275 struct PendingCommandEvent
{
276 RefPtr
<nsMenuItemX
> mMenuItem
;
277 NSEventModifierFlags mModifiers
;
281 // Any pending command events.
282 // These are queued by ActivateItemAfterClosing and run by MenuClosedAsync.
283 nsTArray
<PendingCommandEvent
> mPendingCommandEvents
;
285 GeckoNSMenu
* mNativeMenu
= nil
; // [strong]
286 MenuDelegate
* mMenuDelegate
= nil
; // [strong]
287 // nsMenuX objects should always have a valid native menu item.
288 NSMenuItem
* mNativeMenuItem
= nil
; // [strong]
290 // Nothing() if no item is highlighted. The index only accounts for visible
292 mozilla::Maybe
<uint32_t> mHighlightedItemIndex
;
294 bool mIsEnabled
= true;
295 bool mNeedsRebuild
= true;
297 // Whether the native NSMenu is considered open.
298 // Also affected by MenuOpened() / MenuClosed() calls for simulated opening /
300 bool mIsOpen
= false;
302 // Whether the popup is open from Gecko's perspective, based on popupshowing /
303 // popuphiding events.
304 bool mIsOpenForGecko
= false;
306 bool mVisible
= true;
308 // true between an OnOpen() call that returned true, and the subsequent call
310 bool mDidFirePopupshowingAndIsApprovedToOpen
= false;