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(mozilla::dom::Element
* aPopupElement
,
51 mozilla::dom::Element
* aMenuItemElement
) = 0;
53 // Called when a menu in this menu subtree closed, after popuphidden.
54 // No strong reference is held to the observer during the call.
55 virtual void OnMenuClosed(mozilla::dom::Element
* aPopupElement
) = 0;
58 // Once instantiated, this object lives until its DOM node or its parent window is destroyed.
59 // Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
60 class nsMenuX final
: public nsMenuParentX
,
61 public nsChangeObserver
,
62 public nsMenuItemIconX::Listener
,
63 public nsMenuXObserver
{
65 using Observer
= nsMenuXObserver
;
67 // aParent is optional.
68 nsMenuX(nsMenuParentX
* aParent
, nsMenuGroupOwnerX
* aMenuGroupOwner
, nsIContent
* aContent
);
70 NS_INLINE_DECL_REFCOUNTING(nsMenuX
)
72 // If > 0, the OS is indexing all the app's menus (triggered by opening
73 // Help menu on Leopard and higher). There are some things that are
74 // unsafe to do while this is happening.
75 static int32_t sIndexingMenuLevel
;
77 NS_DECL_CHANGEOBSERVER
79 // nsMenuItemIconX::Listener
80 void IconUpdated() override
;
82 // nsMenuXObserver, to forward notifications from our children to our observer.
83 void OnMenuWillOpen(mozilla::dom::Element
* aPopupElement
) override
;
84 void OnMenuDidOpen(mozilla::dom::Element
* aPopupElement
) override
;
85 void OnMenuWillActivateItem(mozilla::dom::Element
* aPopupElement
,
86 mozilla::dom::Element
* aMenuItemElement
) override
;
87 void OnMenuClosed(mozilla::dom::Element
* aPopupElement
) override
;
89 bool IsVisible() const { return mVisible
; }
91 // Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group owner pointer, on this
92 // nsMenuX and also all nested nsMenuX and nsMenuItemX objects.
93 // This is needed because nsMenuX is reference-counted and can outlive its owner, and the menu
94 // group owner asserts that everything has been unregistered when it is destroyed.
95 void DetachFromGroupOwnerRecursive();
97 // Nulls out our reference to the parent.
98 // This is needed because nsMenuX is reference-counted and can outlive its parent.
99 void DetachFromParent() { mParent
= nullptr; }
101 mozilla::Maybe
<MenuChild
> GetItemAt(uint32_t aPos
);
102 uint32_t GetItemCount();
104 mozilla::Maybe
<MenuChild
> GetVisibleItemAt(uint32_t aPos
);
105 nsresult
GetVisibleItemCount(uint32_t& aCount
);
107 mozilla::Maybe
<MenuChild
> GetItemForElement(mozilla::dom::Element
* aMenuChildElement
);
109 // Asynchronously runs the command event on aItem, after the root menu has closed.
110 void ActivateItemAfterClosing(RefPtr
<nsMenuItemX
>&& aItem
, NSEventModifierFlags aModifiers
,
113 bool IsOpenForGecko() const { return mIsOpenForGecko
; }
115 // Fires the popupshowing event and returns whether the handler allows the popup to open.
116 // When calling this method, the caller must hold a strong reference to this object, because other
117 // references to this object can be dropped during the handling of the DOM event.
118 MOZ_CAN_RUN_SCRIPT
bool OnOpen();
120 void PopupShowingEventWasSentAndApprovedExternally() { DidFirePopupShowing(); }
122 // Called from the menu delegate during menuWillOpen, or to simulate opening.
123 // Ignored if the menu is already considered open.
124 // When calling this method, the caller must hold a strong reference to this object, because other
125 // references to this object can be dropped during the handling of the DOM event.
126 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
127 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void MenuOpened();
129 // Called from the menu delegate during menuDidClose, or to simulate closing.
130 // Ignored if the menu is already considered closed.
131 // When calling this method, the caller must hold a strong reference to this object, because other
132 // references to this object can be dropped during the handling of the DOM event.
135 // Close the menu if it's open, and flush any pending popuphiding / popuphidden events.
138 // Called from the menu delegate during menu:willHighlightItem:.
139 // If called with Nothing(), it means that no item is highlighted.
140 // The index only accounts for visible items, i.e. items for which there exists an NSMenuItem* in
142 void OnHighlightedItemChanged(const mozilla::Maybe
<uint32_t>& aNewHighlightedIndex
);
144 // Called from the menu delegate before an item anywhere in this menu is activated.
145 // Called after MenuClosed().
146 void OnWillActivateItem(NSMenuItem
* aItem
);
148 void SetRebuild(bool aMenuEvent
);
150 nsIContent
* Content() { return mContent
; }
151 NSMenuItem
* NativeNSMenuItem() { return mNativeMenuItem
; }
152 GeckoNSMenu
* NativeNSMenu() { return mNativeMenu
; }
154 void SetIconListener(nsMenuItemIconX::Listener
* aListener
) { mIconListener
= aListener
; }
155 void ClearIconListener() { mIconListener
= nullptr; }
158 void MenuChildChangedVisibility(const MenuChild
& aChild
, bool aIsVisible
) override
;
160 void Dump(uint32_t aIndent
) const;
162 static bool IsXULHelpMenu(nsIContent
* aMenuContent
);
163 static bool IsXULWindowMenu(nsIContent
* aMenuContent
);
165 // Set an observer that gets notified of menu opening and closing.
166 // The menu does not keep a strong reference the observer. The observer must
167 // remove itself before it is destroyed.
168 void SetObserver(Observer
* aObserver
) { mObserver
= aObserver
; }
171 void ClearObserver() { mObserver
= nullptr; }
177 nsresult
RemoveAll();
178 nsresult
SetEnabled(bool aIsEnabled
);
179 nsresult
GetEnabled(bool* aIsEnabled
);
180 already_AddRefed
<nsIContent
> GetMenuPopupContent();
181 void WillInsertChild(const MenuChild
& aChild
);
182 void WillRemoveChild(const MenuChild
& aChild
);
183 void AddMenuChild(MenuChild
&& aChild
);
184 void InsertMenuChild(MenuChild
&& aChild
);
185 void RemoveMenuChild(const MenuChild
& aChild
);
186 mozilla::Maybe
<MenuChild
> CreateMenuChild(nsIContent
* aContent
);
187 RefPtr
<nsMenuItemX
> CreateMenuItem(nsIContent
* aMenuItemContent
);
188 GeckoNSMenu
* CreateMenuWithGeckoString(nsString
& aMenuTitle
);
189 void DidFirePopupShowing();
191 // Find the index at which aChild needs to be inserted into mMenuChildren such that mMenuChildren
192 // remains in correct content order, i.e. the order in mMenuChildren is the same as the order of
193 // the DOM children of our <menupopup>.
194 size_t FindInsertionIndex(const MenuChild
& aChild
);
196 // Calculates the index at which aChild's NSMenuItem should be inserted into our NSMenu.
197 // The order of NSMenuItems in the NSMenu is the same as the order of menu children in
198 // mMenuChildren; the only difference is that mMenuChildren contains both visible and invisible
199 // children, and the NSMenu only contains visible items. So the insertion index is equal to the
200 // number of visible previous siblings of aChild in mMenuChildren.
201 NSInteger
CalculateNativeInsertionPoint(const MenuChild
& aChild
);
203 // Fires the popupshown event.
204 MOZ_CAN_RUN_SCRIPT
void MenuOpenedAsync();
206 // Called from mPendingAsyncMenuCloseRunnable asynchronously after MenuClosed(), so that it runs
207 // after any potential menuItemHit calls for clicked menu items.
208 // Fires popuphiding and popuphidden events.
209 // When calling this method, the caller must hold a strong reference to this object, because other
210 // references to this object can be dropped during the handling of the DOM event.
211 MOZ_CAN_RUN_SCRIPT
void MenuClosedAsync();
213 // If mPendingAsyncMenuOpenRunnable is non-null, call MenuOpenedAsync() to send out the pending
215 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
216 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void FlushMenuOpenedRunnable();
218 // If mPendingAsyncMenuCloseRunnable is non-null, call MenuClosedAsync() to send out pending
219 // popuphiding/popuphidden events.
220 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
221 MOZ_CAN_RUN_SCRIPT_BOUNDARY
void FlushMenuClosedRunnable();
223 // Make sure the NSMenu contains at least one item, even if mVisibleItemsCount is zero.
224 // Otherwise it won't open.
225 void InsertPlaceholderIfNeeded();
226 // Remove the placeholder before adding an item to mNativeNSMenu.
227 void RemovePlaceholderIfPresent();
229 nsCOMPtr
<nsIContent
> mContent
; // XUL <menu> or <menupopup>
231 // Contains nsMenuX and nsMenuItemX objects
232 nsTArray
<MenuChild
> mMenuChildren
;
235 uint32_t mVisibleItemsCount
= 0; // cache
236 nsMenuParentX
* mParent
= nullptr; // [weak]
237 nsMenuGroupOwnerX
* mMenuGroupOwner
= nullptr; // [weak]
238 nsMenuItemIconX::Listener
* mIconListener
= nullptr; // [weak]
239 mozilla::UniquePtr
<nsMenuItemIconX
> mIcon
;
241 Observer
* mObserver
= nullptr; // non-owning pointer to our observer
243 // Non-null between a call to MenuOpened() and MenuOpenedAsync().
244 RefPtr
<mozilla::CancelableRunnable
> mPendingAsyncMenuOpenRunnable
;
246 // Non-null between a call to MenuClosed() and MenuClosedAsync().
247 // This is asynchronous so that, if a menu item is clicked, we can fire popuphiding *after* we
248 // execute the menu item command. The macOS menu system calls menuWillClose *before* it calls
250 RefPtr
<mozilla::CancelableRunnable
> mPendingAsyncMenuCloseRunnable
;
252 struct PendingCommandEvent
{
253 RefPtr
<nsMenuItemX
> mMenuItem
;
254 NSEventModifierFlags mModifiers
;
258 // Any pending command events.
259 // These are queued by ActivateItemAfterClosing and run by MenuClosedAsync.
260 nsTArray
<PendingCommandEvent
> mPendingCommandEvents
;
262 GeckoNSMenu
* mNativeMenu
= nil
; // [strong]
263 MenuDelegate
* mMenuDelegate
= nil
; // [strong]
264 // nsMenuX objects should always have a valid native menu item.
265 NSMenuItem
* mNativeMenuItem
= nil
; // [strong]
267 // Nothing() if no item is highlighted. The index only accounts for visible items.
268 mozilla::Maybe
<uint32_t> mHighlightedItemIndex
;
270 bool mIsEnabled
= true;
271 bool mNeedsRebuild
= true;
273 // Whether the native NSMenu is considered open.
274 // Also affected by MenuOpened() / MenuClosed() calls for simulated opening / closing.
275 bool mIsOpen
= false;
277 // Whether the popup is open from Gecko's perspective, based on popupshowing / popuphiding events.
278 bool mIsOpenForGecko
= false;
280 bool mVisible
= true;
282 // true between an OnOpen() call that returned true, and the subsequent call
284 bool mDidFirePopupshowingAndIsApprovedToOpen
= false;