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