diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp index 68b85e64e617..7fc041b5ac71 100644 --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -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 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"); diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h index e609914ddd73..ce0915f05c00 100644 --- a/layout/xul/nsMenuPopupFrame.h +++ b/layout/xul/nsMenuPopupFrame.h @@ -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 mNativeMenu; + // the content that the popup is anchored to, if any, which may be in a // different document than the popup. nsCOMPtr mAnchorContent; diff --git a/layout/xul/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp index f53c674c0db1..13109f650d41 100644 --- a/layout/xul/nsXULPopupManager.cpp +++ b/layout/xul/nsXULPopupManager.cpp @@ -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 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); diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 80280dc9791f..85748bc9d45c 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -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