feat: native GTK menu integration for Unity global menu support

Source: https://aur.archlinux.org/packages/firefox-esr-globalmenu
This commit is contained in:
Alex Kontos
2025-08-20 10:25:38 +01:00
parent ab06e84102
commit 6098550a55
35 changed files with 4988 additions and 2 deletions

View File

@@ -176,7 +176,7 @@
<toolbarbutton type="menu" class="tabbable" wantdropmarker="true" <toolbarbutton type="menu" class="tabbable" wantdropmarker="true"
data-l10n-id="places-organize-button-mac" data-l10n-id="places-organize-button-mac"
#else #else
<menubar id="placesMenu"> <menubar id="placesMenu" _moz-menubarkeeplocal="true">
<menu class="menu-iconic" data-l10n-id="places-organize-button" <menu class="menu-iconic" data-l10n-id="places-organize-button"
#endif #endif
id="organizeButton"> id="organizeButton">

View File

@@ -238,6 +238,10 @@ void XULPopupElement::GetState(nsString& aState) {
// set this here in case there's no frame for the popup // set this here in case there's no frame for the popup
aState.AssignLiteral("closed"); aState.AssignLiteral("closed");
#ifdef MOZ_WIDGET_GTK
nsAutoString nativeState;
#endif
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
switch (pm->GetPopupState(this)) { switch (pm->GetPopupState(this)) {
case ePopupShown: case ePopupShown:
@@ -260,6 +264,11 @@ void XULPopupElement::GetState(nsString& aState) {
break; break;
} }
} }
#ifdef MOZ_WIDGET_GTK
else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) {
aState = nativeState;
}
#endif
} }
nsINode* XULPopupElement::GetTriggerNode() const { nsINode* XULPopupElement::GetTriggerNode() const {

View File

@@ -91,4 +91,9 @@ LOCAL_INCLUDES += [
include("/ipc/chromium/chromium-config.mozbuild") include("/ipc/chromium/chromium-config.mozbuild")
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
LOCAL_INCLUDES += [
"/widget/gtk",
]
FINAL_LIBRARY = "xul" FINAL_LIBRARY = "xul"

View File

@@ -68,6 +68,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
"/dom/system", "/dom/system",
"/dom/system/android", "/dom/system/android",
] ]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
LOCAL_INCLUDES += [
"/widget/gtk",
]
XPCOM_MANIFESTS += [ XPCOM_MANIFESTS += [
"components.conf", "components.conf",

View File

@@ -157,6 +157,9 @@ pref("dom.text-recognition.enabled", true);
// Fastback caching - if this pref is negative, then we calculate the number // Fastback caching - if this pref is negative, then we calculate the number
// of content viewers to cache based on the amount of available memory. // of content viewers to cache based on the amount of available memory.
pref("browser.sessionhistory.max_total_viewers", -1); pref("browser.sessionhistory.max_total_viewers", -1);
#ifdef MOZ_WIDGET_GTK
pref("ui.use_unity_menubar", true);
#endif
// See http://whatwg.org/specs/web-apps/current-work/#ping // See http://whatwg.org/specs/web-apps/current-work/#ping
pref("browser.send_pings", false); pref("browser.send_pings", false);

View File

@@ -317,6 +317,13 @@ toolbox {
} }
} }
@media (-moz-platform: linux) {
*|*:root[shellshowingmenubar="true"]
toolbar[type="menubar"]:not([customizing="true"]) {
display: none !important;
}
}
toolbarspring { toolbarspring {
flex: 1000 1000; flex: 1000 1000;
} }

View File

