From dfee9ee22b02ab8f18aa98078ac341803b64d00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 15 Mar 2022 08:07:43 +0000 Subject: [PATCH] Bug 1758424 - Fire XUL events on native menus. r=stransky Web extensions etc use these to clean up their menu items, for example. For context menus there's no need to fire popupshowing, it's been done by the popup manager already. Differential Revision: https://phabricator.services.mozilla.com/D140522 --- widget/gtk/GRefPtr.h | 1 + widget/gtk/NativeMenuGtk.cpp | 105 +++++++++++++++++++++++++++-------- widget/gtk/NativeMenuGtk.h | 10 ++-- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/widget/gtk/GRefPtr.h b/widget/gtk/GRefPtr.h index 93009b5bee71..5b6260f3dbee 100644 --- a/widget/gtk/GRefPtr.h +++ b/widget/gtk/GRefPtr.h @@ -26,6 +26,7 @@ struct GObjectRefPtrTraits { GOBJECT_TRAITS(GtkWidget) GOBJECT_TRAITS(GMenu) +GOBJECT_TRAITS(GMenuItem) GOBJECT_TRAITS(GSimpleAction) GOBJECT_TRAITS(GSimpleActionGroup) GOBJECT_TRAITS(GDBusProxy) diff --git a/widget/gtk/NativeMenuGtk.cpp b/widget/gtk/NativeMenuGtk.cpp index dceaec843743..f29dd7b0d3be 100644 --- a/widget/gtk/NativeMenuGtk.cpp +++ b/widget/gtk/NativeMenuGtk.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/XULCommandEvent.h" +#include "mozilla/EventDispatcher.h" #include "nsPresContext.h" #include "nsIWidget.h" #include "nsWindow.h" @@ -58,26 +59,25 @@ struct Actions { RefPtr mGroup; size_t mNextActionIndex = 0; - nsPrintfCString Register(const dom::Element&); + nsPrintfCString Register(const dom::Element&, bool aForSubmenu); void Clear(); }; -void ActivateItem(dom::Element* aElement) { - RefPtr element = aElement; - if (Maybe checked = GetChecked(*element)) { - if (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, +static MOZ_CAN_RUN_SCRIPT void ActivateItem(dom::Element& aElement) { + if (Maybe checked = GetChecked(aElement)) { + if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, nsGkAtoms::_false, eCaseMatters)) { bool newValue = !*checked; if (newValue) { - element->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, u"true"_ns, + aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::checked, u"true"_ns, true); } else { - element->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); + aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); } } } - RefPtr doc = aElement->OwnerDoc(); + RefPtr doc = aElement.OwnerDoc(); RefPtr event = new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr); IgnoredErrorResult rv; event->InitCommandEvent(u"command"_ns, true, true, @@ -88,17 +88,46 @@ void ActivateItem(dom::Element* aElement) { if (MOZ_UNLIKELY(rv.Failed())) { return; } - element->DispatchEvent(*event); + aElement.DispatchEvent(*event); } -void ActivateSignal(GSimpleAction* aAction, GVariant* aParam, - gpointer aUserData) { - ActivateItem(static_cast(aUserData)); +static MOZ_CAN_RUN_SCRIPT void ActivateSignal(GSimpleAction* aAction, + GVariant* aParam, + gpointer aUserData) { + RefPtr element = static_cast(aUserData); + ActivateItem(*element); } -nsPrintfCString Actions::Register(const dom::Element& aMenuItem) { +static MOZ_CAN_RUN_SCRIPT void FireEvent(dom::Element* aTarget, + EventMessage aPopupMessage) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, aPopupMessage, nullptr, WidgetMouseEvent::eReal); + EventDispatcher::Dispatch(aTarget, nullptr, &event, nullptr, &status); +} + +static MOZ_CAN_RUN_SCRIPT void ChangeStateSignal(GSimpleAction* aAction, + GVariant* aParam, + gpointer aUserData) { + // TODO: Fire events when safe. These run at a bad time for now. + static constexpr bool kEnabled = false; + if (!kEnabled) { + return; + } + const bool open = g_variant_get_boolean(aParam); + RefPtr popup = static_cast(aUserData); + if (open) { + FireEvent(popup, eXULPopupShowing); + FireEvent(popup, eXULPopupShown); + } else { + FireEvent(popup, eXULPopupHiding); + FireEvent(popup, eXULPopupHidden); + } +} + +nsPrintfCString Actions::Register(const dom::Element& aMenuItem, + bool aForSubmenu) { nsPrintfCString actionName("item-%zu", mNextActionIndex++); - Maybe paramValue = GetChecked(aMenuItem); + Maybe paramValue = aForSubmenu ? Some(false) : GetChecked(aMenuItem); RefPtr action; if (paramValue) { action = dont_AddRef(g_simple_action_new_stateful( @@ -106,8 +135,13 @@ nsPrintfCString Actions::Register(const dom::Element& aMenuItem) { } else { action = dont_AddRef(g_simple_action_new(actionName.get(), nullptr)); } - g_signal_connect(action, "activate", G_CALLBACK(ActivateSignal), - gpointer(&aMenuItem)); + if (aForSubmenu) { + g_signal_connect(action, "change-state", G_CALLBACK(ChangeStateSignal), + gpointer(&aMenuItem)); + } else { + g_signal_connect(action, "activate", G_CALLBACK(ActivateSignal), + gpointer(&aMenuItem)); + } g_action_map_add_action(G_ACTION_MAP(mGroup.get()), G_ACTION(action.get())); return actionName; } @@ -227,8 +261,10 @@ static void RecomputeModelFor(GMenu* aMenu, Actions& aActions, if (label.IsEmpty()) { child->AsElement()->GetAttr(nsGkAtoms::aria_label, label); } - nsPrintfCString actionName("menu.%s", - aActions.Register(*child->AsElement()).get()); + nsPrintfCString actionName( + "menu.%s", + aActions.Register(*child->AsElement(), /* aForSubmenu = */ false) + .get()); g_menu_append(sectionMenu ? sectionMenu.get() : aMenu, NS_ConvertUTF16toUTF8(label).get(), actionName.get()); continue; @@ -252,9 +288,15 @@ static void RecomputeModelFor(GMenu* aMenu, Actions& aActions, RecomputeModelFor(submenu, aActions, *popup); nsAutoString label; child->AsElement()->GetAttr(nsGkAtoms::label, label); - g_menu_append_submenu(sectionMenu ? sectionMenu.get() : aMenu, - NS_ConvertUTF16toUTF8(label).get(), - G_MENU_MODEL(submenu.get())); + RefPtr submenuItem = dont_AddRef(g_menu_item_new_submenu( + NS_ConvertUTF16toUTF8(label).get(), G_MENU_MODEL(submenu.get()))); + nsPrintfCString actionName( + "menu.%s", + aActions.Register(*popup, /* aForSubmenu = */ true).get()); + g_menu_item_set_attribute_value(submenuItem.get(), "submenu-action", + g_variant_new_string(actionName.get())); + g_menu_append_item(sectionMenu ? sectionMenu.get() : aMenu, + submenuItem.get()); } } } @@ -281,9 +323,16 @@ bool NativeMenuGtk::CanUse() { return StaticPrefs::widget_gtk_native_context_menus() && GetPopupAtRectFn(); } -#define METHOD_SIGNAL(name_) \ - void On##name_##Signal(GtkWidget* widget, gpointer user_data) { \ - return static_cast(user_data)->On##name_(); \ +void NativeMenuGtk::FireEvent(EventMessage aPopupMessage) { + RefPtr target = Element(); + widget::FireEvent(target, aPopupMessage); +} + +#define METHOD_SIGNAL(name_) \ + static MOZ_CAN_RUN_SCRIPT_BOUNDARY void On##name_##Signal( \ + GtkWidget* widget, gpointer user_data) { \ + RefPtr menu = static_cast(user_data); \ + return menu->On##name_(); \ } METHOD_SIGNAL(Unmap); @@ -332,6 +381,9 @@ void NativeMenuGtk::ShowAsContextMenu(nsPresContext* aPc, auto openFn = GetPopupAtRectFn(); openFn(GTK_MENU(mNativeMenu.get()), win, &rect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr); + + RefPtr pin{this}; + FireEvent(eXULPopupShown); } bool NativeMenuGtk::Close() { @@ -343,7 +395,12 @@ bool NativeMenuGtk::Close() { } void NativeMenuGtk::OnUnmap() { + FireEvent(eXULPopupHiding); + mMenuModel->DidHide(); + + FireEvent(eXULPopupHidden); + for (NativeMenu::Observer* observer : mObservers.Clone()) { observer->OnNativeMenuClosed(); } diff --git a/widget/gtk/NativeMenuGtk.h b/widget/gtk/NativeMenuGtk.h index 503a323820e4..900d4f73b374 100644 --- a/widget/gtk/NativeMenuGtk.h +++ b/widget/gtk/NativeMenuGtk.h @@ -8,6 +8,7 @@ #define mozilla_widget_NativeMenuGtk_h #include "mozilla/widget/NativeMenu.h" +#include "mozilla/EventForwards.h" #include "GRefPtr.h" namespace mozilla { @@ -28,7 +29,8 @@ class NativeMenuGtk : public NativeMenu { explicit NativeMenuGtk(dom::Element* aElement); // NativeMenu - void ShowAsContextMenu(nsPresContext*, const CSSIntPoint& aPosition) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY void ShowAsContextMenu( + nsPresContext*, const CSSIntPoint& aPosition) override; bool Close() override; void ActivateItem(dom::Element* aItemElement, Modifiers aModifiers, int16_t aButton, ErrorResult& aRv) override; @@ -42,13 +44,13 @@ class NativeMenuGtk : public NativeMenu { mObservers.RemoveElement(aObserver); } - void Dump(); - - void OnUnmap(); + MOZ_CAN_RUN_SCRIPT void OnUnmap(); protected: virtual ~NativeMenuGtk(); + MOZ_CAN_RUN_SCRIPT void FireEvent(EventMessage); + bool mPoppedUp = false; RefPtr mNativeMenu; RefPtr mMenuModel;