Bug 1698997 - Make nsXULPopupManager::ShowPopupAtScreen open a native context menu, if preffed on. r=tnikkel

This is macOS only and behind the prefs widget.macos.native-context-menus and
browser.proton.contextmenus.enabled .

The big design question here is: Where do we put the fork in the road? How much
should the existing non-native popup management machinery know about the state
of the native menu? Which parts of the non-native state should a) reflect the
true native state, b) enter a special "handled natively" state, or c) lie?

This patch picks the following approach:

 - The nsMenuPopupFrame of the root menupopup knows about the native menu; it
   keeps it alive in its new mNativeMenu field.
 - If the context menu has submenus, i.e. nested <menupopup> elements, the
   nsMenuPopupFrames for those nested menupopups do not know anything about the
   native menu.
 - The mPopupState of natively-handled nsMenuPopupFrames is ePopupClosed.
 - XULPopupElement::GetState, which queries the frame's mPopupState, also
   returns "closed". This might cause problems in the future.
 - The XUL popup manager's mPopups "menu chain" does not know about any open
   native menus.
 - The rollup widget also does not know about the native popup.

I've chosen to use ePopupClosed for native menus for the following reasons:
 1. While mirroring / updating the state for the root menu would be doable
    without too much trouble, it would be much more annoying to do the same for
    nested menupopups of submenus. So if submenus will be ePopupClosed, it's
    consistent for the root menu to also be ePopupClosed.
 2. nsXULPopupManager has assertions (for example in MayShowPopup) that make
    sure that the menu popup frame's mPopupState is consistent with the XUL
    popup manager's mPopups menu chain. Keeping the two both "closed" is the
    easiest way to achieve consistency.

Unless there are grave concerns with this approach, I suggest we go with it for
now and see what trouble arises.

Differential Revision: https://phabricator.services.mozilla.com/D109183
This commit is contained in:
Markus Stange 2021-03-23 14:38:29 +00:00
parent ed5ee3edd2
commit f92e83a7db
4 changed files with 114 additions and 1 deletions

View File

@ -61,6 +61,9 @@
#ifdef MOZ_WAYLAND
# include "mozilla/WidgetUtilsGtk.h"
#endif /* MOZ_WAYLAND */
#ifdef XP_MACOSX
# include "mozilla/widget/NativeMenuSupport.h"
#endif
#include "X11UndefineNone.h"
@ -69,6 +72,7 @@ using mozilla::dom::Document;
using mozilla::dom::Element;
using mozilla::dom::Event;
using mozilla::dom::KeyboardEvent;
using mozilla::widget::NativeMenu;
int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
@ -129,6 +133,12 @@ nsMenuPopupFrame::nsMenuPopupFrame(ComputedStyle* aStyle,
Preferences::GetBool("ui.panel.default_level_parent", false);
} // ctor
nsMenuPopupFrame::~nsMenuPopupFrame() {
if (mNativeMenu) {
mNativeMenu->RemoveObserver(this);
}
}
void nsMenuPopupFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) {
nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
@ -903,6 +913,40 @@ void nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
mPositionedOffset = 0;
}
bool nsMenuPopupFrame::InitializePopupAsNativeContextMenu(
nsIContent* aTriggerContent, int32_t aXPos, int32_t aYPos) {
RefPtr<NativeMenu> menu;
#ifdef XP_MACOSX
if (mContent->IsElement()) {
menu = mozilla::widget::NativeMenuSupport::CreateNativeContextMenu(
mContent->AsElement());
}
#endif
if (!menu) {
return false;
}
mTriggerContent = aTriggerContent;
mNativeMenu = menu;
mPopupState = ePopupClosed; // Treat native popups as closed.
mAnchorContent = nullptr;
mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
mXPos = 0;
mYPos = 0;
mFlip = FlipType_Default;
mPopupAnchor = POPUPALIGNMENT_NONE;
mPopupAlignment = POPUPALIGNMENT_NONE;
mPosition = POPUPPOSITION_UNKNOWN;
mIsContextMenu = true;
mAdjustOffsetForContextMenu = true;
mAnchorType = MenuPopupAnchorType_Point;
mPositionedOffset = 0;
mNativeMenu->AddObserver(this);
return true;
}
void nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
const nsAString& aPosition,
const nsIntRect& aRect,
@ -912,6 +956,17 @@ void nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
mScreenRect = aRect;
}
void nsMenuPopupFrame::ShowNativeMenu() {
MOZ_RELEASE_ASSERT(mNativeMenu);
bool succeeded = mNativeMenu->ShowAsContextMenu(
DesktopPoint::FromUnknownPoint(mScreenRect.TopLeft()));
if (!succeeded) {
// preventDefault() was called on the popupshowing event.
mNativeMenu->RemoveObserver(this);
mNativeMenu = nullptr;
}
}
void nsMenuPopupFrame::ShowPopup(bool aIsContextMenu) {
mIsContextMenu = aIsContextMenu;
@ -953,6 +1008,18 @@ void nsMenuPopupFrame::ShowPopup(bool aIsContextMenu) {
mShouldAutoPosition = true;
}
void nsMenuPopupFrame::OnNativeMenuClosed() {
if (!mNativeMenu) {
return;
}
// The native menu has closed.
// Null out mNativeMenu so that we don't keep it (and mContent) alive
// unnecessarily, and unregister ourselves first.
mNativeMenu->RemoveObserver(this);
mNativeMenu = nullptr;
}
void nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState) {
NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
"popup being set to unexpected state");

