/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsMenuX_h_ #define nsMenuX_h_ #import #include "mozilla/EventForwards.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/Variant.h" #include "nsISupports.h" #include "nsMenuParentX.h" #include "nsMenuBarX.h" #include "nsMenuGroupOwnerX.h" #include "nsMenuItemIconX.h" #include "nsCOMPtr.h" #include "nsChangeObserver.h" #include "nsThreadUtils.h" class nsMenuX; class nsMenuItemX; class nsIWidget; // MenuDelegate is used to receive Cocoa notifications for setting // up carbon events. Protocol is defined as of 10.6 SDK. @interface MenuDelegate : NSObject { nsMenuX* mGeckoMenu; // weak ref NSMutableArray* mBlocksToRunWhenOpen; } - (id)initWithGeckoMenu:(nsMenuX*)geckoMenu; - (void)runBlockWhenOpen:(void (^)())block; - (void)menu:(NSMenu*)menu willActivateItem:(NSMenuItem*)item; @property BOOL menuIsInMenubar; @end class nsMenuXObserver { public: // Called when a menu in this menu subtree opens, before popupshowing. // No strong reference is held to the observer during the call. virtual void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) = 0; // Called when a menu in this menu subtree opened, after popupshown. // No strong reference is held to the observer during the call. virtual void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) = 0; // Called before a menu item is activated. virtual void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement, mozilla::dom::Element* aMenuItemElement) = 0; // Called when a menu in this menu subtree closed, after popuphidden. // No strong reference is held to the observer during the call. virtual void OnMenuClosed(mozilla::dom::Element* aPopupElement) = 0; }; // Once instantiated, this object lives until its DOM node or its parent window is destroyed. // Do not hold references to this, they can become invalid any time the DOM node can be destroyed. class nsMenuX final : public nsMenuParentX, public nsChangeObserver, public nsMenuItemIconX::Listener, public nsMenuXObserver { public: using Observer = nsMenuXObserver; // aParent is optional. nsMenuX(nsMenuParentX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aContent); NS_INLINE_DECL_REFCOUNTING(nsMenuX) // If > 0, the OS is indexing all the app's menus (triggered by opening // Help menu on Leopard and higher). There are some things that are // unsafe to do while this is happening. static int32_t sIndexingMenuLevel; NS_DECL_CHANGEOBSERVER // nsMenuItemIconX::Listener void IconUpdated() override; // nsMenuXObserver, to forward notifications from our children to our observer. void OnMenuWillOpen(mozilla::dom::Element* aPopupElement) override; void OnMenuDidOpen(mozilla::dom::Element* aPopupElement) override; void OnMenuWillActivateItem(mozilla::dom::Element* aPopupElement, mozilla::dom::Element* aMenuItemElement) override; void OnMenuClosed(mozilla::dom::Element* aPopupElement) override; bool IsVisible() const { return mVisible; } // Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group owner pointer, on this // nsMenuX and also all nested nsMenuX and nsMenuItemX objects. // This is needed because nsMenuX is reference-counted and can outlive its owner, and the menu // group owner asserts that everything has been unregistered when it is destroyed. void DetachFromGroupOwnerRecursive(); // Nulls out our reference to the parent. // This is needed because nsMenuX is reference-counted and can outlive its parent. void DetachFromParent() { mParent = nullptr; } mozilla::Maybe GetItemAt(uint32_t aPos); uint32_t GetItemCount(); mozilla::Maybe GetVisibleItemAt(uint32_t aPos); nsresult GetVisibleItemCount(uint32_t& aCount); mozilla::Maybe GetItemForElement(mozilla::dom::Element* aMenuChildElement); // Asynchronously runs the command event on aItem, after the root menu has closed. void ActivateItemAfterClosing(RefPtr&& aItem, NSEventModifierFlags aModifiers, int16_t aButton); bool IsOpenForGecko() const { return mIsOpenForGecko; } // Fires the popupshowing event and returns whether the handler allows the popup to open. // When calling this method, the caller must hold a strong reference to this object, because other // references to this object can be dropped during the handling of the DOM event. MOZ_CAN_RUN_SCRIPT bool OnOpen(); void PopupShowingEventWasSentAndApprovedExternally() { DidFirePopupShowing(); } // Called from the menu delegate during menuWillOpen, or to simulate opening. // Ignored if the menu is already considered open. // When calling this method, the caller must hold a strong reference to this object, because other // references to this object can be dropped during the handling of the DOM event. // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) MOZ_CAN_RUN_SCRIPT_BOUNDARY void MenuOpened(); // Called from the menu delegate during menuDidClose, or to simulate closing. // Ignored if the menu is already considered closed. // When calling this method, the caller must hold a strong reference to this object, because other // references to this object can be dropped during the handling of the DOM event. void MenuClosed(); // Close the menu if it's open, and flush any pending popuphiding / popuphidden events. bool Close(); // Called from the menu delegate during menu:willHighlightItem:. // If called with Nothing(), it means that no item is highlighted. // The index only accounts for visible items, i.e. items for which there exists an NSMenuItem* in // mNativeMenu. void OnHighlightedItemChanged(const mozilla::Maybe& aNewHighlightedIndex); // Called from the menu delegate before an item anywhere in this menu is activated. // Called after MenuClosed(). void OnWillActivateItem(NSMenuItem* aItem); void SetRebuild(bool aMenuEvent); void SetupIcon(); nsIContent* Content() { return mContent; } NSMenuItem* NativeNSMenuItem() { return mNativeMenuItem; } GeckoNSMenu* NativeNSMenu() { return mNativeMenu; } void SetIconListener(nsMenuItemIconX::Listener* aListener) { mIconListener = aListener; } void ClearIconListener() { mIconListener = nullptr; } // nsMenuParentX void MenuChildChangedVisibility(const MenuChild& aChild, bool aIsVisible) override; void Dump(uint32_t aIndent) const; static bool IsXULHelpMenu(nsIContent* aMenuContent); static bool IsXULWindowMenu(nsIContent* aMenuContent); // Set an observer that gets notified of menu opening and closing. // The menu does not keep a strong reference the observer. The observer must // remove itself before it is destroyed. void SetObserver(Observer* aObserver) { mObserver = aObserver; } // Stop observing. void ClearObserver() { mObserver = nullptr; } protected: virtual ~nsMenuX(); void RebuildMenu(); nsresult RemoveAll(); nsresult SetEnabled(bool aIsEnabled); nsresult GetEnabled(bool* aIsEnabled); already_AddRefed GetMenuPopupContent(); void WillInsertChild(const MenuChild& aChild); void WillRemoveChild(const MenuChild& aChild); void AddMenuChild(MenuChild&& aChild); void InsertMenuChild(MenuChild&& aChild); void RemoveMenuChild(const MenuChild& aChild); mozilla::Maybe CreateMenuChild(nsIContent* aContent); RefPtr CreateMenuItem(nsIContent* aMenuItemContent); GeckoNSMenu* CreateMenuWithGeckoString(nsString& aMenuTitle); void DidFirePopupShowing(); // Find the index at which aChild needs to be inserted into mMenuChildren such that mMenuChildren // remains in correct content order, i.e. the order in mMenuChildren is the same as the order of // the DOM children of our . size_t FindInsertionIndex(const MenuChild& aChild); // Calculates the index at which aChild's NSMenuItem should be inserted into our NSMenu. // The order of NSMenuItems in the NSMenu is the same as the order of menu children in // mMenuChildren; the only difference is that mMenuChildren contains both visible and invisible // children, and the NSMenu only contains visible items. So the insertion index is equal to the // number of visible previous siblings of aChild in mMenuChildren. NSInteger CalculateNativeInsertionPoint(const MenuChild& aChild); // Fires the popupshown event. MOZ_CAN_RUN_SCRIPT void MenuOpenedAsync(); // Called from mPendingAsyncMenuCloseRunnable asynchronously after MenuClosed(), so that it runs // after any potential menuItemHit calls for clicked menu items. // Fires popuphiding and popuphidden events. // When calling this method, the caller must hold a strong reference to this object, because other // references to this object can be dropped during the handling of the DOM event. MOZ_CAN_RUN_SCRIPT void MenuClosedAsync(); // If mPendingAsyncMenuOpenRunnable is non-null, call MenuOpenedAsync() to send out the pending // popupshown event. // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) MOZ_CAN_RUN_SCRIPT_BOUNDARY void FlushMenuOpenedRunnable(); // If mPendingAsyncMenuCloseRunnable is non-null, call MenuClosedAsync() to send out pending // popuphiding/popuphidden events. // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) MOZ_CAN_RUN_SCRIPT_BOUNDARY void FlushMenuClosedRunnable(); // Make sure the NSMenu contains at least one item, even if mVisibleItemsCount is zero. // Otherwise it won't open. void InsertPlaceholderIfNeeded(); // Remove the placeholder before adding an item to mNativeNSMenu. void RemovePlaceholderIfPresent(); nsCOMPtr mContent; // XUL or // Contains nsMenuX and nsMenuItemX objects nsTArray mMenuChildren; nsString mLabel; uint32_t mVisibleItemsCount = 0; // cache nsMenuParentX* mParent = nullptr; // [weak] nsMenuGroupOwnerX* mMenuGroupOwner = nullptr; // [weak] nsMenuItemIconX::Listener* mIconListener = nullptr; // [weak] mozilla::UniquePtr mIcon; Observer* mObserver = nullptr; // non-owning pointer to our observer // Non-null between a call to MenuOpened() and MenuOpenedAsync(). RefPtr mPendingAsyncMenuOpenRunnable; // Non-null between a call to MenuClosed() and MenuClosedAsync(). // This is asynchronous so that, if a menu item is clicked, we can fire popuphiding *after* we // execute the menu item command. The macOS menu system calls menuWillClose *before* it calls // menuItemHit. RefPtr mPendingAsyncMenuCloseRunnable; struct PendingCommandEvent { RefPtr mMenuItem; NSEventModifierFlags mModifiers; int16_t mButton; }; // Any pending command events. // These are queued by ActivateItemAfterClosing and run by MenuClosedAsync. nsTArray mPendingCommandEvents; GeckoNSMenu* mNativeMenu = nil; // [strong] MenuDelegate* mMenuDelegate = nil; // [strong] // nsMenuX objects should always have a valid native menu item. NSMenuItem* mNativeMenuItem = nil; // [strong] // Nothing() if no item is highlighted. The index only accounts for visible items. mozilla::Maybe mHighlightedItemIndex; bool mIsEnabled = true; bool mNeedsRebuild = true; // Whether the native NSMenu is considered open. // Also affected by MenuOpened() / MenuClosed() calls for simulated opening / closing. bool mIsOpen = false; // Whether the popup is open from Gecko's perspective, based on popupshowing / popuphiding events. bool mIsOpenForGecko = false; bool mVisible = true; // true between an OnOpen() call that returned true, and the subsequent call // to MenuOpened(). bool mDidFirePopupshowingAndIsApprovedToOpen = false; }; #endif // nsMenuX_h_