Issue MoonchildProductions/UXP#1578 - Add global menubar support for GTK

pull/24/head
Lootyhoof 2 years ago committed by Roy Tam
parent cd1c94480d
commit 564f1026d5
  1. 4
      layout/build/moz.build
  2. 7
      layout/build/nsLayoutStatics.cpp
  3. 4
      modules/libpref/init/all.js
  4. 10
      toolkit/content/widgets/popup.xml
  5. 9
      toolkit/content/xul.css
  6. 11
      widget/gtk/moz.build
  7. 59
      widget/gtk/nsDbusmenu.cpp
  8. 97
      widget/gtk/nsDbusmenu.h
  9. 800
      widget/gtk/nsMenu.cpp
  10. 120
      widget/gtk/nsMenu.h
  11. 541
      widget/gtk/nsMenuBar.cpp
  12. 103
      widget/gtk/nsMenuBar.h
  13. 156
      widget/gtk/nsMenuContainer.cpp
  14. 66
      widget/gtk/nsMenuContainer.h
  15. 712
      widget/gtk/nsMenuItem.cpp
  16. 77
      widget/gtk/nsMenuItem.h
  17. 634
      widget/gtk/nsMenuObject.cpp
  18. 165
      widget/gtk/nsMenuObject.h
  19. 74
      widget/gtk/nsMenuSeparator.cpp
  20. 33
      widget/gtk/nsMenuSeparator.h
  21. 9
      widget/gtk/nsNativeMenuAtomList.h
  22. 35
      widget/gtk/nsNativeMenuAtoms.cpp
  23. 23
      widget/gtk/nsNativeMenuAtoms.h
  24. 329
      widget/gtk/nsNativeMenuDocListener.cpp
  25. 146
      widget/gtk/nsNativeMenuDocListener.h
  26. 485
      widget/gtk/nsNativeMenuService.cpp
  27. 80
      widget/gtk/nsNativeMenuService.h
  28. 8
      widget/gtk/nsWidgetFactory.cpp
  29. 6
      widget/gtk/nsWindow.cpp
  30. 6
      widget/gtk/nsWindow.h
  31. 4
      widget/moz.build
  32. 2
      xpfe/appshell/nsWebShellWindow.cpp

@ -73,6 +73,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
'/dom/system',
'/dom/system/android',
]
elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
LOCAL_INCLUDES += [
'/widget/gtk',
]
if CONFIG['MOZ_WEBSPEECH']:
LOCAL_INCLUDES += [

@ -124,6 +124,10 @@
#include "mozilla/StaticPresData.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#ifdef MOZ_WIDGET_GTK
#include "nsNativeMenuAtoms.h"
#endif
using namespace mozilla;
using namespace mozilla::net;
using namespace mozilla::dom;
@ -158,6 +162,9 @@ nsLayoutStatics::Initialize()
nsTextServicesDocument::RegisterAtoms();
nsHTMLTags::RegisterAtoms();
nsRDFAtoms::RegisterAtoms();
#ifdef MOZ_WIDGET_GTK
nsNativeMenuAtoms::RegisterAtoms();
#endif
NS_SealStaticAtomTable();

@ -251,6 +251,10 @@ pref("browser.sessionhistory.max_total_viewers", -1);
pref("browser.newtabpage.add_to_session_history", false);
pref("ui.use_native_colors", true);
#ifdef MOZ_WIDGET_GTK
// Determines whether the menubar is shown in the global menubar or not.
pref("ui.use_global_menubar", false);
#endif
pref("ui.click_hold_context_menus", false);
// Duration of timeout of incremental search in menus (ms). 0 means infinite.
pref("ui.menu.incremental_search.timeout", 1000);

@ -25,8 +25,14 @@
</getter>
</property>
<property name="state" readonly="true"
onget="return this.popupBoxObject.popupState"/>
<property name="state" readonly="true">
<getter><![CDATA[
if (this.hasAttribute('_moz-nativemenupopupstate'))
return this.getAttribute('_moz-nativemenupopupstate');
else
return this.popupBoxObject.popupState;
]]></getter>
</property>
<property name="triggerNode" readonly="true"
onget="return this.popupBoxObject.triggerNode"/>

@ -307,6 +307,15 @@ toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true
}
%endif
%ifdef MOZ_WIDGET_GTK
window[shellshowingmenubar="true"] menubar,
window[shellshowingmenubar="true"]
toolbar[type="menubar"]:not([customizing="true"]) {
/* If a system-wide global menubar is in use, hide the XUL menubar. */
display: none !important;
}
%endif
toolbarseparator {
-moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
}

