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;