Backed out changeset b4a0f8afc02e (bug 1857946) for causing bc failures at browser...
[gecko.git] / widget / cocoa / nsMenuX.h
blob5f540f053872ff6d72c1fe1f4d0bbc8b84a67962
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(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 {
64 public:
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,
111 int16_t aButton);
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.
133 void MenuClosed();
135 // Close the menu if it's open, and flush any pending popuphiding / popuphidden events.
136 bool Close();
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
141 // mNativeMenu.
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);
149 void SetupIcon();
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; }
157 // nsMenuParentX
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; }
170 // Stop observing.
171 void ClearObserver() { mObserver = nullptr; }
173 protected:
174 virtual ~nsMenuX();
176 void RebuildMenu();
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
214 // popupshown event.
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;
234 nsString mLabel;
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
249 // menuItemHit.
250 RefPtr<mozilla::CancelableRunnable> mPendingAsyncMenuCloseRunnable;
252 struct PendingCommandEvent {
253 RefPtr<nsMenuItemX> mMenuItem;
254 NSEventModifierFlags mModifiers;
255 int16_t mButton;
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
283 // to MenuOpened().
284 bool mDidFirePopupshowingAndIsApprovedToOpen = false;
287 #endif // nsMenuX_h_