@ -24,10 +24,18 @@ UNIFIED_SOURCES += [
'nsAppShell.cpp',
'nsBidiKeyboard.cpp',
'nsColorPicker.cpp',
'nsDbusmenu.cpp',
'nsFilePicker.cpp',
'nsGtkKeyUtils.cpp',
'nsImageToPixbuf.cpp',
'nsLookAndFeel.cpp',
'nsMenuBar.cpp',
'nsMenuContainer.cpp',
'nsMenuItem.cpp',
'nsMenuObject.cpp',
'nsMenuSeparator.cpp',
'nsNativeMenuAtoms.cpp',
'nsNativeMenuDocListener.cpp',
'nsNativeThemeGTK.cpp',
'nsScreenGtk.cpp',
'nsScreenManagerGtk.cpp',
@ -40,6 +48,8 @@ UNIFIED_SOURCES += [
]
SOURCES += [
'nsMenu.cpp', # conflicts with X11 headers
'nsNativeMenuService.cpp',
'nsWindow.cpp', # conflicts with X11 headers
]
@ -104,6 +114,7 @@ FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/layout/generic',
'/layout/style',
'/layout/xul',
'/other-licenses/atk-1.0',
'/widget',

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsDbusmenu.h"
#include "prlink.h"
#include "mozilla/ArrayUtils.h"
#define FUNC(name, type, params) \
nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name;
DBUSMENU_GLIB_FUNCTIONS
DBUSMENU_GTK_FUNCTIONS
#undef FUNC
static PRLibrary *gDbusmenuGlib = nullptr;
static PRLibrary *gDbusmenuGtk = nullptr;
typedef void (*nsDbusmenuFunc)();
struct nsDbusmenuDynamicFunction {
const char *functionName;
nsDbusmenuFunc *function;
};
/* static */ nsresult
nsDbusmenuFunctions::Init() {
#define FUNC(name, type, params) \
{ #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name },
static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = {
DBUSMENU_GLIB_FUNCTIONS
};
static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = {
DBUSMENU_GTK_FUNCTIONS
};
#define LOAD_LIBRARY(symbol, name) \
if (!g##symbol) { \
g##symbol = PR_LoadLibrary(name); \
if (!g##symbol) { \
return NS_ERROR_FAILURE; \
} \
} \
for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \
*k##symbol##Symbols[i].function = \
PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \
if (!*k##symbol##Symbols[i].function) { \
return NS_ERROR_FAILURE; \
} \
}
LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4")
#if (MOZ_WIDGET_GTK == 3)
LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4")
#else
LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk.so.4")
#endif
#undef LOAD_LIBRARY
return NS_OK;
}

@ -0,0 +1,97 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __nsDbusmenu_h__
#define __nsDbusmenu_h__
#include "nsError.h"
#include <glib.h>
#include <gdk/gdk.h>
#define DBUSMENU_GLIB_FUNCTIONS \
FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child, guint position)) \
FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \
FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem* mi, DbusmenuMenuitem* child)) \
FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem* mi)) \
FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \
FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem* mi, const gchar* property)) \
FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property)) \
FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem* mi, const gchar* property)) \
FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gchar* value)) \
FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gboolean value)) \
FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem* mi, const gchar* property, const gint value)) \
FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem* mi, guint timestamp)) \
FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem* mi)) \
FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar* object)) \
FUNC(dbusmenu_server_set_root, void, (DbusmenuServer* server, DbusmenuMenuitem* root)) \
FUNC(dbusmenu_server_set_status, void, (DbusmenuServer* server, DbusmenuStatus status))
#define DBUSMENU_GTK_FUNCTIONS \
FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem* menuitem, const gchar* property, const GdkPixbuf* data)) \
FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem* menuitem, guint key, GdkModifierType modifier))
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
typedef struct _DbusmenuServer DbusmenuServer;
enum DbusmenuStatus {
DBUSMENU_STATUS_NORMAL,
DBUSMENU_STATUS_NOTICE
};
#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu"
#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled"
#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data"
#define DBUSMENU_MENUITEM_PROP_LABEL "label"
#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
#define DBUSMENU_MENUITEM_PROP_TYPE "type"
#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state"
#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type"
#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show"
#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event"
#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1
#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0
#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object"
class nsDbusmenuFunctions {
public:
nsDbusmenuFunctions() = delete;
static nsresult Init();
#define FUNC(name, type, params) \
typedef type (*_##name##_fn) params; \
static _##name##_fn s_##name;
DBUSMENU_GLIB_FUNCTIONS
DBUSMENU_GTK_FUNCTIONS
#undef FUNC
};
#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position
#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append
#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete
#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children
#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new
#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get
#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool
#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove
#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set
#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool
#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int
#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user
#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children
#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new
#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root
#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status
#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image
#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut
#endif /* __nsDbusmenu_h__ */

@ -0,0 +1,800 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define _IMPL_NS_LAYOUT
#include "mozilla/dom/Element.h"
#include "mozilla/Assertions.h"
#include "mozilla/GuardObjects.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Move.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "nsAutoPtr.h"
#include "nsBindingManager.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsCSSValue.h"
#include "nsGkAtoms.h"
#include "nsGtkUtils.h"
#include "nsIAtom.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"
#include "nsIRunnable.h"
#include "nsITimer.h"
#include "nsString.h"
#include "nsStyleContext.h"
#include "nsStyleSet.h"
#include "nsStyleStruct.h"
#include "nsThreadUtils.h"
#include "nsXBLBinding.h"
#include "nsXBLService.h"
#include "nsNativeMenuAtoms.h"
#include "nsNativeMenuDocListener.h"
#include <glib-object.h>
#include "nsMenu.h"
using namespace mozilla;
class nsMenuContentInsertedEvent : public Runnable {
public:
nsMenuContentInsertedEvent(nsMenu* aMenu,
nsIContent* aContainer,
nsIContent* aChild,
nsIContent* aPrevSibling) :
mWeakMenu(aMenu),
mContainer(aContainer),
mChild(aChild),
mPrevSibling(aPrevSibling) { }
NS_IMETHODIMP Run() {
if (!mWeakMenu) {
return NS_OK;
}
static_cast<nsMenu *>(mWeakMenu.get())->HandleContentInserted(mContainer,
mChild,
mPrevSibling);
return NS_OK;
}
private:
nsWeakMenuObject mWeakMenu;
nsCOMPtr<nsIContent> mContainer;
nsCOMPtr<nsIContent> mChild;
nsCOMPtr<nsIContent> mPrevSibling;
};
class nsMenuContentRemovedEvent : public Runnable {
public:
nsMenuContentRemovedEvent(nsMenu* aMenu,
nsIContent* aContainer,
nsIContent* aChild) :
mWeakMenu(aMenu),
mContainer(aContainer),
mChild(aChild) { }
NS_IMETHODIMP Run() {
if (!mWeakMenu) {
return NS_OK;
}
static_cast<nsMenu *>(mWeakMenu.get())->HandleContentRemoved(mContainer,
mChild);
return NS_OK;
}
private:
nsWeakMenuObject mWeakMenu;
nsCOMPtr<nsIContent> mContainer;
nsCOMPtr<nsIContent> mChild;
};
static void
DispatchMouseEvent(nsIContent* aTarget, mozilla::EventMessage aMsg) {
if (!aTarget) {
return;
}
WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal);
aTarget->DispatchDOMEvent(&event, nullptr, nullptr, nullptr);
}
static void
AttachXBLBindings(nsIContent* aContent) {
nsIDocument* doc = aContent->OwnerDoc();
nsIPresShell* shell = doc->GetShell();
if (!shell) {
return;
}
RefPtr<nsStyleContext> sc =
shell->StyleSet()->AsGecko()->ResolveStyleFor(aContent->AsElement(),
nullptr);
if (!sc) {
return;
}
const nsStyleDisplay* display = sc->StyleDisplay();
if (!display->mBinding) {
return;
}
nsXBLService* xbl = nsXBLService::GetInstance();
if (!xbl) {
return;
}
RefPtr<nsXBLBinding> binding;
bool dummy;
nsresult rv = xbl->LoadBindings(aContent, display->mBinding->GetURI(),
display->mBinding->mOriginPrincipal,
getter_AddRefs(binding), &dummy);
if ((NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED) || !binding) {
return;
}
doc->BindingManager()->AddToAttachedQueue(binding);
}
void
nsMenu::SetPopupState(EPopupState aState) {
mPopupState = aState;
if (!mPopupContent) {
return;
}
nsAutoString state;
switch (aState) {
case ePopupState_Showing:
state.Assign(NS_LITERAL_STRING("showing"));
break;
case ePopupState_Open:
state.Assign(NS_LITERAL_STRING("open"));
break;
case ePopupState_Hiding:
state.Assign(NS_LITERAL_STRING("hiding"));
break;
default:
break;
}
if (state.IsEmpty()) {
mPopupContent->UnsetAttr(kNameSpaceID_None,
nsNativeMenuAtoms::_moz_nativemenupopupstate,
false);
} else {
mPopupContent->SetAttr(kNameSpaceID_None,
nsNativeMenuAtoms::_moz_nativemenupopupstate,
state, false);
}
}
/* static */ void
nsMenu::DoOpenCallback(nsITimer* aTimer, void* aClosure) {
nsMenu* self = static_cast<nsMenu *>(aClosure);
dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0);
self->mOpenDelayTimer = nullptr;
}
/* static */ void
nsMenu::menu_event_cb(DbusmenuMenuitem* menu,
const gchar* name,
GVariant* value,
guint timestamp,
gpointer user_data) {
nsMenu* self = static_cast<nsMenu *>(user_data);
nsAutoCString event(name);
if (event.Equals(NS_LITERAL_CSTRING("closed"))) {
self->OnClose();
return;
}
if (event.Equals(NS_LITERAL_CSTRING("opened"))) {
self->OnOpen();
return;
}
}
void
nsMenu::MaybeAddPlaceholderItem() {
MOZ_ASSERT(!IsInBatchedUpdate(),
"Shouldn't be modifying the native menu structure now");
GList* children = dbusmenu_menuitem_get_children(GetNativeData());
if (!children) {
MOZ_ASSERT(!mPlaceholderItem);
mPlaceholderItem = dbusmenu_menuitem_new();
if (!mPlaceholderItem) {
return;
}
dbusmenu_menuitem_property_set_bool(mPlaceholderItem,
DBUSMENU_MENUITEM_PROP_VISIBLE,
false);
MOZ_ALWAYS_TRUE(
dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem));
}
}
void
nsMenu::EnsureNoPlaceholderItem() {
MOZ_ASSERT(!IsInBatchedUpdate(),
"Shouldn't be modifying the native menu structure now");
if (!mPlaceholderItem) {
return;
}
MOZ_ALWAYS_TRUE(
dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem));
MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData()));
g_object_unref(mPlaceholderItem);
mPlaceholderItem = nullptr;
}
void
nsMenu::OnOpen() {
if (mNeedsRebuild) {
Build();
}
nsWeakMenuObject self(this);
nsCOMPtr<nsIContent> origPopupContent(mPopupContent);
{
nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
SetPopupState(ePopupState_Showing);
DispatchMouseEvent(mPopupContent, eXULPopupShowing);
ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
NS_LITERAL_STRING("true"), true);
}
if (!self) {
// We were deleted!
return;
}
// I guess that the popup could have changed
if (origPopupContent != mPopupContent) {
return;
}
nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
size_t count = ChildCount();
for (size_t i = 0; i < count; ++i) {
ChildAt(i)->ContainerIsOpening();
}
SetPopupState(ePopupState_Open);
DispatchMouseEvent(mPopupContent, eXULPopupShown);
}
void
nsMenu::Build() {
mNeedsRebuild = false;
while (ChildCount() > 0) {
RemoveChildAt(0);
}
InitializePopup();
if (!mPopupContent) {
return;
}
uint32_t count = mPopupContent->GetChildCount();
for (uint32_t i = 0; i < count; ++i) {
nsIContent* childContent = mPopupContent->GetChildAt(i);
UniquePtr<nsMenuObject> child = CreateChild(childContent);
if (!child) {
continue;
}
AppendChild(Move(child));
}
}
void
nsMenu::InitializePopup() {
nsCOMPtr<nsIContent> oldPopupContent;
oldPopupContent.swap(mPopupContent);
for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
nsIContent* child = ContentNode()->GetChildAt(i);
int32_t dummy;
nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
if (tag == nsGkAtoms::menupopup) {
mPopupContent = child;
break;
}
}
if (oldPopupContent == mPopupContent) {
return;
}
// The popup has changed
if (oldPopupContent) {
DocListener()->UnregisterForContentChanges(oldPopupContent);
}
SetPopupState(ePopupState_Closed);
if (!mPopupContent) {
return;
}
AttachXBLBindings(mPopupContent);
DocListener()->RegisterForContentChanges(mPopupContent, this);
}
void
nsMenu::RemoveChildAt(size_t aIndex) {
MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem,
"Shouldn't have a placeholder menuitem");
nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate());
StructureMutated();
if (!IsInBatchedUpdate()) {
MaybeAddPlaceholderItem();
}
}
void
nsMenu::RemoveChild(nsIContent* aChild) {
size_t index = IndexOf(aChild);
if (index == NoIndex) {
return;
}
RemoveChildAt(index);
}
void
nsMenu::InsertChildAfter(UniquePtr<nsMenuObject> aChild,
nsIContent* aPrevSibling) {
if (!IsInBatchedUpdate()) {
EnsureNoPlaceholderItem();
}
nsMenuContainer::InsertChildAfter(Move(aChild), aPrevSibling,
!IsInBatchedUpdate());
StructureMutated();
}
void
nsMenu::AppendChild(UniquePtr<nsMenuObject> aChild) {
if (!IsInBatchedUpdate()) {
EnsureNoPlaceholderItem();
}
nsMenuContainer::AppendChild(Move(aChild), !IsInBatchedUpdate());
StructureMutated();
}
bool
nsMenu::IsInBatchedUpdate() const {
return mBatchedUpdateState != eBatchedUpdateState_Inactive;
}
void
nsMenu::StructureMutated() {
if (!IsInBatchedUpdate()) {
return;
}
mBatchedUpdateState = eBatchedUpdateState_DidMutate;
}
bool
nsMenu::CanOpen() const {
bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(),
DBUSMENU_MENUITEM_PROP_VISIBLE);
bool isDisabled = ContentNode()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::disabled,
nsGkAtoms::_true,
eCaseMatters);
return (isVisible && !isDisabled);
}
void
nsMenu::HandleContentInserted(nsIContent* aContainer,
nsIContent* aChild,
nsIContent* aPrevSibling) {
if (aContainer == mPopupContent) {
UniquePtr<nsMenuObject> child = CreateChild(aChild);
if (child) {
InsertChildAfter(Move(child), aPrevSibling);
}
} else {
Build();
}
}
void
nsMenu::HandleContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
if (aContainer == mPopupContent) {
RemoveChild(aChild);
} else {
Build();
}
}
void
nsMenu::InitializeNativeData() {
// Dbusmenu provides an "about-to-show" signal, and also "opened" and
// "closed" events. However, Unity is the only thing that sends
// both "about-to-show" and "opened" events. Unity 2D and the HUD only
// send "opened" events, so we ignore "about-to-show" (I don't think
// there's any real difference between them anyway).
// To complicate things, there are certain conditions where we don't
// get a "closed" event, so we need to be able to handle this :/
g_signal_connect(G_OBJECT(GetNativeData()), "event",
G_CALLBACK(menu_event_cb), this);
mNeedsRebuild = true;
mNeedsUpdate = true;
MaybeAddPlaceholderItem();
AttachXBLBindings(ContentNode());
}
void
nsMenu::Update(nsStyleContext* aStyleContext) {
if (mNeedsUpdate) {
mNeedsUpdate = false;
UpdateLabel();
UpdateSensitivity();
}
UpdateVisibility(aStyleContext);
UpdateIcon(aStyleContext);
}
nsMenuObject::PropertyFlags
nsMenu::SupportedProperties() const {
return static_cast<nsMenuObject::PropertyFlags>(
nsMenuObject::ePropLabel |
nsMenuObject::ePropEnabled |
nsMenuObject::ePropVisible |
nsMenuObject::ePropIconData |
nsMenuObject::ePropChildDisplay
);
}
void
nsMenu::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {
MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
"Received an event that wasn't meant for us!");
if (mNeedsUpdate) {
return;
}
if (aContent != ContentNode()) {
return;
}
if (!Parent()->IsBeingDisplayed()) {
mNeedsUpdate = true;
return;
}
if (aAttribute == nsGkAtoms::disabled) {
UpdateSensitivity();
} else if (aAttribute == nsGkAtoms::label ||
aAttribute == nsGkAtoms::accesskey ||
aAttribute == nsGkAtoms::crop) {
UpdateLabel();
} else if (aAttribute == nsGkAtoms::hidden ||
aAttribute == nsGkAtoms::collapsed) {
RefPtr<nsStyleContext> sc = GetStyleContext();
UpdateVisibility(sc);
} else if (aAttribute == nsGkAtoms::image) {
RefPtr<nsStyleContext> sc = GetStyleContext();
UpdateIcon(sc);
}
}
void
nsMenu::OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
nsIContent* aPrevSibling) {
MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
"Received an event that wasn't meant for us!");
if (mNeedsRebuild) {
return;
}
if (mPopupState == ePopupState_Closed) {
mNeedsRebuild = true;
return;
}
nsContentUtils::AddScriptRunner(
new nsMenuContentInsertedEvent(this, aContainer, aChild,
aPrevSibling));
}
void
nsMenu::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
"Received an event that wasn't meant for us!");
if (mNeedsRebuild) {
return;
}
if (mPopupState == ePopupState_Closed) {
mNeedsRebuild = true;
return;
}
nsContentUtils::AddScriptRunner(
new nsMenuContentRemovedEvent(this, aContainer, aChild));
}
/*
* Some menus (eg, the History menu in Firefox) refresh themselves on
* opening by removing all children and then re-adding new ones. As this
* happens whilst the menu is opening in Unity, it causes some flickering
* as the menu popup is resized multiple times. To avoid this, we try to
* reuse native menu items when the menu structure changes during a
* batched update. If we can handle menu structure changes from Goanna
* just by updating properties of native menu items (rather than destroying
* and creating new ones), then we eliminate any flickering that occurs as
* the menu is opened. To do this, we don't modify any native menu items
* until the end of the update batch.
*/
void
nsMenu::OnBeginUpdates(nsIContent* aContent) {
MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
"Received an event that wasn't meant for us!");
MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!");
if (aContent != mPopupContent) {
return;
}
mBatchedUpdateState = eBatchedUpdateState_Active;
}
void
nsMenu::OnEndUpdates() {
if (!IsInBatchedUpdate()) {
return;
}
bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate;
mBatchedUpdateState = eBatchedUpdateState_Inactive;
/* Optimize for the case where we only had attribute changes */
if (!didMutate) {
return;
}
EnsureNoPlaceholderItem();
GList* nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData());
DbusmenuMenuitem* nextOwnedNativeChild = nullptr;
size_t count = ChildCount();
// Find the first native menu item that is `owned` by a corresponding
// Goanna menuitem
for (size_t i = 0; i < count; ++i) {
if (ChildAt(i)->GetNativeData()) {
nextOwnedNativeChild = ChildAt(i)->GetNativeData();
break;
}
}
// Now iterate over all Goanna menuitems
for (size_t i = 0; i < count; ++i) {
nsMenuObject* child = ChildAt(i);
if (child->GetNativeData()) {
// This child already has a corresponding native menuitem.
// Remove all preceding orphaned native items. At this point, we
// modify the native menu structure.
while (nextNativeChild &&
nextNativeChild->data != nextOwnedNativeChild) {
DbusmenuMenuitem* data =
static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
nextNativeChild = nextNativeChild->next;
MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(),
data));
}
if (nextNativeChild) {
nextNativeChild = nextNativeChild->next;
}
// Now find the next native menu item that is `owned`
nextOwnedNativeChild = nullptr;
for (size_t j = i + 1; j < count; ++j) {
if (ChildAt(j)->GetNativeData()) {
nextOwnedNativeChild = ChildAt(j)->GetNativeData();
break;
}
}
} else {
// This child is new, and doesn't have a native menu item. Find one!
if (nextNativeChild &&
nextNativeChild->data != nextOwnedNativeChild) {
DbusmenuMenuitem* data =
static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
if (NS_SUCCEEDED(child->AdoptNativeData(data))) {
nextNativeChild = nextNativeChild->next;
}
}
// There wasn't a suitable one available, so create a new one.
// At this point, we modify the native menu structure.
if (!child->GetNativeData()) {
child->CreateNativeData();
MOZ_ALWAYS_TRUE(
dbusmenu_menuitem_child_add_position(GetNativeData(),
child->GetNativeData(),
i));
}
}
}
while (nextNativeChild) {
DbusmenuMenuitem* data =
static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
nextNativeChild = nextNativeChild->next;
MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data));
}
MaybeAddPlaceholderItem();
}
nsMenu::nsMenu(nsMenuContainer* aParent, nsIContent* aContent) :
nsMenuContainer(aParent, aContent),
mNeedsRebuild(false),
mNeedsUpdate(false),
mPlaceholderItem(nullptr),
mPopupState(ePopupState_Closed),
mBatchedUpdateState(eBatchedUpdateState_Inactive) {
MOZ_COUNT_CTOR(nsMenu);
}
nsMenu::~nsMenu() {
if (IsInBatchedUpdate()) {
OnEndUpdates();
}
// Although nsTArray will take care of this in its destructor,
// we have to manually ensure children are removed from our native menu
// item, just in case our parent recycles us
while (ChildCount() > 0) {
RemoveChildAt(0);
}
EnsureNoPlaceholderItem();
if (DocListener() && mPopupContent) {
DocListener()->UnregisterForContentChanges(mPopupContent);
}
if (GetNativeData()) {
g_signal_handlers_disconnect_by_func(GetNativeData(),
FuncToGpointer(menu_event_cb),
this);
}
MOZ_COUNT_DTOR(nsMenu);
}
nsMenuObject::EType
nsMenu::Type() const {
return eType_Menu;
}
bool
nsMenu::IsBeingDisplayed() const {
return mPopupState == ePopupState_Open;
}
bool
nsMenu::NeedsRebuild() const {
return mNeedsRebuild;
}
void
nsMenu::OpenMenu() {
if (!CanOpen()) {
return;
}
if (mOpenDelayTimer) {
return;
}
// Here, we synchronously fire popupshowing and popupshown events and then
// open the menu after a short delay. This allows the menu to refresh before
// it's shown, and avoids an issue where keyboard focus is not on the first
// item of the history menu in Firefox when opening it with the keyboard,
// because extra items to appear at the top of the menu
OnOpen();
mOpenDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (!mOpenDelayTimer) {
return;
}
if (NS_FAILED(mOpenDelayTimer->InitWithFuncCallback(DoOpenCallback,
this,
100,
nsITimer::TYPE_ONE_SHOT))) {
mOpenDelayTimer = nullptr;
}
}
void
nsMenu::OnClose() {
if (mPopupState == ePopupState_Closed) {
return;
}
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
// We do this to avoid mutating our view of the menu until
// after we have finished
nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
SetPopupState(ePopupState_Hiding);
DispatchMouseEvent(mPopupContent, eXULPopupHiding);
// Sigh, make sure all of our descendants are closed, as we don't
// always get closed events for submenus when scrubbing quickly through
// the menu
size_t count = ChildCount();
for (size_t i = 0; i < count; ++i) {
if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) {
static_cast<nsMenu *>(ChildAt(i))->OnClose();
}
}
SetPopupState(ePopupState_Closed);
DispatchMouseEvent(mPopupContent, eXULPopupHidden);
ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
}