@@ -10,6 +10,8 @@
#include "NativeMenuGtk.h" #include "NativeMenuGtk.h"
#include "DBusMenu.h" #include "DBusMenu.h"
#include "nsWindow.h" #include "nsWindow.h"
#include "nsINativeMenuService.h"
#include "nsServiceManagerUtils.h"
namespace mozilla::widget { namespace mozilla::widget {
@@ -18,6 +20,14 @@ void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent,
MOZ_RELEASE_ASSERT(NS_IsMainThread(), MOZ_RELEASE_ASSERT(NS_IsMainThread(),
"Attempting to create native menu bar on wrong thread!"); "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);
#ifdef MOZ_ENABLE_DBUS #ifdef MOZ_ENABLE_DBUS
if (aMenuBarElement && StaticPrefs::widget_gtk_global_menu_enabled() && if (aMenuBarElement && StaticPrefs::widget_gtk_global_menu_enabled() &&
DBusMenuFunctions::Init()) { DBusMenuFunctions::Init()) {

View File

@@ -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

View File

@@ -115,6 +115,14 @@ Classes = [
'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'], 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
'constructor': 'nsUserIdleServiceGTK::GetInstance', 'constructor': 'nsUserIdleServiceGTK::GetInstance',
}, },
{
'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('NS_PRINTING'): if defined('NS_PRINTING'):

View File

@@ -106,6 +106,15 @@ UNIFIED_SOURCES += [
SOURCES += [ SOURCES += [
"MediaKeysEventSourceFactory.cpp", "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 "nsNativeThemeGTK.cpp", # conflicts with X11 headers
"nsWindow.cpp", # conflicts with X11 headers "nsWindow.cpp", # conflicts with X11 headers
"WaylandVsyncSource.cpp", # conflicts with X11 headers "WaylandVsyncSource.cpp", # conflicts with X11 headers
@@ -157,6 +166,7 @@ LOCAL_INCLUDES += [
"/layout/base", "/layout/base",
"/layout/forms", "/layout/forms",
"/layout/generic", "/layout/generic",
"/layout/style",
"/layout/xul", "/layout/xul",
"/other-licenses/atk-1.0", "/other-licenses/atk-1.0",
"/third_party/cups/include", "/third_party/cups/include",

61
widget/gtk/nsDbusmenu.cpp Normal file
View File

@@ -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 < std::size(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;
}

101
widget/gtk/nsDbusmenu.h Normal file
View File

@@ -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__ */

795
widget/gtk/nsMenu.cpp Normal file
View File

@@ -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(const 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<const ComputedStyle> style = GetComputedStyle();
UpdateVisibility(style);
} else if (aAttribute == nsGkAtoms::image) {
RefPtr<const 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 = NS_NewTimer();
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);
}

123
widget/gtk/nsMenu.h Normal file
View File

@@ -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(const 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__ */

548
widget/gtk/nsMenuBar.cpp Normal file
View File

@@ -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 = 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();
}

111
widget/gtk/nsMenuBar.h Normal file
View File

@@ -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__ */

View File

@@ -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;
}

View File

@@ -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__ */

766
widget/gtk/nsMenuItem.cpp Normal file
View File

@@ -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 < std::size(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 < std::size(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(const 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<const ComputedStyle> style = GetComputedStyle();
UpdateVisibility(style);
} else if (aAttribute == nsGkAtoms::image) {
RefPtr<const 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;
}

80
widget/gtk/nsMenuItem.h Normal file
View File

@@ -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(const 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__ */

654
widget/gtk/nsMenuObject.cpp Normal file
View File

@@ -0,0 +1,654 @@
/* -*- 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 "mozilla/GRefPtr.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(const ComputedStyle *aComputedStyle);
void Destroy();
private:
~nsMenuObjectIconLoader() { };
nsMenuObject *mOwner;
RefPtr<imgRequestProxy> mImageRequest;
nsCOMPtr<nsIURI> mURI;
};
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;
}
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;
}
RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(img);
if (pixbuf) {
dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(),
DBUSMENU_MENUITEM_PROP_ICON_DATA,
pixbuf);
}
return;
}
void
nsMenuObjectIconLoader::LoadIcon(const ComputedStyle *aComputedStyle)
{
dom::Document *doc = mOwner->ContentNode()->OwnerDoc();
nsCOMPtr<nsIURI> uri;
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));
}
}
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) {
return;
}
if (mImageRequest) {
mImageRequest->Cancel(NS_BINDING_ABORTED);
mImageRequest = nullptr;
}
mURI = uri;
if (imageRequest) {
imageRequest->Clone(this, nullptr, getter_AddRefs(mImageRequest));
} else {
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, 0, dom::FetchPriority::Auto,
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(const 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(const ComputedStyle *aComputedStyle)
{
if (ShouldShowIcon()) {
if (!mIconLoader) {
mIconLoader = new nsMenuObjectIconLoader(this);
}
mIconLoader->LoadIcon(aComputedStyle);
} else {
if (mIconLoader) {
mIconLoader->Destroy();
mIconLoader = nullptr;
}
ClearIcon();
}
}
already_AddRefed<const ComputedStyle>
nsMenuObject::GetComputedStyle()
{
RefPtr<const 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(const 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<const 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;
}
}

169
widget/gtk/nsMenuObject.h Normal file
View File

@@ -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(const mozilla::ComputedStyle *aComputedStyle);
void UpdateSensitivity();
void UpdateIcon(const mozilla::ComputedStyle *aComputedStyle);
already_AddRefed<const 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(const 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__ */

View File

@@ -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(const 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<const 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;
}

View File

@@ -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(const 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__ */

View File

@@ -0,0 +1,346 @@
/* -*- 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::ContentWillBeRemoved(nsIContent* aChild, const BatchRemovalState*)
{
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(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();
}

View File

@@ -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__ */

View File

@@ -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);
}
}

View File

@@ -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__ */

View File

@@ -7299,6 +7299,10 @@ void nsWindow::HideWindowChrome(bool aShouldHide) {
SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle); SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
} }
void nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
mMenuBar = std::move(aMenuBar);
}
bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
bool aAlwaysRollup) { bool aAlwaysRollup) {
LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup); LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);

View File

@@ -31,6 +31,8 @@
#include "IMContextWrapper.h" #include "IMContextWrapper.h"
#include "LookAndFeel.h" #include "LookAndFeel.h"
#include "nsMenuBar.h"
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
# include "mozilla/a11y/LocalAccessible.h" # include "mozilla/a11y/LocalAccessible.h"
#endif #endif
@@ -246,6 +248,8 @@ class nsWindow final : public nsBaseWidget {
nsresult MakeFullScreen(bool aFullScreen) override; nsresult MakeFullScreen(bool aFullScreen) override;
void HideWindowChrome(bool aShouldHide) override; void HideWindowChrome(bool aShouldHide) override;
void SetMenuBar(mozilla::UniquePtr<nsMenuBar> aMenuBar);
/** /**
* GetLastUserInputTime returns a timestamp for the most recent user input * GetLastUserInputTime returns a timestamp for the most recent user input
* event. This is intended for pointer grab requests (including drags). * event. This is intended for pointer grab requests (including drags).
@@ -970,6 +974,8 @@ class nsWindow final : public nsBaseWidget {
static bool sTransparentMainWindow; static bool sTransparentMainWindow;
mozilla::UniquePtr<nsMenuBar> mMenuBar;
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible; RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;

View File

@@ -170,6 +170,11 @@ EXPORTS += [
"PuppetWidget.h", "PuppetWidget.h",
] ]
if toolkit == "gtk":
EXPORTS += [
"nsINativeMenuService.h",
]
EXPORTS.mozilla += [ EXPORTS.mozilla += [
"BasicEvents.h", "BasicEvents.h",
"ClipboardContentAnalysisChild.h", "ClipboardContentAnalysisChild.h",

View File

@@ -0,0 +1,38 @@
/* -*- 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_INLINE_DECL_STATIC_IID(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;
};
#endif // nsINativeMenuService_h_

View File

@@ -38,6 +38,14 @@
// Menus // 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} // {F6CD4F21-53AF-11d2-8DC4-00609703C14E}
#define NS_POPUPMENU_CID \ #define NS_POPUPMENU_CID \
{0xf6cd4f21, 0x53af, 0x11d2, {0x8d, 0xc4, 0x0, 0x60, 0x97, 0x3, 0xc1, 0x4e}} {0xf6cd4f21, 0x53af, 0x11d2, {0x8d, 0xc4, 0x0, 0x60, 0x97, 0x3, 0xc1, 0x4e}}

View File

@@ -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"),
]

View File

@@ -13,6 +13,7 @@ from Atom import (
PseudoElementAtom, PseudoElementAtom,
) )
from HTMLAtoms import HTML_PARSER_ATOMS from HTMLAtoms import HTML_PARSER_ATOMS
from NativeMenuAtoms import NATIVE_MENU_ATOMS
# Static atom definitions, used to generate nsGkAtomList.h. # Static atom definitions, used to generate nsGkAtomList.h.
# #
@@ -2610,7 +2611,7 @@ STATIC_ATOMS = [
InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"), InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"), InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
# END ATOMS # END ATOMS
] + HTML_PARSER_ATOMS ] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS
# fmt: on # fmt: on