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/. */ 5 6 #ifndef nsMenuX_h_ 7 #define nsMenuX_h_ 8 9 #import <Cocoa/Cocoa.h> 10 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" 23 24 class nsMenuX; 25 class nsMenuItemX; 26 class nsIWidget; 27 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; 33 } 34 - (id)initWithGeckoMenu:(nsMenuX*)geckoMenu; 35 - (void)runBlockWhenOpen:(void (^)())block; 36 - (void)menu:(NSMenu*)menu willActivateItem:(NSMenuItem*)item; 37 @property BOOL menuIsInMenubar; 38 @end 39 40 class nsMenuXObserver { 41 public: 42 // Called when a menu in this menu subtree opens, before popupshowing. 43 // No strong reference is held to the observer during the call. 44 virtual void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) = 0; 45 46 // Called when a menu in this menu subtree opened, after popupshown. 47 // No strong reference is held to the observer during the call. 48 virtual void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) = 0; 49 50 // Called before a menu item is activated. 51 virtual void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement, 52 mozilla::dom::Element* aMenuItemElement) = 0; 53 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; 57 }; 58 59 // Once instantiated, this object lives until its DOM node or its parent window is destroyed. 60 // Do not hold references to this, they can become invalid any time the DOM node can be destroyed. 61 class nsMenuX final : public nsMenuParentX, 62 public nsChangeObserver, 63 public nsMenuItemIconX::Listener, 64 public nsMenuXObserver { 65 public: 66 using Observer = nsMenuXObserver; 67 68 // aParent is optional. 69 nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aContent); 70 71 NS_INLINE_DECL_REFCOUNTING(nsMenuX) 72 73 // If > 0, the OS is indexing all the app's menus (triggered by opening 74 // Help menu on Leopard and higher). There are some things that are 75 // unsafe to do while this is happening. 76 static int32_t sIndexingMenuLevel; 77 78 NS_DECL_CHANGEOBSERVER 79 80 // nsMenuItemIconX::Listener 81 void IconUpdated() override; 82 83 // nsMenuXObserver, to forward notifications from our children to our observer. 84 void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) override; 85 void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) override; 86 void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement, 87 mozilla::dom::Element* aMenuItemElement) override; 88 void OnMenuClosed(mozilla::dom::Element* aPopupElement) override; 89 IsVisible()90 bool IsVisible() const { return mVisible; } 91 92 // Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group owner pointer, on this 93 // nsMenuX and also all nested nsMenuX and nsMenuItemX objects. 94 // This is needed because nsMenuX is reference-counted and can outlive its owner, and the menu 95 // group owner asserts that everything has been unregistered when it is destroyed. 96 void DetachFromGroupOwnerRecursive(); 97 98 // Nulls out our reference to the parent. 99 // This is needed because nsMenuX is reference-counted and can outlive its parent. DetachFromParent()100 void DetachFromParent() { mParent = nullptr; } 101 102 mozilla::Maybe<MenuChild> GetItemAt(uint32_t aPos); 103 uint32_t GetItemCount(); 104 105 mozilla::Maybe<MenuChild> GetVisibleItemAt(uint32_t aPos); 106 nsresult GetVisibleItemCount(uint32_t& aCount); 107 108 mozilla::Maybe<MenuChild> GetItemForElement(mozilla::dom::Element* aMenuChildElement); 109 110 // Asynchronously runs the command event on aItem, after the root menu has closed. 111 void ActivateItemAfterClosing(RefPtr<nsMenuItemX>&& aItem, NSEventModifierFlags aModifiers, 112 int16_t aButton); 113 IsOpenForGecko()114 bool IsOpenForGecko() const { return mIsOpenForGecko; } 115 116 // Fires the popupshowing event and returns whether the handler allows the popup to open. 117 // When calling this method, the caller must hold a strong reference to this object, because other 118 // references to this object can be dropped during the handling of the DOM event. 119 bool OnOpen(); 120 PopupShowingEventWasSentAndApprovedExternally()121 void PopupShowingEventWasSentAndApprovedExternally() { DidFirePopupShowing(); } 122 123 // Called from the menu delegate during menuWillOpen, or to simulate opening. 124 // Ignored if the menu is already considered open. 125 // When calling this method, the caller must hold a strong reference to this object, because other 126 // references to this object can be dropped during the handling of the DOM event. 127 void MenuOpened(); 128 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 // If aEntireMenuClosingDueToActivateItem is true, it means that popuphiding/popuphidden events 134 // can be delayed until the event loop for the menu is exited. If this is a submenu, this is 135 // usually not possible because the rest of the menu might stay open. 136 void MenuClosed(bool aEntireMenuClosingDueToActivateItem = false); 137 138 // Close the menu if it's open, and flush any pending popuphiding / popuphidden events. 139 bool Close(); 140 141 // Called from the menu delegate during menu:willHighlightItem:. 142 // If called with Nothing(), it means that no item is highlighted. 143 // The index only accounts for visible items, i.e. items for which there exists an NSMenuItem* in 144 // mNativeMenu. 145 void OnHighlightedItemChanged(const mozilla::Maybe<uint32_t>& aNewHighlightedIndex); 146 147 // Called from the menu delegate before an item anywhere in this menu is activated. 148 // Called after MenuClosed(). 149 void OnWillActivateItem(NSMenuItem* aItem); 150 151 void SetRebuild(bool aMenuEvent); 152 void SetupIcon(); Content()153 nsIContent* Content() { return mContent; } NativeNSMenuItem()154 NSMenuItem* NativeNSMenuItem() { return mNativeMenuItem; } NativeNSMenu()155 GeckoNSMenu* NativeNSMenu() { return mNativeMenu; } 156 SetIconListener(nsMenuItemIconX::Listener * aListener)157 void SetIconListener(nsMenuItemIconX::Listener* aListener) { mIconListener = aListener; } ClearIconListener()158 void ClearIconListener() { mIconListener = nullptr; } 159 160 // nsMenuParentX 161 void MenuChildChangedVisibility(const MenuChild& aChild, bool aIsVisible) override; 162 163 void Dump(uint32_t aIndent) const; 164 165 static bool IsXULHelpMenu(nsIContent* aMenuContent); 166 167 // Set an observer that gets notified of menu opening and closing. 168 // The menu does not keep a strong reference the observer. The observer must 169 // remove itself before it is destroyed. SetObserver(Observer * aObserver)170 void SetObserver(Observer* aObserver) { mObserver = aObserver; } 171 172 // Stop observing. ClearObserver()173 void ClearObserver() { mObserver = nullptr; } 174 175 protected: 176 virtual ~nsMenuX(); 177 178 void RebuildMenu(); 179 nsresult RemoveAll(); 180 nsresult SetEnabled(bool aIsEnabled); 181 nsresult GetEnabled(bool* aIsEnabled); 182 already_AddRefed<nsIContent> GetMenuPopupContent(); 183 void WillInsertChild(const MenuChild& aChild); 184 void WillRemoveChild(const MenuChild& aChild); 185 void AddMenuChild(MenuChild&& aChild); 186 void InsertMenuChild(MenuChild&& aChild); 187 void RemoveMenuChild(const MenuChild& aChild); 188 mozilla::Maybe<MenuChild> CreateMenuChild(nsIContent* aContent); 189 RefPtr<nsMenuItemX> CreateMenuItem(nsIContent* aMenuItemContent); 190 GeckoNSMenu* CreateMenuWithGeckoString(nsString& aMenuTitle); 191 void DidFirePopupShowing(); 192 193 // Find the index at which aChild needs to be inserted into mMenuChildren such that mMenuChildren 194 // remains in correct content order, i.e. the order in mMenuChildren is the same as the order of 195 // the DOM children of our <menupopup>. 196 size_t FindInsertionIndex(const MenuChild& aChild); 197 198 // Calculates the index at which aChild's NSMenuItem should be inserted into our NSMenu. 199 // The order of NSMenuItems in the NSMenu is the same as the order of menu children in 200 // mMenuChildren; the only difference is that mMenuChildren contains both visible and invisible 201 // children, and the NSMenu only contains visible items. So the insertion index is equal to the 202 // number of visible previous siblings of aChild in mMenuChildren. 203 NSInteger CalculateNativeInsertionPoint(const MenuChild& aChild); 204 205 // Fires the popupshown event. 206 void MenuOpenedAsync(); 207 208 // Called from mPendingAsyncMenuCloseRunnable asynchronously after MenuClosed(), so that it runs 209 // after any potential menuItemHit calls for clicked menu items. 210 // Fires popuphiding and popuphidden events. 211 // When calling this method, the caller must hold a strong reference to this object, because other 212 // references to this object can be dropped during the handling of the DOM event. 213 void MenuClosedAsync(); 214 215 // If mPendingAsyncMenuOpenRunnable is non-null, call MenuOpenedAsync() to send out the pending 216 // popupshown event. 217 void FlushMenuOpenedRunnable(); 218 219 // If mPendingAsyncMenuCloseRunnable is non-null, call MenuClosedAsync() to send out pending 220 // popuphiding/popuphidden events. 221 void FlushMenuClosedRunnable(); 222 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(); 228 229 nsCOMPtr<nsIContent> mContent; // XUL <menu> or <menupopup> 230 231 // Contains nsMenuX and nsMenuItemX objects 232 nsTArray<MenuChild> mMenuChildren; 233 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; 240 241 Observer* mObserver = nullptr; // non-owning pointer to our observer 242 243 // Non-null between a call to MenuOpened() and MenuOpenedAsync(). 244 RefPtr<mozilla::CancelableRunnable> mPendingAsyncMenuOpenRunnable; 245 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; 251 252 // Any runnables for running asynchronous command events. 253 // These are only used during automated tests, via ActivateItemAfterClosing. 254 // We keep track of them here so that we can ensure they're run before popuphiding/popuphidden. 255 nsTArray<RefPtr<mozilla::Runnable>> mPendingCommandRunnables; 256 257 GeckoNSMenu* mNativeMenu = nil; // [strong] 258 MenuDelegate* mMenuDelegate = nil; // [strong] 259 // nsMenuX objects should always have a valid native menu item. 260 NSMenuItem* mNativeMenuItem = nil; // [strong] 261 262 // Nothing() if no item is highlighted. The index only accounts for visible items. 263 mozilla::Maybe<uint32_t> mHighlightedItemIndex; 264 265 bool mIsEnabled = true; 266 bool mNeedsRebuild = true; 267 268 // Whether the native NSMenu is considered open. 269 // Also affected by MenuOpened() / MenuClosed() calls for simulated opening / closing. 270 bool mIsOpen = false; 271 272 // Whether the popup is open from Gecko's perspective, based on popupshowing / popuphiding events. 273 bool mIsOpenForGecko = false; 274 275 bool mVisible = true; 276 277 // true between an OnOpen() call that returned true, and the subsequent call 278 // to MenuOpened(). 279 bool mDidFirePopupshowingAndIsApprovedToOpen = false; 280 }; 281 282 #endif // nsMenuX_h_ 283