@ -0,0 +1,120 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __nsMenu_h__
#define __nsMenu_h__
#include "mozilla/Attributes.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsDbusmenu.h"
#include "nsMenuContainer.h"
#include "nsMenuObject.h"
#include <glib.h>
class nsIAtom;
class nsIContent;
class nsITimer;
class nsStyleContext;
#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U
#define NSMENU_NUMBER_OF_FLAGS 4U
// This class represents a menu
class nsMenu final : public nsMenuContainer {
public:
nsMenu(nsMenuContainer* aParent, nsIContent* aContent);
~nsMenu();
nsMenuObject::EType Type() const override;
bool IsBeingDisplayed() const override;
bool NeedsRebuild() const override;
// Tell the desktop shell to display this menu
void OpenMenu();
// Normally called via the shell, but it's public so that child
// menuitems can do the shells work. Sigh....
void OnClose();
private:
friend class nsMenuContentInsertedEvent;
friend class nsMenuContentRemovedEvent;
enum EPopupState {
ePopupState_Closed,
ePopupState_Showing,
ePopupState_Open,
ePopupState_Hiding
};
void SetPopupState(EPopupState aState);
static void DoOpenCallback(nsITimer* aTimer, void* aClosure);
static void menu_event_cb(DbusmenuMenuitem* menu,
const gchar* name,
GVariant* value,
guint timestamp,
gpointer user_data);
// We add a placeholder item to empty menus so that Unity actually treats
// us as a proper menu, rather than a menuitem without a submenu
void MaybeAddPlaceholderItem();
// Removes a placeholder item if it exists and asserts that this succeeds
void EnsureNoPlaceholderItem();
void OnOpen();
void Build();
void InitializePopup();
void RemoveChildAt(size_t aIndex);
void RemoveChild(nsIContent* aChild);
void InsertChildAfter(mozilla::UniquePtr<nsMenuObject> aChild,
nsIContent* aPrevSibling);
void AppendChild(mozilla::UniquePtr<nsMenuObject> aChild);
bool IsInBatchedUpdate() const;
void StructureMutated();
bool CanOpen() const;
void HandleContentInserted(nsIContent* aContainer,
nsIContent* aChild,
nsIContent* aPrevSibling);
void HandleContentRemoved(nsIContent* aContainer,
nsIContent* aChild);
void InitializeNativeData() override;
void Update(nsStyleContext* aStyleContext) override;
nsMenuObject::PropertyFlags SupportedProperties() const override;
void OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) override;
void OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
nsIContent* aPrevSibling) override;
void OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) override;
void OnBeginUpdates(nsIContent* aContent) override;
void OnEndUpdates() override;
bool mNeedsRebuild;
bool mNeedsUpdate;
DbusmenuMenuitem* mPlaceholderItem;
EPopupState mPopupState;
enum EBatchedUpdateState {
eBatchedUpdateState_Inactive,
eBatchedUpdateState_Active,
eBatchedUpdateState_DidMutate
};
EBatchedUpdateState mBatchedUpdateState;
nsCOMPtr<nsIContent> mPopupContent;
nsCOMPtr<nsITimer> mOpenDelayTimer;
};
#endif /* __nsMenu_h__ */

