summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsMenuX.h
diff options
context:
space:
mode:
Diffstat (limited to 'widget/cocoa/nsMenuX.h')
-rw-r--r--widget/cocoa/nsMenuX.h313
1 files changed, 313 insertions, 0 deletions
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
new file mode 100644
index 0000000000..cdc5884a99
--- /dev/null
+++ b/widget/cocoa/nsMenuX.h
@@ -0,0 +1,313 @@
+/* -*- 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;
+@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,
+ bool aShowServices);
+ 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_