mirror of
https://codeberg.org/librewolf/source.git
synced 2024-12-22 13:43:04 -05:00
5298 lines
160 KiB
Diff
5298 lines
160 KiB
Diff
--- a/browser/base/content/browser-menubar.inc
|
|
+++ b/browser/base/content/browser-menubar.inc
|
|
@@ -7,7 +7,12 @@
|
|
# On macOS, we don't track whether activation of the native menubar happened
|
|
# with the keyboard.
|
|
#ifndef XP_MACOSX
|
|
- onpopupshowing="if (event.target.parentNode.parentNode == this)
|
|
+ onpopupshowing="if (event.target.parentNode.parentNode == this &&
|
|
+#ifdef MOZ_WIDGET_GTK
|
|
+ document.documentElement.getAttribute('shellshowingmenubar') != 'true')
|
|
+#else
|
|
+ true)
|
|
+#endif
|
|
this.setAttribute('openedwithkey',
|
|
event.target.parentNode.openedWithKey);"
|
|
#endif
|
|
--- a/browser/base/content/browser.js
|
|
+++ b/browser/base/content/browser.js
|
|
@@ -6251,11 +6251,18 @@ function onViewToolbarsPopupShowing(aEve
|
|
MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
|
|
let firstMenuItem = aInsertPoint || popup.firstElementChild;
|
|
let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
|
|
+
|
|
+ let shellShowingMenubar = document.documentElement.getAttribute("shellshowingmenubar") == "true";
|
|
+
|
|
for (let toolbar of toolbarNodes) {
|
|
if (!toolbar.hasAttribute("toolbarname")) {
|
|
continue;
|
|
}
|
|
|
|
+ if (shellShowingMenubar && toolbar.id == "toolbar-menubar") {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
if (toolbar.id == "PersonalToolbar" && gBookmarksToolbar2h2020) {
|
|
let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
|
|
popup.insertBefore(menu, firstMenuItem);
|
|
--- a/browser/components/places/content/places.xhtml
|
|
+++ b/browser/components/places/content/places.xhtml
|
|
@@ -165,6 +165,7 @@
|
|
#else
|
|
<menubar id="placesMenu">
|
|
<menu class="menu-iconic" data-l10n-id="places-organize-button"
|
|
+ _moz-menubarkeeplocal="true"
|
|
#endif
|
|
id="organizeButton">
|
|
<menupopup id="organizeButtonPopup">
|
|
--- a/dom/xul/XULPopupElement.cpp
|
|
+++ b/dom/xul/XULPopupElement.cpp
|
|
@@ -211,6 +211,10 @@ void XULPopupElement::GetState(nsString&
|
|
// set this here in case there's no frame for the popup
|
|
aState.AssignLiteral("closed");
|
|
|
|
+#ifdef MOZ_WIDGET_GTK
|
|
+ nsAutoString nativeState;
|
|
+#endif
|
|
+
|
|
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
|
|
switch (pm->GetPopupState(this)) {
|
|
case ePopupShown:
|
|
@@ -233,6 +237,11 @@ void XULPopupElement::GetState(nsString&
|
|
break;
|
|
}
|
|
}
|
|
+#ifdef MOZ_WIDGET_GTK
|
|
+ else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) {
|
|
+ aState = nativeState;
|
|
+ }
|
|
+#endif
|
|
}
|
|
|
|
nsINode* XULPopupElement::GetTriggerNode() const {
|
|
--- a/dom/xul/moz.build
|
|
+++ b/dom/xul/moz.build
|
|
@@ -83,6 +83,11 @@ LOCAL_INCLUDES += [
|
|
|
|
include("/ipc/chromium/chromium-config.mozbuild")
|
|
|
|
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
|
|
+ LOCAL_INCLUDES += [
|
|
+ "/widget/gtk",
|
|
+ ]
|
|
+
|
|
FINAL_LIBRARY = "xul"
|
|
|
|
if CONFIG["CC_TYPE"] in ("clang", "gcc"):
|
|
--- a/layout/build/moz.build
|
|
+++ b/layout/build/moz.build
|
|
@@ -68,6 +68,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "an
|
|
"/dom/system",
|
|
"/dom/system/android",
|
|
]
|
|
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
|
|
+ LOCAL_INCLUDES += [
|
|
+ "/widget/gtk",
|
|
+ ]
|
|
|
|
XPCOM_MANIFESTS += [
|
|
"components.conf",
|
|
--- a/modules/libpref/init/all.js
|
|
+++ b/modules/libpref/init/all.js
|
|
@@ -301,6 +301,9 @@ pref("dom.mouseevent.click.hack.use_lega
|
|
// Fastback caching - if this pref is negative, then we calculate the number
|
|
// of content viewers to cache based on the amount of available memory.
|
|
pref("browser.sessionhistory.max_total_viewers", -1);
|
|
+#ifdef MOZ_WIDGET_GTK
|
|
+pref("ui.use_unity_menubar", true);
|
|
+#endif
|
|
|
|
pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline
|
|
// 0 = no external leading,
|
|
--- a/toolkit/content/xul.css
|
|
+++ b/toolkit/content/xul.css
|
|
@@ -230,6 +230,13 @@ toolbar[type="menubar"] {
|
|
}
|
|
%endif
|
|
|
|
+%ifdef MOZ_WIDGET_GTK
|
|
+*|*:root[shellshowingmenubar="true"]
|
|
+toolbar[type="menubar"]:not([customizing="true"]) {
|
|
+ display: none !important;
|
|
+}
|
|
+%endif
|
|
+
|
|
toolbarspring {
|
|
-moz-box-flex: 1000;
|
|
}
|
|
--- a/widget/gtk/moz.build
|
|
+++ b/widget/gtk/moz.build
|
|
@@ -33,6 +33,7 @@
|
|
EXPORTS.mozilla += ["WidgetUtilsGtk.h"]
|
|
|
|
EXPORTS.mozilla.widget += [
|
|
+ "NativeMenuSupport.h",
|
|
"WindowSurface.h",
|
|
"WindowSurfaceProvider.h",
|
|
]
|
|
@@ -45,6 +46,7 @@
|
|
"MozContainer.cpp",
|
|
"MPRISServiceHandler.cpp",
|
|
"NativeKeyBindings.cpp",
|
|
+ "NativeMenuSupport.cpp",
|
|
"nsApplicationChooser.cpp",
|
|
"nsAppShell.cpp",
|
|
"nsBidiKeyboard.cpp",
|
|
@@ -70,6 +72,15 @@
|
|
|
|
SOURCES += [
|
|
"MediaKeysEventSourceFactory.cpp",
|
|
+ "nsDbusmenu.cpp",
|
|
+ "nsMenu.cpp", # conflicts with X11 headers
|
|
+ "nsMenuBar.cpp",
|
|
+ "nsMenuContainer.cpp",
|
|
+ "nsMenuItem.cpp",
|
|
+ "nsMenuObject.cpp",
|
|
+ "nsMenuSeparator.cpp",
|
|
+ "nsNativeMenuDocListener.cpp",
|
|
+ "nsNativeMenuService.cpp",
|
|
"nsNativeThemeGTK.cpp", # conflicts with X11 headers
|
|
"nsWindow.cpp", # conflicts with X11 headers
|
|
"WaylandVsyncSource.cpp", # conflicts with X11 headers
|
|
@@ -138,6 +149,7 @@
|
|
"/layout/base",
|
|
"/layout/forms",
|
|
"/layout/generic",
|
|
+ "/layout/style",
|
|
"/layout/xul",
|
|
"/other-licenses/atk-1.0",
|
|
"/third_party/cups/include",
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsDbusmenu.cpp
|
|
@@ -0,0 +1,61 @@
|
|
+/* -*- 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")
|
|
+#ifdef MOZ_WIDGET_GTK
|
|
+ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4")
|
|
+#endif
|
|
+#undef LOAD_LIBRARY
|
|
+
|
|
+ return NS_OK;
|
|
+}
|
|
--- /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__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenu.cpp
|
|
@@ -0,0 +1,795 @@
|
|
+/* -*- 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/Document.h"
|
|
+#include "mozilla/dom/Element.h"
|
|
+#include "mozilla/Assertions.h"
|
|
+#include "mozilla/ComputedStyleInlines.h"
|
|
+#include "mozilla/EventDispatcher.h"
|
|
+#include "mozilla/MouseEvents.h"
|
|
+#include "mozilla/PresShell.h"
|
|
+#include "mozilla/PresShellInlines.h"
|
|
+#include "nsComponentManagerUtils.h"
|
|
+#include "nsContentUtils.h"
|
|
+#include "nsCSSValue.h"
|
|
+#include "nsGkAtoms.h"
|
|
+#include "nsGtkUtils.h"
|
|
+#include "nsAtom.h"
|
|
+#include "nsIContent.h"
|
|
+#include "nsIRunnable.h"
|
|
+#include "nsITimer.h"
|
|
+#include "nsString.h"
|
|
+#include "nsStyleStruct.h"
|
|
+#include "nsThreadUtils.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);
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenu::SetPopupState(EPopupState aState)
|
|
+{
|
|
+ mPopupState = aState;
|
|
+
|
|
+ if (!mPopupContent) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsAutoString state;
|
|
+ switch (aState) {
|
|
+ case ePopupState_Showing:
|
|
+ state.Assign(u"showing"_ns);
|
|
+ break;
|
|
+ case ePopupState_Open:
|
|
+ state.Assign(u"open"_ns);
|
|
+ break;
|
|
+ case ePopupState_Hiding:
|
|
+ state.Assign(u"hiding"_ns);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (state.IsEmpty()) {
|
|
+ mPopupContent->AsElement()->UnsetAttr(
|
|
+ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate,
|
|
+ false);
|
|
+ } else {
|
|
+ mPopupContent->AsElement()->SetAttr(
|
|
+ kNameSpaceID_None, nsGkAtoms::_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("closed"_ns)) {
|
|
+ self->OnClose();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (event.Equals("opened"_ns)) {
|
|
+ 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()->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
|
|
+ u"true"_ns, 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_Deprecated(i);
|
|
+
|
|
+ UniquePtr<nsMenuObject> child = CreateChild(childContent);
|
|
+
|
|
+ if (!child) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ AppendChild(std::move(child));
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenu::InitializePopup()
|
|
+{
|
|
+ nsCOMPtr<nsIContent> oldPopupContent;
|
|
+ oldPopupContent.swap(mPopupContent);
|
|
+
|
|
+ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
|
|
+ nsIContent *child = ContentNode()->GetChildAt_Deprecated(i);
|
|
+
|
|
+ if (child->NodeInfo()->NameAtom() == nsGkAtoms::menupopup) {
|
|
+ mPopupContent = child;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (oldPopupContent == mPopupContent) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // The popup has changed
|
|
+
|
|
+ if (oldPopupContent) {
|
|
+ DocListener()->UnregisterForContentChanges(oldPopupContent);
|
|
+ }
|
|
+
|
|
+ SetPopupState(ePopupState_Closed);
|
|
+
|
|
+ if (!mPopupContent) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ 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(std::move(aChild), aPrevSibling,
|
|
+ !IsInBatchedUpdate());
|
|
+ StructureMutated();
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenu::AppendChild(UniquePtr<nsMenuObject> aChild)
|
|
+{
|
|
+ if (!IsInBatchedUpdate()) {
|
|
+ EnsureNoPlaceholderItem();
|
|
+ }
|
|
+
|
|
+ nsMenuContainer::AppendChild(std::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()->AsElement()->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(std::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();
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenu::Update(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ if (mNeedsUpdate) {
|
|
+ mNeedsUpdate = false;
|
|
+
|
|
+ UpdateLabel();
|
|
+ UpdateSensitivity();
|
|
+ }
|
|
+
|
|
+ UpdateVisibility(aComputedStyle);
|
|
+ UpdateIcon(aComputedStyle);
|
|
+}
|
|
+
|
|
+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, nsAtom *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<ComputedStyle> style = GetComputedStyle();
|
|
+ UpdateVisibility(style);
|
|
+ } else if (aAttribute == nsGkAtoms::image) {
|
|
+ RefPtr<ComputedStyle> style = GetComputedStyle();
|
|
+ UpdateIcon(style);
|
|
+ }
|
|
+}
|
|
+
|
|
+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()->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open,
|
|
+ true);
|
|
+}
|
|
+
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenu.h
|
|
@@ -0,0 +1,123 @@
|
|
+/* -*- 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 nsAtom;
|
|
+class nsIContent;
|
|
+class nsITimer;
|
|
+
|
|
+#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(mozilla::ComputedStyle *aComputedStyle) override;
|
|
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
|
|
+
|
|
+ void OnAttributeChanged(nsIContent *aContent, nsAtom *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__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuBar.cpp
|
|
@@ -0,0 +1,548 @@
|
|
+/* -*- 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/Document.h"
|
|
+#include "mozilla/dom/Element.h"
|
|
+#include "mozilla/dom/Event.h"
|
|
+#include "mozilla/dom/KeyboardEvent.h"
|
|
+#include "mozilla/dom/KeyboardEventBinding.h"
|
|
+#include "mozilla/Preferences.h"
|
|
+#include "nsContentUtils.h"
|
|
+#include "nsIDOMEventListener.h"
|
|
+#include "nsIRunnable.h"
|
|
+#include "nsIWidget.h"
|
|
+#include "nsTArray.h"
|
|
+#include "nsUnicharUtils.h"
|
|
+
|
|
+#include "nsMenu.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(dom::KeyboardEvent *aEvent)
|
|
+{
|
|
+ return !aEvent->DefaultPrevented() && aEvent->IsTrusted();
|
|
+}
|
|
+
|
|
+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(dom::Event *aEvent)
|
|
+{
|
|
+ nsAutoString type;
|
|
+ aEvent->GetType(type);
|
|
+
|
|
+ if (type.Equals(u"focus"_ns)) {
|
|
+ mOwner->Focus();
|
|
+ } else if (type.Equals(u"blur"_ns)) {
|
|
+ mOwner->Blur();
|
|
+ }
|
|
+
|
|
+ RefPtr<dom::KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
|
|
+ if (!keyEvent) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ if (type.Equals(u"keypress"_ns)) {
|
|
+ return mOwner->Keypress(keyEvent);
|
|
+ } else if (type.Equals(u"keydown"_ns)) {
|
|
+ return mOwner->KeyDown(keyEvent);
|
|
+ } else if (type.Equals(u"keyup"_ns)) {
|
|
+ return mOwner->KeyUp(keyEvent);
|
|
+ }
|
|
+
|
|
+ return NS_OK;
|
|
+}
|
|
+
|
|
+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("/com/canonical/menu/"_ns);
|
|
+ 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 == dom::KeyboardEvent_Binding::DOM_VK_SHIFT) {
|
|
+ mAccessKeyMask = eModifierShift;
|
|
+ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_CONTROL) {
|
|
+ mAccessKeyMask = eModifierCtrl;
|
|
+ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_ALT) {
|
|
+ mAccessKeyMask = eModifierAlt;
|
|
+ } else if (mAccessKey == dom::KeyboardEvent_Binding::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_Deprecated(i);
|
|
+
|
|
+ UniquePtr<nsMenuObject> child = CreateChild(childContent);
|
|
+
|
|
+ if (!child) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ AppendChild(std::move(child));
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuBar::DisconnectDocumentEventListeners()
|
|
+{
|
|
+ mDocument->RemoveEventListener(u"focus"_ns,
|
|
+ mEventListener,
|
|
+ true);
|
|
+ mDocument->RemoveEventListener(u"blur"_ns,
|
|
+ mEventListener,
|
|
+ true);
|
|
+ mDocument->RemoveEventListener(u"keypress"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+ mDocument->RemoveEventListener(u"keydown"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+ mDocument->RemoveEventListener(u"keyup"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuBar::SetShellShowingMenuBar(bool aShowing)
|
|
+{
|
|
+ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr(
|
|
+ kNameSpaceID_None, nsGkAtoms::shellshowingmenubar,
|
|
+ aShowing ? u"true"_ns : u"false"_ns,
|
|
+ true);
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuBar::Focus()
|
|
+{
|
|
+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::openedwithkey,
|
|
+ u"false"_ns, 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(dom::KeyboardEvent *aEvent)
|
|
+{
|
|
+ ModifierFlags modifiers = static_cast<ModifierFlags>(0);
|
|
+
|
|
+ if (aEvent->AltKey()) {
|
|
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
|
|
+ }
|
|
+
|
|
+ if (aEvent->ShiftKey()) {
|
|
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
|
|
+ }
|
|
+
|
|
+ if (aEvent->CtrlKey()) {
|
|
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
|
|
+ }
|
|
+
|
|
+ if (aEvent->MetaKey()) {
|
|
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
|
|
+ }
|
|
+
|
|
+ return modifiers;
|
|
+}
|
|
+
|
|
+nsresult
|
|
+nsMenuBar::Keypress(dom::KeyboardEvent *aEvent)
|
|
+{
|
|
+ if (!ShouldHandleKeyEvent(aEvent)) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ ModifierFlags modifiers = GetModifiersFromEvent(aEvent);
|
|
+ if (((modifiers & mAccessKeyMask) == 0) ||
|
|
+ ((modifiers & ~mAccessKeyMask) != 0)) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ uint32_t charCode = aEvent->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()->AsElement()->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()->AsElement()->SetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::openedwithkey,
|
|
+ u"true"_ns, true);
|
|
+ static_cast<nsMenu *>(found)->OpenMenu();
|
|
+
|
|
+ aEvent->StopPropagation();
|
|
+ aEvent->PreventDefault();
|
|
+
|
|
+ return NS_OK;
|
|
+}
|
|
+
|
|
+nsresult
|
|
+nsMenuBar::KeyDown(dom::KeyboardEvent *aEvent)
|
|
+{
|
|
+ if (!ShouldHandleKeyEvent(aEvent)) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ uint32_t keyCode = aEvent->KeyCode();
|
|
+ ModifierFlags modifiers = GetModifiersFromEvent(aEvent);
|
|
+ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
|
|
+
|
|
+ return NS_OK;
|
|
+}
|
|
+
|
|
+nsresult
|
|
+nsMenuBar::KeyUp(dom::KeyboardEvent *aEvent)
|
|
+{
|
|
+ if (!ShouldHandleKeyEvent(aEvent)) {
|
|
+ return NS_OK;
|
|
+ }
|
|
+
|
|
+ uint32_t keyCode = aEvent->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(std::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 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(u"focus"_ns,
|
|
+ mEventListener,
|
|
+ true);
|
|
+ mDocument->AddEventListener(u"blur"_ns,
|
|
+ mEventListener,
|
|
+ true);
|
|
+ mDocument->AddEventListener(u"keypress"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+ mDocument->AddEventListener(u"keydown"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+ mDocument->AddEventListener(u"keyup"_ns,
|
|
+ mEventListener,
|
|
+ false);
|
|
+
|
|
+ // Clear this. Not sure if we really need to though
|
|
+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::openedwithkey,
|
|
+ u"false"_ns, true);
|
|
+
|
|
+ DocListener()->Start();
|
|
+ Build();
|
|
+ SetShellShowingMenuBar(true);
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuBar::Deactivate()
|
|
+{
|
|
+ if (!mIsActive) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mIsActive = false;
|
|
+
|
|
+ SetShellShowingMenuBar(false);
|
|
+ while (ChildCount() > 0) {
|
|
+ RemoveChildAt(0);
|
|
+ }
|
|
+ DocListener()->Stop();
|
|
+ DisconnectDocumentEventListeners();
|
|
+}
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuBar.h
|
|
@@ -0,0 +1,111 @@
|
|
+/* -*- 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 nsIContent;
|
|
+class nsIWidget;
|
|
+class nsMenuBarDocEventListener;
|
|
+
|
|
+namespace mozilla {
|
|
+namespace dom {
|
|
+class Document;
|
|
+class KeyboardEvent;
|
|
+}
|
|
+}
|
|
+
|
|
+/*
|
|
+ * 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(mozilla::dom::KeyboardEvent *aEvent);
|
|
+ nsresult Keypress(mozilla::dom::KeyboardEvent *aEvent);
|
|
+ nsresult KeyDown(mozilla::dom::KeyboardEvent *aEvent);
|
|
+ nsresult KeyUp(mozilla::dom::KeyboardEvent *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<mozilla::dom::Document> mDocument;
|
|
+ RefPtr<DocEventListener> mEventListener;
|
|
+
|
|
+ uint32_t mAccessKey;
|
|
+ ModifierFlags mAccessKeyMask;
|
|
+ bool mIsActive;
|
|
+};
|
|
+
|
|
+#endif /* __nsMenuBar_h__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuContainer.cpp
|
|
@@ -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/. */
|
|
+
|
|
+#include "mozilla/DebugOnly.h"
|
|
+#include "nsGkAtoms.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 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));
|
|
+ }
|
|
+
|
|
+ mChildren.InsertElementAt(index, std::move(aChild));
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuContainer::AppendChild(UniquePtr<nsMenuObject> aChild,
|
|
+ bool aUpdateNative)
|
|
+{
|
|
+ if (aUpdateNative) {
|
|
+ aChild->CreateNativeData();
|
|
+ MOZ_ALWAYS_TRUE(
|
|
+ dbusmenu_menuitem_child_append(GetNativeData(),
|
|
+ aChild->GetNativeData()));
|
|
+ }
|
|
+
|
|
+ mChildren.AppendElement(std::move(aChild));
|
|
+}
|
|
+
|
|
+bool
|
|
+nsMenuContainer::NeedsRebuild() const
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+
|
|
+/* static */ nsIContent*
|
|
+nsMenuContainer::GetPreviousSupportedSibling(nsIContent *aContent)
|
|
+{
|
|
+ do {
|
|
+ aContent = aContent->GetPreviousSibling();
|
|
+ } while (aContent && !ContentIsSupported(aContent));
|
|
+
|
|
+ return aContent;
|
|
+}
|
|
--- /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__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuItem.cpp
|
|
@@ -0,0 +1,766 @@
|
|
+/* -*- 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/Document.h"
|
|
+#include "mozilla/dom/Element.h"
|
|
+#include "mozilla/dom/KeyboardEventBinding.h"
|
|
+#include "mozilla/dom/XULCommandEvent.h"
|
|
+#include "mozilla/Preferences.h"
|
|
+#include "mozilla/TextEvents.h"
|
|
+#include "nsContentUtils.h"
|
|
+#include "nsCRT.h"
|
|
+#include "nsGkAtoms.h"
|
|
+#include "nsGlobalWindowInner.h"
|
|
+#include "nsGtkUtils.h"
|
|
+#include "nsIContent.h"
|
|
+#include "nsIRunnable.h"
|
|
+#include "nsQueryObject.h"
|
|
+#include "nsReadableUtils.h"
|
|
+#include "nsString.h"
|
|
+#include "nsThreadUtils.h"
|
|
+
|
|
+#include "nsMenu.h"
|
|
+#include "nsMenuBar.h"
|
|
+#include "nsMenuContainer.h"
|
|
+#include "nsNativeMenuDocListener.h"
|
|
+
|
|
+#include <gdk/gdk.h>
|
|
+#include <gdk/gdkkeysyms.h>
|
|
+#include <gdk/gdkkeysyms-compat.h>
|
|
+#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()->AsElement()->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::autocheck,
|
|
+ nsGkAtoms::_false,
|
|
+ eCaseMatters) &&
|
|
+ (mType == eMenuItemType_CheckBox ||
|
|
+ (mType == eMenuItemType_Radio && !mIsChecked))) {
|
|
+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::checked,
|
|
+ mIsChecked ?
|
|
+ u"false"_ns
|
|
+ : u"true"_ns,
|
|
+ true);
|
|
+ }
|
|
+
|
|
+ dom::Document *doc = ContentNode()->OwnerDoc();
|
|
+ ErrorResult rv;
|
|
+ RefPtr<dom::Event> event =
|
|
+ doc->CreateEvent(u"xulcommandevent"_ns,
|
|
+ dom::CallerType::System, rv);
|
|
+ if (!rv.Failed()) {
|
|
+ RefPtr<dom::XULCommandEvent> command = event->AsXULCommandEvent();
|
|
+ if (command) {
|
|
+ command->InitCommandEvent(u"command"_ns, true, true,
|
|
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
|
|
+ 0, false, false, false, false, 0, nullptr, 0, rv);
|
|
+ if (!rv.Failed()) {
|
|
+ event->SetTrusted(true);
|
|
+ ContentNode()->DispatchEvent(*event, rv);
|
|
+ if (rv.Failed()) {
|
|
+ NS_WARNING("Failed to dispatch event");
|
|
+ rv.SuppressException();
|
|
+ }
|
|
+ } else {
|
|
+ NS_WARNING("Failed to initialize command event");
|
|
+ rv.SuppressException();
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ NS_WARNING("CreateEvent failed");
|
|
+ rv.SuppressException();
|
|
+ }
|
|
+
|
|
+ // 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, nsAtom *aAttribute)
|
|
+{
|
|
+ nsAutoString value;
|
|
+ if (aContent->AsElement()->GetAttr(kNameSpaceID_None, aAttribute, value)) {
|
|
+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, aAttribute,
|
|
+ value, true);
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuItem::UpdateState()
|
|
+{
|
|
+ if (!IsCheckboxOrRadioItem()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mIsChecked = ContentNode()->AsElement()->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 mozilla::dom::Element::AttrValuesArray attrs[] =
|
|
+ { nsGkAtoms::checkbox, nsGkAtoms::radio, nullptr };
|
|
+ int32_t type = ContentNode()->AsElement()->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()
|
|
+{
|
|
+ dom::Document *doc = ContentNode()->GetUncomposedDoc();
|
|
+ if (doc) {
|
|
+ nsCOMPtr<nsIContent> oldKeyContent;
|
|
+ oldKeyContent.swap(mKeyContent);
|
|
+
|
|
+ nsAutoString key;
|
|
+ ContentNode()->AsElement()->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->AsElement()->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 == dom::KeyboardEvent_Binding::DOM_VK_META) {
|
|
+ modifier |= GDK_META_MASK;
|
|
+ } else if (accel == dom::KeyboardEvent_Binding::DOM_VK_ALT) {
|
|
+ modifier |= GDK_MOD1_MASK;
|
|
+ } else {
|
|
+ modifier |= GDK_CONTROL_MASK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ token = strtok(nullptr, ", \t");
|
|
+ }
|
|
+
|
|
+ free(str);
|
|
+ }
|
|
+
|
|
+ nsAutoString keyStr;
|
|
+ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key,
|
|
+ keyStr);
|
|
+
|
|
+ guint key = 0;
|
|
+ if (!keyStr.IsEmpty()) {
|
|
+ key = gdk_unicode_to_keyval(*keyStr.BeginReading());
|
|
+ }
|
|
+
|
|
+ if (key == 0) {
|
|
+ mKeyContent->AsElement()->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()->AsElement()->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::type,
|
|
+ nsGkAtoms::radio,
|
|
+ eCaseMatters)) {
|
|
+ // If we're not a radio button, we don't care
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsAutoString name;
|
|
+ ContentNode()->AsElement()->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_Deprecated(i);
|
|
+
|
|
+ if (sibling->IsComment()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ nsAutoString otherName;
|
|
+ sibling->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
|
|
+ otherName);
|
|
+
|
|
+ if (sibling != ContentNode() && otherName == name &&
|
|
+ sibling->AsElement()->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::type,
|
|
+ nsGkAtoms::radio,
|
|
+ eCaseMatters)) {
|
|
+ sibling->AsElement()->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()
|
|
+{
|
|
+ dom::Document *doc = ContentNode()->GetUncomposedDoc();
|
|
+ if (!doc) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsAutoString command;
|
|
+ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::command,
|
|
+ command);
|
|
+ if (command.IsEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsCOMPtr<nsIContent> commandContent = doc->GetElementById(command);
|
|
+ if (!commandContent) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (commandContent->AsElement()->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::disabled,
|
|
+ nsGkAtoms::_true,
|
|
+ eCaseMatters)) {
|
|
+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::disabled,
|
|
+ u"true"_ns, true);
|
|
+ } else {
|
|
+ ContentNode()->AsElement()->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(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ if (mNeedsUpdate) {
|
|
+ mNeedsUpdate = false;
|
|
+
|
|
+ UpdateTypeAndState();
|
|
+ UpdateAccel();
|
|
+ UpdateLabel();
|
|
+ UpdateSensitivity();
|
|
+ }
|
|
+
|
|
+ UpdateVisibility(aComputedStyle);
|
|
+ UpdateIcon(aComputedStyle);
|
|
+}
|
|
+
|
|
+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, nsAtom *aAttribute)
|
|
+{
|
|
+ MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent,
|
|
+ "Received an event that wasn't meant for us!");
|
|
+
|
|
+ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked &&
|
|
+ aContent->AsElement()->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<ComputedStyle> style = GetComputedStyle();
|
|
+ UpdateVisibility(style);
|
|
+ } else if (aAttribute == nsGkAtoms::image) {
|
|
+ RefPtr<ComputedStyle> style = GetComputedStyle();
|
|
+ UpdateIcon(style);
|
|
+ }
|
|
+ } 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;
|
|
+}
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuItem.h
|
|
@@ -0,0 +1,80 @@
|
|
+/* -*- 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 nsAtom;
|
|
+class nsIContent;
|
|
+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, nsAtom *aAtom);
|
|
+ void UpdateState();
|
|
+ void UpdateTypeAndState();
|
|
+ void UpdateAccel();
|
|
+ nsMenuBar* MenuBar();
|
|
+ void UncheckSiblings();
|
|
+
|
|
+ void InitializeNativeData() override;
|
|
+ void UpdateContentAttributes() override;
|
|
+ void Update(mozilla::ComputedStyle *aComputedStyle) override;
|
|
+ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override;
|
|
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
|
|
+
|
|
+ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override;
|
|
+
|
|
+ EMenuItemType mType;
|
|
+
|
|
+ bool mIsChecked;
|
|
+
|
|
+ bool mNeedsUpdate;
|
|
+
|
|
+ nsCOMPtr<nsIContent> mKeyContent;
|
|
+};
|
|
+
|
|
+#endif /* __nsMenuItem_h__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuObject.cpp
|
|
@@ -0,0 +1,664 @@
|
|
+/* -*- 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/Document.h"
|
|
+#include "mozilla/dom/Element.h"
|
|
+#include "mozilla/Preferences.h"
|
|
+#include "mozilla/PresShell.h"
|
|
+#include "mozilla/PresShellInlines.h"
|
|
+#include "nsAttrValue.h"
|
|
+#include "nsComputedDOMStyle.h"
|
|
+#include "nsContentUtils.h"
|
|
+#include "nsGkAtoms.h"
|
|
+#include "nsIContent.h"
|
|
+#include "nsIContentPolicy.h"
|
|
+#include "nsILoadGroup.h"
|
|
+#include "nsImageToPixbuf.h"
|
|
+#include "nsIURI.h"
|
|
+#include "nsNetUtil.h"
|
|
+#include "nsPresContext.h"
|
|
+#include "nsRect.h"
|
|
+#include "nsServiceManagerUtils.h"
|
|
+#include "nsString.h"
|
|
+#include "nsStyleConsts.h"
|
|
+#include "nsStyleStruct.h"
|
|
+#include "nsUnicharUtils.h"
|
|
+
|
|
+#include "nsMenuContainer.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(ComputedStyle *aComputedStyle);
|
|
+ void Destroy();
|
|
+
|
|
+private:
|
|
+ ~nsMenuObjectIconLoader() { };
|
|
+
|
|
+ nsMenuObject *mOwner;
|
|
+ RefPtr<imgRequestProxy> mImageRequest;
|
|
+ nsCOMPtr<nsIURI> mURI;
|
|
+ nsIntRect mImageRect;
|
|
+};
|
|
+
|
|
+NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver)
|
|
+
|
|
+void
|
|
+nsMenuObjectIconLoader::Notify(imgIRequest *aProxy,
|
|
+ int32_t aType, const nsIntRect *aRect)
|
|
+{
|
|
+ if (!mOwner) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (aProxy != mImageRequest) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
|
|
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
|
|
+ mImageRequest = nullptr;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsCOMPtr<imgIContainer> img;
|
|
+ mImageRequest->GetImage(getter_AddRefs(img));
|
|
+ if (!img) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuObjectIconLoader::LoadIcon(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ dom::Document *doc = mOwner->ContentNode()->OwnerDoc();
|
|
+
|
|
+ nsCOMPtr<nsIURI> uri;
|
|
+ nsIntRect imageRect;
|
|
+ imgRequestProxy *imageRequest = nullptr;
|
|
+
|
|
+ nsAutoString uriString;
|
|
+ if (mOwner->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None,
|
|
+ nsGkAtoms::image,
|
|
+ uriString)) {
|
|
+ NS_NewURI(getter_AddRefs(uri), uriString);
|
|
+ } else {
|
|
+ PresShell *shell = doc->GetPresShell();
|
|
+ if (!shell) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsPresContext *pc = shell->GetPresContext();
|
|
+ if (!pc || !aComputedStyle) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ const nsStyleList *list = aComputedStyle->StyleList();
|
|
+ imageRequest = list->mListStyleImage.GetImageRequest();
|
|
+ if (imageRequest) {
|
|
+ imageRequest->GetURI(getter_AddRefs(uri));
|
|
+ auto& rect = list->mImageRegion.AsRect();
|
|
+ imageRect = rect.ToLayoutRect().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,
|
|
+ nullptr, 0, loadGroup, this, nullptr, nullptr,
|
|
+ nsIRequest::LOAD_NORMAL, nullptr,
|
|
+ nsIContentPolicy::TYPE_IMAGE, EmptyString(),
|
|
+ false, 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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
|
|
+
|
|
+ nsAutoString accesskey;
|
|
+ mContent->AsElement()->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 mozilla::dom::Element::AttrValuesArray strings[] = {
|
|
+ nsGkAtoms::left, nsGkAtoms::start,
|
|
+ nsGkAtoms::center, nsGkAtoms::right,
|
|
+ nsGkAtoms::end, nullptr
|
|
+ };
|
|
+
|
|
+ int32_t type = mContent->AsElement()->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(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ bool vis = true;
|
|
+
|
|
+ if (aComputedStyle &&
|
|
+ (aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None ||
|
|
+ aComputedStyle->StyleVisibility()->mVisible ==
|
|
+ StyleVisibility::Collapse)) {
|
|
+ vis = false;
|
|
+ }
|
|
+
|
|
+ dbusmenu_menuitem_property_set_bool(mNativeData,
|
|
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
|
|
+ vis);
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuObject::UpdateSensitivity()
|
|
+{
|
|
+ bool disabled = mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::disabled,
|
|
+ nsGkAtoms::_true,
|
|
+ eCaseMatters);
|
|
+
|
|
+ dbusmenu_menuitem_property_set_bool(mNativeData,
|
|
+ DBUSMENU_MENUITEM_PROP_ENABLED,
|
|
+ !disabled);
|
|
+
|
|
+}
|
|
+
|
|
+void
|
|
+nsMenuObject::UpdateIcon(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ if (ShouldShowIcon()) {
|
|
+ if (!mIconLoader) {
|
|
+ mIconLoader = new nsMenuObjectIconLoader(this);
|
|
+ }
|
|
+
|
|
+ mIconLoader->LoadIcon(aComputedStyle);
|
|
+ } else {
|
|
+ if (mIconLoader) {
|
|
+ mIconLoader->Destroy();
|
|
+ mIconLoader = nullptr;
|
|
+ }
|
|
+
|
|
+ ClearIcon();
|
|
+ }
|
|
+}
|
|
+
|
|
+already_AddRefed<ComputedStyle>
|
|
+nsMenuObject::GetComputedStyle()
|
|
+{
|
|
+ RefPtr<ComputedStyle> style =
|
|
+ nsComputedDOMStyle::GetComputedStyleNoFlush(
|
|
+ mContent->AsElement());
|
|
+
|
|
+ return style.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(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+}
|
|
+
|
|
+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) == nsGkAtoms::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<ComputedStyle> style = GetComputedStyle();
|
|
+ Update(style);
|
|
+}
|
|
+
|
|
+/* 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;
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuObject.h
|
|
@@ -0,0 +1,169 @@
|
|
+/* -*- 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 "mozilla/ComputedStyleInlines.h"
|
|
+#include "nsCOMPtr.h"
|
|
+
|
|
+#include "nsDbusmenu.h"
|
|
+#include "nsNativeMenuDocListener.h"
|
|
+
|
|
+class nsIContent;
|
|
+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(mozilla::ComputedStyle *aComputedStyle);
|
|
+ void UpdateSensitivity();
|
|
+ void UpdateIcon(mozilla::ComputedStyle *aComputedStyle);
|
|
+
|
|
+ already_AddRefed<mozilla::ComputedStyle> GetComputedStyle();
|
|
+
|
|
+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(mozilla::ComputedStyle *aComputedStyle);
|
|
+
|
|
+ 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__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsMenuSeparator.cpp
|
|
@@ -0,0 +1,82 @@
|
|
+/* -*- 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 "nsCRT.h"
|
|
+#include "nsGkAtoms.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(ComputedStyle *aComputedStyle)
|
|
+{
|
|
+ UpdateVisibility(aComputedStyle);
|
|
+}
|
|
+
|
|
+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, nsAtom *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<ComputedStyle> style = GetComputedStyle();
|
|
+ UpdateVisibility(style);
|
|
+ }
|
|
+}
|
|
+
|
|
+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;
|
|
+}
|
|
--- /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 nsAtom;
|
|
+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(mozilla::ComputedStyle *aComputedStyle) override;
|
|
+ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override;
|
|
+ nsMenuObject::PropertyFlags SupportedProperties() const override;
|
|
+
|
|
+ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override;
|
|
+};
|
|
+
|
|
+#endif /* __nsMenuSeparator_h__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsNativeMenuDocListener.cpp
|
|
@@ -0,0 +1,347 @@
|
|
+/* -*- 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/Document.h"
|
|
+#include "mozilla/dom/Element.h"
|
|
+#include "nsContentUtils.h"
|
|
+#include "nsAtom.h"
|
|
+#include "nsIContent.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) :
|
|
+ mObserver(nullptr)
|
|
+ {
|
|
+ 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;
|
|
+};
|
|
+
|
|
+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(mozilla::dom::Element *aElement,
|
|
+ int32_t aNameSpaceID,
|
|
+ nsAtom *aAttribute,
|
|
+ int32_t aModType,
|
|
+ const nsAttrValue* aOldValue)
|
|
+{
|
|
+ if (sUpdateBlockersCount == 0) {
|
|
+ DoAttributeChanged(aElement, aAttribute);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
|
|
+ m->mType = MutationRecord::eAttributeChanged;
|
|
+ m->mTarget = aElement;
|
|
+ m->mAttribute = aAttribute;
|
|
+
|
|
+ ScheduleFlush(this);
|
|
+}
|
|
+
|
|
+void
|
|
+nsNativeMenuDocListener::ContentAppended(nsIContent *aFirstNewContent)
|
|
+{
|
|
+ for (nsIContent *c = aFirstNewContent; c; c = c->GetNextSibling()) {
|
|
+ ContentInserted(c);
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+nsNativeMenuDocListener::ContentInserted(nsIContent *aChild)
|
|
+{
|
|
+ nsIContent* container = aChild->GetParent();
|
|
+ if (!container) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nsIContent *prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild);
|
|
+
|
|
+ if (sUpdateBlockersCount == 0) {
|
|
+ DoContentInserted(container, aChild, prevSibling);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
|
|
+ m->mType = MutationRecord::eContentInserted;
|
|
+ m->mTarget = container;
|
|
+ m->mChild = aChild;
|
|
+ m->mPrevSibling = prevSibling;
|
|
+
|
|
+ ScheduleFlush(this);
|
|
+}
|
|
+
|
|
+void
|
|
+nsNativeMenuDocListener::ContentRemoved(nsIContent *aChild,
|
|
+ nsIContent *aPreviousSibling)
|
|
+{
|
|
+ nsIContent* container = aChild->GetParent();
|
|
+ if (!container) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (sUpdateBlockersCount == 0) {
|
|
+ DoContentRemoved(container, aChild);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
|
|
+ m->mType = MutationRecord::eContentRemoved;
|
|
+ m->mTarget = container;
|
|
+ m->mChild = aChild;
|
|
+
|
|
+ ScheduleFlush(this);
|
|
+}
|
|
+
|
|
+void
|
|
+nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode *aNode)
|
|
+{
|
|
+ mDocument = nullptr;
|
|
+}
|
|
+
|
|
+void
|
|
+nsNativeMenuDocListener::DoAttributeChanged(nsIContent *aContent,
|
|
+ nsAtom *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].get();
|
|
+
|
|
+ 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:
|
|
+ MOZ_ASSERT_UNREACHABLE("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.InsertOrUpdate(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();
|
|
+}
|
|
--- /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/RefPtr.h"
|
|
+#include "mozilla/UniquePtr.h"
|
|
+#include "nsTHashMap.h"
|
|
+#include "nsStubMutationObserver.h"
|
|
+#include "nsTArray.h"
|
|
+
|
|
+class nsAtom;
|
|
+class nsIContent;
|
|
+class nsNativeMenuChangeObserver;
|
|
+
|
|
+namespace mozilla {
|
|
+namespace dom {
|
|
+class Document;
|
|
+}
|
|
+}
|
|
+
|
|
+/*
|
|
+ * 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()
|
|
+ {
|
|
+ nsNativeMenuDocListener::AddUpdateBlocker();
|
|
+ }
|
|
+
|
|
+ ~BlockUpdatesScope()
|
|
+ {
|
|
+ nsNativeMenuDocListener::RemoveUpdateBlocker();
|
|
+ }
|
|
+ };
|
|
+
|
|
+private:
|
|
+ friend class DispatchHelper;
|
|
+
|
|
+ struct MutationRecord {
|
|
+ enum RecordType {
|
|
+ eAttributeChanged,
|
|
+ eContentInserted,
|
|
+ eContentRemoved
|
|
+ } mType;
|
|
+
|
|
+ nsCOMPtr<nsIContent> mTarget;
|
|
+ nsCOMPtr<nsIContent> mChild;
|
|
+ nsCOMPtr<nsIContent> mPrevSibling;
|
|
+ RefPtr<nsAtom> 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, nsAtom *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;
|
|
+ mozilla::dom::Document *mDocument;
|
|
+ nsIContent *mLastSource;
|
|
+ nsNativeMenuChangeObserver *mLastTarget;
|
|
+ nsTArray<mozilla::UniquePtr<MutationRecord> > mPendingMutations;
|
|
+ nsTHashMap<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, nsAtom *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__ */
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsNativeMenuService.cpp
|
|
@@ -0,0 +1,478 @@
|
|
+/* -*- 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/dom/Element.h"
|
|
+#include "mozilla/Assertions.h"
|
|
+#include "mozilla/Preferences.h"
|
|
+#include "mozilla/UniquePtr.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 "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;
|
|
+
|
|
+#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.InsertOrUpdate(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,
|
|
+ mozilla::dom::Element *aMenuBarNode)
|
|
+{
|
|
+ NS_ENSURE_ARG(aParent);
|
|
+ NS_ENSURE_ARG(aMenuBarNode);
|
|
+
|
|
+ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None,
|
|
+ nsGkAtoms::_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(std::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);
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/widget/gtk/nsNativeMenuService.h
|
|
@@ -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/. */
|
|
+
|
|
+#ifndef __nsNativeMenuService_h__
|
|
+#define __nsNativeMenuService_h__
|
|
+
|
|
+#include "mozilla/Attributes.h"
|
|
+#include "nsCOMPtr.h"
|
|
+#include "nsTHashMap.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.
|
|
+ * NativeMenuSupport::CreateNativeMenuBar 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, mozilla::dom::Element* 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;
|
|
+ nsTHashMap<nsPtrHashKey<nsMenuBar>, GCancellable*> mMenuBarRegistrationCancellables;
|
|
+
|
|
+ static bool sShutdown;
|
|
+ static nsNativeMenuService *sService;
|
|
+};
|
|
+
|
|
+#endif /* __nsNativeMenuService_h__ */
|
|
--- a/widget/gtk/nsWindow.cpp
|
|
+++ b/widget/gtk/nsWindow.cpp
|
|
@@ -6436,6 +6436,10 @@ void nsWindow::HideWindowChrome(bool aSh
|
|
SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle);
|
|
}
|
|
|
|
+void nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
|
|
+ mMenuBar = std::move(aMenuBar);
|
|
+}
|
|
+
|
|
bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
|
|
bool aAlwaysRollup) {
|
|
nsIRollupListener* rollupListener = GetActiveRollupListener();
|
|
--- a/widget/gtk/nsWindow.h
|
|
+++ b/widget/gtk/nsWindow.h
|
|
@@ -39,6 +39,8 @@
|
|
#include "nsRefPtrHashtable.h"
|
|
#include "IMContextWrapper.h"
|
|
|
|
+#include "nsMenuBar.h"
|
|
+
|
|
#ifdef ACCESSIBILITY
|
|
# include "mozilla/a11y/LocalAccessible.h"
|
|
#endif
|
|
@@ -168,6 +170,8 @@ class nsWindow final : public nsBaseWidg
|
|
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).
|
|
@@ -708,6 +712,8 @@ class nsWindow final : public nsBaseWidg
|
|
static GtkWindowDecoration sGtkWindowDecoration;
|
|
|
|
static bool sTransparentMainWindow;
|
|
+
|
|
+ mozilla::UniquePtr<nsMenuBar> mMenuBar;
|
|
|
|
#ifdef ACCESSIBILITY
|
|
RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;
|
|
--- /dev/null
|
|
+++ b/xpcom/ds/NativeMenuAtoms.py
|
|
@@ -0,0 +1,9 @@
|
|
+from Atom import Atom
|
|
+
|
|
+NATIVE_MENU_ATOMS = [
|
|
+ Atom("menuitem_with_favicon", "menuitem-with-favicon"),
|
|
+ Atom("_moz_menubarkeeplocal", "_moz-menubarkeeplocal"),
|
|
+ Atom("_moz_nativemenupopupstate", "_moz-nativemenupopupstate"),
|
|
+ Atom("openedwithkey", "openedwithkey"),
|
|
+ Atom("shellshowingmenubar", "shellshowingmenubar"),
|
|
+]
|
|
--- a/xpcom/ds/StaticAtoms.py
|
|
+++ b/xpcom/ds/StaticAtoms.py
|
|
@@ -7,6 +7,7 @@
|
|
from Atom import Atom, InheritingAnonBoxAtom, NonInheritingAnonBoxAtom
|
|
from Atom import PseudoElementAtom
|
|
from HTMLAtoms import HTML_PARSER_ATOMS
|
|
+from NativeMenuAtoms import NATIVE_MENU_ATOMS
|
|
import sys
|
|
|
|
# Static atom definitions, used to generate nsGkAtomList.h.
|
|
@@ -2506,7 +2507,7 @@ STATIC_ATOMS = [
|
|
InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
|
|
InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
|
|
# END ATOMS
|
|
-] + HTML_PARSER_ATOMS
|
|
+] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS
|
|
# fmt: on
|
|
|
|
|
|
--- a/widget/gtk/components.conf
|
|
+++ b/widget/gtk/components.conf
|
|
@@ -82,6 +82,14 @@ Classes = [
|
|
'headers': ['/widget/gtk/nsApplicationChooser.h'],
|
|
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
|
|
},
|
|
+ {
|
|
+ 'cid': '{0b3fe5aa-bc72-4303-85ae-76365df1251d}',
|
|
+ 'contract_ids': ['@mozilla.org/widget/nativemenuservice;1'],
|
|
+ 'singleton': True,
|
|
+ 'type': 'nsNativeMenuService',
|
|
+ 'constructor': 'nsNativeMenuService::GetInstanceForServiceManager',
|
|
+ 'headers': ['/widget/gtk/nsNativeMenuService.h'],
|
|
+ },
|
|
]
|
|
|
|
if defined('MOZ_X11'):
|
|
--- a/xpfe/appshell/AppWindow.cpp
|
|
+++ b/xpfe/appshell/AppWindow.cpp
|
|
@@ -80,7 +80,7 @@
|
|
|
|
#include "mozilla/dom/DocumentL10n.h"
|
|
|
|
-#ifdef XP_MACOSX
|
|
+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
|
|
# include "mozilla/widget/NativeMenuSupport.h"
|
|
# define USE_NATIVE_MENUS
|
|
#endif
|
|
--- /dev/null
|
|
+++ b/widget/gtk/NativeMenuSupport.cpp
|
|
@@ -0,0 +1,25 @@
|
|
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
+/* 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/widget/NativeMenuSupport.h"
|
|
+
|
|
+#include "MainThreadUtils.h"
|
|
+#include "nsINativeMenuService.h"
|
|
+
|
|
+namespace mozilla::widget {
|
|
+
|
|
+void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent, dom::Element* aMenuBarElement) {
|
|
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
|
|
+
|
|
+ nsCOMPtr<nsINativeMenuService> nms =
|
|
+ do_GetService("@mozilla.org/widget/nativemenuservice;1");
|
|
+ if (!nms) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ nms->CreateNativeMenuBar(aParent, aMenuBarElement);
|
|
+}
|
|
+
|
|
+} // namespace mozilla::widget
|
|
--- /dev/null
|
|
+++ b/widget/gtk/NativeMenuSupport.h
|
|
@@ -0,0 +1,31 @@
|
|
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
+/* 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 mozilla_widget_NativeMenuSupport_h
|
|
+#define mozilla_widget_NativeMenuSupport_h
|
|
+
|
|
+class nsIWidget;
|
|
+
|
|
+namespace mozilla {
|
|
+
|
|
+namespace dom {
|
|
+class Element;
|
|
+}
|
|
+
|
|
+namespace widget {
|
|
+
|
|
+class NativeMenuSupport final {
|
|
+public:
|
|
+ // Given a top-level window widget and a menu bar DOM node, sets up native
|
|
+ // menus. Once created, native menus are controlled via the DOM, including
|
|
+ // destruction.
|
|
+ static void CreateNativeMenuBar(nsIWidget* aParent,
|
|
+ dom::Element* aMenuBarElement);
|
|
+};
|
|
+
|
|
+} // namespace widget
|
|
+} // namespace mozilla
|
|
+
|
|
+#endif // mozilla_widget_NativeMenuSupport_h
|
|
--- a/widget/moz.build
|
|
+++ b/widget/moz.build
|
|
@@ -144,6 +144,11 @@ EXPORTS += [
|
|
"PuppetWidget.h",
|
|
]
|
|
|
|
+if toolkit == "gtk":
|
|
+ EXPORTS += [
|
|
+ "nsINativeMenuService.h",
|
|
+ ]
|
|
+
|
|
EXPORTS.mozilla += [
|
|
"BasicEvents.h",
|
|
"CommandList.h",
|
|
--- /dev/null
|
|
+++ b/widget/nsINativeMenuService.h
|
|
@@ -0,0 +1,39 @@
|
|
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
+/* 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 nsINativeMenuService_h_
|
|
+#define nsINativeMenuService_h_
|
|
+
|
|
+#include "nsISupports.h"
|
|
+
|
|
+class nsIWidget;
|
|
+class nsIContent;
|
|
+namespace mozilla {
|
|
+namespace dom {
|
|
+class Element;
|
|
+}
|
|
+} // namespace mozilla
|
|
+
|
|
+// {90DF88F9-F084-4EF3-829A-49496E636DED}
|
|
+#define NS_INATIVEMENUSERVICE_IID \
|
|
+ { \
|
|
+ 0x90DF88F9, 0xF084, 0x4EF3, { \
|
|
+ 0x82, 0x9A, 0x49, 0x49, 0x6E, 0x63, 0x6D, 0xED \
|
|
+ } \
|
|
+ }
|
|
+
|
|
+class nsINativeMenuService : public nsISupports {
|
|
+ public:
|
|
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEMENUSERVICE_IID)
|
|
+ // Given a top-level window widget and a menu bar DOM node, sets up native
|
|
+ // menus. Once created, native menus are controlled via the DOM, including
|
|
+ // destruction.
|
|
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent,
|
|
+ mozilla::dom::Element* aMenuBarNode) = 0;
|
|
+};
|
|
+
|
|
+NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID)
|
|
+
|
|
+#endif // nsINativeMenuService_h_
|
|
--- a/widget/nsWidgetsCID.h
|
|
+++ b/widget/nsWidgetsCID.h
|
|
@@ -66,6 +66,14 @@
|
|
// Menus
|
|
//-----------------------------------------------------------
|
|
|
|
+// {0B3FE5AA-BC72-4303-85AE-76365DF1251D}
|
|
+#define NS_NATIVEMENUSERVICE_CID \
|
|
+ { \
|
|
+ 0x0B3FE5AA, 0xBC72, 0x4303, { \
|
|
+ 0x85, 0xAE, 0x76, 0x36, 0x5D, 0xF1, 0x25, 0x1D \
|
|
+ } \
|
|
+ }
|
|
+
|
|
// {F6CD4F21-53AF-11d2-8DC4-00609703C14E}
|
|
#define NS_POPUPMENU_CID \
|
|
{ \
|