michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsMenuFrame.h" michael@0: #include "nsBoxFrame.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsMenuBarFrame.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsBindingManager.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsIReflowCallback.h" michael@0: #include "nsISound.h" michael@0: #include "nsIDOMXULMenuListElement.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define NS_MENU_POPUP_LIST_INDEX 0 michael@0: michael@0: #if defined(XP_WIN) michael@0: #define NSCONTEXTMENUISMOUSEUP 1 michael@0: #endif michael@0: michael@0: static void michael@0: AssertNotCalled(void* aPropertyValue) michael@0: { michael@0: NS_ERROR("popup list should never be destroyed by the FramePropertyTable"); michael@0: } michael@0: NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled) michael@0: michael@0: static int32_t gEatMouseMove = false; michael@0: michael@0: const int32_t kBlinkDelay = 67; // milliseconds michael@0: michael@0: // this class is used for dispatching menu activation events asynchronously. michael@0: class nsMenuActivateEvent : public nsRunnable michael@0: { michael@0: public: michael@0: nsMenuActivateEvent(nsIContent *aMenu, michael@0: nsPresContext* aPresContext, michael@0: bool aIsActivate) michael@0: : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: nsAutoString domEventToFire; michael@0: michael@0: if (mIsActivate) { michael@0: // Highlight the menu. michael@0: mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, michael@0: NS_LITERAL_STRING("true"), true); michael@0: // The menuactivated event is used by accessibility to track the user's michael@0: // movements through menus michael@0: domEventToFire.AssignLiteral("DOMMenuItemActive"); michael@0: } michael@0: else { michael@0: // Unhighlight the menu. michael@0: mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); michael@0: domEventToFire.AssignLiteral("DOMMenuItemInactive"); michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: if (NS_SUCCEEDED(EventDispatcher::CreateEvent(mMenu, mPresContext, nullptr, michael@0: NS_LITERAL_STRING("Events"), michael@0: getter_AddRefs(event)))) { michael@0: event->InitEvent(domEventToFire, true, true); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, michael@0: mPresContext, nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mMenu; michael@0: nsRefPtr mPresContext; michael@0: bool mIsActivate; michael@0: }; michael@0: michael@0: class nsMenuAttributeChangedEvent : public nsRunnable michael@0: { michael@0: public: michael@0: nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr) michael@0: : mFrame(aFrame), mAttr(aAttr) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: nsMenuFrame* frame = static_cast(mFrame.GetFrame()); michael@0: NS_ENSURE_STATE(frame); michael@0: if (mAttr == nsGkAtoms::checked) { michael@0: frame->UpdateMenuSpecialState(frame->PresContext()); michael@0: } else if (mAttr == nsGkAtoms::acceltext) { michael@0: // someone reset the accelText attribute, michael@0: // so clear the bit that says *we* set it michael@0: frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); michael@0: frame->BuildAcceleratorText(true); michael@0: } michael@0: else if (mAttr == nsGkAtoms::key) { michael@0: frame->BuildAcceleratorText(true); michael@0: } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) { michael@0: frame->UpdateMenuType(frame->PresContext()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: protected: michael@0: nsWeakFrame mFrame; michael@0: nsCOMPtr mAttr; michael@0: }; michael@0: michael@0: // michael@0: // NS_NewMenuFrame and NS_NewMenuItemFrame michael@0: // michael@0: // Wrappers for creating a new menu popup container michael@0: // michael@0: nsIFrame* michael@0: NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); michael@0: it->SetIsMenu(true); michael@0: return it; michael@0: } michael@0: michael@0: nsIFrame* michael@0: NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); michael@0: it->SetIsMenu(false); michael@0: return it; michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsMenuFrame) michael@0: NS_QUERYFRAME_ENTRY(nsMenuFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) michael@0: michael@0: nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): michael@0: nsBoxFrame(aShell, aContext), michael@0: mIsMenu(false), michael@0: mChecked(false), michael@0: mIgnoreAccelTextChange(false), michael@0: mType(eMenuType_Normal), michael@0: mMenuParent(nullptr), michael@0: mBlinkState(0) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::SetParent(nsIFrame* aParent) michael@0: { michael@0: nsBoxFrame::SetParent(aParent); michael@0: InitMenuParent(aParent); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::InitMenuParent(nsIFrame* aParent) michael@0: { michael@0: while (aParent) { michael@0: nsMenuPopupFrame* popup = do_QueryFrame(aParent); michael@0: if (popup) { michael@0: mMenuParent = popup; michael@0: break; michael@0: } michael@0: michael@0: nsMenuBarFrame* menubar = do_QueryFrame(aParent); michael@0: if (menubar) { michael@0: mMenuParent = menubar; michael@0: break; michael@0: } michael@0: michael@0: aParent = aParent->GetParent(); michael@0: } michael@0: } michael@0: michael@0: class nsASyncMenuInitialization MOZ_FINAL : public nsIReflowCallback michael@0: { michael@0: public: michael@0: nsASyncMenuInitialization(nsIFrame* aFrame) michael@0: : mWeakFrame(aFrame) michael@0: { michael@0: } michael@0: michael@0: virtual bool ReflowFinished() MOZ_OVERRIDE michael@0: { michael@0: bool shouldFlush = false; michael@0: nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame()); michael@0: if (menu) { michael@0: menu->UpdateMenuType(menu->PresContext()); michael@0: shouldFlush = true; michael@0: } michael@0: delete this; michael@0: return shouldFlush; michael@0: } michael@0: michael@0: virtual void ReflowCallbackCanceled() MOZ_OVERRIDE michael@0: { michael@0: delete this; michael@0: } michael@0: michael@0: nsWeakFrame mWeakFrame; michael@0: }; michael@0: michael@0: void michael@0: nsMenuFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: // Set up a mediator which can be used for callbacks on this frame. michael@0: mTimerMediator = new nsMenuTimerMediator(this); michael@0: michael@0: InitMenuParent(aParent); michael@0: michael@0: BuildAcceleratorText(false); michael@0: nsIReflowCallback* cb = new nsASyncMenuInitialization(this); michael@0: PresContext()->PresShell()->PostReflowCallback(cb); michael@0: } michael@0: michael@0: const nsFrameList& michael@0: nsMenuFrame::GetChildList(ChildListID aListID) const michael@0: { michael@0: if (kPopupList == aListID) { michael@0: nsFrameList* list = GetPopupList(); michael@0: return list ? *list : nsFrameList::EmptyList(); michael@0: } michael@0: return nsBoxFrame::GetChildList(aListID); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::GetChildLists(nsTArray* aLists) const michael@0: { michael@0: nsBoxFrame::GetChildLists(aLists); michael@0: nsFrameList* list = GetPopupList(); michael@0: if (list) { michael@0: list->AppendIfNonempty(aLists, kPopupList); michael@0: } michael@0: } michael@0: michael@0: nsMenuPopupFrame* michael@0: nsMenuFrame::GetPopup() michael@0: { michael@0: nsFrameList* popupList = GetPopupList(); michael@0: return popupList ? static_cast(popupList->FirstChild()) : michael@0: nullptr; michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsMenuFrame::GetPopupList() const michael@0: { michael@0: if (!HasPopup()) { michael@0: return nullptr; michael@0: } michael@0: nsFrameList* prop = michael@0: static_cast(Properties().Get(PopupListProperty())); michael@0: NS_ASSERTION(prop && prop->GetLength() == 1 && michael@0: prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame, michael@0: "popup list should have exactly one nsMenuPopupFrame"); michael@0: return prop; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::DestroyPopupList() michael@0: { michael@0: NS_ASSERTION(HasPopup(), "huh?"); michael@0: nsFrameList* prop = michael@0: static_cast(Properties().Remove(PopupListProperty())); michael@0: NS_ASSERTION(prop && prop->IsEmpty(), michael@0: "popup list must exist and be empty when destroying"); michael@0: RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST); michael@0: prop->Delete(PresContext()->PresShell()); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) michael@0: { michael@0: for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { michael@0: nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get()); michael@0: if (popupFrame) { michael@0: // Remove the frame from the list and store it in a nsFrameList* property. michael@0: aFrameList.RemoveFrame(popupFrame); michael@0: nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame); michael@0: Properties().Set(PopupListProperty(), popupList); michael@0: AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); michael@0: if (aListID == kPrincipalList || aListID == kPopupList) { michael@0: SetPopupFrame(aChildList); michael@0: } michael@0: return nsBoxFrame::SetInitialChildList(aListID, aChildList); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: // Kill our timer if one is active. This is not strictly necessary as michael@0: // the pointer to this frame will be cleared from the mediator, but michael@0: // this is done for added safety. michael@0: if (mOpenTimer) { michael@0: mOpenTimer->Cancel(); michael@0: } michael@0: michael@0: StopBlinking(); michael@0: michael@0: // Null out the pointer to this frame in the mediator wrapper so that it michael@0: // doesn't try to interact with a deallocated frame. michael@0: mTimerMediator->ClearFrame(); michael@0: michael@0: // if the menu content is just being hidden, it may be made visible again michael@0: // later, so make sure to clear the highlighting. michael@0: mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false); michael@0: michael@0: // are we our menu parent's current menu item? michael@0: if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) { michael@0: // yes; tell it that we're going away michael@0: mMenuParent->CurrentMenuIsBeingDestroyed(); michael@0: } michael@0: michael@0: nsFrameList* popupList = GetPopupList(); michael@0: if (popupList) { michael@0: popupList->DestroyFramesFrom(aDestructRoot); michael@0: DestroyPopupList(); michael@0: } michael@0: michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!aBuilder->IsForEventDelivery()) { michael@0: nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); michael@0: return; michael@0: } michael@0: michael@0: nsDisplayListCollection set; michael@0: nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set); michael@0: michael@0: WrapListsInRedirector(aBuilder, set, aLists); michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEventStatus); michael@0: if (nsEventStatus_eConsumeNoDefault == *aEventStatus || michael@0: (mMenuParent && mMenuParent->IsMenuLocked())) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: if (*aEventStatus == nsEventStatus_eIgnore) michael@0: *aEventStatus = nsEventStatus_eConsumeDoDefault; michael@0: michael@0: bool onmenu = IsOnMenu(); michael@0: michael@0: if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) { michael@0: WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); michael@0: uint32_t keyCode = keyEvent->keyCode; michael@0: #ifdef XP_MACOSX michael@0: // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) michael@0: if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) || michael@0: (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: OpenMenu(false); michael@0: } michael@0: #else michael@0: // On other platforms, toggle menulist on unmodified F4 or Alt arrow michael@0: if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) || michael@0: ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) { michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: ToggleMenuState(); michael@0: } michael@0: #endif michael@0: } michael@0: else if (aEvent->message == NS_MOUSE_BUTTON_DOWN && michael@0: aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && michael@0: !IsDisabled() && IsMenu()) { michael@0: // The menu item was selected. Bring up the menu. michael@0: // We have children. michael@0: // Don't prevent the default action here, since that will also cancel michael@0: // potential drag starts. michael@0: if (!mMenuParent || mMenuParent->IsMenuBar()) { michael@0: ToggleMenuState(); michael@0: } michael@0: else { michael@0: if (!IsOpen()) { michael@0: OpenMenu(false); michael@0: } michael@0: } michael@0: } michael@0: else if ( michael@0: #ifndef NSCONTEXTMENUISMOUSEUP michael@0: (aEvent->message == NS_MOUSE_BUTTON_UP && michael@0: aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && michael@0: #else michael@0: aEvent->message == NS_CONTEXTMENU && michael@0: #endif michael@0: onmenu && !IsMenu() && !IsDisabled()) { michael@0: // if this menu is a context menu it accepts right-clicks...fire away! michael@0: // Make sure we cancel default processing of the context menu event so michael@0: // that it doesn't bubble and get seen again by the popuplistener and show michael@0: // another context menu. michael@0: // michael@0: // Furthermore (there's always more, isn't there?), on some platforms (win32 michael@0: // being one of them) we get the context menu event on a mouse up while michael@0: // on others we get it on a mouse down. For the ones where we get it on a michael@0: // mouse down, we must continue listening for the right button up event to michael@0: // dismiss the menu. michael@0: if (mMenuParent->IsContextMenu()) { michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: Execute(aEvent); michael@0: } michael@0: } michael@0: else if (aEvent->message == NS_MOUSE_BUTTON_UP && michael@0: aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && michael@0: !IsMenu() && !IsDisabled()) { michael@0: // Execute the execute event handler. michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: Execute(aEvent); michael@0: } michael@0: else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { michael@0: // Kill our timer if one is active. michael@0: if (mOpenTimer) { michael@0: mOpenTimer->Cancel(); michael@0: mOpenTimer = nullptr; michael@0: } michael@0: michael@0: // Deactivate the menu. michael@0: if (mMenuParent) { michael@0: bool onmenubar = mMenuParent->IsMenuBar(); michael@0: if (!(onmenubar && mMenuParent->IsActive())) { michael@0: if (IsMenu() && !onmenubar && IsOpen()) { michael@0: // Submenus don't get closed up immediately. michael@0: } michael@0: else if (this == mMenuParent->GetCurrentMenuItem()) { michael@0: mMenuParent->ChangeMenuItem(nullptr, false); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (aEvent->message == NS_MOUSE_MOVE && michael@0: (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) { michael@0: if (gEatMouseMove) { michael@0: gEatMouseMove = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Let the menu parent know we're the new item. michael@0: mMenuParent->ChangeMenuItem(this, false); michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); michael@0: NS_ENSURE_TRUE(mMenuParent, NS_OK); michael@0: michael@0: // we need to check if we really became the current menu michael@0: // item or not michael@0: nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem(); michael@0: if (realCurrentItem != this) { michael@0: // we didn't (presumably because a context menu was active) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Hovering over a menu in a popup should open it without a need for a click. michael@0: // A timer is used so that it doesn't open if the user moves the mouse quickly michael@0: // past the menu. This conditional check ensures that only menus have this michael@0: // behaviour michael@0: if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) { michael@0: int32_t menuDelay = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms michael@0: michael@0: // We're a menu, we're built, we're closed, and no timer has been kicked off. michael@0: mOpenTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::ToggleMenuState() michael@0: { michael@0: if (IsOpen()) michael@0: CloseMenu(false); michael@0: else michael@0: OpenMenu(false); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::PopupOpened() michael@0: { michael@0: nsWeakFrame weakFrame(this); michael@0: mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, michael@0: NS_LITERAL_STRING("true"), true); michael@0: if (!weakFrame.IsAlive()) michael@0: return; michael@0: michael@0: if (mMenuParent) { michael@0: mMenuParent->SetActive(true); michael@0: // Make sure the current menu which is being toggled on michael@0: // the menubar is highlighted michael@0: mMenuParent->SetCurrentMenuItem(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::PopupClosed(bool aDeselectMenu) michael@0: { michael@0: nsWeakFrame weakFrame(this); michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsUnsetAttrRunnable(mContent, nsGkAtoms::open)); michael@0: if (!weakFrame.IsAlive()) michael@0: return; michael@0: michael@0: // if the popup is for a menu on a menubar, inform menubar to deactivate michael@0: if (mMenuParent && mMenuParent->MenuClosed()) { michael@0: if (aDeselectMenu) { michael@0: SelectMenu(false); michael@0: } else { michael@0: // We are not deselecting the parent menu while closing the popup, so send michael@0: // a DOMMenuItemActive event to the menu to indicate that the menu is michael@0: // becoming active again. michael@0: nsMenuFrame *current = mMenuParent->GetCurrentMenuItem(); michael@0: if (current) { michael@0: // However, if the menu is a descendant on a menubar, and the menubar michael@0: // has the 'stay active' flag set, it means that the menubar is switching michael@0: // to another toplevel menu entirely (for example from Edit to View), so michael@0: // don't fire the DOMMenuItemActive event or else we'll send extraneous michael@0: // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected michael@0: // the old menu, so it doesn't need to happen again here, and the new michael@0: // menu can be selected right away. michael@0: nsIFrame* parent = current; michael@0: while (parent) { michael@0: nsMenuBarFrame* menubar = do_QueryFrame(parent); michael@0: if (menubar && menubar->GetStayActive()) michael@0: return; michael@0: michael@0: parent = parent->GetParent(); michael@0: } michael@0: michael@0: nsCOMPtr event = michael@0: new nsMenuActivateEvent(current->GetContent(), michael@0: PresContext(), true); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuFrame::SelectMenu(bool aActivateFlag) michael@0: { michael@0: if (mContent) { michael@0: // When a menu opens a submenu, the mouse will often be moved onto a michael@0: // sibling before moving onto an item within the submenu, causing the michael@0: // parent to become deselected. We need to ensure that the parent menu michael@0: // is reselected when an item in the submenu is selected, so navigate up michael@0: // from the item to its popup, and then to the popup above that. michael@0: if (aActivateFlag) { michael@0: nsIFrame* parent = GetParent(); michael@0: while (parent) { michael@0: nsMenuPopupFrame* menupopup = do_QueryFrame(parent); michael@0: if (menupopup) { michael@0: // a menu is always the direct parent of a menupopup michael@0: nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent()); michael@0: if (menu) { michael@0: // a popup however is not necessarily the direct parent of a menu michael@0: nsIFrame* popupParent = menu->GetParent(); michael@0: while (popupParent) { michael@0: menupopup = do_QueryFrame(popupParent); michael@0: if (menupopup) { michael@0: menupopup->SetCurrentMenuItem(menu); michael@0: break; michael@0: } michael@0: popupParent = popupParent->GetParent(); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: parent = parent->GetParent(); michael@0: } michael@0: } michael@0: michael@0: // cancel the close timer if selecting a menu within the popup to be closed michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) michael@0: pm->CancelMenuTimer(mMenuParent); michael@0: michael@0: nsCOMPtr event = michael@0: new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) { michael@0: // Reset the flag so that only one change is ignored. michael@0: mIgnoreAccelTextChange = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aAttribute == nsGkAtoms::checked || michael@0: aAttribute == nsGkAtoms::acceltext || michael@0: aAttribute == nsGkAtoms::key || michael@0: aAttribute == nsGkAtoms::type || michael@0: aAttribute == nsGkAtoms::name) { michael@0: nsCOMPtr event = michael@0: new nsMenuAttributeChangedEvent(this, aAttribute); michael@0: nsContentUtils::AddScriptRunner(event); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsMenuFrame::GetAnchor() michael@0: { michael@0: mozilla::dom::Element* anchor = nullptr; michael@0: michael@0: nsAutoString id; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id); michael@0: if (!id.IsEmpty()) { michael@0: nsIDocument* doc = mContent->OwnerDoc(); michael@0: michael@0: anchor = michael@0: doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id); michael@0: if (!anchor) { michael@0: anchor = doc->GetElementById(id); michael@0: } michael@0: } michael@0: michael@0: // Always return the menu's content if the anchor wasn't set or wasn't found. michael@0: return anchor && anchor->GetPrimaryFrame() ? anchor : mContent; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::OpenMenu(bool aSelectFirstItem) michael@0: { michael@0: if (!mContent) michael@0: return; michael@0: michael@0: gEatMouseMove = true; michael@0: michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) { michael@0: pm->KillMenuTimer(); michael@0: // This opens the menu asynchronously michael@0: pm->ShowMenu(mContent, aSelectFirstItem, true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::CloseMenu(bool aDeselectMenu) michael@0: { michael@0: gEatMouseMove = true; michael@0: michael@0: // Close the menu asynchronously michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && HasPopup()) michael@0: pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false); michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) michael@0: { michael@0: nsAutoString sizedToPopup; michael@0: aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup); michael@0: return sizedToPopup.EqualsLiteral("always") || michael@0: (!aRequireAlways && sizedToPopup.EqualsLiteral("pref")); michael@0: } michael@0: michael@0: nsSize michael@0: nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState); michael@0: DISPLAY_MIN_SIZE(this, size); michael@0: michael@0: if (IsSizedToPopup(mContent, true)) michael@0: SizeToPopup(aBoxLayoutState, size); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuFrame::DoLayout(nsBoxLayoutState& aState) michael@0: { michael@0: // lay us out michael@0: nsresult rv = nsBoxFrame::DoLayout(aState); michael@0: michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (popupFrame) { michael@0: bool sizeToPopup = IsSizedToPopup(mContent, false); michael@0: popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: nsresult michael@0: nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug) michael@0: { michael@0: // see if our state matches the given debug state michael@0: bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG; michael@0: bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet); michael@0: michael@0: // if it doesn't then tell each child below us the new debug state michael@0: if (debugChanged) michael@0: { michael@0: nsBoxFrame::SetDebug(aState, aDebug); michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (popupFrame) michael@0: SetDebug(aState, popupFrame, aDebug); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug) michael@0: { michael@0: if (!aList) michael@0: return NS_OK; michael@0: michael@0: while (aList) { michael@0: if (aList->IsBoxFrame()) michael@0: aList->SetDebug(aState, aDebug); michael@0: michael@0: aList = aList->GetNextSibling(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: // michael@0: // Enter michael@0: // michael@0: // Called when the user hits the / keys or presses the michael@0: // shortcut key. If this is a leaf item, the item's action will be executed. michael@0: // In either case, do nothing if the item is disabled. michael@0: // michael@0: nsMenuFrame* michael@0: nsMenuFrame::Enter(WidgetGUIEvent* aEvent) michael@0: { michael@0: if (IsDisabled()) { michael@0: #ifdef XP_WIN michael@0: // behavior on Windows - close the popup chain michael@0: if (mMenuParent) { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) { michael@0: nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); michael@0: if (popup) michael@0: pm->HidePopup(popup->GetContent(), true, true, true, false); michael@0: } michael@0: } michael@0: #endif // #ifdef XP_WIN michael@0: // this menu item was disabled - exit michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!IsOpen()) { michael@0: // The enter key press applies to us. michael@0: if (!IsMenu() && mMenuParent) michael@0: Execute(aEvent); // Execute our event handler michael@0: else michael@0: return this; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::IsOpen() michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: return popupFrame && popupFrame->IsOpen(); michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::IsMenu() michael@0: { michael@0: return mIsMenu; michael@0: } michael@0: michael@0: nsMenuListType michael@0: nsMenuFrame::GetParentMenuListType() michael@0: { michael@0: if (mMenuParent && mMenuParent->IsMenu()) { michael@0: nsMenuPopupFrame* popupFrame = static_cast(mMenuParent); michael@0: nsIFrame* parentMenu = popupFrame->GetParent(); michael@0: if (parentMenu) { michael@0: nsCOMPtr menulist = do_QueryInterface(parentMenu->GetContent()); michael@0: if (menulist) { michael@0: bool isEditable = false; michael@0: menulist->GetEditable(&isEditable); michael@0: return isEditable ? eEditableMenuList : eReadonlyMenuList; michael@0: } michael@0: } michael@0: } michael@0: return eNotMenuList; michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::Notify(nsITimer* aTimer) michael@0: { michael@0: // Our timer has fired. michael@0: if (aTimer == mOpenTimer.get()) { michael@0: mOpenTimer = nullptr; michael@0: michael@0: if (!IsOpen() && mMenuParent) { michael@0: // make sure we didn't open a context menu in the meantime michael@0: // (i.e. the user right-clicked while hovering over a submenu). michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) { michael@0: if ((!pm->HasContextMenu(nullptr) || mMenuParent->IsContextMenu()) && michael@0: mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: OpenMenu(false); michael@0: } michael@0: } michael@0: } michael@0: } else if (aTimer == mBlinkTimer) { michael@0: switch (mBlinkState++) { michael@0: case 0: michael@0: NS_ASSERTION(false, "Blink timer fired while not blinking"); michael@0: StopBlinking(); michael@0: break; michael@0: case 1: michael@0: { michael@0: // Turn the highlight back on and wait for a while before closing the menu. michael@0: nsWeakFrame weakFrame(this); michael@0: mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, michael@0: NS_LITERAL_STRING("true"), true); michael@0: if (weakFrame.IsAlive()) { michael@0: aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: if (mMenuParent) { michael@0: mMenuParent->LockMenuUntilClosed(false); michael@0: } michael@0: PassMenuCommandEventToPopupManager(); michael@0: StopBlinking(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::IsDisabled() michael@0: { michael@0: return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext) michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; michael@0: switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, michael@0: strings, eCaseMatters)) { michael@0: case 0: mType = eMenuType_Checkbox; break; michael@0: case 1: michael@0: mType = eMenuType_Radio; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName); michael@0: break; michael@0: michael@0: default: michael@0: if (mType != eMenuType_Normal) { michael@0: nsWeakFrame weakFrame(this); michael@0: mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, michael@0: true); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: } michael@0: mType = eMenuType_Normal; michael@0: break; michael@0: } michael@0: UpdateMenuSpecialState(aPresContext); michael@0: } michael@0: michael@0: /* update checked-ness for type="checkbox" and type="radio" */ michael@0: void michael@0: nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) michael@0: { michael@0: bool newChecked = michael@0: mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: if (newChecked == mChecked) { michael@0: /* checked state didn't change */ michael@0: michael@0: if (mType != eMenuType_Radio) michael@0: return; // only Radio possibly cares about other kinds of change michael@0: michael@0: if (!mChecked || mGroupName.IsEmpty()) michael@0: return; // no interesting change michael@0: } else { michael@0: mChecked = newChecked; michael@0: if (mType != eMenuType_Radio || !mChecked) michael@0: /* michael@0: * Unchecking something requires no further changes, and only michael@0: * menuRadio has to do additional work when checked. michael@0: */ michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * If we get this far, we're type=radio, and: michael@0: * - our name= changed, or michael@0: * - we went from checked="false" to checked="true" michael@0: */ michael@0: michael@0: /* michael@0: * Behavioural note: michael@0: * If we're checked and renamed _into_ an existing radio group, we are michael@0: * made the new checked item, and we unselect the previous one. michael@0: * michael@0: * The only other reasonable behaviour would be to check for another selected michael@0: * item in that group. If found, unselect ourselves, otherwise we're the michael@0: * selected item. That, however, would be a lot more work, and I don't think michael@0: * it's better at all. michael@0: */ michael@0: michael@0: /* walk siblings, looking for the other checked item with the same name */ michael@0: // get the first sibling in this menu popup. This frame may be it, and if we're michael@0: // being called at creation time, this frame isn't yet in the parent's child list. michael@0: // All I'm saying is that this may fail, but it's most likely alright. michael@0: nsIFrame* sib = GetParent()->GetFirstPrincipalChild(); michael@0: michael@0: while (sib) { michael@0: if (sib != this) { michael@0: nsMenuFrame* menu = do_QueryFrame(sib); michael@0: if (menu && menu->GetMenuType() == eMenuType_Radio && michael@0: menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) { michael@0: /* uncheck the old item */ michael@0: sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, michael@0: true); michael@0: /* XXX in DEBUG, check to make sure that there aren't two checked items */ michael@0: return; michael@0: } michael@0: } michael@0: michael@0: sib = sib->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::BuildAcceleratorText(bool aNotify) michael@0: { michael@0: nsAutoString accelText; michael@0: michael@0: if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) { michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText); michael@0: if (!accelText.IsEmpty()) michael@0: return; michael@0: } michael@0: // accelText is definitely empty here. michael@0: michael@0: // Now we're going to compute the accelerator text, so remember that we did. michael@0: AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); michael@0: michael@0: // If anything below fails, just leave the accelerator text blank. michael@0: nsWeakFrame weakFrame(this); michael@0: mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // See if we have a key node and use that instead. michael@0: nsAutoString keyValue; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue); michael@0: if (keyValue.IsEmpty()) michael@0: return; michael@0: michael@0: // Turn the document into a DOM document so we can use getElementById michael@0: nsIDocument *document = mContent->GetDocument(); michael@0: if (!document) michael@0: return; michael@0: michael@0: nsIContent *keyElement = document->GetElementById(keyValue); michael@0: if (!keyElement) { michael@0: #ifdef DEBUG michael@0: nsAutoString label; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); michael@0: nsAutoString msg = NS_LITERAL_STRING("Key '") + michael@0: keyValue + michael@0: NS_LITERAL_STRING("' of menu item '") + michael@0: label + michael@0: NS_LITERAL_STRING("' could not be found"); michael@0: NS_WARNING(NS_ConvertUTF16toUTF8(msg).get()); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: // get the string to display as accelerator text michael@0: // check the key element's attributes in this order: michael@0: // |keytext|, |key|, |keycode| michael@0: nsAutoString accelString; michael@0: keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString); michael@0: michael@0: if (accelString.IsEmpty()) { michael@0: keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString); michael@0: michael@0: if (!accelString.IsEmpty()) { michael@0: ToUpperCase(accelString); michael@0: } else { michael@0: nsAutoString keyCode; michael@0: keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode); michael@0: ToUpperCase(keyCode); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr bundleService = michael@0: mozilla::services::GetStringBundleService(); michael@0: if (bundleService) { michael@0: nsCOMPtr bundle; michael@0: rv = bundleService->CreateBundle("chrome://global/locale/keys.properties", michael@0: getter_AddRefs(bundle)); michael@0: michael@0: if (NS_SUCCEEDED(rv) && bundle) { michael@0: nsXPIDLString keyName; michael@0: rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName)); michael@0: if (keyName) michael@0: accelString = keyName; michael@0: } michael@0: } michael@0: michael@0: // nothing usable found, bail michael@0: if (accelString.IsEmpty()) michael@0: return; michael@0: } michael@0: } michael@0: michael@0: static int32_t accelKey = 0; michael@0: michael@0: if (!accelKey) michael@0: { michael@0: // Compiled-in defaults, in case we can't get LookAndFeel -- michael@0: // command for mac, control for all other platforms. michael@0: #ifdef XP_MACOSX michael@0: accelKey = nsIDOMKeyEvent::DOM_VK_META; michael@0: #else michael@0: accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL; michael@0: #endif michael@0: michael@0: // Get the accelerator key value from prefs, overriding the default: michael@0: accelKey = Preferences::GetInt("ui.key.accelKey", accelKey); michael@0: } michael@0: michael@0: nsAutoString modifiers; michael@0: keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); michael@0: michael@0: char* str = ToNewCString(modifiers); michael@0: char* newStr; michael@0: char* token = nsCRT::strtok(str, ", \t", &newStr); michael@0: michael@0: nsAutoString shiftText; michael@0: nsAutoString altText; michael@0: nsAutoString metaText; michael@0: nsAutoString controlText; michael@0: nsAutoString osText; michael@0: nsAutoString modifierSeparator; michael@0: michael@0: nsContentUtils::GetShiftText(shiftText); michael@0: nsContentUtils::GetAltText(altText); michael@0: nsContentUtils::GetMetaText(metaText); michael@0: nsContentUtils::GetControlText(controlText); michael@0: nsContentUtils::GetOSText(osText); michael@0: nsContentUtils::GetModifierSeparatorText(modifierSeparator); michael@0: michael@0: while (token) { michael@0: michael@0: if (PL_strcmp(token, "shift") == 0) michael@0: accelText += shiftText; michael@0: else if (PL_strcmp(token, "alt") == 0) michael@0: accelText += altText; michael@0: else if (PL_strcmp(token, "meta") == 0) michael@0: accelText += metaText; michael@0: else if (PL_strcmp(token, "os") == 0) michael@0: accelText += osText; michael@0: else if (PL_strcmp(token, "control") == 0) michael@0: accelText += controlText; michael@0: else if (PL_strcmp(token, "accel") == 0) { michael@0: switch (accelKey) michael@0: { michael@0: case nsIDOMKeyEvent::DOM_VK_META: michael@0: accelText += metaText; michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_WIN: michael@0: accelText += osText; michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_ALT: michael@0: accelText += altText; michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_CONTROL: michael@0: default: michael@0: accelText += controlText; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: accelText += modifierSeparator; michael@0: michael@0: token = nsCRT::strtok(newStr, ", \t", &newStr); michael@0: } michael@0: michael@0: nsMemory::Free(str); michael@0: michael@0: accelText += accelString; michael@0: michael@0: mIgnoreAccelTextChange = true; michael@0: mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: mIgnoreAccelTextChange = false; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::Execute(WidgetGUIEvent* aEvent) michael@0: { michael@0: // flip "checked" state if we're a checkbox menu, or an un-checked radio menu michael@0: bool needToFlipChecked = false; michael@0: if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) { michael@0: needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, michael@0: nsGkAtoms::_false, eCaseMatters); michael@0: } michael@0: michael@0: nsCOMPtr sound(do_CreateInstance("@mozilla.org/sound;1")); michael@0: if (sound) michael@0: sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE); michael@0: michael@0: StartBlinking(aEvent, needToFlipChecked); michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::ShouldBlink() michael@0: { michael@0: int32_t shouldBlink = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0); michael@0: if (!shouldBlink) michael@0: return false; michael@0: michael@0: // Don't blink in editable menulists. michael@0: if (GetParentMenuListType() == eEditableMenuList) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked) michael@0: { michael@0: StopBlinking(); michael@0: CreateMenuCommandEvent(aEvent, aFlipChecked); michael@0: michael@0: if (!ShouldBlink()) { michael@0: PassMenuCommandEventToPopupManager(); michael@0: return; michael@0: } michael@0: michael@0: // Blink off. michael@0: nsWeakFrame weakFrame(this); michael@0: mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); michael@0: if (!weakFrame.IsAlive()) michael@0: return; michael@0: michael@0: if (mMenuParent) { michael@0: // Make this menu ignore events from now on. michael@0: mMenuParent->LockMenuUntilClosed(true); michael@0: } michael@0: michael@0: // Set up a timer to blink back on. michael@0: mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); michael@0: mBlinkState = 1; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::StopBlinking() michael@0: { michael@0: mBlinkState = 0; michael@0: if (mBlinkTimer) { michael@0: mBlinkTimer->Cancel(); michael@0: mBlinkTimer = nullptr; michael@0: } michael@0: mDelayedMenuCommandEvent = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked) michael@0: { michael@0: // Create a trusted event if the triggering event was trusted, or if michael@0: // we're called from chrome code (since at least one of our caller michael@0: // passes in a null event). michael@0: bool isTrusted = aEvent ? aEvent->mFlags.mIsTrusted : michael@0: nsContentUtils::IsCallerChrome(); michael@0: michael@0: bool shift = false, control = false, alt = false, meta = false; michael@0: WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr; michael@0: if (inputEvent) { michael@0: shift = inputEvent->IsShift(); michael@0: control = inputEvent->IsControl(); michael@0: alt = inputEvent->IsAlt(); michael@0: meta = inputEvent->IsMeta(); michael@0: } michael@0: michael@0: // Because the command event is firing asynchronously, a flag is needed to michael@0: // indicate whether user input is being handled. This ensures that a popup michael@0: // window won't get blocked. michael@0: bool userinput = EventStateManager::IsHandlingUserInput(); michael@0: michael@0: mDelayedMenuCommandEvent = michael@0: new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta, michael@0: userinput, aFlipChecked); michael@0: } michael@0: michael@0: void michael@0: nsMenuFrame::PassMenuCommandEventToPopupManager() michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && mMenuParent && mDelayedMenuCommandEvent) { michael@0: pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent); michael@0: } michael@0: mDelayedMenuCommandEvent = nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: nsFrameList* popupList = GetPopupList(); michael@0: if (popupList && popupList->FirstChild() == aOldFrame) { michael@0: popupList->RemoveFirstChild(); michael@0: aOldFrame->Destroy(); michael@0: DestroyPopupList(); michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: return NS_OK; michael@0: } michael@0: return nsBoxFrame::RemoveFrame(aListID, aOldFrame); michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { michael@0: SetPopupFrame(aFrameList); michael@0: if (HasPopup()) { michael@0: #ifdef DEBUG_LAYOUT michael@0: nsBoxLayoutState state(PresContext()); michael@0: SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); michael@0: #endif michael@0: michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: michael@0: if (aFrameList.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { michael@0: aPrevFrame = nullptr; michael@0: } michael@0: michael@0: return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { michael@0: SetPopupFrame(aFrameList); michael@0: if (HasPopup()) { michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: nsBoxLayoutState state(PresContext()); michael@0: SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); michael@0: #endif michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: michael@0: if (aFrameList.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: return nsBoxFrame::AppendFrames(aListID, aFrameList); michael@0: } michael@0: michael@0: bool michael@0: nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) michael@0: { michael@0: if (!IsCollapsed()) { michael@0: bool widthSet, heightSet; michael@0: nsSize tmpSize(-1, 0); michael@0: nsIFrame::AddCSSPrefSize(this, tmpSize, widthSet, heightSet); michael@0: if (!widthSet && GetFlex(aState) == 0) { michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (!popupFrame) michael@0: return false; michael@0: tmpSize = popupFrame->GetPrefSize(aState); michael@0: michael@0: // Produce a size such that: michael@0: // (1) the menu and its popup can be the same width michael@0: // (2) there's enough room in the menu for the content and its michael@0: // border-padding michael@0: // (3) there's enough room in the popup for the content and its michael@0: // scrollbar michael@0: nsMargin borderPadding; michael@0: GetBorderAndPadding(borderPadding); michael@0: michael@0: // if there is a scroll frame, add the desired width of the scrollbar as well michael@0: nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild()); michael@0: nscoord scrollbarWidth = 0; michael@0: if (scrollFrame) { michael@0: scrollbarWidth = michael@0: scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight(); michael@0: } michael@0: michael@0: aSize.width = michael@0: tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth); michael@0: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsSize michael@0: nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState) michael@0: { michael@0: nsSize size = nsBoxFrame::GetPrefSize(aState); michael@0: DISPLAY_PREF_SIZE(this, size); michael@0: michael@0: // If we are using sizetopopup="always" then michael@0: // nsBoxFrame will already have enforced the minimum size michael@0: if (!IsSizedToPopup(mContent, true) && michael@0: IsSizedToPopup(mContent, false) && michael@0: SizeToPopup(aState, size)) { michael@0: // We now need to ensure that size is within the min - max range. michael@0: nsSize minSize = nsBoxFrame::GetMinSize(aState); michael@0: nsSize maxSize = GetMaxSize(aState); michael@0: size = BoundsCheck(minSize, size, maxSize); michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (!popupFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem(); michael@0: if (!menuFrame) { michael@0: *aResult = nullptr; michael@0: } michael@0: else { michael@0: nsCOMPtr elt(do_QueryInterface(menuFrame->GetContent())); michael@0: *aResult = elt; michael@0: NS_IF_ADDREF(*aResult); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (!popupFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!aChild) { michael@0: // Remove the current selection michael@0: popupFrame->ChangeMenuItem(nullptr, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr child(do_QueryInterface(aChild)); michael@0: michael@0: nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame()); michael@0: if (menu) michael@0: popupFrame->ChangeMenuItem(menu, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopup(); michael@0: if (!popupFrame) michael@0: return nullptr; michael@0: nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild(); michael@0: if (childFrame) michael@0: return popupFrame->GetScrollFrame(childFrame); michael@0: return nullptr; michael@0: } michael@0: michael@0: // nsMenuTimerMediator implementation. michael@0: NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback) michael@0: michael@0: /** michael@0: * Constructs a wrapper around an nsMenuFrame. michael@0: * @param aFrame nsMenuFrame to create a wrapper around. michael@0: */ michael@0: nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) : michael@0: mFrame(aFrame) michael@0: { michael@0: NS_ASSERTION(mFrame, "Must have frame"); michael@0: } michael@0: michael@0: nsMenuTimerMediator::~nsMenuTimerMediator() michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * Delegates the notification to the contained frame if it has not been destroyed. michael@0: * @param aTimer Timer which initiated the callback. michael@0: * @return NS_ERROR_FAILURE if the frame has been destroyed. michael@0: */ michael@0: NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer) michael@0: { michael@0: if (!mFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mFrame->Notify(aTimer); michael@0: } michael@0: michael@0: /** michael@0: * Clear the pointer to the contained nsMenuFrame. This should be called michael@0: * when the contained nsMenuFrame is destroyed. michael@0: */ michael@0: void nsMenuTimerMediator::ClearFrame() michael@0: { michael@0: mFrame = nullptr; michael@0: }