diff options
author | B Stack <bgstack15@gmail.com> | 2019-06-15 15:52:04 +0000 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2019-06-15 15:52:04 +0000 |
commit | 6a4bb23c77db66d7d07f9a14a4e78e482be014e1 (patch) | |
tree | 51ea98e5a99c3c89970bee1b48a57796e3730bdb /waterfox/debian | |
parent | Merge branch 'palemoon-rawhide' into 'master' (diff) | |
parent | waterfox remove unity patch which was upstreamed (diff) | |
download | stackrpms-6a4bb23c77db66d7d07f9a14a4e78e482be014e1.tar.gz stackrpms-6a4bb23c77db66d7d07f9a14a4e78e482be014e1.tar.bz2 stackrpms-6a4bb23c77db66d7d07f9a14a4e78e482be014e1.zip |
Merge branch 'waterfox-bump' into 'master'
Waterfox 56.2.10.1
See merge request bgstack15/stackrpms!66
Diffstat (limited to 'waterfox/debian')
-rw-r--r-- | waterfox/debian/changelog | 6 | ||||
-rw-r--r-- | waterfox/debian/patches/series | 1 | ||||
-rw-r--r-- | waterfox/debian/patches/unity-menubar.patch | 5471 |
3 files changed, 6 insertions, 5472 deletions
diff --git a/waterfox/debian/changelog b/waterfox/debian/changelog index efd6951..84d934f 100644 --- a/waterfox/debian/changelog +++ b/waterfox/debian/changelog @@ -1,3 +1,9 @@ +waterfox (56.2.10.1-1+devuan) manual; urgency=medium + + * Add Unity/Global Menu support + + -- B Stack <bgstack15@gmail.com> Thu, 13 Jun 2019 09:47:07 -0400 + waterfox (56.2.10-1+devuan) manual; urgency=medium * Latest security fixes up to Firefox ESR 60.7 diff --git a/waterfox/debian/patches/series b/waterfox/debian/patches/series index dbbf392..9dd6a33 100644 --- a/waterfox/debian/patches/series +++ b/waterfox/debian/patches/series @@ -1,4 +1,3 @@ waterfox-install-dir.patch -p1 dont-statically-link-libstdc++.patch -p1 -unity-menubar.patch -p1 bgstack15.patch -p1 diff --git a/waterfox/debian/patches/unity-menubar.patch b/waterfox/debian/patches/unity-menubar.patch deleted file mode 100644 index 77e85ad..0000000 --- a/waterfox/debian/patches/unity-menubar.patch +++ /dev/null @@ -1,5471 +0,0 @@ -diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc -index a18dc2b89617..7afbd8d1b337 100644 ---- a/browser/base/content/browser-menubar.inc -+++ b/browser/base/content/browser-menubar.inc -@@ -5,7 +5,11 @@ - - <menubar id="main-menubar" - onpopupshowing="if (event.target.parentNode.parentNode == this && -+#ifdef MOZ_WIDGET_GTK -+ document.documentElement.getAttribute('shellshowingmenubar') != 'true') -+#else - !('@mozilla.org/widget/nativemenuservice;1' in Cc)) -+#endif - this.setAttribute('openedwithkey', - event.target.parentNode.openedWithKey);" - style="border:0px;padding:0px;margin:0px;-moz-appearance:none"> -diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js -index d55882ba5028..ae9a7fb25e65 100755 ---- a/browser/base/content/browser.js -+++ b/browser/base/content/browser.js -@@ -5357,6 +5357,8 @@ function getTogglableToolbars() { - let toolbarNodes = Array.slice(gNavToolbox.childNodes); - toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars); - toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname")); -+ if (document.documentElement.getAttribute("shellshowingmenubar") == "true") -+ toolbarNodes = toolbarNodes.filter(node => node.id != "toolbar-menubar"); - return toolbarNodes; - } - -diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul -index 24e18b607a0b..793aae929a7e 100644 ---- a/browser/components/places/content/places.xul -+++ b/browser/components/places/content/places.xul -@@ -155,7 +155,7 @@ - <toolbarbutton type="menu" class="tabbable" - onpopupshowing="document.getElementById('placeContent').focus()" - #else -- <menubar id="placesMenu"> -+ <menubar id="placesMenu" _moz-menubarkeeplocal="true"> - <menu accesskey="&organize.accesskey;" class="menu-iconic" - #endif - id="organizeButton" label="&organize.label;" -diff --git a/layout/build/moz.build b/layout/build/moz.build -index 3ed745263093..1fe66323c906 100644 ---- a/layout/build/moz.build -+++ b/layout/build/moz.build -@@ -74,6 +74,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': - '/dom/system', - '/dom/system/android', - ] -+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: -+ LOCAL_INCLUDES += [ -+ '/widget/gtk', -+ ] - - if CONFIG['MOZ_WEBSPEECH']: - LOCAL_INCLUDES += [ -diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp -index ded58ef04e0a..f207dc66ad23 100644 ---- a/layout/build/nsLayoutStatics.cpp -+++ b/layout/build/nsLayoutStatics.cpp -@@ -126,6 +126,10 @@ - #include "mozilla/dom/ipc/IPCBlobInputStreamStorage.h" - #include "mozilla/dom/U2FTokenManager.h" - -+#ifdef MOZ_WIDGET_GTK -+#include "nsNativeMenuAtoms.h" -+#endif -+ - using namespace mozilla; - using namespace mozilla::net; - using namespace mozilla::dom; -@@ -158,6 +162,9 @@ nsLayoutStatics::Initialize() - nsTextServicesDocument::RegisterAtoms(); - nsHTMLTags::RegisterAtoms(); - nsRDFAtoms::RegisterAtoms(); -+#ifdef MOZ_WIDGET_GTK -+ nsNativeMenuAtoms::RegisterAtoms(); -+#endif - - NS_SealStaticAtomTable(); - -diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js -index 988231e96f73..4eb7f7fddfea 100644 ---- a/modules/libpref/init/all.js -+++ b/modules/libpref/init/all.js -@@ -254,6 +254,9 @@ pref("dom.script_loader.bytecode_cache.strategy", 0); - pref("browser.sessionhistory.max_total_viewers", -1); - - pref("ui.use_native_colors", true); -+#ifdef MOZ_WIDGET_GTK -+pref("ui.use_unity_menubar", true); -+#endif - pref("ui.click_hold_context_menus", false); - // Duration of timeout of incremental search in menus (ms). 0 means infinite. - pref("ui.menu.incremental_search.timeout", 1000); -diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml -index 94d013e2c0ef..a071f9908148 100644 ---- a/toolkit/content/widgets/popup.xml -+++ b/toolkit/content/widgets/popup.xml -@@ -27,8 +27,14 @@ - </getter> - </property> - -- <property name="state" readonly="true" -- onget="return this.popupBoxObject.popupState"/> -+ <property name="state" readonly="true"> -+ <getter><![CDATA[ -+ if (this.hasAttribute('_moz-nativemenupopupstate')) -+ return this.getAttribute('_moz-nativemenupopupstate'); -+ else -+ return this.popupBoxObject.popupState; -+ ]]></getter> -+ </property> - - <property name="triggerNode" readonly="true" - onget="return this.popupBoxObject.triggerNode"/> -diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css -index 095e7da69afc..1d2c13a063f5 100644 ---- a/toolkit/content/xul.css -+++ b/toolkit/content/xul.css -@@ -319,6 +319,18 @@ toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true - } - %endif - -+%ifdef MOZ_WIDGET_GTK -+window[shellshowingmenubar="true"] menubar { -+ display: none !important; -+} -+ -+window[shellshowingmenubar="true"] -+toolbar[type="menubar"]:not([customizing="true"]) { -+ min-height: 0 !important; -+ border: 0 !important; -+} -+%endif -+ - toolbarseparator { - -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration"); - } -diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build -index 59eafda2fd1e..05c298249dea 100644 ---- a/widget/gtk/moz.build -+++ b/widget/gtk/moz.build -@@ -33,10 +33,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', - 'nsSound.cpp', - 'nsToolkit.cpp', -@@ -48,6 +56,8 @@ UNIFIED_SOURCES += [ - ] - - SOURCES += [ -+ 'nsMenu.cpp', # conflicts with X11 headers -+ 'nsNativeMenuService.cpp', - 'nsWindow.cpp', # conflicts with X11 headers - ] - -@@ -112,6 +122,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 000000000000..98ba3c975e74 ---- /dev/null -+++ b/widget/gtk/nsDbusmenu.cpp -@@ -0,0 +1,63 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..8d46a0d27bdb ---- /dev/null -+++ b/widget/gtk/nsDbusmenu.h -@@ -0,0 +1,101 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..a376b44cf41f ---- /dev/null -+++ b/widget/gtk/nsMenu.cpp -@@ -0,0 +1,858 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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/EventDispatcher.h" -+#include "mozilla/GeckoStyleContext.h" -+#include "mozilla/GuardObjects.h" -+#include "mozilla/MouseEvents.h" -+#include "mozilla/Move.h" -+#include "mozilla/ServoStyleContext.h" -+#include "mozilla/ServoStyleSet.h" -+#include "mozilla/ServoTypes.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 "nsStyleContextInlines.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) : -+ Runnable("nsMenuContentInsertedEvent"), -+ 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) : -+ Runnable("nsMenuContentRemovedEvent"), -+ 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); -+ EventDispatcher::Dispatch(aTarget, nullptr, &event); -+} -+ -+static void -+AttachXBLBindings(nsIContent *aContent) -+{ -+ nsIDocument *doc = aContent->OwnerDoc(); -+ nsIPresShell *shell = doc->GetShell(); -+ if (!shell) { -+ return; -+ } -+ -+ RefPtr<nsStyleContext> sc; -+ if (shell->StyleSet()->IsGecko()) { -+ sc = shell->StyleSet()->AsGecko()->ResolveStyleFor(aContent->AsElement(), -+ nullptr); -+ } else { -+ sc = shell->StyleSet()->AsServo()->ResolveStyleFor(aContent->AsElement(), -+ nullptr, -+ LazyComputeBehavior::Allow); -+ } -+ -+ 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->mExtraData->GetPrincipal(), -+ 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 Gecko -+ * 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 -+ // Gecko menuitem -+ for (size_t i = 0; i < count; ++i) { -+ if (ChildAt(i)->GetNativeData()) { -+ nextOwnedNativeChild = ChildAt(i)->GetNativeData(); -+ break; -+ } -+ } -+ -+ // Now iterate over all Gecko 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->InitWithNamedFuncCallback(DoOpenCallback, -+ this, -+ 100, -+ nsITimer::TYPE_ONE_SHOT, -+ "nsMenu::DoOpenCallback"))) { -+ 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 000000000000..c840afa957f7 ---- /dev/null -+++ b/widget/gtk/nsMenu.h -@@ -0,0 +1,124 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..755d87174cc7 ---- /dev/null -+++ b/widget/gtk/nsMenuBar.cpp -@@ -0,0 +1,576 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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) : -+ Runnable("nsMenuBarContentInsertedEvent"), -+ 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) : -+ Runnable("nsMenuBarContentRemovedEvent"), -+ 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))); -+} -+ -+nsCString -+nsMenuBar::ObjectPath() const -+{ -+ gchar *tmp; -+ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL); -+ -+ nsCString result; -+ result.Adopt(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 000000000000..706a3d3b4b20 ---- /dev/null -+++ b/widget/gtk/nsMenuBar.h -@@ -0,0 +1,107 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 -+ nsCString 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 000000000000..f17b3e277903 ---- /dev/null -+++ b/widget/gtk/nsMenuContainer.cpp -@@ -0,0 +1,172 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..b7e8fa8db46f ---- /dev/null -+++ b/widget/gtk/nsMenuContainer.h -@@ -0,0 +1,70 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..42e6c033f23e ---- /dev/null -+++ b/widget/gtk/nsMenuItem.cpp -@@ -0,0 +1,737 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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) : -+ Runnable("nsMenuItemUncheckSiblingsRunnable"), -+ 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, 0); -+ -+ 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 000000000000..fb53e53aee34 ---- /dev/null -+++ b/widget/gtk/nsMenuItem.h -@@ -0,0 +1,81 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..4f5aa2cb2654 ---- /dev/null -+++ b/widget/gtk/nsMenuObject.cpp -@@ -0,0 +1,666 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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, nullptr, 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(), -+ false, 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]) { -+ nsString ellipsis; -+ Preferences::GetLocalizedString("intl.ellipsis", ellipsis); -+ if (!ellipsis.IsEmpty()) { -+ uint32_t l = ellipsis.Length(); -+ const nsString::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() -+{ -+ // Gecko 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::GetStyleContextNoFlush( -+ 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 000000000000..b1a58bab8ddb ---- /dev/null -+++ b/widget/gtk/nsMenuObject.h -@@ -0,0 +1,170 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..d3045c4b29d9 ---- /dev/null -+++ b/widget/gtk/nsMenuSeparator.cpp -@@ -0,0 +1,85 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..35140e0596c0 ---- /dev/null -+++ b/widget/gtk/nsMenuSeparator.h -@@ -0,0 +1,37 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..32da73c42c8b ---- /dev/null -+++ b/widget/gtk/nsNativeMenuAtomList.h -@@ -0,0 +1,12 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..2d5492b48147 ---- /dev/null -+++ b/widget/gtk/nsNativeMenuAtoms.cpp -@@ -0,0 +1,39 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..0f053688004f ---- /dev/null -+++ b/widget/gtk/nsNativeMenuAtoms.h -@@ -0,0 +1,27 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..4b106f714b04 ---- /dev/null -+++ b/widget/gtk/nsNativeMenuDocListener.cpp -@@ -0,0 +1,350 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 000000000000..cfdd5b2cf40d ---- /dev/null -+++ b/widget/gtk/nsNativeMenuDocListener.h -@@ -0,0 +1,152 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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 Gecko, 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 000000000000..ab7e7101b91b ---- /dev/null -+++ b/widget/gtk/nsNativeMenuService.cpp -@@ -0,0 +1,508 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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_unity_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_unity_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_unity_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(); -+ nsCString 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 000000000000..82fcb72f8488 ---- /dev/null -+++ b/widget/gtk/nsNativeMenuService.h -@@ -0,0 +1,84 @@ -+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -+/* vim:expandtab:shiftwidth=4:tabstop=4: -+ */ -+/* 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/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp -index 69a8b40e926b..fa63c892d7f0 100644 ---- a/widget/gtk/nsNativeThemeGTK.cpp -+++ b/widget/gtk/nsNativeThemeGTK.cpp -@@ -51,6 +51,7 @@ - - using namespace mozilla; - using namespace mozilla::gfx; -+using mozilla::widget::ScreenHelperGTK; - - NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme, - nsIObserver) -diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp -index 3c8bc02ebbb5..89888e2f3a75 100644 ---- a/widget/gtk/nsWidgetFactory.cpp -+++ b/widget/gtk/nsWidgetFactory.cpp -@@ -48,6 +48,8 @@ - #include "GfxInfoX11.h" - #endif - -+#include "nsNativeMenuService.h" -+ - #include "nsNativeThemeGTK.h" - - #include "nsIComponentRegistrar.h" -@@ -123,6 +125,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) -@@ -225,6 +230,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[] = { -@@ -260,6 +266,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 } - }; - -@@ -297,6 +304,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 11aa0769bb16..8bab873955e7 100644 ---- a/widget/gtk/nsWindow.cpp -+++ b/widget/gtk/nsWindow.cpp -@@ -72,6 +72,7 @@ - - #include "mozilla/Assertions.h" - #include "mozilla/Likely.h" -+#include "mozilla/Move.h" - #include "mozilla/Preferences.h" - #include "nsIPrefService.h" - #include "nsIGConfService.h" -@@ -5092,6 +5093,11 @@ nsWindow::HideWindowChrome(bool aShouldHide) - #endif /* MOZ_X11 */ - } - -+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 dbdff5fefb3e..779e17a97084 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 - -@@ -157,6 +159,8 @@ public: - nsIScreen* aTargetScreen = nullptr) override; - virtual void 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). -@@ -561,6 +565,8 @@ private: - RefPtr<mozilla::widget::IMContextWrapper> mIMContext; - - mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter; -+ -+ mozilla::UniquePtr<nsMenuBar> mMenuBar; - }; - - #endif /* __nsWindow_h__ */ -diff --git a/widget/moz.build b/widget/moz.build -index 4d211bfdbd5e..f05c8a27b40c 100644 ---- a/widget/moz.build -+++ b/widget/moz.build -@@ -63,9 +63,9 @@ elif toolkit == 'cocoa': - 'nsISystemStatusBar.idl', - 'nsITaskbarProgress.idl', - ] -- EXPORTS += [ -- 'nsINativeMenuService.h', -- ] -+ -+if toolkit in ('cocoa', 'gtk2', 'gtk3'): -+ EXPORTS += ['nsINativeMenuService.h'] - - TEST_DIRS += ['tests'] - -diff --git a/xpfe/appshell/nsWebShellWindow.cpp b/xpfe/appshell/nsWebShellWindow.cpp -index 922a883c8ac3..add09fa06cc2 100644 ---- a/xpfe/appshell/nsWebShellWindow.cpp -+++ b/xpfe/appshell/nsWebShellWindow.cpp -@@ -74,7 +74,7 @@ - - #include "gfxPlatform.h" - --#ifdef XP_MACOSX -+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) - #include "nsINativeMenuService.h" - #define USE_NATIVE_MENUS - #endif --- -2.17.1 |