Bug 1880227 - Migrate Focus docs into Sphinx. r=owlish,geckoview-reviewers,android...
[gecko.git] / widget / cocoa / nsMenuX.h
blobcdc5884a99e31f0c1b569f294bc7de15af5bbecb
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/. */
6 #ifndef nsMenuX_h_
7 #define nsMenuX_h_
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"
20 #include "nsCOMPtr.h"
21 #include "nsChangeObserver.h"
22 #include "nsThreadUtils.h"
24 class nsMenuX;
25 class nsMenuItemX;
26 class nsIWidget;
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;
37 @end
39 class nsMenuXObserver {
40 public:
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 {
66 public:
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
86 // observer.
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
104 // parent.
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
117 // closed.
118 void ActivateItemAfterClosing(RefPtr<nsMenuItemX>&& aItem,
119 NSEventModifierFlags aModifiers,
120 int16_t aButton);
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.
147 void MenuClosed();
149 // Close the menu if it's open, and flush any pending popuphiding /
150 // popuphidden events.
151 bool Close();
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);
165 void SetupIcon();
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; }
175 // nsMenuParentX
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; }
189 // Stop observing.
190 void ClearObserver() { mObserver = nullptr; }
192 protected:
193 virtual ~nsMenuX();
195 void RebuildMenu();
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,
208 bool aShowServices);
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
214 // <menupopup>.
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
233 // of the DOM event.
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;
257 nsString mLabel;
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;
278 int16_t mButton;
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
291 // items.
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 /
299 // closing.
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
309 // to MenuOpened().
310 bool mDidFirePopupshowingAndIsApprovedToOpen = false;
313 #endif // nsMenuX_h_