static eEventAction GetActionForEvent(nsIDOMEvent* aEvent) { WidgetKeyboardEvent* keyEvent = aEvent->GetInternalNSEvent()->AsKeyboardEvent(); if (!keyEvent) { return eEventAction_Suppress; } if (keyEvent->mFlags.mInSystemGroup) { NS_ASSERTION(keyEvent->message == NS_KEY_DOWN, "Assuming we're listening only keydown event in system group"); return eEventAction_StopPropagation; } if (keyEvent->IsAlt() || keyEvent->IsControl() || keyEvent->IsMeta()) { // Don't consume keydown event because following keypress event may be // handled as access key or shortcut key. return (keyEvent->message == NS_KEY_DOWN) ? eEventAction_StopPropagation : eEventAction_Suppress; } static const uint32_t kOKKeyCodes[] = { nsIDOMKeyEvent::DOM_VK_PAGE_UP, nsIDOMKeyEvent::DOM_VK_PAGE_DOWN, nsIDOMKeyEvent::DOM_VK_UP, nsIDOMKeyEvent::DOM_VK_DOWN, nsIDOMKeyEvent::DOM_VK_HOME, nsIDOMKeyEvent::DOM_VK_END }; if (keyEvent->keyCode == nsIDOMKeyEvent::DOM_VK_TAB) { return keyEvent->IsShift() ? eEventAction_ShiftTab : eEventAction_Tab; } if (keyEvent->charCode == ' ' || keyEvent->keyCode == NS_VK_SPACE) { return eEventAction_Propagate; } if (keyEvent->IsShift()) { return eEventAction_Suppress; } for (uint32_t i = 0; i < ArrayLength(kOKKeyCodes); ++i) { if (keyEvent->keyCode == kOKKeyCodes[i]) { return eEventAction_Propagate; } } return eEventAction_Suppress; }
nsresult nsMenuFrame::HandleEvent(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus) { NS_ENSURE_ARG_POINTER(aEventStatus); if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { return NS_OK; } nsMenuParent* menuParent = GetMenuParent(); if (menuParent && menuParent->IsMenuLocked()) { return NS_OK; } nsWeakFrame weakFrame(this); if (*aEventStatus == nsEventStatus_eIgnore) *aEventStatus = nsEventStatus_eConsumeDoDefault; bool onmenu = IsOnMenu(); if (aEvent->mMessage == eKeyPress && !IsDisabled()) { WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); uint32_t keyCode = keyEvent->keyCode; #ifdef XP_MACOSX // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) || (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { *aEventStatus = nsEventStatus_eConsumeNoDefault; OpenMenu(false); } #else // On other platforms, toggle menulist on unmodified F4 or Alt arrow if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) || ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) { *aEventStatus = nsEventStatus_eConsumeNoDefault; ToggleMenuState(); } #endif } else if (aEvent->mMessage == eMouseDown && aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && !IsDisabled() && IsMenu()) { // The menu item was selected. Bring up the menu. // We have children. // Don't prevent the default action here, since that will also cancel // potential drag starts. if (!menuParent || menuParent->IsMenuBar()) { ToggleMenuState(); } else { if (!IsOpen()) { menuParent->ChangeMenuItem(this, false, false); OpenMenu(false); } } } else if ( #ifndef NSCONTEXTMENUISMOUSEUP (aEvent->mMessage == eMouseUp && aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && #else aEvent->mMessage == eContextMenu && #endif onmenu && !IsMenu() && !IsDisabled()) { // if this menu is a context menu it accepts right-clicks...fire away! // Make sure we cancel default processing of the context menu event so // that it doesn't bubble and get seen again by the popuplistener and show // another context menu. // // Furthermore (there's always more, isn't there?), on some platforms (win32 // being one of them) we get the context menu event on a mouse up while // on others we get it on a mouse down. For the ones where we get it on a // mouse down, we must continue listening for the right button up event to // dismiss the menu. if (menuParent->IsContextMenu()) { *aEventStatus = nsEventStatus_eConsumeNoDefault; Execute(aEvent); } } else if (aEvent->mMessage == eMouseUp && aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && !IsMenu() && !IsDisabled()) { // Execute the execute event handler. *aEventStatus = nsEventStatus_eConsumeNoDefault; Execute(aEvent); } else if (aEvent->mMessage == eMouseOut) { // Kill our timer if one is active. if (mOpenTimer) { mOpenTimer->Cancel(); mOpenTimer = nullptr; } // Deactivate the menu. if (menuParent) { bool onmenubar = menuParent->IsMenuBar(); if (!(onmenubar && menuParent->IsActive())) { if (IsMenu() && !onmenubar && IsOpen()) { // Submenus don't get closed up immediately. } else if (this == menuParent->GetCurrentMenuItem() #ifdef XP_WIN && GetParentMenuListType() == eNotMenuList #endif ) { menuParent->ChangeMenuItem(nullptr, false, false); } } } } else if (aEvent->mMessage == eMouseMove && (onmenu || (menuParent && menuParent->IsMenuBar()))) { if (gEatMouseMove) { gEatMouseMove = false; return NS_OK; } // Let the menu parent know we're the new item. menuParent->ChangeMenuItem(this, false, false); NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); NS_ENSURE_TRUE(menuParent, NS_OK); // we need to check if we really became the current menu // item or not nsMenuFrame *realCurrentItem = menuParent->GetCurrentMenuItem(); if (realCurrentItem != this) { // we didn't (presumably because a context menu was active) return NS_OK; } // Hovering over a menu in a popup should open it without a need for a click. // A timer is used so that it doesn't open if the user moves the mouse quickly // past the menu. This conditional check ensures that only menus have this // behaviour if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !menuParent->IsMenuBar()) { int32_t menuDelay = LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms // We're a menu, we're built, we're closed, and no timer has been kicked off. mOpenTimer = do_CreateInstance("@mozilla.org/timer;1"); mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); } } return NS_OK; }