layout/xul/nsMenuFrame.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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/. */
     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 <algorithm>
    49 using namespace mozilla;
    51 #define NS_MENU_POPUP_LIST_INDEX 0
    53 #if defined(XP_WIN)
    54 #define NSCONTEXTMENUISMOUSEUP 1
    55 #endif
    57 static void
    58 AssertNotCalled(void* aPropertyValue)
    59 {
    60   NS_ERROR("popup list should never be destroyed by the FramePropertyTable");
    61 }
    62 NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled)
    64 static int32_t gEatMouseMove = false;
    66 const int32_t kBlinkDelay = 67; // milliseconds
    68 // this class is used for dispatching menu activation events asynchronously.
    69 class nsMenuActivateEvent : public nsRunnable
    70 {
    71 public:
    72   nsMenuActivateEvent(nsIContent *aMenu,
    73                       nsPresContext* aPresContext,
    74                       bool aIsActivate)
    75     : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate)
    76   {
    77   }
    79   NS_IMETHOD Run() MOZ_OVERRIDE
    80   {
    81     nsAutoString domEventToFire;
    83     if (mIsActivate) {
    84       // Highlight the menu.
    85       mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
    86                      NS_LITERAL_STRING("true"), true);
    87       // The menuactivated event is used by accessibility to track the user's
    88       // movements through menus
    89       domEventToFire.AssignLiteral("DOMMenuItemActive");
    90     }
    91     else {
    92       // Unhighlight the menu.
    93       mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
    94       domEventToFire.AssignLiteral("DOMMenuItemInactive");
    95     }
    97     nsCOMPtr<nsIDOMEvent> event;
    98     if (NS_SUCCEEDED(EventDispatcher::CreateEvent(mMenu, mPresContext, nullptr,
    99                                                   NS_LITERAL_STRING("Events"),
   100                                                   getter_AddRefs(event)))) {
   101       event->InitEvent(domEventToFire, true, true);
   103       event->SetTrusted(true);
   105       EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event,
   106                                         mPresContext, nullptr);
   107     }
   109     return NS_OK;
   110   }
   112 private:
   113   nsCOMPtr<nsIContent> mMenu;
   114   nsRefPtr<nsPresContext> mPresContext;
   115   bool mIsActivate;
   116 };
   118 class nsMenuAttributeChangedEvent : public nsRunnable
   119 {
   120 public:
   121   nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr)
   122   : mFrame(aFrame), mAttr(aAttr)
   123   {
   124   }
   126   NS_IMETHOD Run() MOZ_OVERRIDE
   127   {
   128     nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
   129     NS_ENSURE_STATE(frame);
   130     if (mAttr == nsGkAtoms::checked) {
   131       frame->UpdateMenuSpecialState(frame->PresContext());
   132     } else if (mAttr == nsGkAtoms::acceltext) {
   133       // someone reset the accelText attribute,
   134       // so clear the bit that says *we* set it
   135       frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
   136       frame->BuildAcceleratorText(true);
   137     }
   138     else if (mAttr == nsGkAtoms::key) {
   139       frame->BuildAcceleratorText(true);
   140     } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
   141       frame->UpdateMenuType(frame->PresContext());
   142     }
   143     return NS_OK;
   144   }
   145 protected:
   146   nsWeakFrame       mFrame;
   147   nsCOMPtr<nsIAtom> mAttr;
   148 };
   150 //
   151 // NS_NewMenuFrame and NS_NewMenuItemFrame
   152 //
   153 // Wrappers for creating a new menu popup container
   154 //
   155 nsIFrame*
   156 NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
   157 {
   158   nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
   159   it->SetIsMenu(true);
   160   return it;
   161 }
   163 nsIFrame*
   164 NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
   165 {
   166   nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
   167   it->SetIsMenu(false);
   168   return it;
   169 }
   171 NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
   173 NS_QUERYFRAME_HEAD(nsMenuFrame)
   174   NS_QUERYFRAME_ENTRY(nsMenuFrame)
   175 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
   177 nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext):
   178   nsBoxFrame(aShell, aContext),
   179     mIsMenu(false),
   180     mChecked(false),
   181     mIgnoreAccelTextChange(false),
   182     mType(eMenuType_Normal),
   183     mMenuParent(nullptr),
   184     mBlinkState(0)
   185 {
   186 }
   188 void
   189 nsMenuFrame::SetParent(nsIFrame* aParent)
   190 {
   191   nsBoxFrame::SetParent(aParent);
   192   InitMenuParent(aParent);
   193 }
   195 void
   196 nsMenuFrame::InitMenuParent(nsIFrame* aParent)
   197 {
   198   while (aParent) {
   199     nsMenuPopupFrame* popup = do_QueryFrame(aParent);
   200     if (popup) {
   201       mMenuParent = popup;
   202       break;
   203     }
   205     nsMenuBarFrame* menubar = do_QueryFrame(aParent);
   206     if (menubar) {
   207       mMenuParent = menubar;
   208       break;
   209     }
   211     aParent = aParent->GetParent();
   212   }
   213 }
   215 class nsASyncMenuInitialization MOZ_FINAL : public nsIReflowCallback
   216 {
   217 public:
   218   nsASyncMenuInitialization(nsIFrame* aFrame)
   219     : mWeakFrame(aFrame)
   220   {
   221   }
   223   virtual bool ReflowFinished() MOZ_OVERRIDE
   224   {
   225     bool shouldFlush = false;
   226     nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame());
   227     if (menu) {
   228       menu->UpdateMenuType(menu->PresContext());
   229       shouldFlush = true;
   230     }
   231     delete this;
   232     return shouldFlush;
   233   }
   235   virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
   236   {
   237     delete this;
   238   }
   240   nsWeakFrame mWeakFrame;
   241 };
   243 void
   244 nsMenuFrame::Init(nsIContent*      aContent,
   245                   nsIFrame*        aParent,
   246                   nsIFrame*        aPrevInFlow)
   247 {
   248   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
   250   // Set up a mediator which can be used for callbacks on this frame.
   251   mTimerMediator = new nsMenuTimerMediator(this);
   253   InitMenuParent(aParent);
   255   BuildAcceleratorText(false);
   256   nsIReflowCallback* cb = new nsASyncMenuInitialization(this);
   257   PresContext()->PresShell()->PostReflowCallback(cb);
   258 }
   260 const nsFrameList&
   261 nsMenuFrame::GetChildList(ChildListID aListID) const
   262 {
   263   if (kPopupList == aListID) {
   264     nsFrameList* list = GetPopupList();
   265     return list ? *list : nsFrameList::EmptyList();
   266   }
   267   return nsBoxFrame::GetChildList(aListID);
   268 }
   270 void
   271 nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const
   272 {
   273   nsBoxFrame::GetChildLists(aLists);
   274   nsFrameList* list = GetPopupList();
   275   if (list) {
   276     list->AppendIfNonempty(aLists, kPopupList);
   277   }
   278 }
   280 nsMenuPopupFrame*
   281 nsMenuFrame::GetPopup()
   282 {
   283   nsFrameList* popupList = GetPopupList();
   284   return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) :
   285                      nullptr;
   286 }
   288 nsFrameList*
   289 nsMenuFrame::GetPopupList() const
   290 {
   291   if (!HasPopup()) {
   292     return nullptr;
   293   }
   294   nsFrameList* prop =
   295     static_cast<nsFrameList*>(Properties().Get(PopupListProperty()));
   296   NS_ASSERTION(prop && prop->GetLength() == 1 &&
   297                prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame,
   298                "popup list should have exactly one nsMenuPopupFrame");
   299   return prop;
   300 }
   302 void
   303 nsMenuFrame::DestroyPopupList()
   304 {
   305   NS_ASSERTION(HasPopup(), "huh?");
   306   nsFrameList* prop =
   307     static_cast<nsFrameList*>(Properties().Remove(PopupListProperty()));
   308   NS_ASSERTION(prop && prop->IsEmpty(),
   309                "popup list must exist and be empty when destroying");
   310   RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
   311   prop->Delete(PresContext()->PresShell());
   312 }
   314 void
   315 nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList)
   316 {
   317   for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
   318     nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get());
   319     if (popupFrame) {
   320       // Remove the frame from the list and store it in a nsFrameList* property.
   321       aFrameList.RemoveFrame(popupFrame);
   322       nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame);
   323       Properties().Set(PopupListProperty(), popupList);
   324       AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
   325       break;
   326     }
   327   }
   328 }
   330 nsresult
   331 nsMenuFrame::SetInitialChildList(ChildListID     aListID,
   332                                  nsFrameList&    aChildList)
   333 {
   334   NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
   335   if (aListID == kPrincipalList || aListID == kPopupList) {
   336     SetPopupFrame(aChildList);
   337   }
   338   return nsBoxFrame::SetInitialChildList(aListID, aChildList);
   339 }
   341 void
   342 nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot)
   343 {
   344   // Kill our timer if one is active. This is not strictly necessary as
   345   // the pointer to this frame will be cleared from the mediator, but
   346   // this is done for added safety.
   347   if (mOpenTimer) {
   348     mOpenTimer->Cancel();
   349   }
   351   StopBlinking();
   353   // Null out the pointer to this frame in the mediator wrapper so that it 
   354   // doesn't try to interact with a deallocated frame.
   355   mTimerMediator->ClearFrame();
   357   // if the menu content is just being hidden, it may be made visible again
   358   // later, so make sure to clear the highlighting.
   359   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false);
   361   // are we our menu parent's current menu item?
   362   if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) {
   363     // yes; tell it that we're going away
   364     mMenuParent->CurrentMenuIsBeingDestroyed();
   365   }
   367   nsFrameList* popupList = GetPopupList();
   368   if (popupList) {
   369     popupList->DestroyFramesFrom(aDestructRoot);
   370     DestroyPopupList();
   371   }
   373   nsBoxFrame::DestroyFrom(aDestructRoot);
   374 }
   376 void
   377 nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder*   aBuilder,
   378                                          const nsRect&           aDirtyRect,
   379                                          const nsDisplayListSet& aLists)
   380 {
   381   if (!aBuilder->IsForEventDelivery()) {
   382     nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
   383     return;
   384   }
   386   nsDisplayListCollection set;
   387   nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
   389   WrapListsInRedirector(aBuilder, set, aLists);
   390 }
   392 nsresult
   393 nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
   394                          WidgetGUIEvent* aEvent,
   395                          nsEventStatus* aEventStatus)
   396 {
   397   NS_ENSURE_ARG_POINTER(aEventStatus);
   398   if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
   399       (mMenuParent && mMenuParent->IsMenuLocked())) {
   400     return NS_OK;
   401   }
   403   nsWeakFrame weakFrame(this);
   404   if (*aEventStatus == nsEventStatus_eIgnore)
   405     *aEventStatus = nsEventStatus_eConsumeDoDefault;
   407   bool onmenu = IsOnMenu();
   409   if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) {
   410     WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
   411     uint32_t keyCode = keyEvent->keyCode;
   412 #ifdef XP_MACOSX
   413     // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
   414     if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) ||
   415         (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
   416       *aEventStatus = nsEventStatus_eConsumeNoDefault;
   417       OpenMenu(false);
   418     }
   419 #else
   420     // On other platforms, toggle menulist on unmodified F4 or Alt arrow
   421     if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
   422         ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
   423       *aEventStatus = nsEventStatus_eConsumeNoDefault;
   424       ToggleMenuState();
   425     }
   426 #endif
   427   }
   428   else if (aEvent->message == NS_MOUSE_BUTTON_DOWN &&
   429            aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
   430            !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     if (!mMenuParent || mMenuParent->IsMenuBar()) {
   436       ToggleMenuState();
   437     }
   438     else {
   439       if (!IsOpen()) {
   440         OpenMenu(false);
   441       }
   442     }
   443   }
   444   else if (
   445 #ifndef NSCONTEXTMENUISMOUSEUP
   446            (aEvent->message == NS_MOUSE_BUTTON_UP &&
   447             aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) &&
   448 #else
   449            aEvent->message == NS_CONTEXTMENU &&
   450 #endif
   451            onmenu && !IsMenu() && !IsDisabled()) {
   452     // if this menu is a context menu it accepts right-clicks...fire away!
   453     // Make sure we cancel default processing of the context menu event so
   454     // that it doesn't bubble and get seen again by the popuplistener and show
   455     // another context menu.
   456     //
   457     // Furthermore (there's always more, isn't there?), on some platforms (win32
   458     // being one of them) we get the context menu event on a mouse up while
   459     // on others we get it on a mouse down. For the ones where we get it on a
   460     // mouse down, we must continue listening for the right button up event to
   461     // dismiss the menu.
   462     if (mMenuParent->IsContextMenu()) {
   463       *aEventStatus = nsEventStatus_eConsumeNoDefault;
   464       Execute(aEvent);
   465     }
   466   }
   467   else if (aEvent->message == NS_MOUSE_BUTTON_UP &&
   468            aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
   469            !IsMenu() && !IsDisabled()) {
   470     // Execute the execute event handler.
   471     *aEventStatus = nsEventStatus_eConsumeNoDefault;
   472     Execute(aEvent);
   473   }
   474   else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) {
   475     // Kill our timer if one is active.
   476     if (mOpenTimer) {
   477       mOpenTimer->Cancel();
   478       mOpenTimer = nullptr;
   479     }
   481     // Deactivate the menu.
   482     if (mMenuParent) {
   483       bool onmenubar = mMenuParent->IsMenuBar();
   484       if (!(onmenubar && mMenuParent->IsActive())) {
   485         if (IsMenu() && !onmenubar && IsOpen()) {
   486           // Submenus don't get closed up immediately.
   487         }
   488         else if (this == mMenuParent->GetCurrentMenuItem()) {
   489           mMenuParent->ChangeMenuItem(nullptr, false);
   490         }
   491       }
   492     }
   493   }
   494   else if (aEvent->message == NS_MOUSE_MOVE &&
   495            (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) {
   496     if (gEatMouseMove) {
   497       gEatMouseMove = false;
   498       return NS_OK;
   499     }
   501     // Let the menu parent know we're the new item.
   502     mMenuParent->ChangeMenuItem(this, false);
   503     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
   504     NS_ENSURE_TRUE(mMenuParent, NS_OK);
   506     // we need to check if we really became the current menu
   507     // item or not
   508     nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem();
   509     if (realCurrentItem != this) {
   510       // we didn't (presumably because a context menu was active)
   511       return NS_OK;
   512     }
   514     // Hovering over a menu in a popup should open it without a need for a click.
   515     // A timer is used so that it doesn't open if the user moves the mouse quickly
   516     // past the menu. This conditional check ensures that only menus have this
   517     // behaviour
   518     if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) {
   519       int32_t menuDelay =
   520         LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
   522       // We're a menu, we're built, we're closed, and no timer has been kicked off.
   523       mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
   524       mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
   525     }
   526   }
   528   return NS_OK;
   529 }
   531 void
   532 nsMenuFrame::ToggleMenuState()
   533 {
   534   if (IsOpen())
   535     CloseMenu(false);
   536   else
   537     OpenMenu(false);
   538 }
   540 void
   541 nsMenuFrame::PopupOpened()
   542 {
   543   nsWeakFrame weakFrame(this);
   544   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
   545                     NS_LITERAL_STRING("true"), true);
   546   if (!weakFrame.IsAlive())
   547     return;
   549   if (mMenuParent) {
   550     mMenuParent->SetActive(true);
   551     // Make sure the current menu which is being toggled on
   552     // the menubar is highlighted
   553     mMenuParent->SetCurrentMenuItem(this);
   554   }
   555 }
   557 void
   558 nsMenuFrame::PopupClosed(bool aDeselectMenu)
   559 {
   560   nsWeakFrame weakFrame(this);
   561   nsContentUtils::AddScriptRunner(
   562     new nsUnsetAttrRunnable(mContent, nsGkAtoms::open));
   563   if (!weakFrame.IsAlive())
   564     return;
   566   // if the popup is for a menu on a menubar, inform menubar to deactivate
   567   if (mMenuParent && mMenuParent->MenuClosed()) {
   568     if (aDeselectMenu) {
   569       SelectMenu(false);
   570     } else {
   571       // We are not deselecting the parent menu while closing the popup, so send
   572       // a DOMMenuItemActive event to the menu to indicate that the menu is
   573       // becoming active again.
   574       nsMenuFrame *current = mMenuParent->GetCurrentMenuItem();
   575       if (current) {
   576         // However, if the menu is a descendant on a menubar, and the menubar
   577         // has the 'stay active' flag set, it means that the menubar is switching
   578         // to another toplevel menu entirely (for example from Edit to View), so
   579         // don't fire the DOMMenuItemActive event or else we'll send extraneous
   580         // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected
   581         // the old menu, so it doesn't need to happen again here, and the new
   582         // menu can be selected right away.
   583         nsIFrame* parent = current;
   584         while (parent) {
   585           nsMenuBarFrame* menubar = do_QueryFrame(parent);
   586           if (menubar && menubar->GetStayActive())
   587             return;
   589           parent = parent->GetParent();
   590         }
   592         nsCOMPtr<nsIRunnable> event =
   593           new nsMenuActivateEvent(current->GetContent(),
   594                                   PresContext(), true);
   595         NS_DispatchToCurrentThread(event);
   596       }
   597     }
   598   }
   599 }
   601 NS_IMETHODIMP
   602 nsMenuFrame::SelectMenu(bool aActivateFlag)
   603 {
   604   if (mContent) {
   605     // When a menu opens a submenu, the mouse will often be moved onto a
   606     // sibling before moving onto an item within the submenu, causing the
   607     // parent to become deselected. We need to ensure that the parent menu
   608     // is reselected when an item in the submenu is selected, so navigate up
   609     // from the item to its popup, and then to the popup above that.
   610     if (aActivateFlag) {
   611       nsIFrame* parent = GetParent();
   612       while (parent) {
   613         nsMenuPopupFrame* menupopup = do_QueryFrame(parent);
   614         if (menupopup) {
   615           // a menu is always the direct parent of a menupopup
   616           nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent());
   617           if (menu) {
   618             // a popup however is not necessarily the direct parent of a menu
   619             nsIFrame* popupParent = menu->GetParent();
   620             while (popupParent) {
   621               menupopup = do_QueryFrame(popupParent);
   622               if (menupopup) {
   623                 menupopup->SetCurrentMenuItem(menu);
   624                 break;
   625               }
   626               popupParent = popupParent->GetParent();
   627             }
   628           }
   629           break;
   630         }
   631         parent = parent->GetParent();
   632       }
   633     }
   635     // cancel the close timer if selecting a menu within the popup to be closed
   636     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   637     if (pm)
   638       pm->CancelMenuTimer(mMenuParent);
   640     nsCOMPtr<nsIRunnable> event =
   641       new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag);
   642     NS_DispatchToCurrentThread(event);
   643   }
   645   return NS_OK;
   646 }
   648 nsresult
   649 nsMenuFrame::AttributeChanged(int32_t aNameSpaceID,
   650                               nsIAtom* aAttribute,
   651                               int32_t aModType)
   652 {
   653   if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) {
   654     // Reset the flag so that only one change is ignored.
   655     mIgnoreAccelTextChange = false;
   656     return NS_OK;
   657   }
   659   if (aAttribute == nsGkAtoms::checked ||
   660       aAttribute == nsGkAtoms::acceltext ||
   661       aAttribute == nsGkAtoms::key ||
   662       aAttribute == nsGkAtoms::type ||
   663       aAttribute == nsGkAtoms::name) {
   664     nsCOMPtr<nsIRunnable> event =
   665       new nsMenuAttributeChangedEvent(this, aAttribute);
   666     nsContentUtils::AddScriptRunner(event);
   667   }
   668   return NS_OK;
   669 }
   671 nsIContent*
   672 nsMenuFrame::GetAnchor()
   673 {
   674   mozilla::dom::Element* anchor = nullptr;
   676   nsAutoString id;
   677   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
   678   if (!id.IsEmpty()) {
   679     nsIDocument* doc = mContent->OwnerDoc();
   681     anchor =
   682       doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
   683     if (!anchor) {
   684       anchor = doc->GetElementById(id);
   685     }
   686   }
   688   // Always return the menu's content if the anchor wasn't set or wasn't found.
   689   return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
   690 }
   692 void
   693 nsMenuFrame::OpenMenu(bool aSelectFirstItem)
   694 {
   695   if (!mContent)
   696     return;
   698   gEatMouseMove = true;
   700   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   701   if (pm) {
   702     pm->KillMenuTimer();
   703     // This opens the menu asynchronously
   704     pm->ShowMenu(mContent, aSelectFirstItem, true);
   705   }
   706 }
   708 void
   709 nsMenuFrame::CloseMenu(bool aDeselectMenu)
   710 {
   711   gEatMouseMove = true;
   713   // Close the menu asynchronously
   714   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   715   if (pm && HasPopup())
   716     pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false);
   717 }
   719 bool
   720 nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways)
   721 {
   722   nsAutoString sizedToPopup;
   723   aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup);
   724   return sizedToPopup.EqualsLiteral("always") ||
   725          (!aRequireAlways && sizedToPopup.EqualsLiteral("pref"));
   726 }
   728 nsSize
   729 nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
   730 {
   731   nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState);
   732   DISPLAY_MIN_SIZE(this, size);
   734   if (IsSizedToPopup(mContent, true))
   735     SizeToPopup(aBoxLayoutState, size);
   737   return size;
   738 }
   740 NS_IMETHODIMP
   741 nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
   742 {
   743   // lay us out
   744   nsresult rv = nsBoxFrame::DoLayout(aState);
   746   nsMenuPopupFrame* popupFrame = GetPopup();
   747   if (popupFrame) {
   748     bool sizeToPopup = IsSizedToPopup(mContent, false);
   749     popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
   750   }
   752   return rv;
   753 }
   755 #ifdef DEBUG_LAYOUT
   756 nsresult
   757 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug)
   758 {
   759   // see if our state matches the given debug state
   760   bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
   761   bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
   763   // if it doesn't then tell each child below us the new debug state
   764   if (debugChanged)
   765   {
   766       nsBoxFrame::SetDebug(aState, aDebug);
   767       nsMenuPopupFrame* popupFrame = GetPopup();
   768       if (popupFrame)
   769         SetDebug(aState, popupFrame, aDebug);
   770   }
   772   return NS_OK;
   773 }
   775 nsresult
   776 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug)
   777 {
   778       if (!aList)
   779           return NS_OK;
   781       while (aList) {
   782         if (aList->IsBoxFrame())
   783           aList->SetDebug(aState, aDebug);
   785         aList = aList->GetNextSibling();
   786       }
   788       return NS_OK;
   789 }
   790 #endif
   792 //
   793 // Enter
   794 //
   795 // Called when the user hits the <Enter>/<Return> keys or presses the
   796 // shortcut key. If this is a leaf item, the item's action will be executed.
   797 // In either case, do nothing if the item is disabled.
   798 //
   799 nsMenuFrame*
   800 nsMenuFrame::Enter(WidgetGUIEvent* aEvent)
   801 {
   802   if (IsDisabled()) {
   803 #ifdef XP_WIN
   804     // behavior on Windows - close the popup chain
   805     if (mMenuParent) {
   806       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   807       if (pm) {
   808         nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
   809         if (popup)
   810           pm->HidePopup(popup->GetContent(), true, true, true, false);
   811       }
   812     }
   813 #endif   // #ifdef XP_WIN
   814     // this menu item was disabled - exit
   815     return nullptr;
   816   }
   818   if (!IsOpen()) {
   819     // The enter key press applies to us.
   820     if (!IsMenu() && mMenuParent)
   821       Execute(aEvent);          // Execute our event handler
   822     else
   823       return this;
   824   }
   826   return nullptr;
   827 }
   829 bool
   830 nsMenuFrame::IsOpen()
   831 {
   832   nsMenuPopupFrame* popupFrame = GetPopup();
   833   return popupFrame && popupFrame->IsOpen();
   834 }
   836 bool
   837 nsMenuFrame::IsMenu()
   838 {
   839   return mIsMenu;
   840 }
   842 nsMenuListType
   843 nsMenuFrame::GetParentMenuListType()
   844 {
   845   if (mMenuParent && mMenuParent->IsMenu()) {
   846     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(mMenuParent);
   847     nsIFrame* parentMenu = popupFrame->GetParent();
   848     if (parentMenu) {
   849       nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
   850       if (menulist) {
   851         bool isEditable = false;
   852         menulist->GetEditable(&isEditable);
   853         return isEditable ? eEditableMenuList : eReadonlyMenuList;
   854       }
   855     }
   856   }
   857   return eNotMenuList;
   858 }
   860 nsresult
   861 nsMenuFrame::Notify(nsITimer* aTimer)
   862 {
   863   // Our timer has fired.
   864   if (aTimer == mOpenTimer.get()) {
   865     mOpenTimer = nullptr;
   867     if (!IsOpen() && mMenuParent) {
   868       // make sure we didn't open a context menu in the meantime
   869       // (i.e. the user right-clicked while hovering over a submenu).
   870       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   871       if (pm) {
   872         if ((!pm->HasContextMenu(nullptr) || mMenuParent->IsContextMenu()) &&
   873             mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
   874                                   nsGkAtoms::_true, eCaseMatters)) {
   875           OpenMenu(false);
   876         }
   877       }
   878     }
   879   } else if (aTimer == mBlinkTimer) {
   880     switch (mBlinkState++) {
   881       case 0:
   882         NS_ASSERTION(false, "Blink timer fired while not blinking");
   883         StopBlinking();
   884         break;
   885       case 1:
   886         {
   887           // Turn the highlight back on and wait for a while before closing the menu.
   888           nsWeakFrame weakFrame(this);
   889           mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
   890                             NS_LITERAL_STRING("true"), true);
   891           if (weakFrame.IsAlive()) {
   892             aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
   893           }
   894         }
   895         break;
   896       default:
   897         if (mMenuParent) {
   898           mMenuParent->LockMenuUntilClosed(false);
   899         }
   900         PassMenuCommandEventToPopupManager();
   901         StopBlinking();
   902         break;
   903     }
   904   }
   906   return NS_OK;
   907 }
   909 bool 
   910 nsMenuFrame::IsDisabled()
   911 {
   912   return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
   913                                nsGkAtoms::_true, eCaseMatters);
   914 }
   916 void
   917 nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext)
   918 {
   919   static nsIContent::AttrValuesArray strings[] =
   920     {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
   921   switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
   922                                     strings, eCaseMatters)) {
   923     case 0: mType = eMenuType_Checkbox; break;
   924     case 1:
   925       mType = eMenuType_Radio;
   926       mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName);
   927       break;
   929     default:
   930       if (mType != eMenuType_Normal) {
   931         nsWeakFrame weakFrame(this);
   932         mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
   933                             true);
   934         ENSURE_TRUE(weakFrame.IsAlive());
   935       }
   936       mType = eMenuType_Normal;
   937       break;
   938   }
   939   UpdateMenuSpecialState(aPresContext);
   940 }
   942 /* update checked-ness for type="checkbox" and type="radio" */
   943 void
   944 nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext)
   945 {
   946   bool newChecked =
   947     mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
   948                           nsGkAtoms::_true, eCaseMatters); 
   949   if (newChecked == mChecked) {
   950     /* checked state didn't change */
   952     if (mType != eMenuType_Radio)
   953       return; // only Radio possibly cares about other kinds of change
   955     if (!mChecked || mGroupName.IsEmpty())
   956       return;                   // no interesting change
   957   } else { 
   958     mChecked = newChecked;
   959     if (mType != eMenuType_Radio || !mChecked)
   960       /*
   961        * Unchecking something requires no further changes, and only
   962        * menuRadio has to do additional work when checked.
   963        */
   964       return;
   965   }
   967   /*
   968    * If we get this far, we're type=radio, and:
   969    * - our name= changed, or
   970    * - we went from checked="false" to checked="true"
   971    */
   973   /*
   974    * Behavioural note:
   975    * If we're checked and renamed _into_ an existing radio group, we are
   976    * made the new checked item, and we unselect the previous one.
   977    *
   978    * The only other reasonable behaviour would be to check for another selected
   979    * item in that group.  If found, unselect ourselves, otherwise we're the
   980    * selected item.  That, however, would be a lot more work, and I don't think
   981    * it's better at all.
   982    */
   984   /* walk siblings, looking for the other checked item with the same name */
   985   // get the first sibling in this menu popup. This frame may be it, and if we're
   986   // being called at creation time, this frame isn't yet in the parent's child list.
   987   // All I'm saying is that this may fail, but it's most likely alright.
   988   nsIFrame* sib = GetParent()->GetFirstPrincipalChild();
   990   while (sib) {
   991     if (sib != this) {
   992       nsMenuFrame* menu = do_QueryFrame(sib);
   993       if (menu && menu->GetMenuType() == eMenuType_Radio &&
   994           menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) {
   995         /* uncheck the old item */
   996         sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
   997                                      true);
   998         /* XXX in DEBUG, check to make sure that there aren't two checked items */
   999         return;
  1003     sib = sib->GetNextSibling();
  1007 void 
  1008 nsMenuFrame::BuildAcceleratorText(bool aNotify)
  1010   nsAutoString accelText;
  1012   if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
  1013     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText);
  1014     if (!accelText.IsEmpty())
  1015       return;
  1017   // accelText is definitely empty here.
  1019   // Now we're going to compute the accelerator text, so remember that we did.
  1020   AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
  1022   // If anything below fails, just leave the accelerator text blank.
  1023   nsWeakFrame weakFrame(this);
  1024   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify);
  1025   ENSURE_TRUE(weakFrame.IsAlive());
  1027   // See if we have a key node and use that instead.
  1028   nsAutoString keyValue;
  1029   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
  1030   if (keyValue.IsEmpty())
  1031     return;
  1033   // Turn the document into a DOM document so we can use getElementById
  1034   nsIDocument *document = mContent->GetDocument();
  1035   if (!document)
  1036     return;
  1038   nsIContent *keyElement = document->GetElementById(keyValue);
  1039   if (!keyElement) {
  1040 #ifdef DEBUG
  1041     nsAutoString label;
  1042     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
  1043     nsAutoString msg = NS_LITERAL_STRING("Key '") +
  1044                        keyValue +
  1045                        NS_LITERAL_STRING("' of menu item '") +
  1046                        label +
  1047                        NS_LITERAL_STRING("' could not be found");
  1048     NS_WARNING(NS_ConvertUTF16toUTF8(msg).get());
  1049 #endif
  1050     return;
  1053   // get the string to display as accelerator text
  1054   // check the key element's attributes in this order:
  1055   // |keytext|, |key|, |keycode|
  1056   nsAutoString accelString;
  1057   keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString);
  1059   if (accelString.IsEmpty()) {
  1060     keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString);
  1062     if (!accelString.IsEmpty()) {
  1063       ToUpperCase(accelString);
  1064     } else {
  1065       nsAutoString keyCode;
  1066       keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode);
  1067       ToUpperCase(keyCode);
  1069       nsresult rv;
  1070       nsCOMPtr<nsIStringBundleService> bundleService =
  1071         mozilla::services::GetStringBundleService();
  1072       if (bundleService) {
  1073         nsCOMPtr<nsIStringBundle> bundle;
  1074         rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
  1075                                          getter_AddRefs(bundle));
  1077         if (NS_SUCCEEDED(rv) && bundle) {
  1078           nsXPIDLString keyName;
  1079           rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
  1080           if (keyName)
  1081             accelString = keyName;
  1085       // nothing usable found, bail
  1086       if (accelString.IsEmpty())
  1087         return;
  1091   static int32_t accelKey = 0;
  1093   if (!accelKey)
  1095     // Compiled-in defaults, in case we can't get LookAndFeel --
  1096     // command for mac, control for all other platforms.
  1097 #ifdef XP_MACOSX
  1098     accelKey = nsIDOMKeyEvent::DOM_VK_META;
  1099 #else
  1100     accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL;
  1101 #endif
  1103     // Get the accelerator key value from prefs, overriding the default:
  1104     accelKey = Preferences::GetInt("ui.key.accelKey", accelKey);
  1107   nsAutoString modifiers;
  1108   keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
  1110   char* str = ToNewCString(modifiers);
  1111   char* newStr;
  1112   char* token = nsCRT::strtok(str, ", \t", &newStr);
  1114   nsAutoString shiftText;
  1115   nsAutoString altText;
  1116   nsAutoString metaText;
  1117   nsAutoString controlText;
  1118   nsAutoString osText;
  1119   nsAutoString modifierSeparator;
  1121   nsContentUtils::GetShiftText(shiftText);
  1122   nsContentUtils::GetAltText(altText);
  1123   nsContentUtils::GetMetaText(metaText);
  1124   nsContentUtils::GetControlText(controlText);
  1125   nsContentUtils::GetOSText(osText);
  1126   nsContentUtils::GetModifierSeparatorText(modifierSeparator);
  1128   while (token) {
  1130     if (PL_strcmp(token, "shift") == 0)
  1131       accelText += shiftText;
  1132     else if (PL_strcmp(token, "alt") == 0) 
  1133       accelText += altText; 
  1134     else if (PL_strcmp(token, "meta") == 0) 
  1135       accelText += metaText; 
  1136     else if (PL_strcmp(token, "os") == 0)
  1137       accelText += osText; 
  1138     else if (PL_strcmp(token, "control") == 0) 
  1139       accelText += controlText; 
  1140     else if (PL_strcmp(token, "accel") == 0) {
  1141       switch (accelKey)
  1143         case nsIDOMKeyEvent::DOM_VK_META:
  1144           accelText += metaText;
  1145           break;
  1147         case nsIDOMKeyEvent::DOM_VK_WIN:
  1148           accelText += osText;
  1149           break;
  1151         case nsIDOMKeyEvent::DOM_VK_ALT:
  1152           accelText += altText;
  1153           break;
  1155         case nsIDOMKeyEvent::DOM_VK_CONTROL:
  1156         default:
  1157           accelText += controlText;
  1158           break;
  1162     accelText += modifierSeparator;
  1164     token = nsCRT::strtok(newStr, ", \t", &newStr);
  1167   nsMemory::Free(str);
  1169   accelText += accelString;
  1171   mIgnoreAccelTextChange = true;
  1172   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify);
  1173   ENSURE_TRUE(weakFrame.IsAlive());
  1175   mIgnoreAccelTextChange = false;
  1178 void
  1179 nsMenuFrame::Execute(WidgetGUIEvent* aEvent)
  1181   // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
  1182   bool needToFlipChecked = false;
  1183   if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
  1184     needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
  1185                                                nsGkAtoms::_false, eCaseMatters);
  1188   nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
  1189   if (sound)
  1190     sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
  1192   StartBlinking(aEvent, needToFlipChecked);
  1195 bool
  1196 nsMenuFrame::ShouldBlink()
  1198   int32_t shouldBlink =
  1199     LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0);
  1200   if (!shouldBlink)
  1201     return false;
  1203   // Don't blink in editable menulists.
  1204   if (GetParentMenuListType() == eEditableMenuList)
  1205     return false;
  1207   return true;
  1210 void
  1211 nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked)
  1213   StopBlinking();
  1214   CreateMenuCommandEvent(aEvent, aFlipChecked);
  1216   if (!ShouldBlink()) {
  1217     PassMenuCommandEventToPopupManager();
  1218     return;
  1221   // Blink off.
  1222   nsWeakFrame weakFrame(this);
  1223   mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
  1224   if (!weakFrame.IsAlive())
  1225     return;
  1227   if (mMenuParent) {
  1228     // Make this menu ignore events from now on.
  1229     mMenuParent->LockMenuUntilClosed(true);
  1232   // Set up a timer to blink back on.
  1233   mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1");
  1234   mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
  1235   mBlinkState = 1;
  1238 void
  1239 nsMenuFrame::StopBlinking()
  1241   mBlinkState = 0;
  1242   if (mBlinkTimer) {
  1243     mBlinkTimer->Cancel();
  1244     mBlinkTimer = nullptr;
  1246   mDelayedMenuCommandEvent = nullptr;
  1249 void
  1250 nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked)
  1252   // Create a trusted event if the triggering event was trusted, or if
  1253   // we're called from chrome code (since at least one of our caller
  1254   // passes in a null event).
  1255   bool isTrusted = aEvent ? aEvent->mFlags.mIsTrusted :
  1256                               nsContentUtils::IsCallerChrome();
  1258   bool shift = false, control = false, alt = false, meta = false;
  1259   WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr;
  1260   if (inputEvent) {
  1261     shift = inputEvent->IsShift();
  1262     control = inputEvent->IsControl();
  1263     alt = inputEvent->IsAlt();
  1264     meta = inputEvent->IsMeta();
  1267   // Because the command event is firing asynchronously, a flag is needed to
  1268   // indicate whether user input is being handled. This ensures that a popup
  1269   // window won't get blocked.
  1270   bool userinput = EventStateManager::IsHandlingUserInput();
  1272   mDelayedMenuCommandEvent =
  1273     new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta,
  1274                               userinput, aFlipChecked);
  1277 void
  1278 nsMenuFrame::PassMenuCommandEventToPopupManager()
  1280   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  1281   if (pm && mMenuParent && mDelayedMenuCommandEvent) {
  1282     pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent);
  1284   mDelayedMenuCommandEvent = nullptr;
  1287 nsresult
  1288 nsMenuFrame::RemoveFrame(ChildListID     aListID,
  1289                          nsIFrame*       aOldFrame)
  1291   nsFrameList* popupList = GetPopupList();
  1292   if (popupList && popupList->FirstChild() == aOldFrame) {
  1293     popupList->RemoveFirstChild();
  1294     aOldFrame->Destroy();
  1295     DestroyPopupList();
  1296     PresContext()->PresShell()->
  1297       FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1298                        NS_FRAME_HAS_DIRTY_CHILDREN);
  1299     return NS_OK;
  1301   return nsBoxFrame::RemoveFrame(aListID, aOldFrame);
  1304 nsresult
  1305 nsMenuFrame::InsertFrames(ChildListID     aListID,
  1306                           nsIFrame*       aPrevFrame,
  1307                           nsFrameList&    aFrameList)
  1309   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
  1310     SetPopupFrame(aFrameList);
  1311     if (HasPopup()) {
  1312 #ifdef DEBUG_LAYOUT
  1313       nsBoxLayoutState state(PresContext());
  1314       SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
  1315 #endif
  1317       PresContext()->PresShell()->
  1318         FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1319                          NS_FRAME_HAS_DIRTY_CHILDREN);
  1323   if (aFrameList.IsEmpty())
  1324     return NS_OK;
  1326   if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
  1327     aPrevFrame = nullptr;
  1330   return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
  1333 nsresult
  1334 nsMenuFrame::AppendFrames(ChildListID     aListID,
  1335                           nsFrameList&    aFrameList)
  1337   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
  1338     SetPopupFrame(aFrameList);
  1339     if (HasPopup()) {
  1341 #ifdef DEBUG_LAYOUT
  1342       nsBoxLayoutState state(PresContext());
  1343       SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
  1344 #endif
  1345       PresContext()->PresShell()->
  1346         FrameNeedsReflow(this, nsIPresShell::eTreeChange,
  1347                          NS_FRAME_HAS_DIRTY_CHILDREN);
  1351   if (aFrameList.IsEmpty())
  1352     return NS_OK;
  1354   return nsBoxFrame::AppendFrames(aListID, aFrameList); 
  1357 bool
  1358 nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
  1360   if (!IsCollapsed()) {
  1361     bool widthSet, heightSet;
  1362     nsSize tmpSize(-1, 0);
  1363     nsIFrame::AddCSSPrefSize(this, tmpSize, widthSet, heightSet);
  1364     if (!widthSet && GetFlex(aState) == 0) {
  1365       nsMenuPopupFrame* popupFrame = GetPopup();
  1366       if (!popupFrame)
  1367         return false;
  1368       tmpSize = popupFrame->GetPrefSize(aState);
  1370       // Produce a size such that:
  1371       //  (1) the menu and its popup can be the same width
  1372       //  (2) there's enough room in the menu for the content and its
  1373       //      border-padding
  1374       //  (3) there's enough room in the popup for the content and its
  1375       //      scrollbar
  1376       nsMargin borderPadding;
  1377       GetBorderAndPadding(borderPadding);
  1379       // if there is a scroll frame, add the desired width of the scrollbar as well
  1380       nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild());
  1381       nscoord scrollbarWidth = 0;
  1382       if (scrollFrame) {
  1383         scrollbarWidth =
  1384           scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
  1387       aSize.width =
  1388         tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth);
  1390       return true;
  1394   return false;
  1397 nsSize
  1398 nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState)
  1400   nsSize size = nsBoxFrame::GetPrefSize(aState);
  1401   DISPLAY_PREF_SIZE(this, size);
  1403   // If we are using sizetopopup="always" then
  1404   // nsBoxFrame will already have enforced the minimum size
  1405   if (!IsSizedToPopup(mContent, true) &&
  1406       IsSizedToPopup(mContent, false) &&
  1407       SizeToPopup(aState, size)) {
  1408     // We now need to ensure that size is within the min - max range.
  1409     nsSize minSize = nsBoxFrame::GetMinSize(aState);
  1410     nsSize maxSize = GetMaxSize(aState);
  1411     size = BoundsCheck(minSize, size, maxSize);
  1414   return size;
  1417 NS_IMETHODIMP
  1418 nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
  1420   nsMenuPopupFrame* popupFrame = GetPopup();
  1421   if (!popupFrame)
  1422     return NS_ERROR_FAILURE;
  1424   nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
  1425   if (!menuFrame) {
  1426     *aResult = nullptr;
  1428   else {
  1429     nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent()));
  1430     *aResult = elt;
  1431     NS_IF_ADDREF(*aResult);
  1434   return NS_OK;
  1437 NS_IMETHODIMP
  1438 nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
  1440   nsMenuPopupFrame* popupFrame = GetPopup();
  1441   if (!popupFrame)
  1442     return NS_ERROR_FAILURE;
  1444   if (!aChild) {
  1445     // Remove the current selection
  1446     popupFrame->ChangeMenuItem(nullptr, false);
  1447     return NS_OK;
  1450   nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
  1452   nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame());
  1453   if (menu)
  1454     popupFrame->ChangeMenuItem(menu, false);
  1455   return NS_OK;
  1458 nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame()
  1460   nsMenuPopupFrame* popupFrame = GetPopup();
  1461   if (!popupFrame)
  1462     return nullptr;
  1463   nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild();
  1464   if (childFrame)
  1465     return popupFrame->GetScrollFrame(childFrame);
  1466   return nullptr;
  1469 // nsMenuTimerMediator implementation.
  1470 NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback)
  1472 /**
  1473  * Constructs a wrapper around an nsMenuFrame.
  1474  * @param aFrame nsMenuFrame to create a wrapper around.
  1475  */
  1476 nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) :
  1477   mFrame(aFrame)
  1479   NS_ASSERTION(mFrame, "Must have frame");
  1482 nsMenuTimerMediator::~nsMenuTimerMediator()
  1486 /**
  1487  * Delegates the notification to the contained frame if it has not been destroyed.
  1488  * @param aTimer Timer which initiated the callback.
  1489  * @return NS_ERROR_FAILURE if the frame has been destroyed.
  1490  */
  1491 NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
  1493   if (!mFrame)
  1494     return NS_ERROR_FAILURE;
  1496   return mFrame->Notify(aTimer);
  1499 /**
  1500  * Clear the pointer to the contained nsMenuFrame. This should be called
  1501  * when the contained nsMenuFrame is destroyed.
  1502  */
  1503 void nsMenuTimerMediator::ClearFrame()
  1505   mFrame = nullptr;

mercurial