gecko-dev/widget/cocoa/nsMenuItemX.h
Markus Stange 954599ea69 Bug 1748815 - Always run command events outside the context menu event loop. r=bradwerth
This patch aligns the code paths for programmatic menu item activation and
user-initiated menu item activation.

Before this patch, user-initiated menu item activation caused
the command event to fire synchronously from menuItemHit.
After this patch, the command event fires from MenuClosedAsync,
which, if an item was activated, is called asynchronously once
the menu's nested event loop has been exited. (If no item has been
activated, MenuClosedAsync is called *inside* the menu's event
loop so that popuphiding / popuphidden events for submenus don't
get delayed.)

This patch makes three major changes to align the two code paths:

 - menuItemHit now calls ActivateItemAfterClosing. This fixes bug 1748815.
 - NativeMenuMac::ActivateItem (used in automated tests) calls the
   relevant methods in the same order as user-initiated item activation.

This means that what we're testing is now closer to what we're shipping.

This patch also removes the call to runAfterMenuClosed. The runnable
that calls MenuClosedAsync is already guaranteed to run outside the
menu's event loop when a menu item was activated (I'm 99% sure about this):
For user-initiated activations, the macOS code exits the loop immediately
after calling menuItemHit and doesn't give our CFRunLoopSource another
chance to run until the stack is unwound. For test-initiated activations,
we set MOZMenuOpeningCoordinator.needToUnwindForMenuClosing which tells
our native event loop to not run anything until the stack is unwound.

Differential Revision: https://phabricator.services.mozilla.com/D149316
2022-06-23 15:05:39 +00:00

106 lines
3.2 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 nsMenuItemX_h_
#define nsMenuItemX_h_
#include "mozilla/RefPtr.h"
#include "nsISupports.h"
#include "nsMenuGroupOwnerX.h"
#include "nsMenuItemIconX.h"
#include "nsChangeObserver.h"
#include "nsStringFwd.h"
#import <Cocoa/Cocoa.h>
class nsMenuItemIconX;
class nsMenuX;
class nsMenuParentX;
namespace mozilla {
namespace dom {
class Element;
}
} // namespace mozilla
enum {
knsMenuItemNoModifier = 0,
knsMenuItemShiftModifier = (1 << 0),
knsMenuItemAltModifier = (1 << 1),
knsMenuItemControlModifier = (1 << 2),
knsMenuItemCommandModifier = (1 << 3)
};
enum EMenuItemType {
eRegularMenuItemType = 0,
eCheckboxMenuItemType,
eRadioMenuItemType,
eSeparatorMenuItemType
};
// 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 nsMenuItemX final : public nsChangeObserver,
public nsMenuItemIconX::Listener {
public:
nsMenuItemX(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
bool IsVisible() const { return mIsVisible; }
// Unregisters nsMenuX from the nsMenuGroupOwner, and nulls out the group
// owner pointer. 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 DetachFromGroupOwner();
// Nulls out our reference to the parent.
// This is needed because nsMenuX is reference-counted and can outlive its
// parent.
void DetachFromParent() { mMenuParent = nullptr; }
NS_INLINE_DECL_REFCOUNTING(nsMenuItemX)
NS_DECL_CHANGEOBSERVER
// nsMenuItemIconX::Listener
void IconUpdated() override;
// nsMenuItemX
nsresult SetChecked(bool aIsChecked);
EMenuItemType GetMenuItemType();
void DoCommand(NSEventModifierFlags aModifierFlags, int16_t aButton);
nsresult DispatchDOMEvent(const nsString& eventName,
bool* preventDefaultCalled);
void SetupIcon();
nsMenuX* ParentMenu() { return mMenuParent; }
nsIContent* Content() { return mContent; }
NSMenuItem* NativeNSMenuItem() { return mNativeMenuItem; }
void Dump(uint32_t aIndent) const;
protected:
virtual ~nsMenuItemX();
void UncheckRadioSiblings(nsIContent* aCheckedElement);
void SetKeyEquiv();
nsCOMPtr<nsIContent> mContent; // XUL <menuitem> or <menuseparator>
EMenuItemType mType;
// nsMenuItemX objects should always have a valid native menu item.
NSMenuItem* mNativeMenuItem = nil; // [strong]
nsMenuX* mMenuParent = nullptr; // [weak]
nsMenuGroupOwnerX* mMenuGroupOwner = nullptr; // [weak]
RefPtr<mozilla::dom::Element> mCommandElement;
mozilla::UniquePtr<nsMenuItemIconX> mIcon; // always non-null
bool mIsChecked = false;
bool mIsVisible = false;
};
#endif // nsMenuItemX_h_