diff options
Diffstat (limited to 'widget/cocoa/nsMenuX.h')
-rw-r--r-- | widget/cocoa/nsMenuX.h | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h new file mode 100644 index 0000000000..ff78196ea7 --- /dev/null +++ b/widget/cocoa/nsMenuX.h @@ -0,0 +1,288 @@ +/* -*- 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 <Cocoa/Cocoa.h> + +#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 <NSMenuDelegate> { + 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<MenuChild> GetItemAt(uint32_t aPos); + uint32_t GetItemCount(); + + mozilla::Maybe<MenuChild> GetVisibleItemAt(uint32_t aPos); + nsresult GetVisibleItemCount(uint32_t& aCount); + + mozilla::Maybe<MenuChild> GetItemForElement(mozilla::dom::Element* aMenuChildElement); + + // Asynchronously runs the command event on aItem, after the root menu has closed. + void ActivateItemAfterClosing(RefPtr<nsMenuItemX>&& 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<uint32_t>& 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<nsIContent> 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<MenuChild> CreateMenuChild(nsIContent* aContent); + RefPtr<nsMenuItemX> 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 <menupopup>. + 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<nsIContent> mContent; // XUL <menu> or <menupopup> + + // Contains nsMenuX and nsMenuItemX objects + nsTArray<MenuChild> mMenuChildren; + + nsString mLabel; + uint32_t mVisibleItemsCount = 0; // cache + nsMenuParentX* mParent = nullptr; // [weak] + nsMenuGroupOwnerX* mMenuGroupOwner = nullptr; // [weak] + nsMenuItemIconX::Listener* mIconListener = nullptr; // [weak] + mozilla::UniquePtr<nsMenuItemIconX> mIcon; + + Observer* mObserver = nullptr; // non-owning pointer to our observer + + // Non-null between a call to MenuOpened() and MenuOpenedAsync(). + RefPtr<mozilla::CancelableRunnable> 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<mozilla::CancelableRunnable> mPendingAsyncMenuCloseRunnable; + + struct PendingCommandEvent { + RefPtr<nsMenuItemX> mMenuItem; + NSEventModifierFlags mModifiers; + int16_t mButton; + }; + + // Any pending command events. + // These are queued by ActivateItemAfterClosing and run by MenuClosedAsync. + nsTArray<PendingCommandEvent> 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<uint32_t> 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_ |