@ -0,0 +1,541 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/Element.h"
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsIDocument.h"
#include "nsIDOMEvent.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMKeyEvent.h"
#include "nsIRunnable.h"
#include "nsIWidget.h"
#include "nsTArray.h"
#include "nsUnicharUtils.h"
#include "nsMenu.h"
#include "nsNativeMenuAtoms.h"
#include "nsNativeMenuService.h"
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <glib.h>
#include <glib-object.h>
#include "nsMenuBar.h"
using namespace mozilla;
static bool
ShouldHandleKeyEvent(nsIDOMEvent* aEvent) {
bool handled, trusted = false;
aEvent->GetPreventDefault(&handled);
aEvent->GetIsTrusted(&trusted);
if (handled || !trusted) {
return false;
}
return true;
}
class nsMenuBarContentInsertedEvent : public Runnable {
public:
nsMenuBarContentInsertedEvent(nsMenuBar* aMenuBar,
nsIContent* aChild,
nsIContent* aPrevSibling) :
mWeakMenuBar(aMenuBar),
mChild(aChild),
mPrevSibling(aPrevSibling) { }
NS_IMETHODIMP Run()
{
if (!mWeakMenuBar) {
return NS_OK;
}
static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentInserted(mChild,
mPrevSibling);
return NS_OK;
}
private:
nsWeakMenuObject mWeakMenuBar;
nsCOMPtr<nsIContent> mChild;
nsCOMPtr<nsIContent> mPrevSibling;
};
class nsMenuBarContentRemovedEvent : public Runnable {
public:
nsMenuBarContentRemovedEvent(nsMenuBar* aMenuBar,
nsIContent* aChild) :
mWeakMenuBar(aMenuBar),
mChild(aChild) { }
NS_IMETHODIMP Run()
{
if (!mWeakMenuBar) {
return NS_OK;
}
static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentRemoved(mChild);
return NS_OK;
}
private:
nsWeakMenuObject mWeakMenuBar;
nsCOMPtr<nsIContent> mChild;
};
class nsMenuBar::DocEventListener final : public nsIDOMEventListener {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
DocEventListener(nsMenuBar* aOwner) : mOwner(aOwner) { };
private:
~DocEventListener() { };
nsMenuBar* mOwner;
};
NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener)
NS_IMETHODIMP
nsMenuBar::DocEventListener::HandleEvent(nsIDOMEvent* aEvent) {
nsAutoString type;
nsresult rv = aEvent->GetType(type);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to determine event type");
return rv;
}
if (type.Equals(NS_LITERAL_STRING("focus"))) {
mOwner->Focus();
} else if (type.Equals(NS_LITERAL_STRING("blur"))) {
mOwner->Blur();
} else if (type.Equals(NS_LITERAL_STRING("keypress"))) {
rv = mOwner->Keypress(aEvent);
} else if (type.Equals(NS_LITERAL_STRING("keydown"))) {
rv = mOwner->KeyDown(aEvent);
} else if (type.Equals(NS_LITERAL_STRING("keyup"))) {
rv = mOwner->KeyUp(aEvent);
}
return rv;
}
nsMenuBar::nsMenuBar(nsIContent* aMenuBarNode) :
nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode),
mTopLevel(nullptr),
mServer(nullptr),
mIsActive(false) {
MOZ_COUNT_CTOR(nsMenuBar);
}
nsresult
nsMenuBar::Init(nsIWidget* aParent) {
MOZ_ASSERT(aParent);
GdkWindow* gdkWin = static_cast<GdkWindow* >(
aParent->GetNativeData(NS_NATIVE_WINDOW));
if (!gdkWin) {
return NS_ERROR_FAILURE;
}
gpointer user_data = nullptr;
gdk_window_get_user_data(gdkWin, &user_data);
if (!user_data || !GTK_IS_CONTAINER(user_data)) {
return NS_ERROR_FAILURE;
}
mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
if (!mTopLevel) {
return NS_ERROR_FAILURE;
}
g_object_ref(mTopLevel);
nsAutoCString path;
path.Append(NS_LITERAL_CSTRING("/com/canonical/menu/"));
char xid[10];
sprintf(xid, "%X", static_cast<uint32_t>(
GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
path.Append(xid);
mServer = dbusmenu_server_new(path.get());
if (!mServer) {
return NS_ERROR_FAILURE;
}
CreateNativeData();
if (!GetNativeData()) {
return NS_ERROR_FAILURE;
}
dbusmenu_server_set_root(mServer, GetNativeData());
mEventListener = new DocEventListener(this);
mDocument = do_QueryInterface(ContentNode()->OwnerDoc());
mAccessKey = Preferences::GetInt("ui.key.menuAccessKey");
if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
mAccessKeyMask = eModifierShift;
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
mAccessKeyMask = eModifierCtrl;
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
mAccessKeyMask = eModifierAlt;
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
mAccessKeyMask = eModifierMeta;
} else {
mAccessKeyMask = eModifierAlt;
}
return NS_OK;
}
void
nsMenuBar::Build() {
uint32_t count = ContentNode()->GetChildCount();
for (uint32_t i = 0; i < count; ++i) {
nsIContent* childContent = ContentNode()->GetChildAt(i);
UniquePtr<nsMenuObject> child = CreateChild(childContent);
if (!child) {
continue;
}
AppendChild(Move(child));
}
}
void
nsMenuBar::DisconnectDocumentEventListeners() {
mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"),