mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
c6f003d117
Differential Revision: https://phabricator.services.mozilla.com/D196296
314 lines
12 KiB
Objective-C
314 lines
12 KiB
Objective-C
/* -*- 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_
|