summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
authorLootyhoof <lootyhoofer@gmail.com>2020-06-06 00:37:29 +0100
committerwolfbeast <mcwerewolf@wolfbeast.com>2020-06-13 11:49:36 +0200
commit6235023c8bff80273981c70a06cb9decb4a4ffa5 (patch)
tree9673404b3735e8a8b6bd3e890c3d6b5d2d34d76c /widget
parentf51beb3fdab3d586af081ac14478ea59e0523af4 (diff)
downloadUXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.gz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.lz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.xz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.zip
Issue MoonchildProductions/UXP#1578 - Add global menubar support for GTK
Diffstat (limited to 'widget')
-rw-r--r--widget/gtk/moz.build11
-rw-r--r--widget/gtk/nsDbusmenu.cpp59
-rw-r--r--widget/gtk/nsDbusmenu.h97
-rw-r--r--widget/gtk/nsMenu.cpp800
-rw-r--r--widget/gtk/nsMenu.h120
-rw-r--r--widget/gtk/nsMenuBar.cpp541
-rw-r--r--widget/gtk/nsMenuBar.h103
-rw-r--r--widget/gtk/nsMenuContainer.cpp156
-rw-r--r--widget/gtk/nsMenuContainer.h66
-rw-r--r--widget/gtk/nsMenuItem.cpp712
-rw-r--r--widget/gtk/nsMenuItem.h77
-rw-r--r--widget/gtk/nsMenuObject.cpp634
-rw-r--r--widget/gtk/nsMenuObject.h165
-rw-r--r--widget/gtk/nsMenuSeparator.cpp74
-rw-r--r--widget/gtk/nsMenuSeparator.h33
-rw-r--r--widget/gtk/nsNativeMenuAtomList.h9
-rw-r--r--widget/gtk/nsNativeMenuAtoms.cpp35
-rw-r--r--widget/gtk/nsNativeMenuAtoms.h23
-rw-r--r--widget/gtk/nsNativeMenuDocListener.cpp329
-rw-r--r--widget/gtk/nsNativeMenuDocListener.h146
-rw-r--r--widget/gtk/nsNativeMenuService.cpp485
-rw-r--r--widget/gtk/nsNativeMenuService.h80
-rw-r--r--widget/gtk/nsWidgetFactory.cpp8
-rw-r--r--widget/gtk/nsWindow.cpp6
-rw-r--r--widget/gtk/nsWindow.h6
-rw-r--r--widget/moz.build4
26 files changed, 4778 insertions, 1 deletions
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
index baccb6ccd..8d621c0a0 100644
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -24,10 +24,18 @@ UNIFIED_SOURCES += [
'nsAppShell.cpp',
'nsBidiKeyboard.cpp',
'nsColorPicker.cpp',
+ 'nsDbusmenu.cpp',
'nsFilePicker.cpp',
'nsGtkKeyUtils.cpp',
'nsImageToPixbuf.cpp',
'nsLookAndFeel.cpp',
+ 'nsMenuBar.cpp',
+ 'nsMenuContainer.cpp',
+ 'nsMenuItem.cpp',
+ 'nsMenuObject.cpp',
+ 'nsMenuSeparator.cpp',
+ 'nsNativeMenuAtoms.cpp',
+ 'nsNativeMenuDocListener.cpp',
'nsNativeThemeGTK.cpp',
'nsScreenGtk.cpp',
'nsScreenManagerGtk.cpp',
@@ -40,6 +48,8 @@ UNIFIED_SOURCES += [
]
SOURCES += [
+ 'nsMenu.cpp', # conflicts with X11 headers
+ 'nsNativeMenuService.cpp',
'nsWindow.cpp', # conflicts with X11 headers
]
@@ -104,6 +114,7 @@ FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/layout/generic',
+ '/layout/style',
'/layout/xul',
'/other-licenses/atk-1.0',
'/widget',
diff --git a/widget/gtk/nsDbusmenu.cpp b/widget/gtk/nsDbusmenu.cpp
new file mode 100644
index 000000000..2849536e9
--- /dev/null
+++ b/widget/gtk/nsDbusmenu.cpp
@@ -0,0 +1,59 @@
+/* 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/. */
+
+#include "nsDbusmenu.h"
+#include "prlink.h"
+#include "mozilla/ArrayUtils.h"
+
+#define FUNC(name, type, params) \
+nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name;
+DBUSMENU_GLIB_FUNCTIONS
+DBUSMENU_GTK_FUNCTIONS
+#undef FUNC
+
+static PRLibrary *gDbusmenuGlib = nullptr;
+static PRLibrary *gDbusmenuGtk = nullptr;
+
+typedef void (*nsDbusmenuFunc)();
+struct nsDbusmenuDynamicFunction {
+ const char *functionName;
+ nsDbusmenuFunc *function;
+};
+
+/* static */ nsresult
+nsDbusmenuFunctions::Init() {
+#define FUNC(name, type, params) \
+ { #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name },
+ static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = {
+ DBUSMENU_GLIB_FUNCTIONS
+ };
+ static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = {
+ DBUSMENU_GTK_FUNCTIONS
+ };
+
+#define LOAD_LIBRARY(symbol, name) \
+ if (!g##symbol) { \
+ g##symbol = PR_LoadLibrary(name); \
+ if (!g##symbol) { \
+ return NS_ERROR_FAILURE; \
+ } \
+ } \
+ for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \
+ *k##symbol##Symbols[i].function = \
+ PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \
+ if (!*k##symbol##Symbols[i].function) { \
+ return NS_ERROR_FAILURE; \
+ } \
+ }
+
+ LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4")
+#if (MOZ_WIDGET_GTK == 3)
+ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4")
+#else
+ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk.so.4")
+#endif
+#undef LOAD_LIBRARY
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsDbusmenu.h b/widget/gtk/nsDbusmenu.h
new file mode 100644
index 000000000..c0d9e7979
--- /dev/null
+++ b/widget/gtk/nsDbusmenu.h
@@ -0,0 +1,97 @@
+/* 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 __nsDbusmenu_h__
+#define __nsDbusmenu_h__
+
+#include "nsError.h"
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+#define DBUSMENU_GLIB_FUNCTIONS \
+ FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child, guint position)) \
+ FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \
+ FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \
+ FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem* mi)) \
+ FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \
+ FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem* mi, const gchar* property)) \
+ FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property)) \
+ FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem* mi, const gchar* property)) \
+ FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gchar* value)) \
+ FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gboolean value)) \
+ FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gint value)) \
+ FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem* mi, guint timestamp)) \
+ FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem* mi)) \
+ FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar* object)) \
+ FUNC(dbusmenu_server_set_root, void, (DbusmenuServer* server, DbusmenuMenuitem* root)) \
+ FUNC(dbusmenu_server_set_status, void, (DbusmenuServer* server, DbusmenuStatus status))
+
+#define DBUSMENU_GTK_FUNCTIONS \
+ FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem* menuitem, const gchar* property, const GdkPixbuf* data)) \
+ FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem* menuitem, guint key, GdkModifierType modifier))
+
+typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
+typedef struct _DbusmenuServer DbusmenuServer;
+
+enum DbusmenuStatus {
+ DBUSMENU_STATUS_NORMAL,
+ DBUSMENU_STATUS_NOTICE
+};
+
+#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu"
+#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
+#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled"
+#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data"
+#define DBUSMENU_MENUITEM_PROP_LABEL "label"
+#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
+#define DBUSMENU_MENUITEM_PROP_TYPE "type"
+#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state"
+#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type"
+#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
+#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show"
+#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event"
+#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
+#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
+#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
+#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1
+#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0
+#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object"
+
+class nsDbusmenuFunctions {
+public:
+ nsDbusmenuFunctions() = delete;
+
+ static nsresult Init();
+
+#define FUNC(name, type, params) \
+ typedef type (*_##name##_fn) params; \
+ static _##name##_fn s_##name;
+ DBUSMENU_GLIB_FUNCTIONS
+ DBUSMENU_GTK_FUNCTIONS
+#undef FUNC
+
+};
+
+#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position
+#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append
+#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete
+#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children
+#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new
+#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get
+#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool
+#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove
+#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set
+#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool
+#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int
+#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user
+#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children
+#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new
+#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root
+#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status
+
+#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image
+#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut
+
+#endif /* __nsDbusmenu_h__ */
diff --git a/widget/gtk/nsMenu.cpp b/widget/gtk/nsMenu.cpp
new file mode 100644
index 000000000..073a4acf6
--- /dev/null
+++ b/widget/gtk/nsMenu.cpp
@@ -0,0 +1,800 @@
+/* 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/. */
+
+#define _IMPL_NS_LAYOUT
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Move.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsAutoPtr.h"
+#include "nsBindingManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSSValue.h"
+#include "nsGkAtoms.h"
+#include "nsGtkUtils.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsIRunnable.h"
+#include "nsITimer.h"
+#include "nsString.h"
+#include "nsStyleContext.h"
+#include "nsStyleSet.h"
+#include "nsStyleStruct.h"
+#include "nsThreadUtils.h"
+#include "nsXBLBinding.h"
+#include "nsXBLService.h"
+
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuDocListener.h"
+
+#include <glib-object.h>
+
+#include "nsMenu.h"
+
+using namespace mozilla;
+
+class nsMenuContentInsertedEvent : public Runnable {
+public:
+ nsMenuContentInsertedEvent(nsMenu* aMenu,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) :
+ mWeakMenu(aMenu),
+ mContainer(aContainer),
+ mChild(aChild),
+ mPrevSibling(aPrevSibling) { }
+
+ NS_IMETHODIMP Run() {
+ if (!mWeakMenu) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenu *>(mWeakMenu.get())->HandleContentInserted(mContainer,
+ mChild,
+ mPrevSibling);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenu;
+
+ nsCOMPtr<nsIContent> mContainer;
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> mPrevSibling;
+};
+
+class nsMenuContentRemovedEvent : public Runnable {
+public:
+ nsMenuContentRemovedEvent(nsMenu* aMenu,
+ nsIContent* aContainer,
+ nsIContent* aChild) :
+ mWeakMenu(aMenu),
+ mContainer(aContainer),
+ mChild(aChild) { }
+
+ NS_IMETHODIMP Run() {
+ if (!mWeakMenu) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenu *>(mWeakMenu.get())->HandleContentRemoved(mContainer,
+ mChild);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenu;
+
+ nsCOMPtr<nsIContent> mContainer;
+ nsCOMPtr<nsIContent> mChild;
+};
+
+static void
+DispatchMouseEvent(nsIContent* aTarget, mozilla::EventMessage aMsg) {
+ if (!aTarget) {
+ return;
+ }
+
+ WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal);
+ aTarget->DispatchDOMEvent(&event, nullptr, nullptr, nullptr);
+}
+
+static void
+AttachXBLBindings(nsIContent* aContent) {
+ nsIDocument* doc = aContent->OwnerDoc();
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return;
+ }
+
+ RefPtr<nsStyleContext> sc =
+ shell->StyleSet()->AsGecko()->ResolveStyleFor(aContent->AsElement(),
+ nullptr);
+ if (!sc) {
+ return;
+ }
+
+ const nsStyleDisplay* display = sc->StyleDisplay();
+ if (!display->mBinding) {
+ return;
+ }
+
+ nsXBLService* xbl = nsXBLService::GetInstance();
+ if (!xbl) {
+ return;
+ }
+
+ RefPtr<nsXBLBinding> binding;
+ bool dummy;
+ nsresult rv = xbl->LoadBindings(aContent, display->mBinding->GetURI(),
+ display->mBinding->mOriginPrincipal,
+ getter_AddRefs(binding), &dummy);
+ if ((NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED) || !binding) {
+ return;
+ }
+
+ doc->BindingManager()->AddToAttachedQueue(binding);
+}
+
+void
+nsMenu::SetPopupState(EPopupState aState) {
+ mPopupState = aState;
+
+ if (!mPopupContent) {
+ return;
+ }
+
+ nsAutoString state;
+ switch (aState) {
+ case ePopupState_Showing:
+ state.Assign(NS_LITERAL_STRING("showing"));
+ break;
+ case ePopupState_Open:
+ state.Assign(NS_LITERAL_STRING("open"));
+ break;
+ case ePopupState_Hiding:
+ state.Assign(NS_LITERAL_STRING("hiding"));
+ break;
+ default:
+ break;
+ }
+
+ if (state.IsEmpty()) {
+ mPopupContent->UnsetAttr(kNameSpaceID_None,
+ nsNativeMenuAtoms::_moz_nativemenupopupstate,
+ false);
+ } else {
+ mPopupContent->SetAttr(kNameSpaceID_None,
+ nsNativeMenuAtoms::_moz_nativemenupopupstate,
+ state, false);
+ }
+}
+
+/* static */ void
+nsMenu::DoOpenCallback(nsITimer* aTimer, void* aClosure) {
+ nsMenu* self = static_cast<nsMenu *>(aClosure);
+
+ dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0);
+
+ self->mOpenDelayTimer = nullptr;
+}
+
+/* static */ void
+nsMenu::menu_event_cb(DbusmenuMenuitem* menu,
+ const gchar* name,
+ GVariant* value,
+ guint timestamp,
+ gpointer user_data) {
+ nsMenu* self = static_cast<nsMenu *>(user_data);
+
+ nsAutoCString event(name);
+
+ if (event.Equals(NS_LITERAL_CSTRING("closed"))) {
+ self->OnClose();
+ return;
+ }
+
+ if (event.Equals(NS_LITERAL_CSTRING("opened"))) {
+ self->OnOpen();
+ return;
+ }
+}
+
+void
+nsMenu::MaybeAddPlaceholderItem() {
+ MOZ_ASSERT(!IsInBatchedUpdate(),
+ "Shouldn't be modifying the native menu structure now");
+
+ GList* children = dbusmenu_menuitem_get_children(GetNativeData());
+ if (!children) {
+ MOZ_ASSERT(!mPlaceholderItem);
+
+ mPlaceholderItem = dbusmenu_menuitem_new();
+ if (!mPlaceholderItem) {
+ return;
+ }
+
+ dbusmenu_menuitem_property_set_bool(mPlaceholderItem,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ false);
+
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem));
+ }
+}
+
+void
+nsMenu::EnsureNoPlaceholderItem() {
+ MOZ_ASSERT(!IsInBatchedUpdate(),
+ "Shouldn't be modifying the native menu structure now");
+
+ if (!mPlaceholderItem) {
+ return;
+ }
+
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem));
+ MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData()));
+
+ g_object_unref(mPlaceholderItem);
+ mPlaceholderItem = nullptr;
+}
+
+void
+nsMenu::OnOpen() {
+ if (mNeedsRebuild) {
+ Build();
+ }
+
+ nsWeakMenuObject self(this);
+ nsCOMPtr<nsIContent> origPopupContent(mPopupContent);
+ {
+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
+
+ SetPopupState(ePopupState_Showing);
+ DispatchMouseEvent(mPopupContent, eXULPopupShowing);
+
+ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
+ NS_LITERAL_STRING("true"), true);
+ }
+
+ if (!self) {
+ // We were deleted!
+ return;
+ }
+
+ // I guess that the popup could have changed
+ if (origPopupContent != mPopupContent) {
+ return;
+ }
+
+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
+
+ size_t count = ChildCount();
+ for (size_t i = 0; i < count; ++i) {
+ ChildAt(i)->ContainerIsOpening();
+ }
+
+ SetPopupState(ePopupState_Open);
+ DispatchMouseEvent(mPopupContent, eXULPopupShown);
+}
+
+void
+nsMenu::Build() {
+ mNeedsRebuild = false;
+
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+
+ InitializePopup();
+
+ if (!mPopupContent) {
+ return;
+ }
+
+ uint32_t count = mPopupContent->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* childContent = mPopupContent->GetChildAt(i);
+
+ UniquePtr<nsMenuObject> child = CreateChild(childContent);
+
+ if (!child) {
+ continue;
+ }
+
+ AppendChild(Move(child));
+ }
+}
+
+void
+nsMenu::InitializePopup() {
+ nsCOMPtr<nsIContent> oldPopupContent;
+ oldPopupContent.swap(mPopupContent);
+
+ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
+ nsIContent* child = ContentNode()->GetChildAt(i);
+
+ int32_t dummy;
+ nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ mPopupContent = child;
+ break;
+ }
+ }
+
+ if (oldPopupContent == mPopupContent) {
+ return;
+ }
+
+ // The popup has changed
+
+ if (oldPopupContent) {
+ DocListener()->UnregisterForContentChanges(oldPopupContent);
+ }
+
+ SetPopupState(ePopupState_Closed);
+
+ if (!mPopupContent) {
+ return;
+ }
+
+ AttachXBLBindings(mPopupContent);
+
+ DocListener()->RegisterForContentChanges(mPopupContent, this);
+}
+
+void
+nsMenu::RemoveChildAt(size_t aIndex) {
+ MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem,
+ "Shouldn't have a placeholder menuitem");
+
+ nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate());
+ StructureMutated();
+
+ if (!IsInBatchedUpdate()) {
+ MaybeAddPlaceholderItem();
+ }
+}
+
+void
+nsMenu::RemoveChild(nsIContent* aChild) {
+ size_t index = IndexOf(aChild);
+ if (index == NoIndex) {
+ return;
+ }
+
+ RemoveChildAt(index);
+}
+
+void
+nsMenu::InsertChildAfter(UniquePtr<nsMenuObject> aChild,
+ nsIContent* aPrevSibling) {
+ if (!IsInBatchedUpdate()) {
+ EnsureNoPlaceholderItem();
+ }
+
+ nsMenuContainer::InsertChildAfter(Move(aChild), aPrevSibling,
+ !IsInBatchedUpdate());
+ StructureMutated();
+}
+
+void
+nsMenu::AppendChild(UniquePtr<nsMenuObject> aChild) {
+ if (!IsInBatchedUpdate()) {
+ EnsureNoPlaceholderItem();
+ }
+
+ nsMenuContainer::AppendChild(Move(aChild), !IsInBatchedUpdate());
+ StructureMutated();
+}
+
+bool
+nsMenu::IsInBatchedUpdate() const {
+ return mBatchedUpdateState != eBatchedUpdateState_Inactive;
+}
+
+void
+nsMenu::StructureMutated() {
+ if (!IsInBatchedUpdate()) {
+ return;
+ }
+
+ mBatchedUpdateState = eBatchedUpdateState_DidMutate;
+}
+
+bool
+nsMenu::CanOpen() const {
+ bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_VISIBLE);
+ bool isDisabled = ContentNode()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::disabled,
+ nsGkAtoms::_true,
+ eCaseMatters);
+
+ return (isVisible && !isDisabled);
+}
+
+void
+nsMenu::HandleContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) {
+ if (aContainer == mPopupContent) {
+ UniquePtr<nsMenuObject> child = CreateChild(aChild);
+
+ if (child) {
+ InsertChildAfter(Move(child), aPrevSibling);
+ }
+ } else {
+ Build();
+ }
+}
+
+void
+nsMenu::HandleContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
+ if (aContainer == mPopupContent) {
+ RemoveChild(aChild);
+ } else {
+ Build();
+ }
+}
+
+void
+nsMenu::InitializeNativeData() {
+ // Dbusmenu provides an "about-to-show" signal, and also "opened" and
+ // "closed" events. However, Unity is the only thing that sends
+ // both "about-to-show" and "opened" events. Unity 2D and the HUD only
+ // send "opened" events, so we ignore "about-to-show" (I don't think
+ // there's any real difference between them anyway).
+ // To complicate things, there are certain conditions where we don't
+ // get a "closed" event, so we need to be able to handle this :/
+ g_signal_connect(G_OBJECT(GetNativeData()), "event",
+ G_CALLBACK(menu_event_cb), this);
+
+ mNeedsRebuild = true;
+ mNeedsUpdate = true;
+
+ MaybeAddPlaceholderItem();
+
+ AttachXBLBindings(ContentNode());
+}
+
+void
+nsMenu::Update(nsStyleContext* aStyleContext) {
+ if (mNeedsUpdate) {
+ mNeedsUpdate = false;
+
+ UpdateLabel();
+ UpdateSensitivity();
+ }
+
+ UpdateVisibility(aStyleContext);
+ UpdateIcon(aStyleContext);
+}
+
+nsMenuObject::PropertyFlags
+nsMenu::SupportedProperties() const {
+ return static_cast<nsMenuObject::PropertyFlags>(
+ nsMenuObject::ePropLabel |
+ nsMenuObject::ePropEnabled |
+ nsMenuObject::ePropVisible |
+ nsMenuObject::ePropIconData |
+ nsMenuObject::ePropChildDisplay
+ );
+}
+
+void
+nsMenu::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {
+ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
+ "Received an event that wasn't meant for us!");
+
+ if (mNeedsUpdate) {
+ return;
+ }
+
+ if (aContent != ContentNode()) {
+ return;
+ }
+
+ if (!Parent()->IsBeingDisplayed()) {
+ mNeedsUpdate = true;
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::disabled) {
+ UpdateSensitivity();
+ } else if (aAttribute == nsGkAtoms::label ||
+ aAttribute == nsGkAtoms::accesskey ||
+ aAttribute == nsGkAtoms::crop) {
+ UpdateLabel();
+ } else if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed) {
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ UpdateVisibility(sc);
+ } else if (aAttribute == nsGkAtoms::image) {
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ UpdateIcon(sc);
+ }
+}
+
+void
+nsMenu::OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPrevSibling) {
+ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
+ "Received an event that wasn't meant for us!");
+
+ if (mNeedsRebuild) {
+ return;
+ }
+
+ if (mPopupState == ePopupState_Closed) {
+ mNeedsRebuild = true;
+ return;
+ }
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuContentInsertedEvent(this, aContainer, aChild,
+ aPrevSibling));
+}
+
+void
+nsMenu::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
+ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
+ "Received an event that wasn't meant for us!");
+
+ if (mNeedsRebuild) {
+ return;
+ }
+
+ if (mPopupState == ePopupState_Closed) {
+ mNeedsRebuild = true;
+ return;
+ }
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuContentRemovedEvent(this, aContainer, aChild));
+}
+
+/*
+ * Some menus (eg, the History menu in Firefox) refresh themselves on
+ * opening by removing all children and then re-adding new ones. As this
+ * happens whilst the menu is opening in Unity, it causes some flickering
+ * as the menu popup is resized multiple times. To avoid this, we try to
+ * reuse native menu items when the menu structure changes during a
+ * batched update. If we can handle menu structure changes from Goanna
+ * just by updating properties of native menu items (rather than destroying
+ * and creating new ones), then we eliminate any flickering that occurs as
+ * the menu is opened. To do this, we don't modify any native menu items
+ * until the end of the update batch.
+ */
+
+void
+nsMenu::OnBeginUpdates(nsIContent* aContent) {
+ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
+ "Received an event that wasn't meant for us!");
+ MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!");
+
+ if (aContent != mPopupContent) {
+ return;
+ }
+
+ mBatchedUpdateState = eBatchedUpdateState_Active;
+}
+
+void
+nsMenu::OnEndUpdates() {
+ if (!IsInBatchedUpdate()) {
+ return;
+ }
+
+ bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate;
+ mBatchedUpdateState = eBatchedUpdateState_Inactive;
+
+ /* Optimize for the case where we only had attribute changes */
+ if (!didMutate) {
+ return;
+ }
+
+ EnsureNoPlaceholderItem();
+
+ GList* nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData());
+ DbusmenuMenuitem* nextOwnedNativeChild = nullptr;
+
+ size_t count = ChildCount();
+
+ // Find the first native menu item that is `owned` by a corresponding
+ // Goanna menuitem
+ for (size_t i = 0; i < count; ++i) {
+ if (ChildAt(i)->GetNativeData()) {
+ nextOwnedNativeChild = ChildAt(i)->GetNativeData();
+ break;
+ }
+ }
+
+ // Now iterate over all Goanna menuitems
+ for (size_t i = 0; i < count; ++i) {
+ nsMenuObject* child = ChildAt(i);
+
+ if (child->GetNativeData()) {
+ // This child already has a corresponding native menuitem.
+ // Remove all preceding orphaned native items. At this point, we
+ // modify the native menu structure.
+ while (nextNativeChild &&
+ nextNativeChild->data != nextOwnedNativeChild) {
+
+ DbusmenuMenuitem* data =
+ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
+ nextNativeChild = nextNativeChild->next;
+
+ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(),
+ data));
+ }
+
+ if (nextNativeChild) {
+ nextNativeChild = nextNativeChild->next;
+ }
+
+ // Now find the next native menu item that is `owned`
+ nextOwnedNativeChild = nullptr;
+ for (size_t j = i + 1; j < count; ++j) {
+ if (ChildAt(j)->GetNativeData()) {
+ nextOwnedNativeChild = ChildAt(j)->GetNativeData();
+ break;
+ }
+ }
+ } else {
+ // This child is new, and doesn't have a native menu item. Find one!
+ if (nextNativeChild &&
+ nextNativeChild->data != nextOwnedNativeChild) {
+
+ DbusmenuMenuitem* data =
+ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
+
+ if (NS_SUCCEEDED(child->AdoptNativeData(data))) {
+ nextNativeChild = nextNativeChild->next;
+ }
+ }
+
+ // There wasn't a suitable one available, so create a new one.
+ // At this point, we modify the native menu structure.
+ if (!child->GetNativeData()) {
+ child->CreateNativeData();
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_add_position(GetNativeData(),
+ child->GetNativeData(),
+ i));
+ }
+ }
+ }
+
+ while (nextNativeChild) {
+ DbusmenuMenuitem* data =
+ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
+ nextNativeChild = nextNativeChild->next;
+
+ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data));
+ }
+
+ MaybeAddPlaceholderItem();
+}
+
+nsMenu::nsMenu(nsMenuContainer* aParent, nsIContent* aContent) :
+ nsMenuContainer(aParent, aContent),
+ mNeedsRebuild(false),
+ mNeedsUpdate(false),
+ mPlaceholderItem(nullptr),
+ mPopupState(ePopupState_Closed),
+ mBatchedUpdateState(eBatchedUpdateState_Inactive) {
+ MOZ_COUNT_CTOR(nsMenu);
+}
+
+nsMenu::~nsMenu() {
+ if (IsInBatchedUpdate()) {
+ OnEndUpdates();
+ }
+
+ // Although nsTArray will take care of this in its destructor,
+ // we have to manually ensure children are removed from our native menu
+ // item, just in case our parent recycles us
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+
+ EnsureNoPlaceholderItem();
+
+ if (DocListener() && mPopupContent) {
+ DocListener()->UnregisterForContentChanges(mPopupContent);
+ }
+
+ if (GetNativeData()) {
+ g_signal_handlers_disconnect_by_func(GetNativeData(),
+ FuncToGpointer(menu_event_cb),
+ this);
+ }
+
+ MOZ_COUNT_DTOR(nsMenu);
+}
+
+nsMenuObject::EType
+nsMenu::Type() const {
+ return eType_Menu;
+}
+
+bool
+nsMenu::IsBeingDisplayed() const {
+ return mPopupState == ePopupState_Open;
+}
+
+bool
+nsMenu::NeedsRebuild() const {
+ return mNeedsRebuild;
+}
+
+void
+nsMenu::OpenMenu() {
+ if (!CanOpen()) {
+ return;
+ }
+
+ if (mOpenDelayTimer) {
+ return;
+ }
+
+ // Here, we synchronously fire popupshowing and popupshown events and then
+ // open the menu after a short delay. This allows the menu to refresh before
+ // it's shown, and avoids an issue where keyboard focus is not on the first
+ // item of the history menu in Firefox when opening it with the keyboard,
+ // because extra items to appear at the top of the menu
+
+ OnOpen();
+
+ mOpenDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mOpenDelayTimer) {
+ return;
+ }
+
+ if (NS_FAILED(mOpenDelayTimer->InitWithFuncCallback(DoOpenCallback,
+ this,
+ 100,
+ nsITimer::TYPE_ONE_SHOT))) {
+ mOpenDelayTimer = nullptr;
+ }
+}
+
+void
+nsMenu::OnClose() {
+ if (mPopupState == ePopupState_Closed) {
+ return;
+ }
+
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ // We do this to avoid mutating our view of the menu until
+ // after we have finished
+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
+
+ SetPopupState(ePopupState_Hiding);
+ DispatchMouseEvent(mPopupContent, eXULPopupHiding);
+
+ // Sigh, make sure all of our descendants are closed, as we don't
+ // always get closed events for submenus when scrubbing quickly through
+ // the menu
+ size_t count = ChildCount();
+ for (size_t i = 0; i < count; ++i) {
+ if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) {
+ static_cast<nsMenu *>(ChildAt(i))->OnClose();
+ }
+ }
+
+ SetPopupState(ePopupState_Closed);
+ DispatchMouseEvent(mPopupContent, eXULPopupHidden);
+
+ ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
+}
diff --git a/widget/gtk/nsMenu.h b/widget/gtk/nsMenu.h
new file mode 100644
index 000000000..a198a8e72
--- /dev/null
+++ b/widget/gtk/nsMenu.h
@@ -0,0 +1,120 @@
+/* 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 __nsMenu_h__
+#define __nsMenu_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenuContainer.h"
+#include "nsMenuObject.h"
+
+#include <glib.h>
+
+class nsIAtom;
+class nsIContent;
+class nsITimer;
+class nsStyleContext;
+
+#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U
+#define NSMENU_NUMBER_OF_FLAGS 4U
+
+// This class represents a menu
+class nsMenu final : public nsMenuContainer {
+public:
+ nsMenu(nsMenuContainer* aParent, nsIContent* aContent);
+ ~nsMenu();
+
+ nsMenuObject::EType Type() const override;
+
+ bool IsBeingDisplayed() const override;
+ bool NeedsRebuild() const override;
+
+ // Tell the desktop shell to display this menu
+ void OpenMenu();
+
+ // Normally called via the shell, but it's public so that child
+ // menuitems can do the shells work. Sigh....
+ void OnClose();
+
+private:
+ friend class nsMenuContentInsertedEvent;
+ friend class nsMenuContentRemovedEvent;
+
+ enum EPopupState {
+ ePopupState_Closed,
+ ePopupState_Showing,
+ ePopupState_Open,
+ ePopupState_Hiding
+ };
+
+ void SetPopupState(EPopupState aState);
+
+ static void DoOpenCallback(nsITimer* aTimer, void* aClosure);
+ static void menu_event_cb(DbusmenuMenuitem* menu,
+ const gchar* name,
+ GVariant* value,
+ guint timestamp,
+ gpointer user_data);
+
+ // We add a placeholder item to empty menus so that Unity actually treats
+ // us as a proper menu, rather than a menuitem without a submenu
+ void MaybeAddPlaceholderItem();
+
+ // Removes a placeholder item if it exists and asserts that this succeeds
+ void EnsureNoPlaceholderItem();
+
+ void OnOpen();
+ void Build();
+ void InitializePopup();
+ void RemoveChildAt(size_t aIndex);
+ void RemoveChild(nsIContent* aChild);
+ void InsertChildAfter(mozilla::UniquePtr<nsMenuObject> aChild,
+ nsIContent* aPrevSibling);
+ void AppendChild(mozilla::UniquePtr<nsMenuObject> aChild);
+ bool IsInBatchedUpdate() const;
+ void StructureMutated();
+ bool CanOpen() const;
+
+ void HandleContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling);
+ void HandleContentRemoved(nsIContent* aContainer,
+ nsIContent* aChild);
+
+ void InitializeNativeData() override;
+ void Update(nsStyleContext* aStyleContext) override;
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
+
+ void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override;
+ void OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPrevSibling) override;
+ void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) override;
+ void OnBeginUpdates(nsIContent* aContent) override;
+ void OnEndUpdates() override;
+
+ bool mNeedsRebuild;
+ bool mNeedsUpdate;
+
+ DbusmenuMenuitem* mPlaceholderItem;
+
+ EPopupState mPopupState;
+
+ enum EBatchedUpdateState {
+ eBatchedUpdateState_Inactive,
+ eBatchedUpdateState_Active,
+ eBatchedUpdateState_DidMutate
+ };
+
+ EBatchedUpdateState mBatchedUpdateState;
+
+ nsCOMPtr<nsIContent> mPopupContent;
+
+ nsCOMPtr<nsITimer> mOpenDelayTimer;
+};
+
+#endif /* __nsMenu_h__ */
diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp
new file mode 100644
index 000000000..e7caf119c
--- /dev/null
+++ b/widget/gtk/nsMenuBar.cpp
@@ -0,0 +1,541 @@
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIRunnable.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+#include "nsMenu.h"
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuService.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "nsMenuBar.h"
+
+using namespace mozilla;
+
+static bool
+ShouldHandleKeyEvent(nsIDOMEvent* aEvent) {
+ bool handled, trusted = false;
+ aEvent->GetPreventDefault(&handled);
+ aEvent->GetIsTrusted(&trusted);
+
+ if (handled || !trusted) {
+ return false;
+ }
+
+ return true;
+}
+
+class nsMenuBarContentInsertedEvent : public Runnable {
+public:
+ nsMenuBarContentInsertedEvent(nsMenuBar* aMenuBar,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) :
+ mWeakMenuBar(aMenuBar),
+ mChild(aChild),
+ mPrevSibling(aPrevSibling) { }
+
+ NS_IMETHODIMP Run()
+ {
+ if (!mWeakMenuBar) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentInserted(mChild,
+ mPrevSibling);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> mPrevSibling;
+};
+
+class nsMenuBarContentRemovedEvent : public Runnable {
+public:
+ nsMenuBarContentRemovedEvent(nsMenuBar* aMenuBar,
+ nsIContent* aChild) :
+ mWeakMenuBar(aMenuBar),
+ mChild(aChild) { }
+
+ NS_IMETHODIMP Run()
+ {
+ if (!mWeakMenuBar) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentRemoved(mChild);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> mChild;
+};
+
+class nsMenuBar::DocEventListener final : public nsIDOMEventListener {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ DocEventListener(nsMenuBar* aOwner) : mOwner(aOwner) { };
+
+private:
+ ~DocEventListener() { };
+
+ nsMenuBar* mOwner;
+};
+
+NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+nsMenuBar::DocEventListener::HandleEvent(nsIDOMEvent* aEvent) {
+ nsAutoString type;
+ nsresult rv = aEvent->GetType(type);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to determine event type");
+ return rv;
+ }
+
+ if (type.Equals(NS_LITERAL_STRING("focus"))) {
+ mOwner->Focus();
+ } else if (type.Equals(NS_LITERAL_STRING("blur"))) {
+ mOwner->Blur();
+ } else if (type.Equals(NS_LITERAL_STRING("keypress"))) {
+ rv = mOwner->Keypress(aEvent);
+ } else if (type.Equals(NS_LITERAL_STRING("keydown"))) {
+ rv = mOwner->KeyDown(aEvent);
+ } else if (type.Equals(NS_LITERAL_STRING("keyup"))) {
+ rv = mOwner->KeyUp(aEvent);
+ }
+
+ return rv;
+}
+
+nsMenuBar::nsMenuBar(nsIContent* aMenuBarNode) :
+ nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode),
+ mTopLevel(nullptr),
+ mServer(nullptr),
+ mIsActive(false) {
+ MOZ_COUNT_CTOR(nsMenuBar);
+}
+
+nsresult
+nsMenuBar::Init(nsIWidget* aParent) {
+ MOZ_ASSERT(aParent);
+
+ GdkWindow* gdkWin = static_cast<GdkWindow* >(
+ aParent->GetNativeData(NS_NATIVE_WINDOW));
+ if (!gdkWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(gdkWin, &user_data);
+ if (!user_data || !GTK_IS_CONTAINER(user_data)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
+ if (!mTopLevel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ g_object_ref(mTopLevel);
+
+ nsAutoCString path;
+ path.Append(NS_LITERAL_CSTRING("/com/canonical/menu/"));
+ char xid[10];
+ sprintf(xid, "%X", static_cast<uint32_t>(
+ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
+ path.Append(xid);
+
+ mServer = dbusmenu_server_new(path.get());
+ if (!mServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CreateNativeData();
+ if (!GetNativeData()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbusmenu_server_set_root(mServer, GetNativeData());
+
+ mEventListener = new DocEventListener(this);
+
+ mDocument = do_QueryInterface(ContentNode()->OwnerDoc());
+
+ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey");
+ if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
+ mAccessKeyMask = eModifierShift;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
+ mAccessKeyMask = eModifierCtrl;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
+ mAccessKeyMask = eModifierAlt;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
+ mAccessKeyMask = eModifierMeta;
+ } else {
+ mAccessKeyMask = eModifierAlt;
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuBar::Build() {
+ uint32_t count = ContentNode()->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* childContent = ContentNode()->GetChildAt(i);
+
+ UniquePtr<nsMenuObject> child = CreateChild(childContent);
+
+ if (!child) {
+ continue;
+ }
+
+ AppendChild(Move(child));
+ }
+}
+
+void
+nsMenuBar::DisconnectDocumentEventListeners() {
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"),
+ mEventListener,
+ true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"),
+ mEventListener,
+ true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener,
+ false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener,
+ false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keyup"),
+ mEventListener,
+ false);
+}
+
+void
+nsMenuBar::SetShellShowingMenuBar(bool aShowing) {
+ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr(
+ kNameSpaceID_None, nsNativeMenuAtoms::shellshowingmenubar,
+ aShowing ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"),
+ true);
+}
+
+void
+nsMenuBar::Focus() {
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("false"), true);
+}
+
+void
+nsMenuBar::Blur() {
+ // We do this here in case we lose focus before getting the
+ // keyup event, which leaves the menubar state looking like
+ // the alt key is stuck down
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
+}
+
+nsMenuBar::ModifierFlags
+nsMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent* aEvent) {
+ ModifierFlags modifiers = static_cast<ModifierFlags>(0);
+ bool modifier;
+
+ aEvent->GetAltKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
+ }
+
+ aEvent->GetShiftKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
+ }
+
+ aEvent->GetCtrlKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
+ }
+
+ aEvent->GetMetaKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
+ }
+
+ return modifiers;
+}
+
+nsresult
+nsMenuBar::Keypress(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
+ if (((modifiers & mAccessKeyMask) == 0) ||
+ ((modifiers & ~mAccessKeyMask) != 0)) {
+ return NS_OK;
+ }
+
+ uint32_t charCode;
+ keyEvent->GetCharCode(&charCode);
+ if (charCode == 0) {
+ return NS_OK;
+ }
+
+ char16_t ch = char16_t(charCode);
+ char16_t chl = ToLowerCase(ch);
+ char16_t chu = ToUpperCase(ch);
+
+ nsMenuObject* found = nullptr;
+ uint32_t count = ChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsAutoString accesskey;
+ ChildAt(i)->ContentNode()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::accesskey,
+ accesskey);
+ const nsAutoString::char_type* key = accesskey.BeginReading();
+ if (*key == chu ||* key == chl) {
+ found = ChildAt(i);
+ break;
+ }
+ }
+
+ if (!found || found->Type() != nsMenuObject::eType_Menu) {
+ return NS_OK;
+ }
+
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("true"), true);
+ static_cast<nsMenu* >(found)->OpenMenu();
+
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuBar::KeyDown(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ uint32_t keyCode;
+ keyEvent->GetKeyCode(&keyCode);
+ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
+ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) {
+ return NS_OK;
+ }
+
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuBar::KeyUp(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ uint32_t keyCode;
+ keyEvent->GetKeyCode(&keyCode);
+ if (keyCode == mAccessKey) {
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuBar::HandleContentInserted(nsIContent* aChild, nsIContent* aPrevSibling) {
+ UniquePtr<nsMenuObject> child = CreateChild(aChild);
+
+ if (!child) {
+ return;
+ }
+
+ InsertChildAfter(Move(child), aPrevSibling);
+}
+
+void
+nsMenuBar::HandleContentRemoved(nsIContent* aChild) {
+ RemoveChild(aChild);
+}
+
+void
+nsMenuBar::OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPrevSibling) {
+ MOZ_ASSERT(aContainer == ContentNode(),
+ "Received an event that wasn't meant for us");
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling));
+}
+
+void
+nsMenuBar::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
+ MOZ_ASSERT(aContainer == ContentNode(),
+ "Received an event that wasn't meant for us");
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuBarContentRemovedEvent(this, aChild));
+}
+
+nsMenuBar::~nsMenuBar() {
+ nsNativeMenuService* service = nsNativeMenuService::GetSingleton();
+ if (service) {
+ service->NotifyNativeMenuBarDestroyed(this);
+ }
+
+ if (ContentNode()) {
+ SetShellShowingMenuBar(false);
+ }
+
+ // We want to destroy all children before dropping our reference
+ // to the doc listener
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+
+ if (mTopLevel) {
+ g_object_unref(mTopLevel);
+ }
+
+ if (DocListener()) {
+ DocListener()->Stop();
+ }
+
+ if (mDocument) {
+ DisconnectDocumentEventListeners();
+ }
+
+ if (mServer) {
+ g_object_unref(mServer);
+ }
+
+ MOZ_COUNT_DTOR(nsMenuBar);
+}
+
+/* static */ UniquePtr<nsMenuBar>
+nsMenuBar::Create(nsIWidget* aParent, nsIContent* aMenuBarNode) {
+ UniquePtr<nsMenuBar> menubar(new nsMenuBar(aMenuBarNode));
+ if (NS_FAILED(menubar->Init(aParent))) {
+ return nullptr;
+ }
+
+ return Move(menubar);
+}
+
+nsMenuObject::EType
+nsMenuBar::Type() const {
+ return eType_MenuBar;
+}
+
+bool
+nsMenuBar::IsBeingDisplayed() const {
+ return true;
+}
+
+uint32_t
+nsMenuBar::WindowId() const {
+ return static_cast<uint32_t>(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
+}
+
+nsAdoptingCString
+nsMenuBar::ObjectPath() const {
+ gchar* tmp;
+ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL);
+ nsAdoptingCString result(tmp);
+
+ return result;
+}
+
+void
+nsMenuBar::Activate() {
+ if (mIsActive) {
+ return;
+ }
+
+ mIsActive = true;
+
+ mDocument->AddEventListener(NS_LITERAL_STRING("focus"),
+ mEventListener,
+ true);
+ mDocument->AddEventListener(NS_LITERAL_STRING("blur"),
+ mEventListener,
+ true);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener,
+ false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener,
+ false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keyup"),
+ mEventListener,
+ false);
+
+ // Clear this. Not sure if we really need to though
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("false"), true);
+
+ DocListener()->Start();
+ Build();
+ SetShellShowingMenuBar(true);
+}
+
+void
+nsMenuBar::Deactivate() {
+ if (!mIsActive) {
+ return;
+ }
+
+ mIsActive = false;
+
+ SetShellShowingMenuBar(false);
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+ DocListener()->Stop();
+ DisconnectDocumentEventListeners();
+}
diff --git a/widget/gtk/nsMenuBar.h b/widget/gtk/nsMenuBar.h
new file mode 100644
index 000000000..9ce179651
--- /dev/null
+++ b/widget/gtk/nsMenuBar.h
@@ -0,0 +1,103 @@
+/* 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 __nsMenuBar_h__
+#define __nsMenuBar_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenuContainer.h"
+#include "nsMenuObject.h"
+
+#include <gtk/gtk.h>
+
+class nsIAtom;
+class nsIContent;
+class nsIDOMEvent;
+class nsIDOMKeyEvent;
+class nsIWidget;
+class nsMenuBarDocEventListener;
+
+/*
+ * The menubar class. There is one of these per window (and the window
+ * owns its menubar). Each menubar has an object path, and the service is
+ * responsible for telling the desktop shell which object path corresponds
+ * to a particular window. A menubar and its hierarchy also own a
+ * nsNativeMenuDocListener.
+ */
+class nsMenuBar final : public nsMenuContainer {
+public:
+ ~nsMenuBar() override;
+
+ static mozilla::UniquePtr<nsMenuBar> Create(nsIWidget* aParent,
+ nsIContent* aMenuBarNode);
+
+ nsMenuObject::EType Type() const override;
+
+ bool IsBeingDisplayed() const override;
+
+ // Get the native window ID for this menubar
+ uint32_t WindowId() const;
+
+ // Get the object path for this menubar
+ nsAdoptingCString ObjectPath() const;
+
+ // Get the top-level GtkWindow handle
+ GtkWidget* TopLevelWindow() { return mTopLevel; }
+
+ // Called from the menuservice when the menubar is about to be registered.
+ // Causes the native menubar to be created, and the XUL menubar to be hidden
+ void Activate();
+
+ // Called from the menuservice when the menubar is no longer registered
+ // with the desktop shell. Will cause the XUL menubar to be shown again
+ void Deactivate();
+
+private:
+ class DocEventListener;
+ friend class nsMenuBarContentInsertedEvent;
+ friend class nsMenuBarContentRemovedEvent;
+
+ enum ModifierFlags {
+ eModifierShift = (1 << 0),
+ eModifierCtrl = (1 << 1),
+ eModifierAlt = (1 << 2),
+ eModifierMeta = (1 << 3)
+ };
+
+ nsMenuBar(nsIContent* aMenuBarNode);
+ nsresult Init(nsIWidget* aParent);
+ void Build();
+ void DisconnectDocumentEventListeners();
+ void SetShellShowingMenuBar(bool aShowing);
+ void Focus();
+ void Blur();
+ ModifierFlags GetModifiersFromEvent(nsIDOMKeyEvent* aEvent);
+ nsresult Keypress(nsIDOMEvent* aEvent);
+ nsresult KeyDown(nsIDOMEvent* aEvent);
+ nsresult KeyUp(nsIDOMEvent* aEvent);
+
+ void HandleContentInserted(nsIContent* aChild,
+ nsIContent* aPrevSibling);
+ void HandleContentRemoved(nsIContent* aChild);
+
+ void OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPrevSibling) override;
+ void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) override;
+
+ GtkWidget* mTopLevel;
+ DbusmenuServer* mServer;
+ nsCOMPtr<nsIDOMEventTarget> mDocument;
+ RefPtr<DocEventListener> mEventListener;
+
+ uint32_t mAccessKey;
+ ModifierFlags mAccessKeyMask;
+ bool mIsActive;
+};
+
+#endif /* __nsMenuBar_h__ */
diff --git a/widget/gtk/nsMenuContainer.cpp b/widget/gtk/nsMenuContainer.cpp
new file mode 100644
index 000000000..081e98a6a
--- /dev/null
+++ b/widget/gtk/nsMenuContainer.cpp
@@ -0,0 +1,156 @@
+/* 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/. */
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include "nsGkAtoms.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenu.h"
+#include "nsMenuItem.h"
+#include "nsMenuSeparator.h"
+
+#include "nsMenuContainer.h"
+
+using namespace mozilla;
+
+const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex;
+
+typedef UniquePtr<nsMenuObject> (*nsMenuObjectConstructor)(nsMenuContainer*,
+ nsIContent*);
+
+template<class T>
+static UniquePtr<nsMenuObject> CreateMenuObject(nsMenuContainer* aContainer,
+ nsIContent* aContent) {
+ return UniquePtr<T>(new T(aContainer, aContent));
+}
+
+static nsMenuObjectConstructor
+GetMenuObjectConstructor(nsIContent* aContent) {
+ if (aContent->IsXULElement(nsGkAtoms::menuitem)) {
+ return CreateMenuObject<nsMenuItem>;
+ } else if (aContent->IsXULElement(nsGkAtoms::menu)) {
+ return CreateMenuObject<nsMenu>;
+ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) {
+ return CreateMenuObject<nsMenuSeparator>;
+ }
+
+ return nullptr;
+}
+
+static bool
+ContentIsSupported(nsIContent* aContent) {
+ return GetMenuObjectConstructor(aContent) ? true : false;
+}
+
+nsMenuContainer::nsMenuContainer(nsMenuContainer* aParent,
+ nsIContent* aContent) :
+ nsMenuObject(aParent, aContent) {
+}
+
+nsMenuContainer::nsMenuContainer(nsNativeMenuDocListener* aListener,
+ nsIContent* aContent) :
+ nsMenuObject(aListener, aContent) {
+}
+
+UniquePtr<nsMenuObject>
+nsMenuContainer::CreateChild(nsIContent* aContent) {
+ nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent);
+ if (!ctor) {
+ // There are plenty of node types we might stumble across that
+ // aren't supported
+ return nullptr;
+ }
+
+ UniquePtr<nsMenuObject> res = ctor(this, aContent);
+ return Move(res);
+}
+
+size_t
+nsMenuContainer::IndexOf(nsIContent* aChild) const {
+ if (!aChild) {
+ return NoIndex;
+ }
+
+ size_t count = ChildCount();
+ for (size_t i = 0; i < count; ++i) {
+ if (ChildAt(i)->ContentNode() == aChild) {
+ return i;
+ }
+ }
+
+ return NoIndex;
+}
+
+void
+nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative) {
+ MOZ_ASSERT(aIndex < ChildCount());
+
+ if (aUpdateNative) {
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_delete(GetNativeData(),
+ ChildAt(aIndex)->GetNativeData()));
+ }
+
+ mChildren.RemoveElementAt(aIndex);
+}
+
+void
+nsMenuContainer::RemoveChild(nsIContent* aChild, bool aUpdateNative) {
+ size_t index = IndexOf(aChild);
+ if (index == NoIndex) {
+ return;
+ }
+
+ RemoveChildAt(index, aUpdateNative);
+}
+
+void
+nsMenuContainer::InsertChildAfter(UniquePtr<nsMenuObject> aChild,
+ nsIContent* aPrevSibling,
+ bool aUpdateNative) {
+ size_t index = IndexOf(aPrevSibling);
+ MOZ_ASSERT(!aPrevSibling || index != NoIndex);
+
+ ++index;
+
+ if (aUpdateNative) {
+ aChild->CreateNativeData();
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_add_position(GetNativeData(),
+ aChild->GetNativeData(),
+ index));
+ }
+
+ MOZ_ALWAYS_TRUE(mChildren.InsertElementAt(index, Move(aChild)));
+}
+
+void
+nsMenuContainer::AppendChild(UniquePtr<nsMenuObject> aChild,
+ bool aUpdateNative) {
+ if (aUpdateNative) {
+ aChild->CreateNativeData();
+ MOZ_ALWAYS_TRUE(
+ dbusmenu_menuitem_child_append(GetNativeData(),
+ aChild->GetNativeData()));
+ }
+
+ MOZ_ALWAYS_TRUE(mChildren.AppendElement(Move(aChild)));
+}
+
+bool
+nsMenuContainer::NeedsRebuild() const {
+ return false;
+}
+
+/* static */ nsIContent*
+nsMenuContainer::GetPreviousSupportedSibling(nsIContent* aContent) {
+ do {
+ aContent = aContent->GetPreviousSibling();
+ } while (aContent && !ContentIsSupported(aContent));
+
+ return aContent;
+}
diff --git a/widget/gtk/nsMenuContainer.h b/widget/gtk/nsMenuContainer.h
new file mode 100644
index 000000000..95d65a2f1
--- /dev/null
+++ b/widget/gtk/nsMenuContainer.h
@@ -0,0 +1,66 @@
+/* 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 __nsMenuContainer_h__
+#define __nsMenuContainer_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+#include "nsMenuObject.h"
+
+class nsIContent;
+class nsNativeMenuDocListener;
+
+// Base class for containers (menus and menubars)
+class nsMenuContainer : public nsMenuObject {
+public:
+ typedef nsTArray<mozilla::UniquePtr<nsMenuObject> > ChildTArray;
+
+ // Determine if this container is being displayed on screen. Must be
+ // implemented by subclasses. Must return true if the container is
+ // in the fully open state, or false otherwise
+ virtual bool IsBeingDisplayed() const = 0;
+
+ // Determine if this container will be rebuilt the next time it opens.
+ // Returns false by default but can be overridden by subclasses
+ virtual bool NeedsRebuild() const;
+
+ // Return the first previous sibling that is of a type supported by the
+ // menu system
+ static nsIContent* GetPreviousSupportedSibling(nsIContent* aContent);
+
+ static const ChildTArray::index_type NoIndex;
+
+protected:
+ nsMenuContainer(nsMenuContainer* aParent, nsIContent* aContent);
+ nsMenuContainer(nsNativeMenuDocListener* aListener, nsIContent* aContent);
+
+ // Create a new child element for the specified content node
+ mozilla::UniquePtr<nsMenuObject> CreateChild(nsIContent* aContent);
+
+ // Return the index of the child for the specified content node
+ size_t IndexOf(nsIContent* aChild) const;
+
+ size_t ChildCount() const { return mChildren.Length(); }
+ nsMenuObject* ChildAt(size_t aIndex) const { return mChildren[aIndex].get(); }
+
+ void RemoveChildAt(size_t aIndex, bool aUpdateNative = true);
+
+ // Remove the child that owns the specified content node
+ void RemoveChild(nsIContent* aChild, bool aUpdateNative = true);
+
+ // Insert a new child after the child that owns the specified content node
+ void InsertChildAfter(mozilla::UniquePtr<nsMenuObject> aChild,
+ nsIContent* aPrevSibling,
+ bool aUpdateNative = true);
+
+ void AppendChild(mozilla::UniquePtr<nsMenuObject> aChild,
+ bool aUpdateNative = true);
+
+private:
+ ChildTArray mChildren;
+};
+
+#endif /* __nsMenuContainer_h__ */
diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp
new file mode 100644
index 000000000..00cc5477c
--- /dev/null
+++ b/widget/gtk/nsMenuItem.cpp
@@ -0,0 +1,712 @@
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsGtkUtils.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsIRunnable.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStyleContext.h"
+#include "nsThreadUtils.h"
+
+#include "nsMenu.h"
+#include "nsMenuBar.h"
+#include "nsMenuContainer.h"
+#include "nsNativeMenuDocListener.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "nsMenuItem.h"
+
+using namespace mozilla;
+
+struct KeyCodeData {
+ const char* str;
+ size_t strlength;
+ uint32_t keycode;
+};
+
+static struct KeyCodeData gKeyCodes[] = {
+#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
+ { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
+#include "mozilla/VirtualKeyCodeList.h"
+#undef NS_DEFINE_VK
+ { nullptr, 0, 0 }
+};
+
+struct KeyPair {
+ uint32_t DOMKeyCode;
+ guint GDKKeyval;
+};
+
+//
+// Netscape keycodes are defined in widget/public/nsGUIEvent.h
+// GTK keycodes are defined in <gdk/gdkkeysyms.h>
+//
+static const KeyPair gKeyPairs[] = {
+ { NS_VK_CANCEL, GDK_Cancel },
+ { NS_VK_BACK, GDK_BackSpace },
+ { NS_VK_TAB, GDK_Tab },
+ { NS_VK_TAB, GDK_ISO_Left_Tab },
+ { NS_VK_CLEAR, GDK_Clear },
+ { NS_VK_RETURN, GDK_Return },
+ { NS_VK_SHIFT, GDK_Shift_L },
+ { NS_VK_SHIFT, GDK_Shift_R },
+ { NS_VK_SHIFT, GDK_Shift_Lock },
+ { NS_VK_CONTROL, GDK_Control_L },
+ { NS_VK_CONTROL, GDK_Control_R },
+ { NS_VK_ALT, GDK_Alt_L },
+ { NS_VK_ALT, GDK_Alt_R },
+ { NS_VK_META, GDK_Meta_L },
+ { NS_VK_META, GDK_Meta_R },
+
+ // Assume that Super or Hyper is always mapped to physical Win key.
+ { NS_VK_WIN, GDK_Super_L },
+ { NS_VK_WIN, GDK_Super_R },
+ { NS_VK_WIN, GDK_Hyper_L },
+ { NS_VK_WIN, GDK_Hyper_R },
+
+ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
+ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
+ // it's really different from Alt key on Windows.
+ // On the other hand, GTK's AltGrapsh keys are really different from
+ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
+ // both Ctrl and Alt keys are pressed internally when AltGr key is pressed.
+ // For some languages' users, AltGraph key is important, so, web
+ // applications on such locale may want to know AltGraph key press.
+ // Therefore, we should map AltGr keycode for them only on GTK.
+ { NS_VK_ALTGR, GDK_ISO_Level3_Shift },
+ { NS_VK_ALTGR, GDK_ISO_Level5_Shift },
+ // We assume that Mode_switch is always used for level3 shift.
+ { NS_VK_ALTGR, GDK_Mode_switch },
+
+ { NS_VK_PAUSE, GDK_Pause },
+ { NS_VK_CAPS_LOCK, GDK_Caps_Lock },
+ { NS_VK_KANA, GDK_Kana_Lock },
+ { NS_VK_KANA, GDK_Kana_Shift },
+ { NS_VK_HANGUL, GDK_Hangul },
+ // { NS_VK_JUNJA, GDK_XXX },
+ // { NS_VK_FINAL, GDK_XXX },
+ { NS_VK_HANJA, GDK_Hangul_Hanja },
+ { NS_VK_KANJI, GDK_Kanji },
+ { NS_VK_ESCAPE, GDK_Escape },
+ { NS_VK_CONVERT, GDK_Henkan },
+ { NS_VK_NONCONVERT, GDK_Muhenkan },
+ // { NS_VK_ACCEPT, GDK_XXX },
+ // { NS_VK_MODECHANGE, GDK_XXX },
+ { NS_VK_SPACE, GDK_space },
+ { NS_VK_PAGE_UP, GDK_Page_Up },
+ { NS_VK_PAGE_DOWN, GDK_Page_Down },
+ { NS_VK_END, GDK_End },
+ { NS_VK_HOME, GDK_Home },
+ { NS_VK_LEFT, GDK_Left },
+ { NS_VK_UP, GDK_Up },
+ { NS_VK_RIGHT, GDK_Right },
+ { NS_VK_DOWN, GDK_Down },
+ { NS_VK_SELECT, GDK_Select },
+ { NS_VK_PRINT, GDK_Print },
+ { NS_VK_EXECUTE, GDK_Execute },
+ { NS_VK_PRINTSCREEN, GDK_Print },
+ { NS_VK_INSERT, GDK_Insert },
+ { NS_VK_DELETE, GDK_Delete },
+ { NS_VK_HELP, GDK_Help },
+
+ // keypad keys
+ { NS_VK_LEFT, GDK_KP_Left },
+ { NS_VK_RIGHT, GDK_KP_Right },
+ { NS_VK_UP, GDK_KP_Up },
+ { NS_VK_DOWN, GDK_KP_Down },
+ { NS_VK_PAGE_UP, GDK_KP_Page_Up },
+ // Not sure what these are
+ //{ NS_VK_, GDK_KP_Prior },
+ //{ NS_VK_, GDK_KP_Next },
+ { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5
+ { NS_VK_PAGE_DOWN, GDK_KP_Page_Down },
+ { NS_VK_HOME, GDK_KP_Home },
+ { NS_VK_END, GDK_KP_End },
+ { NS_VK_INSERT, GDK_KP_Insert },
+ { NS_VK_DELETE, GDK_KP_Delete },
+ { NS_VK_RETURN, GDK_KP_Enter },
+
+ { NS_VK_NUM_LOCK, GDK_Num_Lock },
+ { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock },
+
+ // Function keys
+ { NS_VK_F1, GDK_F1 },
+ { NS_VK_F2, GDK_F2 },
+ { NS_VK_F3, GDK_F3 },
+ { NS_VK_F4, GDK_F4 },
+ { NS_VK_F5, GDK_F5 },
+ { NS_VK_F6, GDK_F6 },
+ { NS_VK_F7, GDK_F7 },
+ { NS_VK_F8, GDK_F8 },
+ { NS_VK_F9, GDK_F9 },
+ { NS_VK_F10, GDK_F10 },
+ { NS_VK_F11, GDK_F11 },
+ { NS_VK_F12, GDK_F12 },
+ { NS_VK_F13, GDK_F13 },
+ { NS_VK_F14, GDK_F14 },
+ { NS_VK_F15, GDK_F15 },
+ { NS_VK_F16, GDK_F16 },
+ { NS_VK_F17, GDK_F17 },
+ { NS_VK_F18, GDK_F18 },
+ { NS_VK_F19, GDK_F19 },
+ { NS_VK_F20, GDK_F20 },
+ { NS_VK_F21, GDK_F21 },
+ { NS_VK_F22, GDK_F22 },
+ { NS_VK_F23, GDK_F23 },
+ { NS_VK_F24, GDK_F24 },
+
+ // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft)
+ // x86 keyboards, located between right 'Windows' key and right Ctrl key
+ { NS_VK_CONTEXT_MENU, GDK_Menu },
+ { NS_VK_SLEEP, GDK_Sleep },
+
+ { NS_VK_ATTN, GDK_3270_Attn },
+ { NS_VK_CRSEL, GDK_3270_CursorSelect },
+ { NS_VK_EXSEL, GDK_3270_ExSelect },
+ { NS_VK_EREOF, GDK_3270_EraseEOF },
+ { NS_VK_PLAY, GDK_3270_Play },
+ //{ NS_VK_ZOOM, GDK_XXX },
+ { NS_VK_PA1, GDK_3270_PA1 },
+};
+
+static guint
+ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName) {
+ NS_ConvertUTF16toUTF8 keyName(aKeyName);
+ ToUpperCase(keyName); // We want case-insensitive comparison with data
+ // stored as uppercase.
+
+ uint32_t keyCode = 0;
+
+ uint32_t keyNameLength = keyName.Length();
+ const char* keyNameStr = keyName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) {
+ if (keyNameLength == gKeyCodes[i].strlength &&
+ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
+ keyCode = gKeyCodes[i].keycode;
+ break;
+ }
+ }
+
+ // First, try to handle alphanumeric input, not listed in nsKeycodes:
+ // most likely, more letters will be getting typed in than things in
+ // the key list, so we will look through these first.
+
+ if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) {
+ // gdk and DOM both use the ASCII codes for these keys.
+ return keyCode;
+ }
+
+ // numbers
+ if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) {
+ // gdk and DOM both use the ASCII codes for these keys.
+ return keyCode - NS_VK_0 + GDK_0;
+ }
+
+ switch (keyCode) {
+ // keys in numpad
+ case NS_VK_MULTIPLY: return GDK_KP_Multiply;
+ case NS_VK_ADD: return GDK_KP_Add;
+ case NS_VK_SEPARATOR: return GDK_KP_Separator;
+ case NS_VK_SUBTRACT: return GDK_KP_Subtract;
+ case NS_VK_DECIMAL: return GDK_KP_Decimal;
+ case NS_VK_DIVIDE: return GDK_KP_Divide;
+ case NS_VK_NUMPAD0: return GDK_KP_0;
+ case NS_VK_NUMPAD1: return GDK_KP_1;
+ case NS_VK_NUMPAD2: return GDK_KP_2;
+ case NS_VK_NUMPAD3: return GDK_KP_3;
+ case NS_VK_NUMPAD4: return GDK_KP_4;
+ case NS_VK_NUMPAD5: return GDK_KP_5;
+ case NS_VK_NUMPAD6: return GDK_KP_6;
+ case NS_VK_NUMPAD7: return GDK_KP_7;
+ case NS_VK_NUMPAD8: return GDK_KP_8;
+ case NS_VK_NUMPAD9: return GDK_KP_9;
+ // other prinable keys
+ case NS_VK_SPACE: return GDK_space;
+ case NS_VK_COLON: return GDK_colon;
+ case NS_VK_SEMICOLON: return GDK_semicolon;
+ case NS_VK_LESS_THAN: return GDK_less;
+ case NS_VK_EQUALS: return GDK_equal;
+ case NS_VK_GREATER_THAN: return GDK_greater;
+ case NS_VK_QUESTION_MARK: return GDK_question;
+ case NS_VK_AT: return GDK_at;
+ case NS_VK_CIRCUMFLEX: return GDK_asciicircum;
+ case NS_VK_EXCLAMATION: return GDK_exclam;
+ case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl;
+ case NS_VK_HASH: return GDK_numbersign;
+ case NS_VK_DOLLAR: return GDK_dollar;
+ case NS_VK_PERCENT: return GDK_percent;
+ case NS_VK_AMPERSAND: return GDK_ampersand;
+ case NS_VK_UNDERSCORE: return GDK_underscore;
+ case NS_VK_OPEN_PAREN: return GDK_parenleft;
+ case NS_VK_CLOSE_PAREN: return GDK_parenright;
+ case NS_VK_ASTERISK: return GDK_asterisk;
+ case NS_VK_PLUS: return GDK_plus;
+ case NS_VK_PIPE: return GDK_bar;
+ case NS_VK_HYPHEN_MINUS: return GDK_minus;
+ case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft;
+ case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright;
+ case NS_VK_TILDE: return GDK_asciitilde;
+ case NS_VK_COMMA: return GDK_comma;
+ case NS_VK_PERIOD: return GDK_period;
+ case NS_VK_SLASH: return GDK_slash;
+ case NS_VK_BACK_QUOTE: return GDK_grave;
+ case NS_VK_OPEN_BRACKET: return GDK_bracketleft;
+ case NS_VK_BACK_SLASH: return GDK_backslash;
+ case NS_VK_CLOSE_BRACKET: return GDK_bracketright;
+ case NS_VK_QUOTE: return GDK_apostrophe;
+ }
+
+ // misc other things
+ for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) {
+ if (gKeyPairs[i].DOMKeyCode == keyCode) {
+ return gKeyPairs[i].GDKKeyval;
+ }
+ }
+
+ return 0;
+}
+
+class nsMenuItemUncheckSiblingsRunnable final : public Runnable {
+public:
+ NS_IMETHODIMP Run() {
+ if (mMenuItem) {
+ static_cast<nsMenuItem* >(mMenuItem.get())->UncheckSiblings();
+ }
+ return NS_OK;
+ }
+
+ nsMenuItemUncheckSiblingsRunnable(nsMenuItem* aMenuItem) :
+ mMenuItem(aMenuItem) { };
+
+private:
+ nsWeakMenuObject mMenuItem;
+};
+
+bool
+nsMenuItem::IsCheckboxOrRadioItem() const {
+ return mType == eMenuItemType_Radio ||
+ mType == eMenuItemType_CheckBox;
+}
+
+/* static */ void
+nsMenuItem::item_activated_cb(DbusmenuMenuitem* menuitem,
+ guint timestamp,
+ gpointer user_data) {
+ nsMenuItem* item = static_cast<nsMenuItem* >(user_data);
+ item->Activate(timestamp);
+}
+
+void
+nsMenuItem::Activate(uint32_t aTimestamp) {
+ GdkWindow* window = gtk_widget_get_window(MenuBar()->TopLevelWindow());
+ gdk_x11_window_set_user_time(
+ window, std::min(aTimestamp, gdk_x11_get_server_time(window)));
+
+ // We do this to avoid mutating our view of the menu until
+ // after we have finished
+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
+
+ if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters) &&
+ (mType == eMenuItemType_CheckBox ||
+ (mType == eMenuItemType_Radio && !mIsChecked))) {
+ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ mIsChecked ?
+ NS_LITERAL_STRING("false") : NS_LITERAL_STRING("true"),
+ true);
+ }
+
+ nsIDocument* doc = ContentNode()->OwnerDoc();
+ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(ContentNode());
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
+ if (domDoc && target) {
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
+ getter_AddRefs(event));
+ nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryInterface(event);
+ if (command) {
+ command->InitCommandEvent(NS_LITERAL_STRING("command"),
+ true, true, doc->GetInnerWindow(), 0,
+ false, false, false, false, nullptr);
+
+ event->SetTrusted(true);
+ bool dummy;
+ target->DispatchEvent(event, &dummy);
+ }
+ }
+
+ // This kinda sucks, but Unity doesn't send a closed event
+ // after activating a menuitem
+ nsMenuObject* ancestor = Parent();
+ while (ancestor && ancestor->Type() == eType_Menu) {
+ static_cast<nsMenu* >(ancestor)->OnClose();
+ ancestor = ancestor->Parent();
+ }
+}
+
+void
+nsMenuItem::CopyAttrFromNodeIfExists(nsIContent* aContent, nsIAtom* aAttribute) {
+ nsAutoString value;
+ if (aContent->GetAttr(kNameSpaceID_None, aAttribute, value)) {
+ ContentNode()->SetAttr(kNameSpaceID_None, aAttribute, value, true);
+ }
+}
+
+void
+nsMenuItem::UpdateState() {
+ if (!IsCheckboxOrRadioItem()) {
+ return;
+ }
+
+ mIsChecked = ContentNode()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::checked,
+ nsGkAtoms::_true,
+ eCaseMatters);
+ dbusmenu_menuitem_property_set_int(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ mIsChecked ?
+ DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED :
+ DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+}
+
+void
+nsMenuItem::UpdateTypeAndState() {
+ static nsIContent::AttrValuesArray attrs[] =
+ { &nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr };
+ int32_t type = ContentNode()->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::type,
+ attrs, eCaseMatters);
+
+ if (type >= 0 && type < 2) {
+ if (type == 0) {
+ dbusmenu_menuitem_property_set(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+ DBUSMENU_MENUITEM_TOGGLE_CHECK);
+ mType = eMenuItemType_CheckBox;
+ } else if (type == 1) {
+ dbusmenu_menuitem_property_set(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+ DBUSMENU_MENUITEM_TOGGLE_RADIO);
+ mType = eMenuItemType_Radio;
+ }
+
+ UpdateState();
+ } else {
+ dbusmenu_menuitem_property_remove(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE);
+ dbusmenu_menuitem_property_remove(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE);
+ mType = eMenuItemType_Normal;
+ }
+}
+
+void
+nsMenuItem::UpdateAccel() {
+ nsIDocument* doc = ContentNode()->GetUncomposedDoc();
+ if (doc) {
+ nsCOMPtr<nsIContent> oldKeyContent;
+ oldKeyContent.swap(mKeyContent);
+
+ nsAutoString key;
+ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
+ if (!key.IsEmpty()) {
+ mKeyContent = doc->GetElementById(key);
+ }
+
+ if (mKeyContent != oldKeyContent) {
+ if (oldKeyContent) {
+ DocListener()->UnregisterForContentChanges(oldKeyContent);
+ }
+ if (mKeyContent) {
+ DocListener()->RegisterForContentChanges(mKeyContent, this);
+ }
+ }
+ }
+
+ if (!mKeyContent) {
+ dbusmenu_menuitem_property_remove(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_SHORTCUT);
+ return;
+ }
+
+ nsAutoString modifiers;
+ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
+
+ uint32_t modifier = 0;
+
+ if (!modifiers.IsEmpty()) {
+ char* str = ToNewUTF8String(modifiers);
+ char* token = strtok(str, ", \t");
+ while(token) {
+ if (nsCRT::strcmp(token, "shift") == 0) {
+ modifier |= GDK_SHIFT_MASK;
+ } else if (nsCRT::strcmp(token, "alt") == 0) {
+ modifier |= GDK_MOD1_MASK;
+ } else if (nsCRT::strcmp(token, "meta") == 0) {
+ modifier |= GDK_META_MASK;
+ } else if (nsCRT::strcmp(token, "control") == 0) {
+ modifier |= GDK_CONTROL_MASK;
+ } else if (nsCRT::strcmp(token, "accel") == 0) {
+ int32_t accel = Preferences::GetInt("ui.key.accelKey");
+ if (accel == nsIDOMKeyEvent::DOM_VK_META) {
+ modifier |= GDK_META_MASK;
+ } else if (accel == nsIDOMKeyEvent::DOM_VK_ALT) {
+ modifier |= GDK_MOD1_MASK;
+ } else {
+ modifier |= GDK_CONTROL_MASK;
+ }
+ }
+
+ token = strtok(nullptr, ", \t");
+ }
+
+ free(str);
+ }
+
+ nsAutoString keyStr;
+ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
+
+ guint key = 0;
+ if (!keyStr.IsEmpty()) {
+ key = gdk_unicode_to_keyval(*keyStr.BeginReading());
+ }
+
+ if (key == 0) {
+ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyStr);
+ if (!keyStr.IsEmpty()) {
+ key = ConvertGeckoKeyNameToGDKKeyval(keyStr);
+ }
+ }
+
+ if (key == 0) {
+ key = GDK_VoidSymbol;
+ }
+
+ if (key != GDK_VoidSymbol) {
+ dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key,
+ static_cast<GdkModifierType>(modifier));
+ } else {
+ dbusmenu_menuitem_property_remove(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_SHORTCUT);
+ }
+}
+
+nsMenuBar*
+nsMenuItem::MenuBar() {
+ nsMenuObject* tmp = this;
+ while (tmp->Parent()) {
+ tmp = tmp->Parent();
+ }
+
+ MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar");
+
+ return static_cast<nsMenuBar* >(tmp);
+}
+
+void
+nsMenuItem::UncheckSiblings() {
+ if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::radio, eCaseMatters)) {
+ // If we're not a radio button, we don't care
+ return;
+ }
+
+ nsAutoString name;
+ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ nsIContent* parent = ContentNode()->GetParent();
+ if (!parent) {
+ return;
+ }
+
+ uint32_t count = parent->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* sibling = parent->GetChildAt(i);
+
+ nsAutoString otherName;
+ sibling->GetAttr(kNameSpaceID_None, nsGkAtoms::name, otherName);
+
+ if (sibling != ContentNode() && otherName == name &&
+ sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::radio, eCaseMatters)) {
+ sibling->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
+ }
+ }
+}
+
+void
+nsMenuItem::InitializeNativeData() {
+ g_signal_connect(G_OBJECT(GetNativeData()),
+ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK(item_activated_cb), this);
+ mNeedsUpdate = true;
+}
+
+void
+nsMenuItem::UpdateContentAttributes() {
+ nsIDocument* doc = ContentNode()->GetUncomposedDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsAutoString command;
+ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
+ if (command.IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> commandContent = doc->GetElementById(command);
+ if (!commandContent) {
+ return;
+ }
+
+ if (commandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters)) {
+ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
+ NS_LITERAL_STRING("true"), true);
+ } else {
+ ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ }
+
+ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked);
+ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey);
+ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label);
+ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden);
+}
+
+void
+nsMenuItem::Update(nsStyleContext* aStyleContext) {
+ if (mNeedsUpdate) {
+ mNeedsUpdate = false;
+
+ UpdateTypeAndState();
+ UpdateAccel();
+ UpdateLabel();
+ UpdateSensitivity();
+ }
+
+ UpdateVisibility(aStyleContext);
+ UpdateIcon(aStyleContext);
+}
+
+bool
+nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const {
+ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
+ DBUSMENU_MENUITEM_PROP_TYPE),
+ "separator") != 0;
+}
+
+nsMenuObject::PropertyFlags
+nsMenuItem::SupportedProperties() const {
+ return static_cast<nsMenuObject::PropertyFlags>(
+ nsMenuObject::ePropLabel |
+ nsMenuObject::ePropEnabled |
+ nsMenuObject::ePropVisible |
+ nsMenuObject::ePropIconData |
+ nsMenuObject::ePropShortcut |
+ nsMenuObject::ePropToggleType |
+ nsMenuObject::ePropToggleState
+ );
+}
+
+void
+nsMenuItem::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {
+ MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent,
+ "Received an event that wasn't meant for us!");
+
+ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked &&
+ aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsContentUtils::AddScriptRunner(
+ new nsMenuItemUncheckSiblingsRunnable(this));
+ }
+
+ if (mNeedsUpdate) {
+ return;
+ }
+
+ if (!Parent()->IsBeingDisplayed()) {
+ mNeedsUpdate = true;
+ return;
+ }
+
+ if (aContent == ContentNode()) {
+ if (aAttribute == nsGkAtoms::key) {
+ UpdateAccel();
+ } else if (aAttribute == nsGkAtoms::label ||
+ aAttribute == nsGkAtoms::accesskey ||
+ aAttribute == nsGkAtoms::crop) {
+ UpdateLabel();
+ } else if (aAttribute == nsGkAtoms::disabled) {
+ UpdateSensitivity();
+ } else if (aAttribute == nsGkAtoms::type) {
+ UpdateTypeAndState();
+ } else if (aAttribute == nsGkAtoms::checked) {
+ UpdateState();
+ } else if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed) {
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ UpdateVisibility(sc);
+ } else if (aAttribute == nsGkAtoms::image) {
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ UpdateIcon(sc);
+ }
+ } else if (aContent == mKeyContent &&
+ (aAttribute == nsGkAtoms::key ||
+ aAttribute == nsGkAtoms::keycode ||
+ aAttribute == nsGkAtoms::modifiers)) {
+ UpdateAccel();
+ }
+}
+
+nsMenuItem::nsMenuItem(nsMenuContainer* aParent, nsIContent* aContent) :
+ nsMenuObject(aParent, aContent),
+ mType(eMenuItemType_Normal),
+ mIsChecked(false),
+ mNeedsUpdate(false) {
+ MOZ_COUNT_CTOR(nsMenuItem);
+}
+
+nsMenuItem::~nsMenuItem() {
+ if (DocListener() && mKeyContent) {
+ DocListener()->UnregisterForContentChanges(mKeyContent);
+ }
+
+ if (GetNativeData()) {
+ g_signal_handlers_disconnect_by_func(GetNativeData(),
+ FuncToGpointer(item_activated_cb),
+ this);
+ }
+
+ MOZ_COUNT_DTOR(nsMenuItem);
+}
+
+nsMenuObject::EType
+nsMenuItem::Type() const {
+ return eType_MenuItem;
+}
diff --git a/widget/gtk/nsMenuItem.h b/widget/gtk/nsMenuItem.h
new file mode 100644
index 000000000..e81b6e308
--- /dev/null
+++ b/widget/gtk/nsMenuItem.h
@@ -0,0 +1,77 @@
+/* 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 __nsMenuItem_h__
+#define __nsMenuItem_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenuObject.h"
+
+#include <glib.h>
+
+class nsIAtom;
+class nsIContent;
+class nsStyleContext;
+class nsMenuBar;
+class nsMenuContainer;
+
+/*
+ * This class represents 3 main classes of menuitems: labels, checkboxes and
+ * radio buttons (with/without an icon)
+ */
+class nsMenuItem final : public nsMenuObject {
+public:
+ nsMenuItem(nsMenuContainer* aParent, nsIContent* aContent);
+ ~nsMenuItem() override;
+
+ nsMenuObject::EType Type() const override;
+
+private:
+ friend class nsMenuItemUncheckSiblingsRunnable;
+
+ enum {
+ eMenuItemFlag_ToggleState = (1 << 0)
+ };
+
+ enum EMenuItemType {
+ eMenuItemType_Normal,
+ eMenuItemType_Radio,
+ eMenuItemType_CheckBox
+ };
+
+ bool IsCheckboxOrRadioItem() const;
+
+ static void item_activated_cb(DbusmenuMenuitem* menuitem,
+ guint timestamp,
+ gpointer user_data);
+ void Activate(uint32_t aTimestamp);
+
+ void CopyAttrFromNodeIfExists(nsIContent* aContent, nsIAtom* aAtom);
+ void UpdateState();
+ void UpdateTypeAndState();
+ void UpdateAccel();
+ nsMenuBar* MenuBar();
+ void UncheckSiblings();
+
+ void InitializeNativeData() override;
+ void UpdateContentAttributes() override;
+ void Update(nsStyleContext* aStyleContext) override;
+ bool IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const override;
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
+
+ void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override;
+
+ EMenuItemType mType;
+
+ bool mIsChecked;
+
+ bool mNeedsUpdate;
+
+ nsCOMPtr<nsIContent> mKeyContent;
+};
+
+#endif /* __nsMenuItem_h__ */
diff --git a/widget/gtk/nsMenuObject.cpp b/widget/gtk/nsMenuObject.cpp
new file mode 100644
index 000000000..58d1716fd
--- /dev/null
+++ b/widget/gtk/nsMenuObject.cpp
@@ -0,0 +1,634 @@
+/* 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/. */
+
+#include "ImageOps.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+#include "nsAttrValue.h"
+#include "nsComputedDOMStyle.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIContentPolicy.h"
+#include "nsIDocument.h"
+#include "nsILoadGroup.h"
+#include "nsImageToPixbuf.h"
+#include "nsIPresShell.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+#include "nsStyleContext.h"
+#include "nsStyleStruct.h"
+#include "nsUnicharUtils.h"
+
+#include "nsMenuContainer.h"
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuDocListener.h"
+
+#include <gdk/gdk.h>
+#include <glib-object.h>
+#include <pango/pango.h>
+
+#include "nsMenuObject.h"
+
+// X11's None clashes with StyleDisplay::None
+#include "X11UndefineNone.h"
+
+#undef None
+
+using namespace mozilla;
+using mozilla::image::ImageOps;
+
+#define MAX_WIDTH 350000
+
+const char* gPropertyStrings[] = {
+#define DBUSMENU_PROPERTY(e, s, b) s,
+ DBUSMENU_PROPERTIES
+#undef DBUSMENU_PROPERTY
+ nullptr
+};
+
+nsWeakMenuObject* nsWeakMenuObject::sHead;
+PangoLayout* gPangoLayout = nullptr;
+
+class nsMenuObjectIconLoader final : public imgINotificationObserver {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ nsMenuObjectIconLoader(nsMenuObject* aOwner) : mOwner(aOwner) { };
+
+ void LoadIcon(nsStyleContext* aStyleContext);
+ void Destroy();
+
+private:
+ ~nsMenuObjectIconLoader() { };
+
+ nsMenuObject* mOwner;
+ RefPtr<imgRequestProxy> mImageRequest;
+ nsCOMPtr<nsIURI> mURI;
+ nsIntRect mImageRect;
+};
+
+NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver)
+
+NS_IMETHODIMP
+nsMenuObjectIconLoader::Notify(imgIRequest* aProxy,
+ int32_t aType, const nsIntRect* aRect) {
+ if (!mOwner) {
+ return NS_OK;
+ }
+
+ if (aProxy != mImageRequest) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ if (NS_FAILED(mImageRequest->GetImageStatus(&status)) ||
+ (status & imgIRequest::STATUS_ERROR)) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ mImageRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+ return NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ return NS_OK;
+ }
+
+ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<imgIContainer> img;
+ mImageRequest->GetImage(getter_AddRefs(img));
+ if (!img) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mImageRect.IsEmpty()) {
+ img = ImageOps::Clip(img, mImageRect);
+ }
+
+ int32_t width, height;
+ img->GetWidth(&width);
+ img->GetHeight(&height);
+
+ if (width <= 0 || height <= 0) {
+ mOwner->ClearIcon();
+ return NS_OK;
+ }
+
+ if (width > 100 || height > 100) {
+ // The icon data needs to go across DBus. Make sure the icon
+ // data isn't too large, else our connection gets terminated and
+ // GDbus helpfully aborts the application. Thank you :)
+ NS_WARNING("Icon data too large");
+ mOwner->ClearIcon();
+ return NS_OK;
+ }
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(img);
+ if (pixbuf) {
+ dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_ICON_DATA,
+ pixbuf);
+ g_object_unref(pixbuf);
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuObjectIconLoader::LoadIcon(nsStyleContext* aStyleContext) {
+ nsIDocument* doc = mOwner->ContentNode()->OwnerDoc();
+
+ nsCOMPtr<nsIURI> uri;
+ nsIntRect imageRect;
+ imgRequestProxy* imageRequest = nullptr;
+
+ nsAutoString uriString;
+ if (mOwner->ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::image,
+ uriString)) {
+ NS_NewURI(getter_AddRefs(uri), uriString);
+ } else {
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return;
+ }
+
+ nsPresContext* pc = shell->GetPresContext();
+ if (!pc || !aStyleContext) {
+ return;
+ }
+
+ const nsStyleList* list = aStyleContext->StyleList();
+ imageRequest = list->GetListStyleImage();
+ if (imageRequest) {
+ imageRequest->GetURI(getter_AddRefs(uri));
+ imageRect = list->mImageRegion.ToNearestPixels(
+ pc->AppUnitsPerDevPixel());
+ }
+ }
+
+ if (!uri) {
+ mOwner->ClearIcon();
+ mURI = nullptr;
+
+ if (mImageRequest) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ }
+
+ return;
+ }
+
+ bool same;
+ if (mURI && NS_SUCCEEDED(mURI->Equals(uri, &same)) && same &&
+ (!imageRequest || imageRect == mImageRect)) {
+ return;
+ }
+
+ if (mImageRequest) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ }
+
+ mURI = uri;
+
+ if (imageRequest) {
+ mImageRect = imageRect;
+ imageRequest->Clone(this, getter_AddRefs(mImageRequest));
+ } else {
+ mImageRect.SetEmpty();
+ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+ RefPtr<imgLoader> loader =
+ nsContentUtils::GetImgLoaderForDocument(doc);
+ if (!loader || !loadGroup) {
+ NS_WARNING("Failed to get loader or load group for image load");
+ return;
+ }
+
+ loader->LoadImage(uri, nullptr, nullptr, mozilla::net::RP_Unset,
+ nullptr, loadGroup, this, nullptr, nullptr,
+ nsIRequest::LOAD_NORMAL, nullptr,
+ nsIContentPolicy::TYPE_IMAGE, EmptyString(),
+ getter_AddRefs(mImageRequest));
+ }
+}
+
+void
+nsMenuObjectIconLoader::Destroy() {
+ if (mImageRequest) {
+ mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ }
+
+ mOwner = nullptr;
+}
+
+static int
+CalculateTextWidth(const nsAString& aText) {
+ if (!gPangoLayout) {
+ PangoFontMap* fontmap = pango_cairo_font_map_get_default();
+ PangoContext* ctx = pango_font_map_create_context(fontmap);
+ gPangoLayout = pango_layout_new(ctx);
+ g_object_unref(ctx);
+ }
+
+ pango_layout_set_text(gPangoLayout, NS_ConvertUTF16toUTF8(aText).get(), -1);
+
+ int width, dummy;
+ pango_layout_get_size(gPangoLayout, &width, &dummy);
+
+ return width;
+}
+
+static const nsDependentString
+GetEllipsis() {
+ static char16_t sBuf[4] = { 0, 0, 0, 0 };
+ if (!sBuf[0]) {
+ nsAdoptingString ellipsis = Preferences::GetLocalizedString("intl.ellipsis");
+ if (!ellipsis.IsEmpty()) {
+ uint32_t l = ellipsis.Length();
+ const nsAdoptingString::char_type* c = ellipsis.BeginReading();
+ uint32_t i = 0;
+ while (i < 3 && i < l) {
+ sBuf[i++] =* (c++);
+ }
+ } else {
+ sBuf[0] = '.';
+ sBuf[1] = '.';
+ sBuf[2] = '.';
+ }
+ }
+
+ return nsDependentString(sBuf);
+}
+
+static int
+GetEllipsisWidth() {
+ static int sEllipsisWidth = -1;
+
+ if (sEllipsisWidth == -1) {
+ sEllipsisWidth = CalculateTextWidth(GetEllipsis());
+ }
+
+ return sEllipsisWidth;
+}
+
+nsMenuObject::nsMenuObject(nsMenuContainer* aParent, nsIContent* aContent) :
+ mContent(aContent),
+ mListener(aParent->DocListener()),
+ mParent(aParent),
+ mNativeData(nullptr) {
+ MOZ_ASSERT(mContent);
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mParent);
+}
+
+nsMenuObject::nsMenuObject(nsNativeMenuDocListener* aListener,
+ nsIContent* aContent) :
+ mContent(aContent),
+ mListener(aListener),
+ mParent(nullptr),
+ mNativeData(nullptr) {
+ MOZ_ASSERT(mContent);
+ MOZ_ASSERT(mListener);
+}
+
+void
+nsMenuObject::UpdateLabel() {
+ // Goanna stores the label and access key in separate attributes
+ // so we need to convert label="Foo_Bar"/accesskey="F" in to
+ // label="_Foo__Bar" for dbusmenu
+
+ nsAutoString label;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
+
+ nsAutoString accesskey;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey);
+
+ const nsAutoString::char_type* akey = accesskey.BeginReading();
+ char16_t keyLower = ToLowerCase(*akey);
+ char16_t keyUpper = ToUpperCase(*akey);
+
+ const nsAutoString::char_type* iter = label.BeginReading();
+ const nsAutoString::char_type* end = label.EndReading();
+ uint32_t length = label.Length();
+ uint32_t pos = 0;
+ bool foundAccessKey = false;
+
+ while (iter != end) {
+ if (*iter != char16_t('_')) {
+ if ((*iter != keyLower &&* iter != keyUpper) || foundAccessKey) {
+ ++iter;
+ ++pos;
+ continue;
+ }
+ foundAccessKey = true;
+ }
+
+ label.SetLength(++length);
+
+ iter = label.BeginReading() + pos;
+ end = label.EndReading();
+ nsAutoString::char_type* cur = label.BeginWriting() + pos;
+
+ memmove(cur + 1, cur, (length - 1 - pos)* sizeof(nsAutoString::char_type));
+ * cur = nsAutoString::char_type('_');
+
+ iter += 2;
+ pos += 2;
+ }
+
+ if (CalculateTextWidth(label) <= MAX_WIDTH) {
+ dbusmenu_menuitem_property_set(mNativeData,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ NS_ConvertUTF16toUTF8(label).get());
+ return;
+ }
+
+ // This sucks.
+ // This should be done at the point where the menu is drawn (hello Unity),
+ // but unfortunately it doesn't do that and will happily fill your entire
+ // screen width with a menu if you have a bookmark with a really long title.
+ // This leaves us with no other option but to ellipsize here, with no proper
+ // knowledge of Unity's render path, font size etc. This is better than nothing
+ nsAutoString truncated;
+ int target = MAX_WIDTH - GetEllipsisWidth();
+ length = label.Length();
+
+ static nsIContent::AttrValuesArray strings[] = {
+ &nsGkAtoms::left, &nsGkAtoms::start,
+ &nsGkAtoms::center, &nsGkAtoms::right,
+ &nsGkAtoms::end, nullptr
+ };
+
+ int32_t type = mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::crop,
+ strings, eCaseMatters);
+
+ switch (type) {
+ case 0:
+ case 1:
+ // FIXME: Implement left cropping
+ case 2:
+ // FIXME: Implement center cropping
+ case 3:
+ case 4:
+ default:
+ for (uint32_t i = 0; i < length; i++) {
+ truncated.Append(label.CharAt(i));
+ if (CalculateTextWidth(truncated) > target) {
+ break;
+ }
+ }
+
+ truncated.Append(GetEllipsis());
+ }
+
+ dbusmenu_menuitem_property_set(mNativeData,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ NS_ConvertUTF16toUTF8(truncated).get());
+}
+
+void
+nsMenuObject::UpdateVisibility(nsStyleContext* aStyleContext) {
+ bool vis = true;
+
+ if (aStyleContext &&
+ (aStyleContext->StyleDisplay()->mDisplay == StyleDisplay::None ||
+ aStyleContext->StyleVisibility()->mVisible ==
+ NS_STYLE_VISIBILITY_COLLAPSE)) {
+ vis = false;
+ }
+
+ dbusmenu_menuitem_property_set_bool(mNativeData,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ vis);
+}
+
+void
+nsMenuObject::UpdateSensitivity() {
+ bool disabled = mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+
+ dbusmenu_menuitem_property_set_bool(mNativeData,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ !disabled);
+
+}
+
+void
+nsMenuObject::UpdateIcon(nsStyleContext* aStyleContext) {
+ if (ShouldShowIcon()) {
+ if (!mIconLoader) {
+ mIconLoader = new nsMenuObjectIconLoader(this);
+ }
+
+ mIconLoader->LoadIcon(aStyleContext);
+ } else {
+ if (mIconLoader) {
+ mIconLoader->Destroy();
+ mIconLoader = nullptr;
+ }
+
+ ClearIcon();
+ }
+}
+
+already_AddRefed<nsStyleContext>
+nsMenuObject::GetStyleContext() {
+ nsIPresShell* shell = mContent->OwnerDoc()->GetShell();
+ if (!shell) {
+ return nullptr;
+ }
+
+ RefPtr<nsStyleContext> sc =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(
+ mContent->AsElement(), nullptr, shell);
+
+ return sc.forget();
+}
+
+void
+nsMenuObject::InitializeNativeData() {
+}
+
+nsMenuObject::PropertyFlags
+nsMenuObject::SupportedProperties() const {
+ return static_cast<nsMenuObject::PropertyFlags>(0);
+}
+
+bool
+nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const {
+ return true;
+}
+
+void
+nsMenuObject::UpdateContentAttributes() {
+}
+
+void
+nsMenuObject::Update(nsStyleContext* aStyleContext) {
+}
+
+bool
+nsMenuObject::ShouldShowIcon() const {
+ // Ideally we want to know the visibility of the anonymous XUL image in
+ // our menuitem, but this isn't created because we don't have a frame.
+ // The following works by default (because xul.css hides images in menuitems
+ // that don't have the "menuitem-with-favicon" class). It's possible a third
+ // party theme could override this, but, oh well...
+ const nsAttrValue* classes = mContent->AsElement()->GetClasses();
+ if (!classes) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) {
+ if (classes->AtomAt(i) == nsNativeMenuAtoms::menuitem_with_favicon) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsMenuObject::ClearIcon() {
+ dbusmenu_menuitem_property_remove(mNativeData,
+ DBUSMENU_MENUITEM_PROP_ICON_DATA);
+}
+
+nsMenuObject::~nsMenuObject() {
+ nsWeakMenuObject::NotifyDestroyed(this);
+
+ if (mIconLoader) {
+ mIconLoader->Destroy();
+ }
+
+ if (mListener) {
+ mListener->UnregisterForContentChanges(mContent);
+ }
+
+ if (mNativeData) {
+ g_object_unref(mNativeData);
+ mNativeData = nullptr;
+ }
+}
+
+void
+nsMenuObject::CreateNativeData() {
+ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
+
+ mNativeData = dbusmenu_menuitem_new();
+ InitializeNativeData();
+ if (mParent && mParent->IsBeingDisplayed()) {
+ ContainerIsOpening();
+ }
+
+ mListener->RegisterForContentChanges(mContent, this);
+}
+
+nsresult
+nsMenuObject::AdoptNativeData(DbusmenuMenuitem* aNativeData) {
+ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
+
+ if (!IsCompatibleWithNativeData(aNativeData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mNativeData = aNativeData;
+ g_object_ref(mNativeData);
+
+ PropertyFlags supported = SupportedProperties();
+ PropertyFlags mask = static_cast<PropertyFlags>(1);
+
+ for (uint32_t i = 0; gPropertyStrings[i]; ++i) {
+ if (!(mask & supported)) {
+ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]);
+ }
+ mask = static_cast<PropertyFlags>(mask << 1);
+ }
+
+ InitializeNativeData();
+ if (mParent && mParent->IsBeingDisplayed()) {
+ ContainerIsOpening();
+ }
+
+ mListener->RegisterForContentChanges(mContent, this);
+
+ return NS_OK;
+}
+
+void
+nsMenuObject::ContainerIsOpening() {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ UpdateContentAttributes();
+
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ Update(sc);
+}
+
+/* static */ void
+nsWeakMenuObject::AddWeakReference(nsWeakMenuObject* aWeak) {
+ aWeak->mPrev = sHead;
+ sHead = aWeak;
+}
+
+/* static */ void
+nsWeakMenuObject::RemoveWeakReference(nsWeakMenuObject* aWeak) {
+ if (aWeak == sHead) {
+ sHead = aWeak->mPrev;
+ return;
+ }
+
+ nsWeakMenuObject* weak = sHead;
+ while (weak && weak->mPrev != aWeak) {
+ weak = weak->mPrev;
+ }
+
+ if (weak) {
+ weak->mPrev = aWeak->mPrev;
+ }
+}
+
+/* static */ void
+nsWeakMenuObject::NotifyDestroyed(nsMenuObject* aMenuObject) {
+ nsWeakMenuObject* weak = sHead;
+ while (weak) {
+ if (weak->mMenuObject == aMenuObject) {
+ weak->mMenuObject = nullptr;
+ }
+
+ weak = weak->mPrev;
+ }
+}
diff --git a/widget/gtk/nsMenuObject.h b/widget/gtk/nsMenuObject.h
new file mode 100644
index 000000000..c7637cd05
--- /dev/null
+++ b/widget/gtk/nsMenuObject.h
@@ -0,0 +1,165 @@
+/* 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 __nsMenuObject_h__
+#define __nsMenuObject_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+
+#include "nsDbusmenu.h"
+#include "nsNativeMenuDocListener.h"
+
+class nsIAtom;
+class nsIContent;
+class nsStyleContext;
+class nsMenuContainer;
+class nsMenuObjectIconLoader;
+
+#define DBUSMENU_PROPERTIES \
+ DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \
+ DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \
+ DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \
+ DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \
+ DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \
+ DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \
+ DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \
+ DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \
+ DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8)
+
+/*
+ * This is the base class for all menu nodes. Each instance represents
+ * a single node in the menu hierarchy. It wraps the corresponding DOM node and
+ * native menu node, keeps them in sync and transfers events between the two.
+ * It is not reference counted - each node is owned by its parent (the top
+ * level menubar is owned by the window) and keeps a weak pointer to its
+ * parent (which is guaranteed to always be valid because a node will never
+ * outlive its parent). It is not safe to keep a reference to nsMenuObject
+ * externally.
+ */
+class nsMenuObject : public nsNativeMenuChangeObserver {
+public:
+ enum EType {
+ eType_MenuBar,
+ eType_Menu,
+ eType_MenuItem
+ };
+
+ virtual ~nsMenuObject();
+
+ // Get the native menu item node
+ DbusmenuMenuitem* GetNativeData() const { return mNativeData; }
+
+ // Get the parent menu object
+ nsMenuContainer* Parent() const { return mParent; }
+
+ // Get the content node
+ nsIContent* ContentNode() const { return mContent; }
+
+ // Get the type of this node. Must be provided by subclasses
+ virtual EType Type() const = 0;
+
+ // Get the document listener
+ nsNativeMenuDocListener* DocListener() const { return mListener; }
+
+ // Create the native menu item node (called by containers)
+ void CreateNativeData();
+
+ // Adopt the specified native menu item node (called by containers)
+ nsresult AdoptNativeData(DbusmenuMenuitem* aNativeData);
+
+ // Called by the container to tell us that it's opening
+ void ContainerIsOpening();
+
+protected:
+ nsMenuObject(nsMenuContainer* aParent, nsIContent* aContent);
+ nsMenuObject(nsNativeMenuDocListener* aListener, nsIContent* aContent);
+
+ enum PropertyFlags {
+#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b),
+ DBUSMENU_PROPERTIES
+#undef DBUSMENU_PROPERTY
+ };
+
+ void UpdateLabel();
+ void UpdateVisibility(nsStyleContext* aStyleContext);
+ void UpdateSensitivity();
+ void UpdateIcon(nsStyleContext* aStyleContext);
+
+ already_AddRefed<nsStyleContext> GetStyleContext();
+
+private:
+ friend class nsMenuObjectIconLoader;
+
+ // Set up initial properties on the native data, connect to signals etc.
+ // This should be implemented by subclasses
+ virtual void InitializeNativeData();
+
+ // Return the properties that this menu object type supports
+ // This should be implemented by subclasses
+ virtual PropertyFlags SupportedProperties() const;
+
+ // Determine whether this menu object could use the specified
+ // native item. Returns true by default but can be overridden by subclasses
+ virtual bool
+ IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const;
+
+ // Update attributes on this objects content node when the container opens.
+ // This is called before style resolution, and should be implemented by
+ // subclasses who want to modify attributes that might affect style.
+ // This will not be called when there are script blockers
+ virtual void UpdateContentAttributes();
+
+ // Update properties that should be refreshed when the container opens.
+ // This should be implemented by subclasses that have properties which
+ // need refreshing
+ virtual void Update(nsStyleContext* aStyleContext);
+
+ bool ShouldShowIcon() const;
+ void ClearIcon();
+
+ nsCOMPtr<nsIContent> mContent;
+ // mListener is a strong ref for simplicity - someone in the tree needs to
+ // own it, and this only really needs to be the top-level object (as no
+ // children outlives their parent). However, we need to keep it alive until
+ // after running the nsMenuObject destructor for the top-level menu object,
+ // hence the strong ref
+ RefPtr<nsNativeMenuDocListener> mListener;
+ nsMenuContainer* mParent; // [weak]
+ DbusmenuMenuitem* mNativeData; // [strong]
+ RefPtr<nsMenuObjectIconLoader> mIconLoader;
+};
+
+// Keep a weak pointer to a menu object
+class nsWeakMenuObject {
+public:
+ nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {}
+
+ nsWeakMenuObject(nsMenuObject* aMenuObject) :
+ mPrev(nullptr), mMenuObject(aMenuObject)
+ {
+ AddWeakReference(this);
+ }
+
+ ~nsWeakMenuObject() { RemoveWeakReference(this); }
+
+ nsMenuObject* get() const { return mMenuObject; }
+
+ nsMenuObject* operator->() const { return mMenuObject; }
+
+ explicit operator bool() const { return !!mMenuObject; }
+
+ static void NotifyDestroyed(nsMenuObject* aMenuObject);
+
+private:
+ static void AddWeakReference(nsWeakMenuObject* aWeak);
+ static void RemoveWeakReference(nsWeakMenuObject* aWeak);
+
+ nsWeakMenuObject* mPrev;
+ static nsWeakMenuObject* sHead;
+
+ nsMenuObject* mMenuObject;
+};
+
+#endif /* __nsMenuObject_h__ */
diff --git a/widget/gtk/nsMenuSeparator.cpp b/widget/gtk/nsMenuSeparator.cpp
new file mode 100644
index 000000000..893c5c7f0
--- /dev/null
+++ b/widget/gtk/nsMenuSeparator.cpp
@@ -0,0 +1,74 @@
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+#include "nsAutoPtr.h"
+#include "nsCRT.h"
+#include "nsGkAtoms.h"
+#include "nsStyleContext.h"
+
+#include "nsDbusmenu.h"
+
+#include "nsMenuContainer.h"
+#include "nsMenuSeparator.h"
+
+using namespace mozilla;
+
+void
+nsMenuSeparator::InitializeNativeData() {
+ dbusmenu_menuitem_property_set(GetNativeData(),
+ DBUSMENU_MENUITEM_PROP_TYPE,
+ "separator");
+}
+
+void
+nsMenuSeparator::Update(nsStyleContext* aContext) {
+ UpdateVisibility(aContext);
+}
+
+bool
+nsMenuSeparator::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const {
+ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
+ DBUSMENU_MENUITEM_PROP_TYPE),
+ "separator") == 0;
+}
+
+nsMenuObject::PropertyFlags
+nsMenuSeparator::SupportedProperties() const {
+ return static_cast<nsMenuObject::PropertyFlags>(
+ nsMenuObject::ePropVisible |
+ nsMenuObject::ePropType
+ );
+}
+
+void
+nsMenuSeparator::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {
+ MOZ_ASSERT(aContent == ContentNode(), "Received an event that wasn't meant for us!");
+
+ if (!Parent()->IsBeingDisplayed()) {
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed) {
+ RefPtr<nsStyleContext> sc = GetStyleContext();
+ UpdateVisibility(sc);
+ }
+}
+
+nsMenuSeparator::nsMenuSeparator(nsMenuContainer* aParent,
+ nsIContent* aContent) :
+ nsMenuObject(aParent, aContent) {
+ MOZ_COUNT_CTOR(nsMenuSeparator);
+}
+
+nsMenuSeparator::~nsMenuSeparator() {
+ MOZ_COUNT_DTOR(nsMenuSeparator);
+}
+
+nsMenuObject::EType
+nsMenuSeparator::Type() const {
+ return eType_MenuItem;
+}
diff --git a/widget/gtk/nsMenuSeparator.h b/widget/gtk/nsMenuSeparator.h
new file mode 100644
index 000000000..9ba770a85
--- /dev/null
+++ b/widget/gtk/nsMenuSeparator.h
@@ -0,0 +1,33 @@
+/* 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 __nsMenuSeparator_h__
+#define __nsMenuSeparator_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsMenuObject.h"
+
+class nsIContent;
+class nsIAtom;
+class nsMenuContainer;
+
+// Menu separator class
+class nsMenuSeparator final : public nsMenuObject {
+public:
+ nsMenuSeparator(nsMenuContainer* aParent, nsIContent* aContent);
+ ~nsMenuSeparator();
+
+ nsMenuObject::EType Type() const override;
+
+private:
+ void InitializeNativeData() override;
+ void Update(nsStyleContext* aStyleContext) override;
+ bool IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const override;
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
+
+ void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override;
+};
+
+#endif /* __nsMenuSeparator_h__ */
diff --git a/widget/gtk/nsNativeMenuAtomList.h b/widget/gtk/nsNativeMenuAtomList.h
new file mode 100644
index 000000000..4a8b3869a
--- /dev/null
+++ b/widget/gtk/nsNativeMenuAtomList.h
@@ -0,0 +1,9 @@
+/* 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/. */
+
+WIDGET_ATOM2(menuitem_with_favicon, "menuitem-with-favicon")
+WIDGET_ATOM2(_moz_menubarkeeplocal, "_moz-menubarkeeplocal")
+WIDGET_ATOM2(_moz_nativemenupopupstate, "_moz-nativemenupopupstate")
+WIDGET_ATOM(openedwithkey)
+WIDGET_ATOM(shellshowingmenubar)
diff --git a/widget/gtk/nsNativeMenuAtoms.cpp b/widget/gtk/nsNativeMenuAtoms.cpp
new file mode 100644
index 000000000..f43d8b24b
--- /dev/null
+++ b/widget/gtk/nsNativeMenuAtoms.cpp
@@ -0,0 +1,35 @@
+/* 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/. */
+
+#include "nsIAtom.h"
+#include "nsStaticAtom.h"
+
+#include "nsNativeMenuAtoms.h"
+
+using namespace mozilla;
+
+#define WIDGET_ATOM(_name) nsIAtom* nsNativeMenuAtoms::_name;
+#define WIDGET_ATOM2(_name, _value) nsIAtom* nsNativeMenuAtoms::_name;
+#include "nsNativeMenuAtomList.h"
+#undef WIDGET_ATOM
+#undef WIDGET_ATOM2
+
+#define WIDGET_ATOM(name_) NS_STATIC_ATOM_BUFFER(name_##_buffer, #name_)
+#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsNativeMenuAtomList.h"
+#undef WIDGET_ATOM
+#undef WIDGET_ATOM2
+
+static const nsStaticAtom gAtoms[] = {
+#define WIDGET_ATOM(name_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_),
+#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_),
+#include "nsNativeMenuAtomList.h"
+#undef WIDGET_ATOM
+#undef WIDGET_ATOM2
+};
+
+/* static */ void
+nsNativeMenuAtoms::RegisterAtoms() {
+ NS_RegisterStaticAtoms(gAtoms);
+}
diff --git a/widget/gtk/nsNativeMenuAtoms.h b/widget/gtk/nsNativeMenuAtoms.h
new file mode 100644
index 000000000..4a9766ee8
--- /dev/null
+++ b/widget/gtk/nsNativeMenuAtoms.h
@@ -0,0 +1,23 @@
+/* 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 __nsNativeMenuAtoms_h__
+#define __nsNativeMenuAtoms_h__
+
+class nsIAtom;
+
+class nsNativeMenuAtoms {
+public:
+ nsNativeMenuAtoms() = delete;
+
+ static void RegisterAtoms();
+
+#define WIDGET_ATOM(_name) static nsIAtom* _name;
+#define WIDGET_ATOM2(_name, _value) static nsIAtom* _name;
+#include "nsNativeMenuAtomList.h"
+#undef WIDGET_ATOM
+#undef WIDGET_ATOM2
+};
+
+#endif /* __nsNativeMenuAtoms_h__ */
diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp
new file mode 100644
index 000000000..46a9c3aa9
--- /dev/null
+++ b/widget/gtk/nsNativeMenuDocListener.cpp
@@ -0,0 +1,329 @@
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+
+#include "nsMenuContainer.h"
+
+#include "nsNativeMenuDocListener.h"
+
+using namespace mozilla;
+
+uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 0;
+
+nsNativeMenuDocListenerTArray* gPendingListeners;
+
+/*
+ * Small helper which caches a single listener, so that consecutive
+ * events which go to the same node avoid multiple hash table lookups
+ */
+class MOZ_STACK_CLASS DispatchHelper {
+public:
+ DispatchHelper(nsNativeMenuDocListener* aListener,
+ nsIContent* aContent
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
+ mObserver(nullptr) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ if (aContent == aListener->mLastSource) {
+ mObserver = aListener->mLastTarget;
+ } else {
+ mObserver = aListener->mContentToObserverTable.Get(aContent);
+ if (mObserver) {
+ aListener->mLastSource = aContent;
+ aListener->mLastTarget = mObserver;
+ }
+ }
+ }
+
+ ~DispatchHelper() { };
+
+ nsNativeMenuChangeObserver* Observer() const {
+ return mObserver;
+ }
+
+ bool HasObserver() const {
+ return !!mObserver;
+ }
+
+private:
+ nsNativeMenuChangeObserver* mObserver;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver)
+
+nsNativeMenuDocListener::~nsNativeMenuDocListener() {
+ MOZ_ASSERT(mContentToObserverTable.Count() == 0,
+ "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)");
+ MOZ_COUNT_DTOR(nsNativeMenuDocListener);
+}
+
+void
+nsNativeMenuDocListener::AttributeChanged(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (sUpdateBlockersCount == 0) {
+ DoAttributeChanged(aElement, aAttribute);
+ return;
+ }
+
+ MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord);
+ m->mType = MutationRecord::eAttributeChanged;
+ m->mTarget = aElement;
+ m->mAttribute = aAttribute;
+
+ ScheduleFlush(this);
+}
+
+void
+nsNativeMenuDocListener::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer) {
+ for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
+ ContentInserted(aDocument, aContainer, c, 0);
+ }
+}
+
+void
+nsNativeMenuDocListener::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer) {
+ nsIContent* prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild);
+
+ if (sUpdateBlockersCount == 0) {
+ DoContentInserted(aContainer, aChild, prevSibling);
+ return;
+ }
+
+ MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord);
+ m->mType = MutationRecord::eContentInserted;
+ m->mTarget = aContainer;
+ m->mChild = aChild;
+ m->mPrevSibling = prevSibling;
+
+ ScheduleFlush(this);
+}
+
+void
+nsNativeMenuDocListener::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling) {
+ if (sUpdateBlockersCount == 0) {
+ DoContentRemoved(aContainer, aChild);
+ return;
+ }
+
+ MutationRecord* m =* mPendingMutations.AppendElement(new MutationRecord);
+ m->mType = MutationRecord::eContentRemoved;
+ m->mTarget = aContainer;
+ m->mChild = aChild;
+
+ ScheduleFlush(this);
+}
+
+void
+nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode* aNode) {
+ mDocument = nullptr;
+}
+
+void
+nsNativeMenuDocListener::DoAttributeChanged(nsIContent* aContent,
+ nsIAtom* aAttribute) {
+ DispatchHelper h(this, aContent);
+ if (h.HasObserver()) {
+ h.Observer()->OnAttributeChanged(aContent, aAttribute);
+ }
+}
+
+void
+nsNativeMenuDocListener::DoContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) {
+ DispatchHelper h(this, aContainer);
+ if (h.HasObserver()) {
+ h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling);
+ }
+}
+
+void
+nsNativeMenuDocListener::DoContentRemoved(nsIContent* aContainer,
+ nsIContent* aChild) {
+ DispatchHelper h(this, aContainer);
+ if (h.HasObserver()) {
+ h.Observer()->OnContentRemoved(aContainer, aChild);
+ }
+}
+
+void
+nsNativeMenuDocListener::DoBeginUpdates(nsIContent* aTarget) {
+ DispatchHelper h(this, aTarget);
+ if (h.HasObserver()) {
+ h.Observer()->OnBeginUpdates(aTarget);
+ }
+}
+
+void
+nsNativeMenuDocListener::DoEndUpdates(nsIContent* aTarget) {
+ DispatchHelper h(this, aTarget);
+ if (h.HasObserver()) {
+ h.Observer()->OnEndUpdates();
+ }
+}
+
+void
+nsNativeMenuDocListener::FlushPendingMutations() {
+ nsIContent* currentTarget = nullptr;
+ bool inUpdateSequence = false;
+
+ while (mPendingMutations.Length() > 0) {
+ MutationRecord* m = mPendingMutations[0];
+
+ if (m->mTarget != currentTarget) {
+ if (inUpdateSequence) {
+ DoEndUpdates(currentTarget);
+ inUpdateSequence = false;
+ }
+
+ currentTarget = m->mTarget;
+
+ if (mPendingMutations.Length() > 1 &&
+ mPendingMutations[1]->mTarget == currentTarget) {
+ DoBeginUpdates(currentTarget);
+ inUpdateSequence = true;
+ }
+ }
+
+ switch (m->mType) {
+ case MutationRecord::eAttributeChanged:
+ DoAttributeChanged(m->mTarget, m->mAttribute);
+ break;
+ case MutationRecord::eContentInserted:
+ DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling);
+ break;
+ case MutationRecord::eContentRemoved:
+ DoContentRemoved(m->mTarget, m->mChild);
+ break;
+ default:
+ NS_NOTREACHED("Invalid type");
+ }
+
+ mPendingMutations.RemoveElementAt(0);
+ }
+
+ if (inUpdateSequence) {
+ DoEndUpdates(currentTarget);
+ }
+}
+
+/* static */ void
+nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener* aListener) {
+ MOZ_ASSERT(sUpdateBlockersCount > 0, "Shouldn't be doing this now");
+
+ if (!gPendingListeners) {
+ gPendingListeners = new nsNativeMenuDocListenerTArray;
+ }
+
+ if (gPendingListeners->IndexOf(aListener) ==
+ nsNativeMenuDocListenerTArray::NoIndex) {
+ gPendingListeners->AppendElement(aListener);
+ }
+}
+
+/* static */ void
+nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener* aListener) {
+ if (!gPendingListeners) {
+ return;
+ }
+
+ gPendingListeners->RemoveElement(aListener);
+}
+
+/* static */ void
+nsNativeMenuDocListener::RemoveUpdateBlocker() {
+ if (sUpdateBlockersCount == 1 && gPendingListeners) {
+ while (gPendingListeners->Length() > 0) {
+ (*gPendingListeners)[0]->FlushPendingMutations();
+ gPendingListeners->RemoveElementAt(0);
+ }
+ }
+
+ MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!");
+ sUpdateBlockersCount--;
+}
+
+nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent* aRootNode) :
+ mRootNode(aRootNode),
+ mDocument(nullptr),
+ mLastSource(nullptr),
+ mLastTarget(nullptr) {
+ MOZ_COUNT_CTOR(nsNativeMenuDocListener);
+}
+
+void
+nsNativeMenuDocListener::RegisterForContentChanges(nsIContent* aContent,
+ nsNativeMenuChangeObserver* aObserver) {
+ MOZ_ASSERT(aContent, "Need content parameter");
+ MOZ_ASSERT(aObserver, "Need observer parameter");
+ if (!aContent || !aObserver) {
+ return;
+ }
+
+ DebugOnly<nsNativeMenuChangeObserver* > old;
+ MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver,
+ "Multiple observers for the same content node are not supported");
+
+ mContentToObserverTable.Put(aContent, aObserver);
+}
+
+void
+nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent* aContent) {
+ MOZ_ASSERT(aContent, "Need content parameter");
+ if (!aContent) {
+ return;
+ }
+
+ mContentToObserverTable.Remove(aContent);
+ if (aContent == mLastSource) {
+ mLastSource = nullptr;
+ mLastTarget = nullptr;
+ }
+}
+
+void
+nsNativeMenuDocListener::Start() {
+ if (mDocument) {
+ return;
+ }
+
+ mDocument = mRootNode->OwnerDoc();
+ if (!mDocument) {
+ return;
+ }
+
+ mDocument->AddMutationObserver(this);
+}
+
+void
+nsNativeMenuDocListener::Stop() {
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+
+ CancelFlush(this);
+ mPendingMutations.Clear();
+}
diff --git a/widget/gtk/nsNativeMenuDocListener.h b/widget/gtk/nsNativeMenuDocListener.h
new file mode 100644
index 000000000..c0a503da1
--- /dev/null
+++ b/widget/gtk/nsNativeMenuDocListener.h
@@ -0,0 +1,146 @@
+/* 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 __nsNativeMenuDocListener_h__
+#define __nsNativeMenuDocListener_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/RefPtr.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsStubMutationObserver.h"
+#include "nsTArray.h"
+
+class nsIAtom;
+class nsIContent;
+class nsIDocument;
+class nsNativeMenuChangeObserver;
+
+/*
+ * This class keeps a mapping of content nodes to observers and forwards DOM
+ * mutations to these. There is exactly one of these for every menubar.
+ */
+class nsNativeMenuDocListener final : nsStubMutationObserver {
+public:
+ NS_DECL_ISUPPORTS
+
+ nsNativeMenuDocListener(nsIContent* aRootNode);
+
+ // Register an observer to receive mutation events for the specified
+ // content node. The caller must keep the observer alive until
+ // UnregisterForContentChanges is called.
+ void RegisterForContentChanges(nsIContent* aContent,
+ nsNativeMenuChangeObserver* aObserver);
+
+ // Unregister the registered observer for the specified content node
+ void UnregisterForContentChanges(nsIContent* aContent);
+
+ // Start listening to the document and forwarding DOM mutations to
+ // registered observers.
+ void Start();
+
+ // Stop listening to the document. No DOM mutations will be forwarded
+ // to registered observers.
+ void Stop();
+
+ /*
+ * This class is intended to be used inside GObject signal handlers.
+ * It allows us to queue updates until we have finished delivering
+ * events to Goanna, and then we can batch updates to our view of the
+ * menu. This allows us to do menu updates without altering the structure
+ * seen by the OS.
+ */
+ class MOZ_STACK_CLASS BlockUpdatesScope {
+ public:
+ BlockUpdatesScope(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ nsNativeMenuDocListener::AddUpdateBlocker();
+ }
+
+ ~BlockUpdatesScope() {
+ nsNativeMenuDocListener::RemoveUpdateBlocker();
+ }
+
+ private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ };
+
+private:
+ friend class DispatchHelper;
+
+ struct MutationRecord {
+ enum RecordType {
+ eAttributeChanged,
+ eContentInserted,
+ eContentRemoved
+ } mType;
+
+ nsCOMPtr<nsIContent> mTarget;
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> mPrevSibling;
+ nsCOMPtr<nsIAtom> mAttribute;
+ };
+
+ ~nsNativeMenuDocListener();
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ void DoAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute);
+ void DoContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling);
+ void DoContentRemoved(nsIContent* aContainer, nsIContent* aChild);
+ void DoBeginUpdates(nsIContent* aTarget);
+ void DoEndUpdates(nsIContent* aTarget);
+
+ void FlushPendingMutations();
+ static void ScheduleFlush(nsNativeMenuDocListener* aListener);
+ static void CancelFlush(nsNativeMenuDocListener* aListener);
+
+ static void AddUpdateBlocker() {
+ ++sUpdateBlockersCount;
+ }
+ static void RemoveUpdateBlocker();
+
+ nsCOMPtr<nsIContent> mRootNode;
+ nsIDocument* mDocument;
+ nsIContent* mLastSource;
+ nsNativeMenuChangeObserver* mLastTarget;
+ nsTArray<nsAutoPtr<MutationRecord> > mPendingMutations;
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsNativeMenuChangeObserver* > mContentToObserverTable;
+
+ static uint32_t sUpdateBlockersCount;
+};
+
+typedef nsTArray<RefPtr<nsNativeMenuDocListener> > nsNativeMenuDocListenerTArray;
+
+/*
+ * Implemented by classes that want to listen to mutation events from content
+ * nodes.
+ */
+class nsNativeMenuChangeObserver {
+public:
+ virtual void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {}
+
+ virtual void OnContentInserted(nsIContent* aContainer,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) {}
+
+ virtual void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {}
+
+ // Signals the start of a sequence of more than 1 event for the specified
+ // node. This only happens when events are flushed as all BlockUpdatesScope
+ // instances go out of scope
+ virtual void OnBeginUpdates(nsIContent* aContent) {};
+
+ // Signals the end of a sequence of events
+ virtual void OnEndUpdates() {};
+};
+
+#endif /* __nsNativeMenuDocListener_h__ */
diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp
new file mode 100644
index 000000000..eabe77ca8
--- /dev/null
+++ b/widget/gtk/nsNativeMenuService.cpp
@@ -0,0 +1,485 @@
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsGtkUtils.h"
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
+#include "prlink.h"
+
+#include "nsDbusmenu.h"
+#include "nsMenuBar.h"
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuDocListener.h"
+
+#include <glib-object.h>
+#include <pango/pango.h>
+#include <stdlib.h>
+
+#include "nsNativeMenuService.h"
+
+using namespace mozilla;
+
+nsNativeMenuService* nsNativeMenuService::sService = nullptr;
+
+extern PangoLayout* gPangoLayout;
+extern nsNativeMenuDocListenerTArray* gPendingListeners;
+
+static const nsTArray<nsMenuBar* >::index_type NoIndex = nsTArray<nsMenuBar* >::NoIndex;
+
+#if not GLIB_CHECK_VERSION(2,26,0)
+enum GBusType {
+ G_BUS_TYPE_STARTER = -1,
+ G_BUS_TYPE_NONE = 0,
+ G_BUS_TYPE_SYSTEM = 1,
+ G_BUS_TYPE_SESSION = 2
+};
+
+enum GDBusProxyFlags {
+ G_DBUS_PROXY_FLAGS_NONE = 0,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = 1 << 0,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = 1 << 1,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = 1 << 2,
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = 1 << 3
+};
+
+enum GDBusCallFlags {
+ G_DBUS_CALL_FLAGS_NONE = 0,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START = 1 << 0
+};
+
+typedef _GDBusInterfaceInfo GDBusInterfaceInfo;
+typedef _GDBusProxy GDBusProxy;
+typedef _GVariant GVariant;
+#endif
+
+#undef g_dbus_proxy_new_for_bus
+#undef g_dbus_proxy_new_for_bus_finish
+#undef g_dbus_proxy_call
+#undef g_dbus_proxy_call_finish
+#undef g_dbus_proxy_get_name_owner
+
+typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags,
+ GDBusInterfaceInfo*,
+ const gchar*, const gchar*,
+ const gchar*, GCancellable*,
+ GAsyncReadyCallback, gpointer);
+
+typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*,
+ GError**);
+typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*,
+ GDBusCallFlags, gint, GCancellable*,
+ GAsyncReadyCallback, gpointer);
+typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*,
+ GError**);
+typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*);
+
+static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus;
+static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish;
+static _g_dbus_proxy_call_fn _g_dbus_proxy_call;
+static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish;
+static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner;
+
+#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus
+#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish
+#define g_dbus_proxy_call _g_dbus_proxy_call
+#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish
+#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner
+
+static PRLibrary* gGIOLib = nullptr;
+
+static nsresult
+GDBusInit() {
+ gGIOLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gGIOLib) {
+ return NS_ERROR_FAILURE;
+ }
+
+ g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus");
+ g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish");
+ g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call");
+ g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish");
+ g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner");
+
+ if (!g_dbus_proxy_new_for_bus ||
+ !g_dbus_proxy_new_for_bus_finish ||
+ !g_dbus_proxy_call ||
+ !g_dbus_proxy_call_finish ||
+ !g_dbus_proxy_get_name_owner) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService)
+
+nsNativeMenuService::nsNativeMenuService() :
+ mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false) {
+}
+
+nsNativeMenuService::~nsNativeMenuService() {
+ SetOnline(false);
+
+ if (mCreateProxyCancellable) {
+ g_cancellable_cancel(mCreateProxyCancellable);
+ g_object_unref(mCreateProxyCancellable);
+ mCreateProxyCancellable = nullptr;
+ }
+
+ // Make sure we disconnect map-event handlers
+ while (mMenuBars.Length() > 0) {
+ NotifyNativeMenuBarDestroyed(mMenuBars[0]);
+ }
+
+ Preferences::UnregisterCallback(PrefChangedCallback,
+ "ui.use_global_menubar");
+
+ if (mDbusProxy) {
+ g_signal_handlers_disconnect_by_func(mDbusProxy,
+ FuncToGpointer(name_owner_changed_cb),
+ NULL);
+ g_object_unref(mDbusProxy);
+ }
+
+ if (gPendingListeners) {
+ delete gPendingListeners;
+ gPendingListeners = nullptr;
+ }
+ if (gPangoLayout) {
+ g_object_unref(gPangoLayout);
+ gPangoLayout = nullptr;
+ }
+
+ MOZ_ASSERT(sService == this);
+ sService = nullptr;
+}
+
+nsresult
+nsNativeMenuService::Init() {
+ nsresult rv = nsDbusmenuFunctions::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = GDBusInit();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Preferences::RegisterCallback(PrefChangedCallback,
+ "ui.use_global_menubar");
+
+ mCreateProxyCancellable = g_cancellable_new();
+
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ static_cast<GDBusProxyFlags>(
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
+ nullptr,
+ "com.canonical.AppMenu.Registrar",
+ "/com/canonical/AppMenu/Registrar",
+ "com.canonical.AppMenu.Registrar",
+ mCreateProxyCancellable, proxy_created_cb,
+ nullptr);
+
+ /* We don't technically know that the shell will draw the menubar until
+ * we know whether anybody owns the name of the menubar service on the
+ * session bus. However, discovering this happens asynchronously so
+ * we optimize for the common case here by assuming that the shell will
+ * draw window menubars if we are running inside Unity. This should
+ * mean that we avoid temporarily displaying the window menubar ourselves
+ */
+ const char* desktop = getenv("XDG_CURRENT_DESKTOP");
+ if (nsCRT::strcmp(desktop, "Unity") == 0) {
+ SetOnline(true);
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsNativeMenuService::EnsureInitialized() {
+ if (sService) {
+ return;
+ }
+ nsCOMPtr<nsINativeMenuService> service =
+ do_GetService("@mozilla.org/widget/nativemenuservice;1");
+}
+
+void
+nsNativeMenuService::SetOnline(bool aOnline) {
+ if (!Preferences::GetBool("ui.use_global_menubar", true)) {
+ aOnline = false;
+ }
+
+ mOnline = aOnline;
+ if (aOnline) {
+ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
+ RegisterNativeMenuBar(mMenuBars[i]);
+ }
+ } else {
+ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
+ mMenuBars[i]->Deactivate();
+ }
+ }
+}
+
+void
+nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar* aMenuBar) {
+ if (!mOnline) {
+ return;
+ }
+
+ // This will effectively create the native menubar for
+ // exporting over the session bus, and hide the XUL menubar
+ aMenuBar->Activate();
+
+ if (!mDbusProxy ||
+ !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) ||
+ mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) {
+ // Don't go further if we don't have a proxy for the shell menu
+ // service, the window isn't mapped or there is a request in progress.
+ return;
+ }
+
+ uint32_t xid = aMenuBar->WindowId();
+ nsAdoptingCString path = aMenuBar->ObjectPath();
+ if (xid == 0 || path.IsEmpty()) {
+ NS_WARNING("Menubar has invalid XID or object path");
+ return;
+ }
+
+ GCancellable* cancellable = g_cancellable_new();
+ mMenuBarRegistrationCancellables.Put(aMenuBar, cancellable);
+
+ // We keep a weak ref because we can't assume that GDBus cancellation
+ // is reliable (see https://launchpad.net/bugs/953562)
+
+ g_dbus_proxy_call(mDbusProxy, "RegisterWindow",
+ g_variant_new("(uo)", xid, path.get()),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ cancellable,
+ register_native_menubar_cb, aMenuBar);
+}
+
+/* static */ void
+nsNativeMenuService::name_owner_changed_cb(GObject* gobject,
+ GParamSpec* pspec,
+ gpointer user_data) {
+ nsNativeMenuService::GetSingleton()->OnNameOwnerChanged();
+}
+
+/* static */ void
+nsNativeMenuService::proxy_created_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ GError* error = nullptr;
+ GDBusProxy* proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(error);
+ return;
+ }
+
+ if (error) {
+ g_error_free(error);
+ }
+
+ // We need this check because we can't assume that GDBus cancellation
+ // is reliable (see https://launchpad.net/bugs/953562)
+ nsNativeMenuService* self = nsNativeMenuService::GetSingleton();
+ if (!self) {
+ if (proxy) {
+ g_object_unref(proxy);
+ }
+ return;
+ }
+
+ self->OnProxyCreated(proxy);
+}
+
+/* static */ void
+nsNativeMenuService::register_native_menubar_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data) {
+ nsMenuBar* menuBar = static_cast<nsMenuBar* >(user_data);
+
+ GError* error = nullptr;
+ GVariant* results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object),
+ res, &error);
+ if (results) {
+ // There's nothing useful in the response
+ g_variant_unref(results);
+ }
+
+ bool success = error ? false : true;
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(error);
+ return;
+ }
+
+ if (error) {
+ g_error_free(error);
+ }
+
+ nsNativeMenuService* self = nsNativeMenuService::GetSingleton();
+ if (!self) {
+ return;
+ }
+
+ self->OnNativeMenuBarRegistered(menuBar, success);
+}
+
+/* static */ gboolean
+nsNativeMenuService::map_event_cb(GtkWidget* widget,
+ GdkEvent* event,
+ gpointer user_data) {
+ nsMenuBar* menubar = static_cast<nsMenuBar* >(user_data);
+ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar);
+
+ return FALSE;
+}
+
+void
+nsNativeMenuService::OnNameOwnerChanged() {
+ char* owner = g_dbus_proxy_get_name_owner(mDbusProxy);
+ SetOnline(owner ? true : false);
+ g_free(owner);
+}
+
+void
+nsNativeMenuService::OnProxyCreated(GDBusProxy* aProxy) {
+ mDbusProxy = aProxy;
+
+ g_object_unref(mCreateProxyCancellable);
+ mCreateProxyCancellable = nullptr;
+
+ if (!mDbusProxy) {
+ SetOnline(false);
+ return;
+ }
+
+ g_signal_connect(mDbusProxy, "notify::g-name-owner",
+ G_CALLBACK(name_owner_changed_cb), nullptr);
+
+ OnNameOwnerChanged();
+}
+
+void
+nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar* aMenuBar,
+ bool aSuccess) {
+ // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might
+ // have already been deleted (see https://launchpad.net/bugs/953562)
+ GCancellable* cancellable = nullptr;
+ if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
+ return;
+ }
+
+ g_object_unref(cancellable);
+ mMenuBarRegistrationCancellables.Remove(aMenuBar);
+
+ if (!aSuccess) {
+ aMenuBar->Deactivate();
+ }
+}
+
+/* static */ void
+nsNativeMenuService::PrefChangedCallback(const char* aPref,
+ void* aClosure) {
+ nsNativeMenuService::GetSingleton()->PrefChanged();
+}
+
+void
+nsNativeMenuService::PrefChanged() {
+ if (!mDbusProxy) {
+ SetOnline(false);
+ return;
+ }
+
+ OnNameOwnerChanged();
+}
+
+NS_IMETHODIMP
+nsNativeMenuService::CreateNativeMenuBar(nsIWidget* aParent,
+ nsIContent* aMenuBarNode) {
+ NS_ENSURE_ARG(aParent);
+ NS_ENSURE_ARG(aMenuBarNode);
+
+ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None,
+ nsNativeMenuAtoms::_moz_menubarkeeplocal,
+ nsGkAtoms::_true,
+ eCaseMatters)) {
+ return NS_OK;
+ }
+
+ UniquePtr<nsMenuBar> menubar(nsMenuBar::Create(aParent, aMenuBarNode));
+ if (!menubar) {
+ NS_WARNING("Failed to create menubar");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Unity forgets our window if it is unmapped by the application, which
+ // happens with some extensions that add "minimize to tray" type
+ // functionality. We hook on to the MapNotify event to re-register our menu
+ // with Unity
+ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()),
+ "map-event", G_CALLBACK(map_event_cb),
+ menubar.get());
+
+ mMenuBars.AppendElement(menubar.get());
+ RegisterNativeMenuBar(menubar.get());
+
+ static_cast<nsWindow* >(aParent)->SetMenuBar(Move(menubar));
+
+ return NS_OK;
+}
+
+/* static */ already_AddRefed<nsNativeMenuService>
+nsNativeMenuService::GetInstanceForServiceManager() {
+ RefPtr<nsNativeMenuService> service(sService);
+
+ if (service) {
+ return service.forget();
+ }
+
+ service = new nsNativeMenuService();
+
+ if (NS_FAILED(service->Init())) {
+ return nullptr;
+ }
+
+ sService = service.get();
+ return service.forget();
+}
+
+/* static */ nsNativeMenuService*
+nsNativeMenuService::GetSingleton() {
+ EnsureInitialized();
+ return sService;
+}
+
+void
+nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar* aMenuBar) {
+ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(),
+ FuncToGpointer(map_event_cb),
+ aMenuBar);
+
+ mMenuBars.RemoveElement(aMenuBar);
+
+ GCancellable* cancellable = nullptr;
+ if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
+ mMenuBarRegistrationCancellables.Remove(aMenuBar);
+ g_cancellable_cancel(cancellable);
+ g_object_unref(cancellable);
+ }
+}
diff --git a/widget/gtk/nsNativeMenuService.h b/widget/gtk/nsNativeMenuService.h
new file mode 100644
index 000000000..5ce022526
--- /dev/null
+++ b/widget/gtk/nsNativeMenuService.h
@@ -0,0 +1,80 @@
+/* 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 __nsNativeMenuService_h__
+#define __nsNativeMenuService_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsINativeMenuService.h"
+#include "nsTArray.h"
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+class nsMenuBar;
+
+/*
+ * The main native menu service singleton. nsWebShellWindow calls in to this when
+ * a new top level window is created.
+ *
+ * Menubars are owned by their nsWindow. This service holds a weak reference to
+ * each menubar for the purpose of re-registering them with the shell if it
+ * needs to. The menubar is responsible for notifying the service when the last
+ * reference to it is dropped.
+ */
+class nsNativeMenuService final : public nsINativeMenuService {
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) override;
+
+ // Returns the singleton addref'd for the service manager
+ static already_AddRefed<nsNativeMenuService> GetInstanceForServiceManager();
+
+ // Returns the singleton without increasing the reference count
+ static nsNativeMenuService* GetSingleton();
+
+ // Called by a menubar when it is deleted
+ void NotifyNativeMenuBarDestroyed(nsMenuBar* aMenuBar);
+
+private:
+ nsNativeMenuService();
+ ~nsNativeMenuService();
+ nsresult Init();
+
+ static void EnsureInitialized();
+ void SetOnline(bool aOnline);
+ void RegisterNativeMenuBar(nsMenuBar* aMenuBar);
+ static void name_owner_changed_cb(GObject* gobject,
+ GParamSpec* pspec,
+ gpointer user_data);
+ static void proxy_created_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data);
+ static void register_native_menubar_cb(GObject* source_object,
+ GAsyncResult* res,
+ gpointer user_data);
+ static gboolean map_event_cb(GtkWidget* widget, GdkEvent* event,
+ gpointer user_data);
+ void OnNameOwnerChanged();
+ void OnProxyCreated(GDBusProxy* aProxy);
+ void OnNativeMenuBarRegistered(nsMenuBar* aMenuBar,
+ bool aSuccess);
+ static void PrefChangedCallback(const char* aPref, void* aClosure);
+ void PrefChanged();
+
+ GCancellable* mCreateProxyCancellable;
+ GDBusProxy* mDbusProxy;
+ bool mOnline;
+ nsTArray<nsMenuBar* > mMenuBars;
+ nsDataHashtable<nsPtrHashKey<nsMenuBar>, GCancellable*> mMenuBarRegistrationCancellables;
+
+ static bool sShutdown;
+ static nsNativeMenuService* sService;
+};
+
+#endif /* __nsNativeMenuService_h__ */
diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp
index 7e4274377..a1508d1d6 100644
--- a/widget/gtk/nsWidgetFactory.cpp
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -49,6 +49,8 @@
#include "GfxInfoX11.h"
#endif
+#include "nsNativeMenuService.h"
+
#include "nsNativeThemeGTK.h"
#include "nsIComponentRegistrar.h"
@@ -121,6 +123,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
}
#endif
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNativeMenuService,
+ nsNativeMenuService::GetInstanceForServiceManager)
+
#ifdef NS_PRINTING
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init)
@@ -223,6 +228,7 @@ NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_CID);
NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
#endif
+NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
@@ -258,6 +264,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
{ &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor },
{ &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
#endif
+ { &kNS_NATIVEMENUSERVICE_CID, true, NULL, nsNativeMenuServiceConstructor },
{ nullptr }
};
@@ -295,6 +302,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
{ "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
{ "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
#endif
+ { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
{ nullptr }
};
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index e4e69c1b4..6f222a705 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -67,6 +67,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Likely.h"
+#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "nsIPrefService.h"
#include "nsIGConfService.h"
@@ -5175,6 +5176,11 @@ nsWindow::HideWindowChrome(bool aShouldHide)
return NS_OK;
}
+void
+nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
+ mMenuBar = mozilla::Move(aMenuBar);
+}
+
bool
nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY,
bool aIsWheel, bool aAlwaysRollup)
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
index 49a8d4baf..c45176cea 100644
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -35,6 +35,8 @@
#include "IMContextWrapper.h"
+#include "nsMenuBar.h"
+
#undef LOG
#ifdef MOZ_LOGGING
@@ -162,6 +164,8 @@ public:
nsIScreen* aTargetScreen = nullptr) override;
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+ void SetMenuBar(mozilla::UniquePtr<nsMenuBar> aMenuBar);
+
/**
* GetLastUserInputTime returns a timestamp for the most recent user input
* event. This is intended for pointer grab requests (including drags).
@@ -569,6 +573,8 @@ private:
RefPtr<mozilla::widget::IMContextWrapper> mIMContext;
mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+
+ mozilla::UniquePtr<nsMenuBar> mMenuBar;
};
class nsChildWindow : public nsWindow {
diff --git a/widget/moz.build b/widget/moz.build
index 3ca4c9785..3a52805b0 100644
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -38,10 +38,12 @@ elif toolkit == 'cocoa':
'nsITaskbarProgress.idl',
]
EXPORTS += [
- 'nsINativeMenuService.h',
'nsIPrintDialogService.h',
]
+if toolkit in ('cocoa', 'gtk2', 'gtk3'):
+ EXPORTS += ['nsINativeMenuService.h']
+
# Don't build the DSO under the 'build' directory as windows does.
#
# The DSOs get built in the toolkit dir itself. Do this so that