View File

@ -14,6 +14,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/widget/NativeMenu.h"
#include "nsAtom.h"
#include "nsGkAtoms.h"
#include "nsCOMPtr.h"
@ -167,12 +168,14 @@ class nsXULPopupShownEvent final : public mozilla::Runnable,
class nsMenuPopupFrame final : public nsBoxFrame,
public nsMenuParent,
public nsIReflowCallback {
public nsIReflowCallback,
public mozilla::widget::NativeMenu::Observer {
public:
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
explicit nsMenuPopupFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
~nsMenuPopupFrame();
// nsMenuParent interface
virtual nsMenuFrame* GetCurrentMenuItem() override;
@ -317,6 +320,16 @@ class nsMenuPopupFrame final : public nsBoxFrame,
void InitializePopupAtScreen(nsIContent* aTriggerContent, int32_t aXPos,
int32_t aYPos, bool aIsContextMenu);
// Called if this popup should be displayed as an OS-native context menu.
// This is achieved with the help of mNativeMenu.
// Returns whether the native context menu was created successfully.
bool InitializePopupAsNativeContextMenu(nsIContent* aTriggerContent,
int32_t aXPos, int32_t aYPos);
// Must only be called after a call to InitializePopupAsNativeContextMenu
// that returned true.
void ShowNativeMenu();
// indicate that the popup should be opened
void ShowPopup(bool aIsContextMenu);
// indicate that the popup should be hidden. The new state should either be
@ -443,6 +456,10 @@ class nsMenuPopupFrame final : public nsBoxFrame,
virtual bool ReflowFinished() override;
virtual void ReflowCallbackCanceled() override;
// NativeMenu::Observer
void OnNativeMenuOpened() override {}
void OnNativeMenuClosed() override;
protected:
// returns the popup's level.
nsPopupLevel PopupLevel(bool aIsNoAutoHide) const;
@ -561,6 +578,11 @@ class nsMenuPopupFrame final : public nsBoxFrame,
protected:
nsString mIncrementalString; // for incremental typing navigation
// If this popup is displayed as a native menu, this is non-null while the
// native menu is open.
// mNativeMenu has a strong reference to the menupopup nsIContent.
RefPtr<mozilla::widget::NativeMenu> mNativeMenu;
// the content that the popup is anchored to, if any, which may be in a
// different document than the popup.
nsCOMPtr<nsIContent> mAnchorContent;

View File

@ -47,6 +47,7 @@
#include "mozilla/PresShell.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/StaticPrefs_xul.h"
#include "mozilla/widget/nsAutoRollup.h"
@ -708,6 +709,15 @@ void nsXULPopupManager::ShowPopup(nsIContent* aPopup,
aTriggerEvent);
}
static bool ShouldUseNativeContextMenus() {
#ifdef XP_MACOSX
return StaticPrefs::widget_macos_native_context_menus() &&
Preferences::GetBool("browser.proton.contextmenus.enabled", false);
#else
return false;
#endif
}
void nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, int32_t aXPos,
int32_t aYPos, bool aIsContextMenu,
Event* aTriggerEvent) {
@ -717,6 +727,15 @@ void nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, int32_t aXPos,
nsCOMPtr<nsIContent> triggerContent;
InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
if (aIsContextMenu && ShouldUseNativeContextMenus()) {
bool haveNativeMenu = popupFrame->InitializePopupAsNativeContextMenu(
triggerContent, aXPos, aYPos);
if (haveNativeMenu) {
popupFrame->ShowNativeMenu();
return;
}
}
popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos,
aIsContextMenu);
FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);

View File

@ -10829,6 +10829,11 @@
mirror: always
#ifdef XP_MACOSX
- name: widget.macos.native-context-menus
type: RelaxedAtomicBool
value: false
mirror: always
- name: widget.macos.respect-system-appearance
type: RelaxedAtomicBool
value: false