LCOV - code coverage report
Current view: top level - layout/xul - nsMenuFrame.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 173 701 24.7 %
Date: 2017-07-14 16:53:18 Functions: 32 72 44.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "nsGkAtoms.h"
       7             : #include "nsHTMLParts.h"
       8             : #include "nsMenuFrame.h"
       9             : #include "nsBoxFrame.h"
      10             : #include "nsIContent.h"
      11             : #include "nsIAtom.h"
      12             : #include "nsPresContext.h"
      13             : #include "nsIPresShell.h"
      14             : #include "nsStyleContext.h"
      15             : #include "nsCSSRendering.h"
      16             : #include "nsNameSpaceManager.h"
      17             : #include "nsMenuPopupFrame.h"
      18             : #include "nsMenuBarFrame.h"
      19             : #include "nsIDocument.h"
      20             : #include "nsIDOMElement.h"
      21             : #include "nsIComponentManager.h"
      22             : #include "nsBoxLayoutState.h"
      23             : #include "nsIScrollableFrame.h"
      24             : #include "nsBindingManager.h"
      25             : #include "nsIServiceManager.h"
      26             : #include "nsCSSFrameConstructor.h"
      27             : #include "nsIDOMKeyEvent.h"
      28             : #include "nsXPIDLString.h"
      29             : #include "nsReadableUtils.h"
      30             : #include "nsUnicharUtils.h"
      31             : #include "nsIStringBundle.h"
      32             : #include "nsContentUtils.h"
      33             : #include "nsDisplayList.h"
      34             : #include "nsIReflowCallback.h"
      35             : #include "nsISound.h"
      36             : #include "nsIDOMXULMenuListElement.h"
      37             : #include "mozilla/Attributes.h"
      38             : #include "mozilla/EventDispatcher.h"
      39             : #include "mozilla/EventStateManager.h"
      40             : #include "mozilla/Likely.h"
      41             : #include "mozilla/LookAndFeel.h"
      42             : #include "mozilla/MouseEvents.h"
      43             : #include "mozilla/Preferences.h"
      44             : #include "mozilla/Services.h"
      45             : #include "mozilla/TextEvents.h"
      46             : #include "mozilla/dom/Element.h"
      47             : #include "mozilla/dom/Event.h"
      48             : #include <algorithm>
      49             : 
      50             : using namespace mozilla;
      51             : 
      52             : #define NS_MENU_POPUP_LIST_INDEX 0
      53             : 
      54             : #if defined(XP_WIN)
      55             : #define NSCONTEXTMENUISMOUSEUP 1
      56             : #endif
      57             : 
      58         547 : NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PopupListProperty)
      59             : 
      60             : // This global flag indicates that a menu just opened or closed and is used
      61             : // to ignore the mousemove and mouseup events that would fire on the menu after
      62             : // the mousedown occurred.
      63             : static int32_t gMenuJustOpenedOrClosed = false;
      64             : 
      65             : const int32_t kBlinkDelay = 67; // milliseconds
      66             : 
      67             : // this class is used for dispatching menu activation events asynchronously.
      68           0 : class nsMenuActivateEvent : public Runnable
      69             : {
      70             : public:
      71           0 :   nsMenuActivateEvent(nsIContent* aMenu,
      72             :                       nsPresContext* aPresContext,
      73             :                       bool aIsActivate)
      74           0 :     : mozilla::Runnable("nsMenuActivateEvent")
      75             :     , mMenu(aMenu)
      76             :     , mPresContext(aPresContext)
      77           0 :     , mIsActivate(aIsActivate)
      78             :   {
      79           0 :   }
      80             : 
      81           0 :   NS_IMETHOD Run() override
      82             :   {
      83           0 :     nsAutoString domEventToFire;
      84             : 
      85           0 :     if (mIsActivate) {
      86             :       // Highlight the menu.
      87           0 :       mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
      88           0 :                      NS_LITERAL_STRING("true"), true);
      89             :       // The menuactivated event is used by accessibility to track the user's
      90             :       // movements through menus
      91           0 :       domEventToFire.AssignLiteral("DOMMenuItemActive");
      92             :     }
      93             :     else {
      94             :       // Unhighlight the menu.
      95           0 :       mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
      96           0 :       domEventToFire.AssignLiteral("DOMMenuItemInactive");
      97             :     }
      98             : 
      99           0 :     RefPtr<Event> event = NS_NewDOMEvent(mMenu, mPresContext, nullptr);
     100           0 :     event->InitEvent(domEventToFire, true, true);
     101             : 
     102           0 :     event->SetTrusted(true);
     103             : 
     104           0 :     EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event,
     105           0 :         mPresContext, nullptr);
     106             : 
     107           0 :     return NS_OK;
     108             :   }
     109             : 
     110             : private:
     111             :   nsCOMPtr<nsIContent> mMenu;
     112             :   RefPtr<nsPresContext> mPresContext;
     113             :   bool mIsActivate;
     114             : };
     115             : 
     116           0 : class nsMenuAttributeChangedEvent : public Runnable
     117             : {
     118             : public:
     119           0 :   nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr)
     120           0 :     : mozilla::Runnable("nsMenuAttributeChangedEvent")
     121             :     , mFrame(aFrame)
     122           0 :     , mAttr(aAttr)
     123             :   {
     124           0 :   }
     125             : 
     126           0 :   NS_IMETHOD Run() override
     127             :   {
     128           0 :     nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
     129           0 :     NS_ENSURE_STATE(frame);
     130           0 :     if (mAttr == nsGkAtoms::checked) {
     131           0 :       frame->UpdateMenuSpecialState();
     132           0 :     } else if (mAttr == nsGkAtoms::acceltext) {
     133             :       // someone reset the accelText attribute,
     134             :       // so clear the bit that says *we* set it
     135           0 :       frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
     136           0 :       frame->BuildAcceleratorText(true);
     137             :     }
     138           0 :     else if (mAttr == nsGkAtoms::key) {
     139           0 :       frame->BuildAcceleratorText(true);
     140           0 :     } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
     141           0 :       frame->UpdateMenuType();
     142             :     }
     143           0 :     return NS_OK;
     144             :   }
     145             : protected:
     146             :   WeakFrame         mFrame;
     147             :   nsCOMPtr<nsIAtom> mAttr;
     148             : };
     149             : 
     150             : //
     151             : // NS_NewMenuFrame and NS_NewMenuItemFrame
     152             : //
     153             : // Wrappers for creating a new menu popup container
     154             : //
     155             : nsIFrame*
     156          15 : NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
     157             : {
     158          15 :   nsMenuFrame* it = new (aPresShell) nsMenuFrame(aContext);
     159          15 :   it->SetIsMenu(true);
     160          15 :   return it;
     161             : }
     162             : 
     163             : nsIFrame*
     164           0 : NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
     165             : {
     166           0 :   nsMenuFrame* it = new (aPresShell) nsMenuFrame(aContext);
     167           0 :   it->SetIsMenu(false);
     168           0 :   return it;
     169             : }
     170             : 
     171          15 : NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
     172             : 
     173         494 : NS_QUERYFRAME_HEAD(nsMenuFrame)
     174         232 :   NS_QUERYFRAME_ENTRY(nsMenuFrame)
     175         262 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
     176             : 
     177          15 : nsMenuFrame::nsMenuFrame(nsStyleContext* aContext)
     178             :   : nsBoxFrame(aContext, kClassID)
     179             :   , mIsMenu(false)
     180             :   , mChecked(false)
     181             :   , mIgnoreAccelTextChange(false)
     182             :   , mReflowCallbackPosted(false)
     183             :   , mType(eMenuType_Normal)
     184          15 :   , mBlinkState(0)
     185             : {
     186          15 : }
     187             : 
     188             : nsMenuParent*
     189         232 : nsMenuFrame::GetMenuParent() const
     190             : {
     191         232 :   nsContainerFrame* parent = GetParent();
     192         248 :   for (; parent; parent = parent->GetParent()) {
     193         239 :     nsMenuPopupFrame* popup = do_QueryFrame(parent);
     194         239 :     if (popup) {
     195           0 :       return popup;
     196             :     }
     197         239 :     nsMenuBarFrame* menubar = do_QueryFrame(parent);
     198         239 :     if (menubar) {
     199         231 :       return menubar;
     200             :     }
     201             :   }
     202           1 :   return nullptr;
     203             : }
     204             : 
     205             : bool
     206          15 : nsMenuFrame::ReflowFinished()
     207             : {
     208          15 :   mReflowCallbackPosted = false;
     209             : 
     210          15 :   UpdateMenuType();
     211          15 :   return true;
     212             : }
     213             : 
     214             : void
     215           0 : nsMenuFrame::ReflowCallbackCanceled()
     216             : {
     217           0 :   mReflowCallbackPosted = false;
     218           0 : }
     219             : 
     220             : void
     221          15 : nsMenuFrame::Init(nsIContent*       aContent,
     222             :                   nsContainerFrame* aParent,
     223             :                   nsIFrame*         aPrevInFlow)
     224             : {
     225          15 :   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
     226             : 
     227             :   // Set up a mediator which can be used for callbacks on this frame.
     228          15 :   mTimerMediator = new nsMenuTimerMediator(this);
     229             : 
     230          15 :   BuildAcceleratorText(false);
     231          15 :   if (!mReflowCallbackPosted) {
     232          15 :     mReflowCallbackPosted = true;
     233          15 :     PresContext()->PresShell()->PostReflowCallback(this);
     234             :   }
     235          15 : }
     236             : 
     237             : const nsFrameList&
     238         212 : nsMenuFrame::GetChildList(ChildListID aListID) const
     239             : {
     240         212 :   if (kPopupList == aListID) {
     241           0 :     nsFrameList* list = GetPopupList();
     242           0 :     return list ? *list : nsFrameList::EmptyList();
     243             :   }
     244         212 :   return nsBoxFrame::GetChildList(aListID);
     245             : }
     246             : 
     247             : void
     248         494 : nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const
     249             : {
     250         494 :   nsBoxFrame::GetChildLists(aLists);
     251         494 :   nsFrameList* list = GetPopupList();
     252         494 :   if (list) {
     253         494 :     list->AppendIfNonempty(aLists, kPopupList);
     254             :   }
     255         494 : }
     256             : 
     257             : nsMenuPopupFrame*
     258          36 : nsMenuFrame::GetPopup()
     259             : {
     260          36 :   nsFrameList* popupList = GetPopupList();
     261          36 :   return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) :
     262          36 :                      nullptr;
     263             : }
     264             : 
     265             : nsFrameList*
     266         531 : nsMenuFrame::GetPopupList() const
     267             : {
     268         531 :   if (!HasPopup()) {
     269           0 :     return nullptr;
     270             :   }
     271         531 :   nsFrameList* prop = GetProperty(PopupListProperty());
     272         531 :   NS_ASSERTION(prop && prop->GetLength() == 1 &&
     273             :                prop->FirstChild()->IsMenuPopupFrame(),
     274             :                "popup list should have exactly one nsMenuPopupFrame");
     275         531 :   return prop;
     276             : }
     277             : 
     278             : void
     279           1 : nsMenuFrame::DestroyPopupList()
     280             : {
     281           1 :   NS_ASSERTION(HasPopup(), "huh?");
     282           1 :   nsFrameList* prop = RemoveProperty(PopupListProperty());
     283           1 :   NS_ASSERTION(prop && prop->IsEmpty(),
     284             :                "popup list must exist and be empty when destroying");
     285           1 :   RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
     286           1 :   prop->Delete(PresContext()->PresShell());
     287           1 : }
     288             : 
     289             : void
     290          15 : nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList)
     291             : {
     292          22 :   for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
     293          22 :     nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get());
     294          22 :     if (popupFrame) {
     295             :       // Remove the frame from the list and store it in a nsFrameList* property.
     296          15 :       aFrameList.RemoveFrame(popupFrame);
     297          15 :       nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame);
     298          15 :       SetProperty(PopupListProperty(), popupList);
     299          15 :       AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
     300          15 :       break;
     301             :     }
     302             :   }
     303          15 : }
     304             : 
     305             : void
     306          15 : nsMenuFrame::SetInitialChildList(ChildListID     aListID,
     307             :                                  nsFrameList&    aChildList)
     308             : {
     309          15 :   if (aListID == kPrincipalList || aListID == kPopupList) {
     310          15 :     NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
     311          15 :     SetPopupFrame(aChildList);
     312             :   }
     313          15 :   nsBoxFrame::SetInitialChildList(aListID, aChildList);
     314          15 : }
     315             : 
     316             : void
     317           1 : nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot)
     318             : {
     319           1 :   if (mReflowCallbackPosted) {
     320           0 :     PresContext()->PresShell()->CancelReflowCallback(this);
     321           0 :     mReflowCallbackPosted = false;
     322             :   }
     323             : 
     324             :   // Kill our timer if one is active. This is not strictly necessary as
     325             :   // the pointer to this frame will be cleared from the mediator, but
     326             :   // this is done for added safety.
     327           1 :   if (mOpenTimer) {
     328           0 :     mOpenTimer->Cancel();
     329             :   }
     330             : 
     331           1 :   StopBlinking();
     332             : 
     333             :   // Null out the pointer to this frame in the mediator wrapper so that it
     334             :   // doesn't try to interact with a deallocated frame.
     335           1 :   mTimerMediator->ClearFrame();
     336             : 
     337             :   // if the menu content is just being hidden, it may be made visible again
     338             :   // later, so make sure to clear the highlighting.
     339           1 :   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false);
     340             : 
     341             :   // are we our menu parent's current menu item?
     342           1 :   nsMenuParent* menuParent = GetMenuParent();
     343           1 :   if (menuParent && menuParent->GetCurrentMenuItem() == this) {
     344             :     // yes; tell it that we're going away
     345           0 :     menuParent->CurrentMenuIsBeingDestroyed();
     346             :   }
     347             : 
     348           1 :   nsFrameList* popupList = GetPopupList();
     349           1 :   if (popupList) {
     350           1 :     popupList->DestroyFramesFrom(aDestructRoot);
     351           1 :     DestroyPopupList();
     352             :   }
     353             : 
     354           1 :   nsBoxFrame::DestroyFrom(aDestructRoot);
     355           1 : }
     356             : 
     357             : void
     358          92 : nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder*   aBuilder,
     359             :                                          const nsRect&           aDirtyRect,
     360             :                                          const nsDisplayListSet& aLists)
     361             : {
     362          92 :   if (!aBuilder->IsForEventDelivery()) {
     363          92 :     nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
     364          92 :     return;
     365             :   }
     366             : 
     367           0 :   nsDisplayListCollection set;
     368           0 :   nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
     369             : 
     370           0 :   WrapListsInRedirector(aBuilder, set, aLists);
     371             : }
     372             : 
     373             : nsresult
     374           0 : nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
     375             :                          WidgetGUIEvent* aEvent,
     376             :                          nsEventStatus* aEventStatus)
     377             : {
     378           0 :   NS_ENSURE_ARG_POINTER(aEventStatus);
     379           0 :   if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
     380           0 :     return NS_OK;
     381             :   }
     382           0 :   nsMenuParent* menuParent = GetMenuParent();
     383           0 :   if (menuParent && menuParent->IsMenuLocked()) {
     384           0 :     return NS_OK;
     385             :   }
     386             : 
     387           0 :   AutoWeakFrame weakFrame(this);
     388           0 :   if (*aEventStatus == nsEventStatus_eIgnore)
     389           0 :     *aEventStatus = nsEventStatus_eConsumeDoDefault;
     390             : 
     391             :   // If a menu just opened, ignore the mouseup event that might occur after a
     392             :   // the mousedown event that opened it. However, if a different mousedown
     393             :   // event occurs, just clear this flag.
     394           0 :   if (gMenuJustOpenedOrClosed) {
     395           0 :     if (aEvent->mMessage == eMouseDown) {
     396           0 :       gMenuJustOpenedOrClosed = false;
     397           0 :     } else if (aEvent->mMessage == eMouseUp) {
     398           0 :       return NS_OK;
     399             :     }
     400             :   }
     401             : 
     402           0 :   bool onmenu = IsOnMenu();
     403             : 
     404           0 :   if (aEvent->mMessage == eKeyPress && !IsDisabled()) {
     405           0 :     WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
     406           0 :     uint32_t keyCode = keyEvent->mKeyCode;
     407             : #ifdef XP_MACOSX
     408             :     // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
     409             :     if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
     410             :         (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
     411             : 
     412             :       // When pressing space, don't open the menu if performing an incremental search.
     413             :       if (keyEvent->mCharCode != ' ' ||
     414             :           !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) {
     415             :         *aEventStatus = nsEventStatus_eConsumeNoDefault;
     416             :         OpenMenu(false);
     417             :       }
     418             :     }
     419             : #else
     420             :     // On other platforms, toggle menulist on unmodified F4 or Alt arrow
     421           0 :     if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
     422           0 :         ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
     423           0 :       *aEventStatus = nsEventStatus_eConsumeNoDefault;
     424           0 :       ToggleMenuState();
     425             :     }
     426             : #endif
     427             :   }
     428           0 :   else if (aEvent->mMessage == eMouseDown &&
     429           0 :            aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
     430           0 :            !IsDisabled() && IsMenu()) {
     431             :     // The menu item was selected. Bring up the menu.
     432             :     // We have children.
     433             :     // Don't prevent the default action here, since that will also cancel
     434             :     // potential drag starts.
     435           0 :     if (!menuParent || menuParent->IsMenuBar()) {
     436           0 :       ToggleMenuState();
     437             :     }
     438             :     else {
     439           0 :       if (!IsOpen()) {
     440           0 :         menuParent->ChangeMenuItem(this, false, false);
     441           0 :         OpenMenu(false);
     442             :       }
     443             :     }
     444             :   }
     445           0 :   else if (
     446             : #ifndef NSCONTEXTMENUISMOUSEUP
     447           0 :            (aEvent->mMessage == eMouseUp &&
     448           0 :             aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) &&
     449             : #else
     450             :            aEvent->mMessage == eContextMenu &&
     451             : #endif
     452           0 :            onmenu && !IsMenu() && !IsDisabled()) {
     453             :     // if this menu is a context menu it accepts right-clicks...fire away!
     454             :     // Make sure we cancel default processing of the context menu event so
     455             :     // that it doesn't bubble and get seen again by the popuplistener and show
     456             :     // another context menu.
     457             :     //
     458             :     // Furthermore (there's always more, isn't there?), on some platforms (win32
     459             :     // being one of them) we get the context menu event on a mouse up while
     460             :     // on others we get it on a mouse down. For the ones where we get it on a
     461             :     // mouse down, we must continue listening for the right button up event to
     462             :     // dismiss the menu.
     463           0 :     if (menuParent->IsContextMenu()) {
     464           0 :       *aEventStatus = nsEventStatus_eConsumeNoDefault;
     465           0 :       Execute(aEvent);
     466             :     }
     467             :   }
     468           0 :   else if (aEvent->mMessage == eMouseUp &&
     469           0 :            aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
     470           0 :            !IsMenu() && !IsDisabled()) {
     471             :     // Execute the execute event handler.
     472           0 :     *aEventStatus = nsEventStatus_eConsumeNoDefault;
     473           0 :     Execute(aEvent);
     474             :   }
     475           0 :   else if (aEvent->mMessage == eMouseOut) {
     476             :     // Kill our timer if one is active.
     477           0 :     if (mOpenTimer) {
     478           0 :       mOpenTimer->Cancel();
     479           0 :       mOpenTimer = nullptr;
     480             :     }
     481             : 
     482             :     // Deactivate the menu.
     483           0 :     if (menuParent) {
     484           0 :       bool onmenubar = menuParent->IsMenuBar();
     485           0 :       if (!(onmenubar && menuParent->IsActive())) {
     486           0 :         if (IsMenu() && !onmenubar && IsOpen()) {
     487             :           // Submenus don't get closed up immediately.
     488             :         }
     489           0 :         else if (this == menuParent->GetCurrentMenuItem()
     490             : #ifdef XP_WIN
     491             :                  && GetParentMenuListType() == eNotMenuList
     492             : #endif
     493             :         ) {
     494           0 :           menuParent->ChangeMenuItem(nullptr, false, false);
     495             :         }
     496             :       }
     497             :     }
     498             :   }
     499           0 :   else if (aEvent->mMessage == eMouseMove &&
     500           0 :            (onmenu || (menuParent && menuParent->IsMenuBar()))) {
     501           0 :     if (gMenuJustOpenedOrClosed) {
     502           0 :       gMenuJustOpenedOrClosed = false;
     503           0 :       return NS_OK;
     504             :     }
     505             : 
     506           0 :     if (IsDisabled() && GetParentMenuListType() != eNotMenuList) {
     507           0 :       return NS_OK;
     508             :     }
     509             : 
     510             :     // Let the menu parent know we're the new item.
     511           0 :     menuParent->ChangeMenuItem(this, false, false);
     512           0 :     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
     513           0 :     NS_ENSURE_TRUE(menuParent, NS_OK);
     514             : 
     515             :     // we need to check if we really became the current menu
     516             :     // item or not
     517           0 :     nsMenuFrame *realCurrentItem = menuParent->GetCurrentMenuItem();
     518           0 :     if (realCurrentItem != this) {
     519             :       // we didn't (presumably because a context menu was active)
     520           0 :       return NS_OK;
     521             :     }
     522             : 
     523             :     // Hovering over a menu in a popup should open it without a need for a click.
     524             :     // A timer is used so that it doesn't open if the user moves the mouse quickly
     525             :     // past the menu. This conditional check ensures that only menus have this
     526             :     // behaviour
     527           0 :     if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !menuParent->IsMenuBar()) {
     528             :       int32_t menuDelay =
     529           0 :         LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
     530             : 
     531             :       // We're a menu, we're built, we're closed, and no timer has been kicked off.
     532           0 :       mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
     533           0 :       mOpenTimer->SetTarget(
     534           0 :           mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
     535           0 :       mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
     536             :     }
     537             :   }
     538             : 
     539           0 :   return NS_OK;
     540             : }
     541             : 
     542             : void
     543           0 : nsMenuFrame::ToggleMenuState()
     544             : {
     545           0 :   if (IsOpen())
     546           0 :     CloseMenu(false);
     547             :   else
     548           0 :     OpenMenu(false);
     549           0 : }
     550             : 
     551             : void
     552           0 : nsMenuFrame::PopupOpened()
     553             : {
     554           0 :   gMenuJustOpenedOrClosed = true;
     555             : 
     556           0 :   AutoWeakFrame weakFrame(this);
     557           0 :   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
     558           0 :                     NS_LITERAL_STRING("true"), true);
     559           0 :   if (!weakFrame.IsAlive())
     560           0 :     return;
     561             : 
     562           0 :   nsMenuParent* menuParent = GetMenuParent();
     563           0 :   if (menuParent) {
     564           0 :     menuParent->SetActive(true);
     565             :     // Make sure the current menu which is being toggled on
     566             :     // the menubar is highlighted
     567           0 :     menuParent->SetCurrentMenuItem(this);
     568             :   }
     569             : }
     570             : 
     571             : void
     572           0 : nsMenuFrame::PopupClosed(bool aDeselectMenu)
     573             : {
     574           0 :   AutoWeakFrame weakFrame(this);
     575             :   nsContentUtils::AddScriptRunner(
     576           0 :     new nsUnsetAttrRunnable(mContent, nsGkAtoms::open));
     577           0 :   if (!weakFrame.IsAlive())
     578           0 :     return;
     579             : 
     580             :   // if the popup is for a menu on a menubar, inform menubar to deactivate
     581           0 :   nsMenuParent* menuParent = GetMenuParent();
     582           0 :   if (menuParent && menuParent->MenuClosed()) {
     583           0 :     if (aDeselectMenu) {
     584           0 :       SelectMenu(false);
     585             :     } else {
     586             :       // We are not deselecting the parent menu while closing the popup, so send
     587             :       // a DOMMenuItemActive event to the menu to indicate that the menu is
     588             :       // becoming active again.
     589           0 :       nsMenuFrame *current = menuParent->GetCurrentMenuItem();
     590           0 :       if (current) {
     591             :         // However, if the menu is a descendant on a menubar, and the menubar
     592             :         // has the 'stay active' flag set, it means that the menubar is switching
     593             :         // to another toplevel menu entirely (for example from Edit to View), so
     594             :         // don't fire the DOMMenuItemActive event or else we'll send extraneous
     595             :         // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected
     596             :         // the old menu, so it doesn't need to happen again here, and the new
     597             :         // menu can be selected right away.
     598           0 :         nsIFrame* parent = current;
     599           0 :         while (parent) {
     600           0 :           nsMenuBarFrame* menubar = do_QueryFrame(parent);
     601           0 :           if (menubar && menubar->GetStayActive())
     602           0 :             return;
     603             : 
     604           0 :           parent = parent->GetParent();
     605             :         }
     606             : 
     607             :         nsCOMPtr<nsIRunnable> event =
     608           0 :           new nsMenuActivateEvent(current->GetContent(),
     609           0 :                                   PresContext(), true);
     610           0 :         mContent->OwnerDoc()->Dispatch("nsMenuActivateEvent",
     611             :                                        TaskCategory::Other,
     612           0 :                                        event.forget());
     613             :       }
     614             :     }
     615             :   }
     616             : }
     617             : 
     618             : NS_IMETHODIMP
     619           0 : nsMenuFrame::SelectMenu(bool aActivateFlag)
     620             : {
     621           0 :   if (mContent) {
     622             :     // When a menu opens a submenu, the mouse will often be moved onto a
     623             :     // sibling before moving onto an item within the submenu, causing the
     624             :     // parent to become deselected. We need to ensure that the parent menu
     625             :     // is reselected when an item in the submenu is selected, so navigate up
     626             :     // from the item to its popup, and then to the popup above that.
     627           0 :     if (aActivateFlag) {
     628           0 :       nsIFrame* parent = GetParent();
     629           0 :       while (parent) {
     630           0 :         nsMenuPopupFrame* menupopup = do_QueryFrame(parent);
     631           0 :         if (menupopup) {
     632             :           // a menu is always the direct parent of a menupopup
     633           0 :           nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent());
     634           0 :           if (menu) {
     635             :             // a popup however is not necessarily the direct parent of a menu
     636           0 :             nsIFrame* popupParent = menu->GetParent();
     637           0 :             while (popupParent) {
     638           0 :               menupopup = do_QueryFrame(popupParent);
     639           0 :               if (menupopup) {
     640           0 :                 menupopup->SetCurrentMenuItem(menu);
     641           0 :                 break;
     642             :               }
     643           0 :               popupParent = popupParent->GetParent();
     644             :             }
     645             :           }
     646           0 :           break;
     647             :         }
     648           0 :         parent = parent->GetParent();
     649             :       }
     650             :     }
     651             : 
     652             :     // cancel the close timer if selecting a menu within the popup to be closed
     653           0 :     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     654           0 :     if (pm) {
     655           0 :       nsMenuParent* menuParent = GetMenuParent();
     656           0 :       pm->CancelMenuTimer(menuParent);
     657             :     }
     658             : 
     659             :     nsCOMPtr<nsIRunnable> event =
     660           0 :       new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag);
     661           0 :     mContent->OwnerDoc()->Dispatch("nsMenuActivateEvent",
     662             :                                    TaskCategory::Other,
     663           0 :                                    event.forget());
     664             :   }
     665             : 
     666           0 :   return NS_OK;
     667             : }
     668             : 
     669             : nsresult
     670           1 : nsMenuFrame::AttributeChanged(int32_t aNameSpaceID,
     671             :                               nsIAtom* aAttribute,
     672             :                               int32_t aModType)
     673             : {
     674           1 :   if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) {
     675             :     // Reset the flag so that only one change is ignored.
     676           0 :     mIgnoreAccelTextChange = false;
     677           0 :     return NS_OK;
     678             :   }
     679             : 
     680           2 :   if (aAttribute == nsGkAtoms::checked ||
     681           2 :       aAttribute == nsGkAtoms::acceltext ||
     682           2 :       aAttribute == nsGkAtoms::key ||
     683           2 :       aAttribute == nsGkAtoms::type ||
     684           1 :       aAttribute == nsGkAtoms::name) {
     685             :     nsCOMPtr<nsIRunnable> event =
     686           0 :       new nsMenuAttributeChangedEvent(this, aAttribute);
     687           0 :     nsContentUtils::AddScriptRunner(event);
     688             :   }
     689           1 :   return NS_OK;
     690             : }
     691             : 
     692             : nsIContent*
     693          36 : nsMenuFrame::GetAnchor()
     694             : {
     695          36 :   mozilla::dom::Element* anchor = nullptr;
     696             : 
     697          72 :   nsAutoString id;
     698          36 :   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
     699          36 :   if (!id.IsEmpty()) {
     700           3 :     nsIDocument* doc = mContent->OwnerDoc();
     701             : 
     702             :     anchor =
     703           3 :       doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
     704           3 :     if (!anchor) {
     705           0 :       anchor = doc->GetElementById(id);
     706             :     }
     707             :   }
     708             : 
     709             :   // Always return the menu's content if the anchor wasn't set or wasn't found.
     710          72 :   return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
     711             : }
     712             : 
     713             : void
     714           0 : nsMenuFrame::OpenMenu(bool aSelectFirstItem)
     715             : {
     716           0 :   if (!mContent)
     717           0 :     return;
     718             : 
     719           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     720           0 :   if (pm) {
     721           0 :     pm->KillMenuTimer();
     722             :     // This opens the menu asynchronously
     723           0 :     pm->ShowMenu(mContent, aSelectFirstItem, true);
     724             :   }
     725             : }
     726             : 
     727             : void
     728           0 : nsMenuFrame::CloseMenu(bool aDeselectMenu)
     729             : {
     730           0 :   gMenuJustOpenedOrClosed = true;
     731             : 
     732             :   // Close the menu asynchronously
     733           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     734           0 :   if (pm && HasPopup())
     735           0 :     pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false);
     736           0 : }
     737             : 
     738             : bool
     739         475 : nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways)
     740             : {
     741         950 :   nsAutoString sizedToPopup;
     742         475 :   aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup);
     743         950 :   return sizedToPopup.EqualsLiteral("always") ||
     744        1595 :          (!aRequireAlways && sizedToPopup.EqualsLiteral("pref"));
     745             : }
     746             : 
     747             : nsSize
     748         171 : nsMenuFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState)
     749             : {
     750         171 :   nsSize size = nsBoxFrame::GetXULMinSize(aBoxLayoutState);
     751         342 :   DISPLAY_MIN_SIZE(this, size);
     752             : 
     753         171 :   if (IsSizedToPopup(mContent, true))
     754           0 :     SizeToPopup(aBoxLayoutState, size);
     755             : 
     756         342 :   return size;
     757             : }
     758             : 
     759             : NS_IMETHODIMP
     760          36 : nsMenuFrame::DoXULLayout(nsBoxLayoutState& aState)
     761             : {
     762             :   // lay us out
     763          36 :   nsresult rv = nsBoxFrame::DoXULLayout(aState);
     764             : 
     765          36 :   nsMenuPopupFrame* popupFrame = GetPopup();
     766          36 :   if (popupFrame) {
     767          36 :     bool sizeToPopup = IsSizedToPopup(mContent, false);
     768          36 :     popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
     769             :   }
     770             : 
     771          36 :   return rv;
     772             : }
     773             : 
     774             : #ifdef DEBUG_LAYOUT
     775             : nsresult
     776             : nsMenuFrame::SetXULDebug(nsBoxLayoutState& aState, bool aDebug)
     777             : {
     778             :   // see if our state matches the given debug state
     779             :   bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
     780             :   bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
     781             : 
     782             :   // if it doesn't then tell each child below us the new debug state
     783             :   if (debugChanged)
     784             :   {
     785             :       nsBoxFrame::SetXULDebug(aState, aDebug);
     786             :       nsMenuPopupFrame* popupFrame = GetPopup();
     787             :       if (popupFrame)
     788             :         SetXULDebug(aState, popupFrame, aDebug);
     789             :   }
     790             : 
     791             :   return NS_OK;
     792             : }
     793             : 
     794             : nsresult
     795             : nsMenuFrame::SetXULDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug)
     796             : {
     797             :       if (!aList)
     798             :           return NS_OK;
     799             : 
     800             :       while (aList) {
     801             :         if (aList->IsXULBoxFrame())
     802             :           aList->SetXULDebug(aState, aDebug);
     803             : 
     804             :         aList = aList->GetNextSibling();
     805             :       }
     806             : 
     807             :       return NS_OK;
     808             : }
     809             : #endif
     810             : 
     811             : //
     812             : // Enter
     813             : //
     814             : // Called when the user hits the <Enter>/<Return> keys or presses the
     815             : // shortcut key. If this is a leaf item, the item's action will be executed.
     816             : // In either case, do nothing if the item is disabled.
     817             : //
     818             : nsMenuFrame*
     819           0 : nsMenuFrame::Enter(WidgetGUIEvent* aEvent)
     820             : {
     821           0 :   if (IsDisabled()) {
     822             : #ifdef XP_WIN
     823             :     // behavior on Windows - close the popup chain
     824             :     nsMenuParent* menuParent = GetMenuParent();
     825             :     if (menuParent) {
     826             :       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     827             :       if (pm) {
     828             :         nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
     829             :         if (popup)
     830             :           pm->HidePopup(popup->GetContent(), true, true, true, false);
     831             :       }
     832             :     }
     833             : #endif   // #ifdef XP_WIN
     834             :     // this menu item was disabled - exit
     835           0 :     return nullptr;
     836             :   }
     837             : 
     838           0 :   if (!IsOpen()) {
     839             :     // The enter key press applies to us.
     840           0 :     nsMenuParent* menuParent = GetMenuParent();
     841           0 :     if (!IsMenu() && menuParent)
     842           0 :       Execute(aEvent);          // Execute our event handler
     843             :     else
     844           0 :       return this;
     845             :   }
     846             : 
     847           0 :   return nullptr;
     848             : }
     849             : 
     850             : bool
     851           0 : nsMenuFrame::IsOpen()
     852             : {
     853           0 :   nsMenuPopupFrame* popupFrame = GetPopup();
     854           0 :   return popupFrame && popupFrame->IsOpen();
     855             : }
     856             : 
     857             : bool
     858           0 : nsMenuFrame::IsMenu()
     859             : {
     860           0 :   return mIsMenu;
     861             : }
     862             : 
     863             : nsMenuListType
     864           0 : nsMenuFrame::GetParentMenuListType()
     865             : {
     866           0 :   nsMenuParent* menuParent = GetMenuParent();
     867           0 :   if (menuParent && menuParent->IsMenu()) {
     868           0 :     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
     869           0 :     nsIFrame* parentMenu = popupFrame->GetParent();
     870           0 :     if (parentMenu) {
     871           0 :       nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
     872           0 :       if (menulist) {
     873           0 :         bool isEditable = false;
     874           0 :         menulist->GetEditable(&isEditable);
     875           0 :         return isEditable ? eEditableMenuList : eReadonlyMenuList;
     876             :       }
     877             :     }
     878             :   }
     879           0 :   return eNotMenuList;
     880             : }
     881             : 
     882             : nsresult
     883           0 : nsMenuFrame::Notify(nsITimer* aTimer)
     884             : {
     885             :   // Our timer has fired.
     886           0 :   if (aTimer == mOpenTimer.get()) {
     887           0 :     mOpenTimer = nullptr;
     888             : 
     889           0 :     nsMenuParent* menuParent = GetMenuParent();
     890           0 :     if (!IsOpen() && menuParent) {
     891             :       // make sure we didn't open a context menu in the meantime
     892             :       // (i.e. the user right-clicked while hovering over a submenu).
     893           0 :       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     894           0 :       if (pm) {
     895           0 :         if ((!pm->HasContextMenu(nullptr) || menuParent->IsContextMenu()) &&
     896           0 :             mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
     897             :                                   nsGkAtoms::_true, eCaseMatters)) {
     898           0 :           OpenMenu(false);
     899             :         }
     900             :       }
     901             :     }
     902           0 :   } else if (aTimer == mBlinkTimer) {
     903           0 :     switch (mBlinkState++) {
     904             :       case 0:
     905           0 :         NS_ASSERTION(false, "Blink timer fired while not blinking");
     906           0 :         StopBlinking();
     907           0 :         break;
     908             :       case 1:
     909             :         {
     910             :           // Turn the highlight back on and wait for a while before closing the menu.
     911           0 :           AutoWeakFrame weakFrame(this);
     912           0 :           mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
     913           0 :                             NS_LITERAL_STRING("true"), true);
     914           0 :           if (weakFrame.IsAlive()) {
     915           0 :             aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
     916             :           }
     917             :         }
     918           0 :         break;
     919             :       default: {
     920           0 :         nsMenuParent* menuParent = GetMenuParent();
     921           0 :         if (menuParent) {
     922           0 :           menuParent->LockMenuUntilClosed(false);
     923             :         }
     924           0 :         PassMenuCommandEventToPopupManager();
     925           0 :         StopBlinking();
     926           0 :         break;
     927             :       }
     928             :     }
     929             :   }
     930             : 
     931           0 :   return NS_OK;
     932             : }
     933             : 
     934             : bool
     935           0 : nsMenuFrame::IsDisabled()
     936             : {
     937           0 :   return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
     938           0 :                                nsGkAtoms::_true, eCaseMatters);
     939             : }
     940             : 
     941             : void
     942          15 : nsMenuFrame::UpdateMenuType()
     943             : {
     944             :   static nsIContent::AttrValuesArray strings[] =
     945             :     {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
     946          30 :   switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
     947          15 :                                     strings, eCaseMatters)) {
     948           0 :     case 0: mType = eMenuType_Checkbox; break;
     949             :     case 1:
     950           0 :       mType = eMenuType_Radio;
     951           0 :       mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName);
     952           0 :       break;
     953             : 
     954             :     default:
     955          15 :       if (mType != eMenuType_Normal) {
     956           0 :         AutoWeakFrame weakFrame(this);
     957           0 :         mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
     958           0 :                             true);
     959           0 :         ENSURE_TRUE(weakFrame.IsAlive());
     960             :       }
     961          15 :       mType = eMenuType_Normal;
     962          15 :       break;
     963             :   }
     964          15 :   UpdateMenuSpecialState();
     965             : }
     966             : 
     967             : /* update checked-ness for type="checkbox" and type="radio" */
     968             : void
     969          15 : nsMenuFrame::UpdateMenuSpecialState()
     970             : {
     971             :   bool newChecked =
     972          15 :     mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
     973          15 :                           nsGkAtoms::_true, eCaseMatters);
     974          15 :   if (newChecked == mChecked) {
     975             :     /* checked state didn't change */
     976             : 
     977          15 :     if (mType != eMenuType_Radio)
     978          15 :       return; // only Radio possibly cares about other kinds of change
     979             : 
     980           0 :     if (!mChecked || mGroupName.IsEmpty())
     981           0 :       return;                   // no interesting change
     982             :   } else {
     983           0 :     mChecked = newChecked;
     984           0 :     if (mType != eMenuType_Radio || !mChecked)
     985             :       /*
     986             :        * Unchecking something requires no further changes, and only
     987             :        * menuRadio has to do additional work when checked.
     988             :        */
     989           0 :       return;
     990             :   }
     991             : 
     992             :   /*
     993             :    * If we get this far, we're type=radio, and:
     994             :    * - our name= changed, or
     995             :    * - we went from checked="false" to checked="true"
     996             :    */
     997             : 
     998             :   /*
     999             :    * Behavioural note:
    1000             :    * If we're checked and renamed _into_ an existing radio group, we are
    1001             :    * made the new checked item, and we unselect the previous one.
    1002             :    *
    1003             :    * The only other reasonable behaviour would be to check for another selected
    1004             :    * item in that group.  If found, unselect ourselves, otherwise we're the
    1005             :    * selected item.  That, however, would be a lot more work, and I don't think
    1006             :    * it's better at all.
    1007             :    */
    1008             : 
    1009             :   /* walk siblings, looking for the other checked item with the same name */
    1010             :   // get the first sibling in this menu popup. This frame may be it, and if we're
    1011             :   // being called at creation time, this frame isn't yet in the parent's child list.
    1012             :   // All I'm saying is that this may fail, but it's most likely alright.
    1013           0 :   nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(GetParent(), nullptr, true, false);
    1014           0 :   nsIFrame* sib = firstMenuItem;
    1015           0 :   while (sib) {
    1016           0 :     nsMenuFrame* menu = do_QueryFrame(sib);
    1017           0 :     if (sib != this) {
    1018           0 :       if (menu && menu->GetMenuType() == eMenuType_Radio &&
    1019           0 :           menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) {
    1020             :         /* uncheck the old item */
    1021           0 :         sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
    1022           0 :                                      true);
    1023             :         /* XXX in DEBUG, check to make sure that there aren't two checked items */
    1024           0 :         return;
    1025             :       }
    1026             :     }
    1027           0 :     sib = nsXULPopupManager::GetNextMenuItem(GetParent(), menu, true, true);
    1028           0 :     if (sib == firstMenuItem) {
    1029           0 :       break;
    1030             :     }
    1031             :   }
    1032             : }
    1033             : 
    1034             : void
    1035          15 : nsMenuFrame::BuildAcceleratorText(bool aNotify)
    1036             : {
    1037          15 :   nsAutoString accelText;
    1038             : 
    1039          15 :   if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
    1040          15 :     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText);
    1041          15 :     if (!accelText.IsEmpty())
    1042           0 :       return;
    1043             :   }
    1044             :   // accelText is definitely empty here.
    1045             : 
    1046             :   // Now we're going to compute the accelerator text, so remember that we did.
    1047          15 :   AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
    1048             : 
    1049             :   // If anything below fails, just leave the accelerator text blank.
    1050          15 :   AutoWeakFrame weakFrame(this);
    1051          15 :   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify);
    1052          15 :   ENSURE_TRUE(weakFrame.IsAlive());
    1053             : 
    1054             :   // See if we have a key node and use that instead.
    1055          15 :   nsAutoString keyValue;
    1056          15 :   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
    1057          15 :   if (keyValue.IsEmpty())
    1058          15 :     return;
    1059             : 
    1060             :   // Turn the document into a DOM document so we can use getElementById
    1061           0 :   nsIDocument *document = mContent->GetUncomposedDoc();
    1062           0 :   if (!document)
    1063           0 :     return;
    1064             : 
    1065             :   //XXXsmaug If mContent is in shadow dom, should we use
    1066             :   //         ShadowRoot::GetElementById()?
    1067           0 :   nsIContent *keyElement = document->GetElementById(keyValue);
    1068           0 :   if (!keyElement) {
    1069             : #ifdef DEBUG
    1070           0 :     nsAutoString label;
    1071           0 :     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
    1072           0 :     nsAutoString msg = NS_LITERAL_STRING("Key '") +
    1073           0 :                        keyValue +
    1074           0 :                        NS_LITERAL_STRING("' of menu item '") +
    1075           0 :                        label +
    1076           0 :                        NS_LITERAL_STRING("' could not be found");
    1077           0 :     NS_WARNING(NS_ConvertUTF16toUTF8(msg).get());
    1078             : #endif
    1079           0 :     return;
    1080             :   }
    1081             : 
    1082             :   // get the string to display as accelerator text
    1083             :   // check the key element's attributes in this order:
    1084             :   // |keytext|, |key|, |keycode|
    1085           0 :   nsAutoString accelString;
    1086           0 :   keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString);
    1087             : 
    1088           0 :   if (accelString.IsEmpty()) {
    1089           0 :     keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString);
    1090             : 
    1091           0 :     if (!accelString.IsEmpty()) {
    1092           0 :       ToUpperCase(accelString);
    1093             :     } else {
    1094           0 :       nsAutoString keyCode;
    1095           0 :       keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode);
    1096           0 :       ToUpperCase(keyCode);
    1097             : 
    1098             :       nsresult rv;
    1099             :       nsCOMPtr<nsIStringBundleService> bundleService =
    1100           0 :         mozilla::services::GetStringBundleService();
    1101           0 :       if (bundleService) {
    1102           0 :         nsCOMPtr<nsIStringBundle> bundle;
    1103           0 :         rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
    1104           0 :                                          getter_AddRefs(bundle));
    1105             : 
    1106           0 :         if (NS_SUCCEEDED(rv) && bundle) {
    1107           0 :           nsXPIDLString keyName;
    1108           0 :           rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
    1109           0 :           if (keyName)
    1110           0 :             accelString = keyName;
    1111             :         }
    1112             :       }
    1113             : 
    1114             :       // nothing usable found, bail
    1115           0 :       if (accelString.IsEmpty())
    1116           0 :         return;
    1117             :     }
    1118             :   }
    1119             : 
    1120           0 :   nsAutoString modifiers;
    1121           0 :   keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
    1122             : 
    1123           0 :   char* str = ToNewCString(modifiers);
    1124             :   char* newStr;
    1125           0 :   char* token = nsCRT::strtok(str, ", \t", &newStr);
    1126             : 
    1127           0 :   nsAutoString shiftText;
    1128           0 :   nsAutoString altText;
    1129           0 :   nsAutoString metaText;
    1130           0 :   nsAutoString controlText;
    1131           0 :   nsAutoString osText;
    1132           0 :   nsAutoString modifierSeparator;
    1133             : 
    1134           0 :   nsContentUtils::GetShiftText(shiftText);
    1135           0 :   nsContentUtils::GetAltText(altText);
    1136           0 :   nsContentUtils::GetMetaText(metaText);
    1137           0 :   nsContentUtils::GetControlText(controlText);
    1138           0 :   nsContentUtils::GetOSText(osText);
    1139           0 :   nsContentUtils::GetModifierSeparatorText(modifierSeparator);
    1140             : 
    1141           0 :   while (token) {
    1142             : 
    1143           0 :     if (PL_strcmp(token, "shift") == 0)
    1144           0 :       accelText += shiftText;
    1145           0 :     else if (PL_strcmp(token, "alt") == 0)
    1146           0 :       accelText += altText;
    1147           0 :     else if (PL_strcmp(token, "meta") == 0)
    1148           0 :       accelText += metaText;
    1149           0 :     else if (PL_strcmp(token, "os") == 0)
    1150           0 :       accelText += osText;
    1151           0 :     else if (PL_strcmp(token, "control") == 0)
    1152           0 :       accelText += controlText;
    1153           0 :     else if (PL_strcmp(token, "accel") == 0) {
    1154           0 :       switch (WidgetInputEvent::AccelModifier()) {
    1155             :         case MODIFIER_META:
    1156           0 :           accelText += metaText;
    1157           0 :           break;
    1158             :         case MODIFIER_OS:
    1159           0 :           accelText += osText;
    1160           0 :           break;
    1161             :         case MODIFIER_ALT:
    1162           0 :           accelText += altText;
    1163           0 :           break;
    1164             :         case MODIFIER_CONTROL:
    1165           0 :           accelText += controlText;
    1166           0 :           break;
    1167             :         default:
    1168           0 :           MOZ_CRASH(
    1169             :             "Handle the new result of WidgetInputEvent::AccelModifier()");
    1170             :           break;
    1171             :       }
    1172             :     }
    1173             : 
    1174           0 :     accelText += modifierSeparator;
    1175             : 
    1176           0 :     token = nsCRT::strtok(newStr, ", \t", &newStr);
    1177             :   }
    1178             : 
    1179           0 :   free(str);
    1180             : 
    1181           0 :   accelText += accelString;
    1182             : 
    1183           0 :   mIgnoreAccelTextChange = true;
    1184           0 :   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify);
    1185           0 :   ENSURE_TRUE(weakFrame.IsAlive());
    1186             : 
    1187           0 :   mIgnoreAccelTextChange = false;
    1188             : }
    1189             : 
    1190             : void
    1191           0 : nsMenuFrame::Execute(WidgetGUIEvent* aEvent)
    1192             : {
    1193             :   // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
    1194           0 :   bool needToFlipChecked = false;
    1195           0 :   if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
    1196           0 :     needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
    1197             :                                                nsGkAtoms::_false, eCaseMatters);
    1198             :   }
    1199             : 
    1200           0 :   nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
    1201           0 :   if (sound)
    1202           0 :     sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
    1203             : 
    1204           0 :   StartBlinking(aEvent, needToFlipChecked);
    1205           0 : }
    1206             : 
    1207             : bool
    1208           0 : nsMenuFrame::ShouldBlink()
    1209             : {
    1210             :   int32_t shouldBlink =
    1211           0 :     LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0);
    1212           0 :   if (!shouldBlink)
    1213           0 :     return false;
    1214             : 
    1215             :   // Don't blink in editable menulists.
    1216           0 :   if (GetParentMenuListType() == eEditableMenuList)
    1217           0 :     return false;
    1218             : 
    1219           0 :   return true;
    1220             : }
    1221             : 
    1222             : void
    1223           0 : nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked)
    1224             : {
    1225           0 :   StopBlinking();
    1226           0 :   CreateMenuCommandEvent(aEvent, aFlipChecked);
    1227             : 
    1228           0 :   if (!ShouldBlink()) {
    1229           0 :     PassMenuCommandEventToPopupManager();
    1230           0 :     return;
    1231             :   }
    1232             : 
    1233             :   // Blink off.
    1234           0 :   AutoWeakFrame weakFrame(this);
    1235           0 :   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
    1236           0 :   if (!weakFrame.IsAlive())
    1237           0 :     return;
    1238             : 
    1239           0 :   nsMenuParent* menuParent = GetMenuParent();
    1240           0 :   if (menuParent) {
    1241             :     // Make this menu ignore events from now on.
    1242           0 :     menuParent->LockMenuUntilClosed(true);
    1243             :   }
    1244             : 
    1245             :   // Set up a timer to blink back on.
    1246           0 :   mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1");
    1247           0 :   mBlinkTimer->SetTarget(
    1248           0 :       mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
    1249           0 :   mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
    1250           0 :   mBlinkState = 1;
    1251             : }
    1252             : 
    1253             : void
    1254           1 : nsMenuFrame::StopBlinking()
    1255             : {
    1256           1 :   mBlinkState = 0;
    1257           1 :   if (mBlinkTimer) {
    1258           0 :     mBlinkTimer->Cancel();
    1259           0 :     mBlinkTimer = nullptr;
    1260             :   }
    1261           1 :   mDelayedMenuCommandEvent = nullptr;
    1262           1 : }
    1263             : 
    1264             : void
    1265           0 : nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked)
    1266             : {
    1267             :   // Create a trusted event if the triggering event was trusted, or if
    1268             :   // we're called from chrome code (since at least one of our caller
    1269             :   // passes in a null event).
    1270           0 :   bool isTrusted = aEvent ? aEvent->IsTrusted() :
    1271           0 :                               nsContentUtils::IsCallerChrome();
    1272             : 
    1273           0 :   bool shift = false, control = false, alt = false, meta = false;
    1274           0 :   WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr;
    1275           0 :   if (inputEvent) {
    1276           0 :     shift = inputEvent->IsShift();
    1277           0 :     control = inputEvent->IsControl();
    1278           0 :     alt = inputEvent->IsAlt();
    1279           0 :     meta = inputEvent->IsMeta();
    1280             :   }
    1281             : 
    1282             :   // Because the command event is firing asynchronously, a flag is needed to
    1283             :   // indicate whether user input is being handled. This ensures that a popup
    1284             :   // window won't get blocked.
    1285           0 :   bool userinput = EventStateManager::IsHandlingUserInput();
    1286             : 
    1287             :   mDelayedMenuCommandEvent =
    1288             :     new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta,
    1289           0 :                               userinput, aFlipChecked);
    1290           0 : }
    1291             : 
    1292             : void
    1293           0 : nsMenuFrame::PassMenuCommandEventToPopupManager()
    1294             : {
    1295           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    1296           0 :   nsMenuParent* menuParent = GetMenuParent();
    1297           0 :   if (pm && menuParent && mDelayedMenuCommandEvent) {
    1298           0 :     pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent);
    1299             :   }
    1300           0 :   mDelayedMenuCommandEvent = nullptr;
    1301           0 : }
    1302             : 
    1303             : void
    1304           0 : nsMenuFrame::RemoveFrame(ChildListID     aListID,
    1305             :                          nsIFrame*       aOldFrame)
    1306             : {
    1307           0 :   nsFrameList* popupList = GetPopupList();
    1308           0 :   if (popupList && popupList->FirstChild() == aOldFrame) {
    1309           0 :     popupList->RemoveFirstChild();
    1310           0 :     aOldFrame->Destroy();
    1311           0 :     DestroyPopupList();
    1312           0 :     PresContext()->PresShell()->
    1313           0 :       FrameNeedsReflow(this, nsIPresShell::eTreeChange,
    1314           0 :                        NS_FRAME_HAS_DIRTY_CHILDREN);
    1315           0 :     return;
    1316             :   }
    1317           0 :   nsBoxFrame::RemoveFrame(aListID, aOldFrame);
    1318             : }
    1319             : 
    1320             : void
    1321           0 : nsMenuFrame::InsertFrames(ChildListID     aListID,
    1322             :                           nsIFrame*       aPrevFrame,
    1323             :                           nsFrameList&    aFrameList)
    1324             : {
    1325           0 :   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
    1326           0 :     SetPopupFrame(aFrameList);
    1327           0 :     if (HasPopup()) {
    1328             : #ifdef DEBUG_LAYOUT
    1329             :       nsBoxLayoutState state(PresContext());
    1330             :       SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
    1331             : #endif
    1332             : 
    1333           0 :       PresContext()->PresShell()->
    1334           0 :         FrameNeedsReflow(this, nsIPresShell::eTreeChange,
    1335           0 :                          NS_FRAME_HAS_DIRTY_CHILDREN);
    1336             :     }
    1337             :   }
    1338             : 
    1339           0 :   if (aFrameList.IsEmpty())
    1340           0 :     return;
    1341             : 
    1342           0 :   if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
    1343           0 :     aPrevFrame = nullptr;
    1344             :   }
    1345             : 
    1346           0 :   nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
    1347             : }
    1348             : 
    1349             : void
    1350           0 : nsMenuFrame::AppendFrames(ChildListID     aListID,
    1351             :                           nsFrameList&    aFrameList)
    1352             : {
    1353           0 :   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
    1354           0 :     SetPopupFrame(aFrameList);
    1355           0 :     if (HasPopup()) {
    1356             : 
    1357             : #ifdef DEBUG_LAYOUT
    1358             :       nsBoxLayoutState state(PresContext());
    1359             :       SetXULDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
    1360             : #endif
    1361           0 :       PresContext()->PresShell()->
    1362           0 :         FrameNeedsReflow(this, nsIPresShell::eTreeChange,
    1363           0 :                          NS_FRAME_HAS_DIRTY_CHILDREN);
    1364             :     }
    1365             :   }
    1366             : 
    1367           0 :   if (aFrameList.IsEmpty())
    1368           0 :     return;
    1369             : 
    1370           0 :   nsBoxFrame::AppendFrames(aListID, aFrameList);
    1371             : }
    1372             : 
    1373             : bool
    1374           0 : nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
    1375             : {
    1376           0 :   if (!IsXULCollapsed()) {
    1377             :     bool widthSet, heightSet;
    1378           0 :     nsSize tmpSize(-1, 0);
    1379           0 :     nsIFrame::AddXULPrefSize(this, tmpSize, widthSet, heightSet);
    1380           0 :     if (!widthSet && GetXULFlex() == 0) {
    1381           0 :       nsMenuPopupFrame* popupFrame = GetPopup();
    1382           0 :       if (!popupFrame)
    1383           0 :         return false;
    1384           0 :       tmpSize = popupFrame->GetXULPrefSize(aState);
    1385             : 
    1386             :       // Produce a size such that:
    1387             :       //  (1) the menu and its popup can be the same width
    1388             :       //  (2) there's enough room in the menu for the content and its
    1389             :       //      border-padding
    1390             :       //  (3) there's enough room in the popup for the content and its
    1391             :       //      scrollbar
    1392           0 :       nsMargin borderPadding;
    1393           0 :       GetXULBorderAndPadding(borderPadding);
    1394             : 
    1395             :       // if there is a scroll frame, add the desired width of the scrollbar as well
    1396           0 :       nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->PrincipalChildList().FirstChild());
    1397           0 :       nscoord scrollbarWidth = 0;
    1398           0 :       if (scrollFrame) {
    1399           0 :         scrollbarWidth =
    1400           0 :           scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
    1401             :       }
    1402             : 
    1403           0 :       aSize.width =
    1404           0 :         tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth);
    1405             : 
    1406           0 :       return true;
    1407             :     }
    1408             :   }
    1409             : 
    1410           0 :   return false;
    1411             : }
    1412             : 
    1413             : nsSize
    1414         134 : nsMenuFrame::GetXULPrefSize(nsBoxLayoutState& aState)
    1415             : {
    1416         134 :   nsSize size = nsBoxFrame::GetXULPrefSize(aState);
    1417         268 :   DISPLAY_PREF_SIZE(this, size);
    1418             : 
    1419             :   // If we are using sizetopopup="always" then
    1420             :   // nsBoxFrame will already have enforced the minimum size
    1421         402 :   if (!IsSizedToPopup(mContent, true) &&
    1422         134 :       IsSizedToPopup(mContent, false) &&
    1423           0 :       SizeToPopup(aState, size)) {
    1424             :     // We now need to ensure that size is within the min - max range.
    1425           0 :     nsSize minSize = nsBoxFrame::GetXULMinSize(aState);
    1426           0 :     nsSize maxSize = GetXULMaxSize(aState);
    1427           0 :     size = BoundsCheck(minSize, size, maxSize);
    1428             :   }
    1429             : 
    1430         268 :   return size;
    1431             : }
    1432             : 
    1433             : NS_IMETHODIMP
    1434           0 : nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
    1435             : {
    1436           0 :   nsMenuPopupFrame* popupFrame = GetPopup();
    1437           0 :   if (!popupFrame)
    1438           0 :     return NS_ERROR_FAILURE;
    1439             : 
    1440           0 :   nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
    1441           0 :   if (!menuFrame) {
    1442           0 :     *aResult = nullptr;
    1443             :   }
    1444             :   else {
    1445           0 :     nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent()));
    1446           0 :     *aResult = elt;
    1447           0 :     NS_IF_ADDREF(*aResult);
    1448             :   }
    1449             : 
    1450           0 :   return NS_OK;
    1451             : }
    1452             : 
    1453             : NS_IMETHODIMP
    1454           0 : nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
    1455             : {
    1456           0 :   nsMenuPopupFrame* popupFrame = GetPopup();
    1457           0 :   if (!popupFrame)
    1458           0 :     return NS_ERROR_FAILURE;
    1459             : 
    1460           0 :   if (!aChild) {
    1461             :     // Remove the current selection
    1462           0 :     popupFrame->ChangeMenuItem(nullptr, false, false);
    1463           0 :     return NS_OK;
    1464             :   }
    1465             : 
    1466           0 :   nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
    1467             : 
    1468           0 :   nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame());
    1469           0 :   if (menu)
    1470           0 :     popupFrame->ChangeMenuItem(menu, false, false);
    1471           0 :   return NS_OK;
    1472             : }
    1473             : 
    1474           0 : nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame()
    1475             : {
    1476           0 :   nsMenuPopupFrame* popupFrame = GetPopup();
    1477           0 :   if (!popupFrame)
    1478           0 :     return nullptr;
    1479           0 :   nsIFrame* childFrame = popupFrame->PrincipalChildList().FirstChild();
    1480           0 :   if (childFrame)
    1481           0 :     return popupFrame->GetScrollFrame(childFrame);
    1482           0 :   return nullptr;
    1483             : }
    1484             : 
    1485             : // nsMenuTimerMediator implementation.
    1486          17 : NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback)
    1487             : 
    1488             : /**
    1489             :  * Constructs a wrapper around an nsMenuFrame.
    1490             :  * @param aFrame nsMenuFrame to create a wrapper around.
    1491             :  */
    1492          15 : nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame)
    1493          15 :   : mFrame(aFrame)
    1494             : {
    1495          15 :   NS_ASSERTION(mFrame, "Must have frame");
    1496          15 : }
    1497             : 
    1498           1 : nsMenuTimerMediator::~nsMenuTimerMediator()
    1499             : {
    1500           1 : }
    1501             : 
    1502             : /**
    1503             :  * Delegates the notification to the contained frame if it has not been destroyed.
    1504             :  * @param aTimer Timer which initiated the callback.
    1505             :  * @return NS_ERROR_FAILURE if the frame has been destroyed.
    1506             :  */
    1507           0 : NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
    1508             : {
    1509           0 :   if (!mFrame)
    1510           0 :     return NS_ERROR_FAILURE;
    1511             : 
    1512           0 :   return mFrame->Notify(aTimer);
    1513             : }
    1514             : 
    1515             : /**
    1516             :  * Clear the pointer to the contained nsMenuFrame. This should be called
    1517             :  * when the contained nsMenuFrame is destroyed.
    1518             :  */
    1519           1 : void nsMenuTimerMediator::ClearFrame()
    1520             : {
    1521           1 :   mFrame = nullptr;
    1522           1 : }
    1523             : 
    1524             : /**
    1525             :  * Get the name of this timer callback.
    1526             :  * @param aName the name to return
    1527             :  */
    1528             : NS_IMETHODIMP
    1529           0 : nsMenuTimerMediator::GetName(nsACString& aName)
    1530             : {
    1531           0 :   aName.AssignLiteral("nsMenuTimerMediator");
    1532           0 :   return NS_OK;
    1533             : }
    1534             : 
    1535             : /**
    1536             :  * Set the name to this timer callback.
    1537             :  * @param aName the name to set
    1538             :  */
    1539             : NS_IMETHODIMP
    1540           0 : nsMenuTimerMediator::SetName(const char* aName)
    1541             : {
    1542             :   // We don't need to change the name for nsMenuTimerMediator.
    1543           0 :   return NS_ERROR_NOT_IMPLEMENTED;
    1544             : }

Generated by: LCOV version 1.13