layout/xul/nsXULPopupManager.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     1 /* -*- Mode: C++; tab-width: 2; 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 "nsXULPopupManager.h"
     8 #include "nsMenuFrame.h"
     9 #include "nsMenuPopupFrame.h"
    10 #include "nsMenuBarFrame.h"
    11 #include "nsIPopupBoxObject.h"
    12 #include "nsMenuBarListener.h"
    13 #include "nsContentUtils.h"
    14 #include "nsIDOMDocument.h"
    15 #include "nsIDOMEvent.h"
    16 #include "nsIDOMXULElement.h"
    17 #include "nsIXULDocument.h"
    18 #include "nsIXULTemplateBuilder.h"
    19 #include "nsCSSFrameConstructor.h"
    20 #include "nsLayoutUtils.h"
    21 #include "nsViewManager.h"
    22 #include "nsIComponentManager.h"
    23 #include "nsITimer.h"
    24 #include "nsFocusManager.h"
    25 #include "nsIDocShell.h"
    26 #include "nsPIDOMWindow.h"
    27 #include "nsIInterfaceRequestorUtils.h"
    28 #include "nsIBaseWindow.h"
    29 #include "nsIDOMKeyEvent.h"
    30 #include "nsIDOMMouseEvent.h"
    31 #include "nsCaret.h"
    32 #include "nsIDocument.h"
    33 #include "nsPIWindowRoot.h"
    34 #include "nsFrameManager.h"
    35 #include "nsIObserverService.h"
    36 #include "mozilla/dom/Element.h"
    37 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
    38 #include "mozilla/EventDispatcher.h"
    39 #include "mozilla/EventStateManager.h"
    40 #include "mozilla/LookAndFeel.h"
    41 #include "mozilla/MouseEvents.h"
    42 #include "mozilla/Services.h"
    44 using namespace mozilla;
    45 using namespace mozilla::dom;
    47 static_assert(nsIDOMKeyEvent::DOM_VK_HOME  == nsIDOMKeyEvent::DOM_VK_END + 1 &&
    48               nsIDOMKeyEvent::DOM_VK_LEFT  == nsIDOMKeyEvent::DOM_VK_END + 2 &&
    49               nsIDOMKeyEvent::DOM_VK_UP    == nsIDOMKeyEvent::DOM_VK_END + 3 &&
    50               nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 &&
    51               nsIDOMKeyEvent::DOM_VK_DOWN  == nsIDOMKeyEvent::DOM_VK_END + 5,
    52               "nsXULPopupManager assumes some keyCode values are consecutive");
    54 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
    55   {
    56     eNavigationDirection_Last,   // nsIDOMKeyEvent::DOM_VK_END
    57     eNavigationDirection_First,  // nsIDOMKeyEvent::DOM_VK_HOME
    58     eNavigationDirection_Start,  // nsIDOMKeyEvent::DOM_VK_LEFT
    59     eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
    60     eNavigationDirection_End,    // nsIDOMKeyEvent::DOM_VK_RIGHT
    61     eNavigationDirection_After   // nsIDOMKeyEvent::DOM_VK_DOWN
    62   },
    63   {
    64     eNavigationDirection_Last,   // nsIDOMKeyEvent::DOM_VK_END
    65     eNavigationDirection_First,  // nsIDOMKeyEvent::DOM_VK_HOME
    66     eNavigationDirection_End,    // nsIDOMKeyEvent::DOM_VK_LEFT
    67     eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
    68     eNavigationDirection_Start,  // nsIDOMKeyEvent::DOM_VK_RIGHT
    69     eNavigationDirection_After   // nsIDOMKeyEvent::DOM_VK_DOWN
    70   }
    71 };
    73 nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
    75 nsIContent* nsMenuChainItem::Content()
    76 {
    77   return mFrame->GetContent();
    78 }
    80 void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
    81 {
    82   if (mParent) {
    83     NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
    84     mParent->mChild = nullptr;
    85   }
    86   mParent = aParent;
    87   if (mParent) {
    88     if (mParent->mChild)
    89       mParent->mChild->mParent = nullptr;
    90     mParent->mChild = this;
    91   }
    92 }
    94 void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
    95 {
    96   // If the item has a child, set the child's parent to this item's parent,
    97   // effectively removing the item from the chain. If the item has no child,
    98   // just set the parent to null.
    99   if (mChild) {
   100     NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
   101     mChild->SetParent(mParent);
   102   }
   103   else {
   104     // An item without a child should be the first item in the chain, so set
   105     // the first item pointer, pointed to by aRoot, to the parent.
   106     NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
   107     *aRoot = mParent;
   108     SetParent(nullptr);
   109   }
   110 }
   112 NS_IMPL_ISUPPORTS(nsXULPopupManager,
   113                   nsIDOMEventListener,
   114                   nsITimerCallback,
   115                   nsIObserver)
   117 nsXULPopupManager::nsXULPopupManager() :
   118   mRangeOffset(0),
   119   mCachedMousePoint(0, 0),
   120   mCachedModifiers(0),
   121   mActiveMenuBar(nullptr),
   122   mPopups(nullptr),
   123   mNoHidePanels(nullptr),
   124   mTimerMenu(nullptr)
   125 {
   126   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   127   if (obs) {
   128     obs->AddObserver(this, "xpcom-shutdown", false);
   129   }
   130 }
   132 nsXULPopupManager::~nsXULPopupManager() 
   133 {
   134   NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
   135 }
   137 nsresult
   138 nsXULPopupManager::Init()
   139 {
   140   sInstance = new nsXULPopupManager();
   141   NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
   142   NS_ADDREF(sInstance);
   143   return NS_OK;
   144 }
   146 void
   147 nsXULPopupManager::Shutdown()
   148 {
   149   NS_IF_RELEASE(sInstance);
   150 }
   152 NS_IMETHODIMP
   153 nsXULPopupManager::Observe(nsISupports *aSubject,
   154                            const char *aTopic,
   155                            const char16_t *aData)
   156 {
   157   if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
   158     if (mKeyListener) {
   159       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
   160       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
   161       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
   162       mKeyListener = nullptr;
   163     }
   164     mRangeParent = nullptr;
   165     // mOpeningPopup is cleared explicitly soon after using it.
   166     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   167     if (obs) {
   168       obs->RemoveObserver(this, "xpcom-shutdown");
   169     }
   170   }
   172   return NS_OK;
   173 }
   175 nsXULPopupManager*
   176 nsXULPopupManager::GetInstance()
   177 {
   178   MOZ_ASSERT(sInstance);
   179   return sInstance;
   180 }
   182 bool
   183 nsXULPopupManager::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
   184 {
   185   bool consume = false;
   187   nsMenuChainItem* item = GetTopVisibleMenu();
   188   if (item) {
   189     if (aLastRolledUp) {
   190       // we need to get the popup that will be closed last, so that
   191       // widget can keep track of it so it doesn't reopen if a mouse
   192       // down event is going to processed.
   193       // Keep going up the menu chain to get the first level menu. This will
   194       // be the one that closes up last. It's possible that this menu doesn't
   195       // end up closing because the popuphiding event was cancelled, but in
   196       // that case we don't need to deal with the menu reopening as it will
   197       // already still be open.
   198       nsMenuChainItem* first = item;
   199       while (first->GetParent())
   200         first = first->GetParent();
   201       *aLastRolledUp = first->Content();
   202     }
   204     consume = item->Frame()->ConsumeOutsideClicks();
   205     // If the click was over the anchor, always consume the click. This way,
   206     // clicking on a menu doesn't reopen the menu.
   207     if (!consume && pos) {
   208       nsCOMPtr<nsIContent> anchor = item->Frame()->GetAnchor();
   209       if (anchor && anchor->GetPrimaryFrame()) {
   210         // It's possible that some other element is above the anchor at the same
   211         // position, but the only thing that would happen is that the mouse
   212         // event will get consumed, so here only a quick coordinates check is
   213         // done rather than a slower complete check of what is at that location.
   214         if (anchor->GetPrimaryFrame()->GetScreenRect().Contains(*pos)) {
   215           consume = true;
   216         }
   217       }
   218     }
   220     // if a number of popups to close has been specified, determine the last
   221     // popup to close
   222     nsIContent* lastPopup = nullptr;
   223     if (aCount != UINT32_MAX) {
   224       nsMenuChainItem* last = item;
   225       while (--aCount && last->GetParent()) {
   226         last = last->GetParent();
   227       }
   228       if (last) {
   229         lastPopup = last->Content();
   230       }
   231     }
   233     HidePopup(item->Content(), true, true, false, true, lastPopup);
   234   }
   236   return consume;
   237 }
   239 ////////////////////////////////////////////////////////////////////////
   240 bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
   241 {
   242   // should rollup only for autocomplete widgets
   243   // XXXndeakin this should really be something the popup has more control over
   245   nsMenuChainItem* item = GetTopVisibleMenu();
   246   if (!item)
   247     return false;
   249   nsIContent* content = item->Frame()->GetContent();
   250   if (!content)
   251     return false;
   253   if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
   254                            nsGkAtoms::_true, eCaseMatters))
   255     return true;
   257   if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
   258                            nsGkAtoms::_false, eCaseMatters))
   259     return false;
   261   nsAutoString value;
   262   content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
   263   return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
   264 }
   266 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
   267 {
   268   nsMenuChainItem* item = GetTopVisibleMenu();
   269   if (!item)
   270     return false;
   272   nsMenuPopupFrame* frame = item->Frame();
   273   if (frame->PopupType() != ePopupTypePanel)
   274     return true;
   276   nsIContent* content = frame->GetContent();
   277   return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
   278                                            nsGkAtoms::arrow, eCaseMatters));
   279 }
   281 // a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
   282 bool nsXULPopupManager::ShouldRollupOnMouseActivate()
   283 {
   284   return false;
   285 }
   287 uint32_t
   288 nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain)
   289 {
   290   // this method is used by the widget code to determine the list of popups
   291   // that are open. If a mouse click occurs outside one of these popups, the
   292   // panels will roll up. If the click is inside a popup, they will not roll up
   293   uint32_t count = 0, sameTypeCount = 0;
   295   NS_ASSERTION(aWidgetChain, "null parameter");
   296   nsMenuChainItem* item = GetTopVisibleMenu();
   297   while (item) {
   298     nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
   299     NS_ASSERTION(widget, "open popup has no widget");
   300     aWidgetChain->AppendElement(widget.get());
   301     // In the case when a menulist inside a panel is open, clicking in the
   302     // panel should still roll up the menu, so if a different type is found,
   303     // stop scanning.
   304     nsMenuChainItem* parent = item->GetParent();
   305     if (!sameTypeCount) {
   306       count++;
   307       if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
   308                      item->IsContextMenu() != parent->IsContextMenu()) {
   309         sameTypeCount = count;
   310       }
   311     }
   312     item = parent;
   313   }
   315   return sameTypeCount;
   316 }
   318 nsIWidget*
   319 nsXULPopupManager::GetRollupWidget()
   320 {
   321   nsMenuChainItem* item = GetTopVisibleMenu();
   322   return item ? item->Frame()->GetWidget() : nullptr;
   323 }
   325 void
   326 nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow)
   327 {
   328   // When the parent window is moved, adjust any child popups. Dismissable
   329   // menus and panels are expected to roll up when a window is moved, so there
   330   // is no need to check these popups, only the noautohide popups.
   332   // The items are added to a list so that they can be adjusted bottom to top.
   333   nsTArray<nsMenuPopupFrame *> list;
   335   nsMenuChainItem* item = mNoHidePanels;
   336   while (item) {
   337     // only move popups that are within the same window and where auto
   338     // positioning has not been disabled
   339     nsMenuPopupFrame* frame = item->Frame();
   340     if (frame->GetAutoPosition()) {
   341       nsIContent* popup = frame->GetContent();
   342       if (popup) {
   343         nsIDocument* document = popup->GetCurrentDoc();
   344         if (document) {
   345           nsPIDOMWindow* window = document->GetWindow();
   346           if (window) {
   347             window = window->GetPrivateRoot();
   348             if (window == aWindow) {
   349               list.AppendElement(frame);
   350             }
   351           }
   352         }
   353       }
   354     }
   356     item = item->GetParent();
   357   }
   359   for (int32_t l = list.Length() - 1; l >= 0; l--) {
   360     list[l]->SetPopupPosition(nullptr, true, false);
   361   }
   362 }
   364 void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell)
   365 {
   366   if (aPresShell->GetDocument()) {
   367     AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
   368   }
   369 }
   371 static
   372 nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
   373 {
   374   nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
   375   if (!menuPopupFrame)
   376     return nullptr;
   378   // no point moving or resizing hidden popups
   379   if (menuPopupFrame->PopupState() != ePopupOpenAndVisible)
   380     return nullptr;
   382   nsIWidget* widget = menuPopupFrame->GetWidget();
   383   if (widget && !widget->IsVisible())
   384     return nullptr;
   386   return menuPopupFrame;
   387 }
   389 void
   390 nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
   391 {
   392   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
   393   if (!menuPopupFrame)
   394     return;
   396   nsView* view = menuPopupFrame->GetView();
   397   if (!view)
   398     return;
   400   // Don't do anything if the popup is already at the specified location. This
   401   // prevents recursive calls when a popup is positioned.
   402   nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
   403   nsIWidget* widget = menuPopupFrame->GetWidget();
   404   if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
   405       (!widget || widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) {
   406     return;
   407   }
   409   // Update the popup's position using SetPopupPosition if the popup is
   410   // anchored and at the parent level as these maintain their position
   411   // relative to the parent window. Otherwise, just update the popup to
   412   // the specified screen coordinates.
   413   if (menuPopupFrame->IsAnchored() &&
   414       menuPopupFrame->PopupLevel() == ePopupLevelParent) {
   415     menuPopupFrame->SetPopupPosition(nullptr, true, false);
   416   }
   417   else {
   418     nsPresContext* presContext = menuPopupFrame->PresContext();
   419     aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x);
   420     aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y);
   421     menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false);
   422   }
   423 }
   425 void
   426 nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize)
   427 {
   428   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
   429   if (!menuPopupFrame)
   430     return;
   432   nsView* view = menuPopupFrame->GetView();
   433   if (!view)
   434     return;
   436   nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
   437   // If the size is what we think it is, we have nothing to do.
   438   if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
   439     return;
   441   // The size is different. Convert the actual size to css pixels and store it
   442   // as 'width' and 'height' attributes on the popup.
   443   nsPresContext* presContext = menuPopupFrame->PresContext();
   445   nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
   446                    presContext->DevPixelsToIntCSSPixels(aSize.height));
   448   nsIContent* popup = menuPopupFrame->GetContent();
   449   nsAutoString width, height;
   450   width.AppendInt(newCSS.width);
   451   height.AppendInt(newCSS.height);
   452   popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
   453   popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
   454 }
   456 nsMenuPopupFrame*
   457 nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
   458 {
   459   if (aShouldFlush) {
   460     nsIDocument *document = aContent->GetCurrentDoc();
   461     if (document) {
   462       nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   463       if (presShell)
   464         presShell->FlushPendingNotifications(Flush_Layout);
   465     }
   466   }
   468   return do_QueryFrame(aContent->GetPrimaryFrame());
   469 }
   471 nsMenuChainItem*
   472 nsXULPopupManager::GetTopVisibleMenu()
   473 {
   474   nsMenuChainItem* item = mPopups;
   475   while (item && item->Frame()->PopupState() == ePopupInvisible)
   476     item = item->GetParent();
   477   return item;
   478 }
   480 void
   481 nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset)
   482 {
   483   *aNode = mRangeParent;
   484   NS_IF_ADDREF(*aNode);
   485   *aOffset = mRangeOffset;
   486 }
   488 void
   489 nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
   490                                     nsIContent** aTriggerContent)
   491 {
   492   mCachedMousePoint = nsIntPoint(0, 0);
   494   if (aTriggerContent) {
   495     *aTriggerContent = nullptr;
   496     if (aEvent) {
   497       // get the trigger content from the event
   498       nsCOMPtr<nsIContent> target = do_QueryInterface(
   499         aEvent->InternalDOMEvent()->GetTarget());
   500       target.forget(aTriggerContent);
   501     }
   502   }
   504   mCachedModifiers = 0;
   506   nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aEvent);
   507   if (uiEvent) {
   508     uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
   509     uiEvent->GetRangeOffset(&mRangeOffset);
   511     // get the event coordinates relative to the root frame of the document
   512     // containing the popup.
   513     NS_ASSERTION(aPopup, "Expected a popup node");
   514     WidgetEvent* event = aEvent->GetInternalNSEvent();
   515     if (event) {
   516       WidgetInputEvent* inputEvent = event->AsInputEvent();
   517       if (inputEvent) {
   518         mCachedModifiers = inputEvent->modifiers;
   519       }
   520       nsIDocument* doc = aPopup->GetCurrentDoc();
   521       if (doc) {
   522         nsIPresShell* presShell = doc->GetShell();
   523         nsPresContext* presContext;
   524         if (presShell && (presContext = presShell->GetPresContext())) {
   525           nsPresContext* rootDocPresContext =
   526             presContext->GetRootPresContext();
   527           if (!rootDocPresContext)
   528             return;
   529           nsIFrame* rootDocumentRootFrame = rootDocPresContext->
   530               PresShell()->FrameManager()->GetRootFrame();
   531           if ((event->eventStructType == NS_MOUSE_EVENT || 
   532                event->eventStructType == NS_MOUSE_SCROLL_EVENT ||
   533                event->eventStructType == NS_WHEEL_EVENT) &&
   534                !event->AsGUIEvent()->widget) {
   535             // no widget, so just use the client point if available
   536             nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
   537             nsIntPoint clientPt;
   538             mouseEvent->GetClientX(&clientPt.x);
   539             mouseEvent->GetClientY(&clientPt.y);
   541             // XXX this doesn't handle IFRAMEs in transforms
   542             nsPoint thisDocToRootDocOffset = presShell->FrameManager()->
   543               GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
   544             // convert to device pixels
   545             mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
   546                 nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x);
   547             mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
   548                 nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y);
   549           }
   550           else if (rootDocumentRootFrame) {
   551             nsPoint pnt =
   552               nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
   553             mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
   554                                            rootDocPresContext->AppUnitsToDevPixels(pnt.y));
   555           }
   556         }
   557       }
   558     }
   559   }
   560   else {
   561     mRangeParent = nullptr;
   562     mRangeOffset = 0;
   563   }
   564 }
   566 void
   567 nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
   568 {
   569   if (aActivate)
   570     mActiveMenuBar = aMenuBar;
   571   else if (mActiveMenuBar == aMenuBar)
   572     mActiveMenuBar = nullptr;
   574   UpdateKeyboardListeners();
   575 }
   577 void
   578 nsXULPopupManager::ShowMenu(nsIContent *aMenu,
   579                             bool aSelectFirstItem,
   580                             bool aAsynchronous)
   581 {
   582   // generate any template content first. Otherwise, the menupopup may not
   583   // have been created yet.
   584   if (aMenu) {
   585     nsIContent* element = aMenu;
   586     do {
   587       nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element);
   588       if (xulelem) {
   589         nsCOMPtr<nsIXULTemplateBuilder> builder;
   590         xulelem->GetBuilder(getter_AddRefs(builder));
   591         if (builder) {
   592           builder->CreateContents(aMenu, true);
   593           break;
   594         }
   595       }
   596       element = element->GetParent();
   597     } while (element);
   598   }
   600   nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
   601   if (!menuFrame || !menuFrame->IsMenu())
   602     return;
   604   nsMenuPopupFrame* popupFrame =  menuFrame->GetPopup();
   605   if (!popupFrame || !MayShowPopup(popupFrame))
   606     return;
   608   // inherit whether or not we're a context menu from the parent
   609   bool parentIsContextMenu = false;
   610   bool onMenuBar = false;
   611   bool onmenu = menuFrame->IsOnMenu();
   613   nsMenuParent* parent = menuFrame->GetMenuParent();
   614   if (parent && onmenu) {
   615     parentIsContextMenu = parent->IsContextMenu();
   616     onMenuBar = parent->IsMenuBar();
   617   }
   619   nsAutoString position;
   620   if (onMenuBar || !onmenu)
   621     position.AssignLiteral("after_start");
   622   else
   623     position.AssignLiteral("end_before");
   625   // there is no trigger event for menus
   626   InitTriggerEvent(nullptr, nullptr, nullptr);
   627   popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true);
   629   if (aAsynchronous) {
   630     nsCOMPtr<nsIRunnable> event =
   631       new nsXULPopupShowingEvent(popupFrame->GetContent(),
   632                                  parentIsContextMenu, aSelectFirstItem);
   633     NS_DispatchToCurrentThread(event);
   634   }
   635   else {
   636     nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
   637     FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem);
   638   }
   639 }
   641 void
   642 nsXULPopupManager::ShowPopup(nsIContent* aPopup,
   643                              nsIContent* aAnchorContent,
   644                              const nsAString& aPosition,
   645                              int32_t aXPos, int32_t aYPos,
   646                              bool aIsContextMenu,
   647                              bool aAttributesOverride,
   648                              bool aSelectFirstItem,
   649                              nsIDOMEvent* aTriggerEvent)
   650 {
   651   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
   652   if (!popupFrame || !MayShowPopup(popupFrame))
   653     return;
   655   nsCOMPtr<nsIContent> triggerContent;
   656   InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
   658   popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
   659                               aXPos, aYPos, aAttributesOverride);
   661   FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem);
   662 }
   664 void
   665 nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
   666                                      int32_t aXPos, int32_t aYPos,
   667                                      bool aIsContextMenu,
   668                                      nsIDOMEvent* aTriggerEvent)
   669 {
   670   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
   671   if (!popupFrame || !MayShowPopup(popupFrame))
   672     return;
   674   nsCOMPtr<nsIContent> triggerContent;
   675   InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
   677   popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
   678   FirePopupShowingEvent(aPopup, aIsContextMenu, false);
   679 }
   681 void
   682 nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
   683                                        nsIContent* aTriggerContent,
   684                                        int32_t aXPos, int32_t aYPos)
   685 {
   686   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
   687   if (!popupFrame || !MayShowPopup(popupFrame))
   688     return;
   690   InitTriggerEvent(nullptr, nullptr, nullptr);
   692   nsPresContext* pc = popupFrame->PresContext();
   693   mCachedMousePoint = nsIntPoint(pc->CSSPixelsToDevPixels(aXPos),
   694                                  pc->CSSPixelsToDevPixels(aYPos));
   696   // coordinates are relative to the root widget
   697   nsPresContext* rootPresContext = pc->GetRootPresContext();
   698   if (rootPresContext) {
   699     nsIWidget *rootWidget = rootPresContext->GetRootWidget();
   700     if (rootWidget) {
   701       mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
   702     }
   703   }
   705   popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
   707   FirePopupShowingEvent(aPopup, false, false);
   708 }
   710 void
   711 nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
   712                                             nsIContent* aAnchorContent,
   713                                             nsAString& aAnchor,
   714                                             nsAString& aAlign,
   715                                             int32_t aXPos, int32_t aYPos,
   716                                             bool aIsContextMenu)
   717 {
   718   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
   719   if (!popupFrame || !MayShowPopup(popupFrame))
   720     return;
   722   InitTriggerEvent(nullptr, nullptr, nullptr);
   724   popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
   725                                              aAlign, aXPos, aYPos);
   726   FirePopupShowingEvent(aPopup, aIsContextMenu, false);
   727 }
   729 static void
   730 CheckCaretDrawingState() {
   732   // There is 1 caret per document, we need to find the focused
   733   // document and erase its caret.
   734   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   735   if (fm) {
   736     nsCOMPtr<nsIDOMWindow> window;
   737     fm->GetFocusedWindow(getter_AddRefs(window));
   738     if (!window)
   739       return;
   741     nsCOMPtr<nsIDOMDocument> domDoc;
   742     nsCOMPtr<nsIDocument> focusedDoc;
   743     window->GetDocument(getter_AddRefs(domDoc));
   744     focusedDoc = do_QueryInterface(domDoc);
   745     if (!focusedDoc)
   746       return;
   748     nsIPresShell* presShell = focusedDoc->GetShell();
   749     if (!presShell)
   750       return;
   752     nsRefPtr<nsCaret> caret = presShell->GetCaret();
   753     if (!caret)
   754       return;
   755     caret->CheckCaretDrawingState();
   756   }
   757 }
   759 void
   760 nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
   761                                      nsMenuPopupFrame* aPopupFrame,
   762                                      bool aIsContextMenu,
   763                                      bool aSelectFirstItem)
   764 {
   765   nsPopupType popupType = aPopupFrame->PopupType();
   766   bool ismenu = (popupType == ePopupTypeMenu);
   768   nsMenuChainItem* item =
   769     new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
   770   if (!item)
   771     return;
   773   // install keyboard event listeners for navigating menus. For panels, the
   774   // escape key may be used to close the panel. However, the ignorekeys
   775   // attribute may be used to disable adding these event listeners for popups
   776   // that want to handle their own keyboard events.
   777   if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys,
   778                            nsGkAtoms::_true, eCaseMatters))
   779     item->SetIgnoreKeys(true);
   781   if (ismenu) {
   782     // if the menu is on a menubar, use the menubar's listener instead
   783     nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
   784     if (menuFrame) {
   785       item->SetOnMenuBar(menuFrame->IsOnMenuBar());
   786     }
   787   }
   789   // use a weak frame as the popup will set an open attribute if it is a menu
   790   nsWeakFrame weakFrame(aPopupFrame);
   791   aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem);
   792   ENSURE_TRUE(weakFrame.IsAlive());
   794   // popups normally hide when an outside click occurs. Panels may use
   795   // the noautohide attribute to disable this behaviour. It is expected
   796   // that the application will hide these popups manually. The tooltip
   797   // listener will handle closing the tooltip also.
   798   if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) {
   799     item->SetParent(mNoHidePanels);
   800     mNoHidePanels = item;
   801   }
   802   else {
   803     nsIContent* oldmenu = nullptr;
   804     if (mPopups)
   805       oldmenu = mPopups->Content();
   806     item->SetParent(mPopups);
   807     mPopups = item;
   808     SetCaptureState(oldmenu);
   809   }
   811   if (aSelectFirstItem) {
   812     nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true);
   813     aPopupFrame->SetCurrentMenuItem(next);
   814   }
   816   if (ismenu)
   817     UpdateMenuItems(aPopup);
   819   // Caret visibility may have been affected, ensure that
   820   // the caret isn't now drawn when it shouldn't be.
   821   CheckCaretDrawingState();
   822 }
   824 void
   825 nsXULPopupManager::HidePopup(nsIContent* aPopup,
   826                              bool aHideChain,
   827                              bool aDeselectMenu,
   828                              bool aAsynchronous,
   829                              bool aIsRollup,
   830                              nsIContent* aLastPopup)
   831 {
   832   // if the popup is on the nohide panels list, remove it but don't close any
   833   // other panels
   834   nsMenuPopupFrame* popupFrame = nullptr;
   835   bool foundPanel = false;
   836   nsMenuChainItem* item = mNoHidePanels;
   837   while (item) {
   838     if (item->Content() == aPopup) {
   839       foundPanel = true;
   840       popupFrame = item->Frame();
   841       break;
   842     }
   843     item = item->GetParent();
   844   }
   846   // when removing a menu, all of the child popups must be closed
   847   nsMenuChainItem* foundMenu = nullptr;
   848   item = mPopups;
   849   while (item) {
   850     if (item->Content() == aPopup) {
   851       foundMenu = item;
   852       break;
   853     }
   854     item = item->GetParent();
   855   }
   857   nsPopupType type = ePopupTypePanel;
   858   bool deselectMenu = false;
   859   nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
   860   if (foundMenu) {
   861     // at this point, foundMenu will be set to the found item in the list. If
   862     // foundMenu is the topmost menu, the one to remove, then there are no other
   863     // popups to hide. If foundMenu is not the topmost menu, then there may be
   864     // open submenus below it. In this case, we need to make sure that those
   865     // submenus are closed up first. To do this, we scan up the menu list to
   866     // find the topmost popup with only menus between it and foundMenu and
   867     // close that menu first. In synchronous mode, the FirePopupHidingEvent
   868     // method will be called which in turn calls HidePopupCallback to close up
   869     // the next popup in the chain. These two methods will be called in
   870     // sequence recursively to close up all the necessary popups. In
   871     // asynchronous mode, a similar process occurs except that the
   872     // FirePopupHidingEvent method is called asynchronously. In either case,
   873     // nextPopup is set to the content node of the next popup to close, and
   874     // lastPopup is set to the last popup in the chain to close, which will be
   875     // aPopup, or null to close up all menus.
   877     nsMenuChainItem* topMenu = foundMenu;
   878     // Use IsMenu to ensure that foundMenu is a menu and scan down the child
   879     // list until a non-menu is found. If foundMenu isn't a menu at all, don't
   880     // scan and just close up this menu.
   881     if (foundMenu->IsMenu()) {
   882       item = topMenu->GetChild();
   883       while (item && item->IsMenu()) {
   884         topMenu = item;
   885         item = item->GetChild();
   886       }
   887     }
   889     deselectMenu = aDeselectMenu;
   890     popupToHide = topMenu->Content();
   891     popupFrame = topMenu->Frame();
   892     type = popupFrame->PopupType();
   894     nsMenuChainItem* parent = topMenu->GetParent();
   896     // close up another popup if there is one, and we are either hiding the
   897     // entire chain or the item to hide isn't the topmost popup.
   898     if (parent && (aHideChain || topMenu != foundMenu))
   899       nextPopup = parent->Content();
   901     lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
   902   }
   903   else if (foundPanel) {
   904     popupToHide = aPopup;
   905   }
   907   if (popupFrame) {
   908     nsPopupState state = popupFrame->PopupState();
   909     // if the popup is already being hidden, don't attempt to hide it again
   910     if (state == ePopupHiding)
   911       return;
   912     // change the popup state to hiding. Don't set the hiding state if the
   913     // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
   914     // run again. In the invisible state, we just want the events to fire.
   915     if (state != ePopupInvisible)
   916       popupFrame->SetPopupState(ePopupHiding);
   918     // for menus, popupToHide is always the frontmost item in the list to hide.
   919     if (aAsynchronous) {
   920       nsCOMPtr<nsIRunnable> event =
   921         new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
   922                                   type, deselectMenu, aIsRollup);
   923         NS_DispatchToCurrentThread(event);
   924     }
   925     else {
   926       FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
   927                            popupFrame->PresContext(), type, deselectMenu, aIsRollup);
   928     }
   929   }
   930 }
   932 // This is used to hide the popup after a transition finishes.
   933 class TransitionEnder : public nsIDOMEventListener
   934 {
   935 public:
   937   nsCOMPtr<nsIContent> mContent;
   938   bool mDeselectMenu;
   940   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   941   NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
   943   TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
   944     : mContent(aContent), mDeselectMenu(aDeselectMenu)
   945   {
   946   }
   948   virtual ~TransitionEnder() { }
   950   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE
   951   {
   952     mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
   954     nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
   956     // Now hide the popup. There could be other properties transitioning, but
   957     // we'll assume they all end at the same time and just hide the popup upon
   958     // the first one ending.
   959     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   960     if (pm && popupFrame) {
   961       pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
   962                             popupFrame->PopupType(), mDeselectMenu);
   963     }
   965     return NS_OK;
   966   }
   967 };
   969 NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
   970 NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
   971 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
   972   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   973   NS_INTERFACE_MAP_ENTRY(nsISupports)
   974 NS_INTERFACE_MAP_END
   976 NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
   978 void
   979 nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
   980                                      nsMenuPopupFrame* aPopupFrame,
   981                                      nsIContent* aNextPopup,
   982                                      nsIContent* aLastPopup,
   983                                      nsPopupType aPopupType,
   984                                      bool aDeselectMenu)
   985 {
   986   if (mCloseTimer && mTimerMenu == aPopupFrame) {
   987     mCloseTimer->Cancel();
   988     mCloseTimer = nullptr;
   989     mTimerMenu = nullptr;
   990   }
   992   // The popup to hide is aPopup. Search the list again to find the item that
   993   // corresponds to the popup to hide aPopup. This is done because it's
   994   // possible someone added another item (attempted to open another popup)
   995   // or removed a popup frame during the event processing so the item isn't at
   996   // the front anymore.
   997   nsMenuChainItem* item = mNoHidePanels;
   998   while (item) {
   999     if (item->Content() == aPopup) {
  1000       item->Detach(&mNoHidePanels);
  1001       break;
  1003     item = item->GetParent();
  1006   if (!item) {
  1007     item = mPopups;
  1008     while (item) {
  1009       if (item->Content() == aPopup) {
  1010         item->Detach(&mPopups);
  1011         SetCaptureState(aPopup);
  1012         break;
  1014       item = item->GetParent();
  1018   delete item;
  1020   nsWeakFrame weakFrame(aPopupFrame);
  1021   aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
  1022   ENSURE_TRUE(weakFrame.IsAlive());
  1024   // send the popuphidden event synchronously. This event has no default
  1025   // behaviour.
  1026   nsEventStatus status = nsEventStatus_eIgnore;
  1027   WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr,
  1028                          WidgetMouseEvent::eReal);
  1029   EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
  1030                             &event, nullptr, &status);
  1031   ENSURE_TRUE(weakFrame.IsAlive());
  1033   // if there are more popups to close, look for the next one
  1034   if (aNextPopup && aPopup != aLastPopup) {
  1035     nsMenuChainItem* foundMenu = nullptr;
  1036     nsMenuChainItem* item = mPopups;
  1037     while (item) {
  1038       if (item->Content() == aNextPopup) {
  1039         foundMenu = item;
  1040         break;
  1042       item = item->GetParent();
  1045     // continue hiding the chain of popups until the last popup aLastPopup
  1046     // is reached, or until a popup of a different type is reached. This
  1047     // last check is needed so that a menulist inside a non-menu panel only
  1048     // closes the menu and not the panel as well.
  1049     if (foundMenu &&
  1050         (aLastPopup || aPopupType == foundMenu->PopupType())) {
  1052       nsCOMPtr<nsIContent> popupToHide = item->Content();
  1053       nsMenuChainItem* parent = item->GetParent();
  1055       nsCOMPtr<nsIContent> nextPopup;
  1056       if (parent && popupToHide != aLastPopup)
  1057         nextPopup = parent->Content();
  1059       nsMenuPopupFrame* popupFrame = item->Frame();
  1060       nsPopupState state = popupFrame->PopupState();
  1061       if (state == ePopupHiding)
  1062         return;
  1063       if (state != ePopupInvisible)
  1064         popupFrame->SetPopupState(ePopupHiding);
  1066       FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
  1067                            popupFrame->PresContext(),
  1068                            foundMenu->PopupType(), aDeselectMenu, false);
  1073 void
  1074 nsXULPopupManager::HidePopup(nsIFrame* aFrame)
  1076   nsMenuPopupFrame* popup = do_QueryFrame(aFrame);
  1077   if (popup)
  1078     HidePopup(aFrame->GetContent(), false, true, false, false);
  1081 void
  1082 nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
  1084   // Don't close up immediately.
  1085   // Kick off a close timer.
  1086   KillMenuTimer();
  1088   int32_t menuDelay =
  1089     LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
  1091   // Kick off the timer.
  1092   mCloseTimer = do_CreateInstance("@mozilla.org/timer;1");
  1093   mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT);
  1095   // the popup will call PopupDestroyed if it is destroyed, which checks if it
  1096   // is set to mTimerMenu, so it should be safe to keep a reference to it
  1097   mTimerMenu = aPopup;
  1100 void
  1101 nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames,
  1102                                     bool aDeselectMenu)
  1104   // Create a weak frame list. This is done in a separate array with the
  1105   // right capacity predetermined, otherwise the array would get resized and
  1106   // move the weak frame pointers around.
  1107   nsTArray<nsWeakFrame> weakPopups(aFrames.Length());
  1108   uint32_t f;
  1109   for (f = 0; f < aFrames.Length(); f++) {
  1110     nsWeakFrame* wframe = weakPopups.AppendElement();
  1111     if (wframe)
  1112       *wframe = aFrames[f];
  1115   for (f = 0; f < weakPopups.Length(); f++) {
  1116     // check to ensure that the frame is still alive before hiding it.
  1117     if (weakPopups[f].IsAlive()) {
  1118       nsMenuPopupFrame* frame =
  1119         static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame());
  1120       frame->HidePopup(true, ePopupInvisible);
  1124   SetCaptureState(nullptr);
  1127 bool
  1128 nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
  1130   nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
  1131   while(docShellItem) {
  1132     if (docShellItem == aExpected)
  1133       return true;
  1135     nsCOMPtr<nsIDocShellTreeItem> parent;
  1136     docShellItem->GetParent(getter_AddRefs(parent));
  1137     docShellItem = parent;
  1140   return false;
  1143 void
  1144 nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
  1146   nsTArray<nsMenuPopupFrame *> popupsToHide;
  1148   // iterate to get the set of popup frames to hide
  1149   nsMenuChainItem* item = mPopups;
  1150   while (item) {
  1151     nsMenuChainItem* parent = item->GetParent();
  1152     if (item->Frame()->PopupState() != ePopupInvisible &&
  1153         IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
  1154       nsMenuPopupFrame* frame = item->Frame();
  1155       item->Detach(&mPopups);
  1156       delete item;
  1157       popupsToHide.AppendElement(frame);
  1159     item = parent;
  1162   // now look for panels to hide
  1163   item = mNoHidePanels;
  1164   while (item) {
  1165     nsMenuChainItem* parent = item->GetParent();
  1166     if (item->Frame()->PopupState() != ePopupInvisible &&
  1167         IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
  1168       nsMenuPopupFrame* frame = item->Frame();
  1169       item->Detach(&mNoHidePanels);
  1170       delete item;
  1171       popupsToHide.AppendElement(frame);
  1173     item = parent;
  1176   HidePopupsInList(popupsToHide, true);
  1179 void
  1180 nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
  1182   CloseMenuMode cmm = CloseMenuMode_Auto;
  1184   static nsIContent::AttrValuesArray strings[] =
  1185     {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
  1187   switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
  1188                                  strings, eCaseMatters)) {
  1189     case 0:
  1190       cmm = CloseMenuMode_None;
  1191       break;
  1192     case 1:
  1193       cmm = CloseMenuMode_Single;
  1194       break;
  1195     default:
  1196       break;
  1199   // When a menuitem is selected to be executed, first hide all the open
  1200   // popups, but don't remove them yet. This is needed when a menu command
  1201   // opens a modal dialog. The views associated with the popups needed to be
  1202   // hidden and the accesibility events fired before the command executes, but
  1203   // the popuphiding/popuphidden events are fired afterwards.
  1204   nsTArray<nsMenuPopupFrame *> popupsToHide;
  1205   nsMenuChainItem* item = GetTopVisibleMenu();
  1206   if (cmm != CloseMenuMode_None) {
  1207     while (item) {
  1208       // if it isn't a <menupopup>, don't close it automatically
  1209       if (!item->IsMenu())
  1210         break;
  1211       nsMenuChainItem* next = item->GetParent();
  1212       popupsToHide.AppendElement(item->Frame());
  1213       if (cmm == CloseMenuMode_Single) // only close one level of menu
  1214         break;
  1215       item = next;
  1218     // Now hide the popups. If the closemenu mode is auto, deselect the menu,
  1219     // otherwise only one popup is closing, so keep the parent menu selected.
  1220     HidePopupsInList(popupsToHide, cmm == CloseMenuMode_Auto);
  1223   aEvent->SetCloseMenuMode(cmm);
  1224   nsCOMPtr<nsIRunnable> event = aEvent;
  1225   NS_DispatchToCurrentThread(event);
  1228 void
  1229 nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
  1230                                          bool aIsContextMenu,
  1231                                          bool aSelectFirstItem)
  1233   nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
  1235   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
  1236   if (!popupFrame)
  1237     return;
  1239   nsPresContext *presContext = popupFrame->PresContext();
  1240   nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
  1241   nsPopupType popupType = popupFrame->PopupType();
  1243   // generate the child frames if they have not already been generated
  1244   if (!popupFrame->HasGeneratedChildren()) {
  1245     popupFrame->SetGeneratedChildren();
  1246     presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
  1249   // get the frame again
  1250   nsIFrame* frame = aPopup->GetPrimaryFrame();
  1251   if (!frame)
  1252     return;
  1254   presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
  1255                               NS_FRAME_HAS_DIRTY_CHILDREN);
  1257   // cache the popup so that document.popupNode can retrieve the trigger node
  1258   // during the popupshowing event. It will be cleared below after the event
  1259   // has fired.
  1260   mOpeningPopup = aPopup;
  1262   nsEventStatus status = nsEventStatus_eIgnore;
  1263   WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr,
  1264                          WidgetMouseEvent::eReal);
  1266   // coordinates are relative to the root widget
  1267   nsPresContext* rootPresContext =
  1268     presShell->GetPresContext()->GetRootPresContext();
  1269   if (rootPresContext) {
  1270     rootPresContext->PresShell()->GetViewManager()->
  1271       GetRootWidget(getter_AddRefs(event.widget));
  1273   else {
  1274     event.widget = nullptr;
  1277   event.refPoint = LayoutDeviceIntPoint::FromUntyped(mCachedMousePoint);
  1278   event.modifiers = mCachedModifiers;
  1279   EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
  1281   mCachedMousePoint = nsIntPoint(0, 0);
  1282   mOpeningPopup = nullptr;
  1284   mCachedModifiers = 0;
  1286   // if a panel, blur whatever has focus so that the panel can take the focus.
  1287   // This is done after the popupshowing event in case that event is cancelled.
  1288   // Using noautofocus="true" will disable this behaviour, which is needed for
  1289   // the autocomplete widget as it manages focus itself.
  1290   if (popupType == ePopupTypePanel &&
  1291       !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
  1292                            nsGkAtoms::_true, eCaseMatters)) {
  1293     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  1294     if (fm) {
  1295       nsIDocument* doc = popup->GetCurrentDoc();
  1297       // Only remove the focus if the currently focused item is ouside the
  1298       // popup. It isn't a big deal if the current focus is in a child popup
  1299       // inside the popup as that shouldn't be visible. This check ensures that
  1300       // a node inside the popup that is focused during a popupshowing event
  1301       // remains focused.
  1302       nsCOMPtr<nsIDOMElement> currentFocusElement;
  1303       fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
  1304       nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
  1305       if (doc && currentFocus &&
  1306           !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
  1307         fm->ClearFocus(doc->GetWindow());
  1312   // clear these as they are no longer valid
  1313   mRangeParent = nullptr;
  1314   mRangeOffset = 0;
  1316   // get the frame again in case it went away
  1317   popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
  1318   if (popupFrame) {
  1319     // if the event was cancelled, don't open the popup, reset its state back
  1320     // to closed and clear its trigger content.
  1321     if (status == nsEventStatus_eConsumeNoDefault) {
  1322       popupFrame->SetPopupState(ePopupClosed);
  1323       popupFrame->ClearTriggerContent();
  1325     else {
  1326       ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem);
  1331 void
  1332 nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
  1333                                         nsIContent* aNextPopup,
  1334                                         nsIContent* aLastPopup,
  1335                                         nsPresContext *aPresContext,
  1336                                         nsPopupType aPopupType,
  1337                                         bool aDeselectMenu,
  1338                                         bool aIsRollup)
  1340   nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
  1342   nsEventStatus status = nsEventStatus_eIgnore;
  1343   WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr,
  1344                          WidgetMouseEvent::eReal);
  1345   EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
  1347   // when a panel is closed, blur whatever has focus inside the popup
  1348   if (aPopupType == ePopupTypePanel &&
  1349       !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
  1350                            nsGkAtoms::_true, eCaseMatters)) {
  1351     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  1352     if (fm) {
  1353       nsIDocument* doc = aPopup->GetCurrentDoc();
  1355       // Remove the focus from the focused node only if it is inside the popup.
  1356       nsCOMPtr<nsIDOMElement> currentFocusElement;
  1357       fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
  1358       nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
  1359       if (doc && currentFocus &&
  1360           nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
  1361         fm->ClearFocus(doc->GetWindow());
  1366   // get frame again in case it went away
  1367   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
  1368   if (popupFrame) {
  1369     // if the event was cancelled, don't hide the popup, and reset its
  1370     // state back to open. Only popups in chrome shells can prevent a popup
  1371     // from hiding.
  1372     if (status == nsEventStatus_eConsumeNoDefault &&
  1373         !popupFrame->IsInContentShell()) {
  1374       popupFrame->SetPopupState(ePopupOpenAndVisible);
  1376     else {
  1377       // If the popup has an animate attribute and it is not set to false, assume
  1378       // that it has a closing transition and wait for it to finish. The transition
  1379       // may still occur either way, but the view will be hidden and you won't be
  1380       // able to see it. If there is a next popup, indicating that mutliple popups
  1381       // are rolling up, don't wait and hide the popup right away since the effect
  1382       // would likely be undesirable. This also does a quick check to see if the
  1383       // popup has a transition defined, and skips the wait if not. Transitions
  1384       // are currently disabled on Linux due to rendering issues on certain
  1385       // configurations.
  1386 #ifndef MOZ_WIDGET_GTK
  1387       if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate) &&
  1388           popupFrame->StyleDisplay()->mTransitionPropertyCount > 0) {
  1389         nsAutoString animate;
  1390         aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate);
  1392         // If animate="false" then don't transition at all. If animate="cancel",
  1393         // only show the transition if cancelling the popup or rolling up.
  1394         // Otherwise, always show the transition.
  1395         if (!animate.EqualsLiteral("false") &&
  1396             (!animate.EqualsLiteral("cancel") || aIsRollup)) {
  1397           nsCOMPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
  1398           aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
  1399                                          ender, false, false);
  1400           return;
  1403 #endif
  1405       HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
  1406                         aPopupType, aDeselectMenu);
  1411 bool
  1412 nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
  1414   // a popup is open if it is in the open list. The assertions ensure that the
  1415   // frame is in the correct state. If the popup is in the hiding or invisible
  1416   // state, it will still be in the open popup list until it is closed.
  1417   nsMenuChainItem* item = mPopups;
  1418   while (item) {
  1419     if (item->Content() == aPopup) {
  1420       NS_ASSERTION(item->Frame()->IsOpen() ||
  1421                    item->Frame()->PopupState() == ePopupHiding ||
  1422                    item->Frame()->PopupState() == ePopupInvisible,
  1423                    "popup in open list not actually open");
  1424       return true;
  1426     item = item->GetParent();
  1429   item = mNoHidePanels;
  1430   while (item) {
  1431     if (item->Content() == aPopup) {
  1432       NS_ASSERTION(item->Frame()->IsOpen() ||
  1433                    item->Frame()->PopupState() == ePopupHiding ||
  1434                    item->Frame()->PopupState() == ePopupInvisible,
  1435                    "popup in open list not actually open");
  1436       return true;
  1438     item = item->GetParent();
  1441   return false;
  1444 bool
  1445 nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
  1447   nsMenuChainItem* item = GetTopVisibleMenu();
  1448   while (item) {
  1449     nsMenuPopupFrame* popup = item->Frame();
  1450     if (popup && popup->IsOpen()) {
  1451       nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
  1452       if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
  1453         return true;
  1456     item = item->GetParent();
  1459   return false;
  1462 nsIFrame*
  1463 nsXULPopupManager::GetTopPopup(nsPopupType aType)
  1465   if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
  1466     return mNoHidePanels->Frame();
  1468   nsMenuChainItem* item = GetTopVisibleMenu();
  1469   while (item) {
  1470     if (item->PopupType() == aType || aType == ePopupTypeAny)
  1471       return item->Frame();
  1472     item = item->GetParent();
  1475   return nullptr;
  1478 void
  1479 nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
  1481   aPopups.Clear();
  1483   // Iterate over both lists of popups
  1484   nsMenuChainItem* item = mPopups;
  1485   for (int32_t list = 0; list < 2; list++) {
  1486     while (item) {
  1487       // Skip panels which are not open and visible as well as popups that
  1488       // are transparent to mouse events.
  1489       if (item->Frame()->PopupState() == ePopupOpenAndVisible &&
  1490           !item->Frame()->IsMouseTransparent()) {
  1491         aPopups.AppendElement(item->Frame());
  1494       item = item->GetParent();
  1497     item = mNoHidePanels;
  1501 already_AddRefed<nsIDOMNode>
  1502 nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
  1504   if (!aDocument)
  1505     return nullptr;
  1507   nsCOMPtr<nsIDOMNode> node;
  1509   // if mOpeningPopup is set, it means that a popupshowing event is being
  1510   // fired. In this case, just use the cached node, as the popup is not yet in
  1511   // the list of open popups.
  1512   if (mOpeningPopup && mOpeningPopup->GetCurrentDoc() == aDocument &&
  1513       aIsTooltip == (mOpeningPopup->Tag() == nsGkAtoms::tooltip)) {
  1514     node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false)));
  1516   else {
  1517     nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
  1518     while (item) {
  1519       // look for a popup of the same type and document.
  1520       if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
  1521           item->Content()->GetCurrentDoc() == aDocument) {
  1522         node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame()));
  1523         if (node)
  1524           break;
  1526       item = item->GetParent();
  1530   return node.forget();
  1533 bool
  1534 nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
  1536   // if a popup's IsOpen method returns true, then the popup must always be in
  1537   // the popup chain scanned in IsPopupOpen.
  1538   NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
  1539                "popup frame state doesn't match XULPopupManager open state");
  1541   nsPopupState state = aPopup->PopupState();
  1543   // if the popup is not in the open popup chain, then it must have a state that
  1544   // is either closed, in the process of being shown, or invisible.
  1545   NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
  1546                state == ePopupShowing || state == ePopupInvisible,
  1547                "popup not in XULPopupManager open list is open");
  1549   // don't show popups unless they are closed or invisible
  1550   if (state != ePopupClosed && state != ePopupInvisible)
  1551     return false;
  1553   // Don't show popups that we already have in our popup chain
  1554   if (IsPopupOpen(aPopup->GetContent())) {
  1555     NS_WARNING("Refusing to show duplicate popup");
  1556     return false;
  1559   // if the popup was just rolled up, don't reopen it
  1560   nsCOMPtr<nsIWidget> widget = aPopup->GetWidget();
  1561   if (widget && widget->GetLastRollup() == aPopup->GetContent())
  1562       return false;
  1564   nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
  1565   nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
  1566   if (!baseWin)
  1567     return false;
  1569   // chrome shells can always open popups, but other types of shells can only
  1570   // open popups when they are focused and visible
  1571   if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
  1572     // only allow popups in active windows
  1573     nsCOMPtr<nsIDocShellTreeItem> root;
  1574     dsti->GetRootTreeItem(getter_AddRefs(root));
  1575     nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(root);
  1577     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
  1578     if (!fm || !rootWin)
  1579       return false;
  1581     nsCOMPtr<nsIDOMWindow> activeWindow;
  1582     fm->GetActiveWindow(getter_AddRefs(activeWindow));
  1583     if (activeWindow != rootWin)
  1584       return false;
  1586     // only allow popups in visible frames
  1587     bool visible;
  1588     baseWin->GetVisibility(&visible);
  1589     if (!visible)
  1590       return false;
  1593   // platforms respond differently when an popup is opened in a minimized
  1594   // window, so this is always disabled.
  1595   nsCOMPtr<nsIWidget> mainWidget;
  1596   baseWin->GetMainWidget(getter_AddRefs(mainWidget));
  1597   if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
  1598     return false;
  1601   // cannot open a popup that is a submenu of a menupopup that isn't open.
  1602   nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
  1603   if (menuFrame) {
  1604     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
  1605     if (parentPopup && !parentPopup->IsOpen())
  1606       return false;
  1609   return true;
  1612 void
  1613 nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
  1615   // when a popup frame is destroyed, just unhook it from the list of popups
  1616   if (mTimerMenu == aPopup) {
  1617     if (mCloseTimer) {
  1618       mCloseTimer->Cancel();
  1619       mCloseTimer = nullptr;
  1621     mTimerMenu = nullptr;
  1624   nsMenuChainItem* item = mNoHidePanels;
  1625   while (item) {
  1626     if (item->Frame() == aPopup) {
  1627       item->Detach(&mNoHidePanels);
  1628       delete item;
  1629       break;
  1631     item = item->GetParent();
  1634   nsTArray<nsMenuPopupFrame *> popupsToHide;
  1636   item = mPopups;
  1637   while (item) {
  1638     nsMenuPopupFrame* frame = item->Frame();
  1639     if (frame == aPopup) {
  1640       if (frame->PopupState() != ePopupInvisible) {
  1641         // Iterate through any child menus and hide them as well, since the
  1642         // parent is going away. We won't remove them from the list yet, just
  1643         // hide them, as they will be removed from the list when this function
  1644         // gets called for that child frame.
  1645         nsMenuChainItem* child = item->GetChild();
  1646         while (child) {
  1647           // if the popup is a child frame of the menu that was destroyed, add
  1648           // it to the list of popups to hide. Don't bother with the events
  1649           // since the frames are going away. If the child menu is not a child
  1650           // frame, for example, a context menu, use HidePopup instead, but call
  1651           // it asynchronously since we are in the middle of frame destruction.
  1652           nsMenuPopupFrame* childframe = child->Frame();
  1653           if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
  1654             popupsToHide.AppendElement(childframe);
  1656           else {
  1657             // HidePopup will take care of hiding any of its children, so
  1658             // break out afterwards
  1659             HidePopup(child->Content(), false, false, true, false);
  1660             break;
  1663           child = child->GetChild();
  1667       item->Detach(&mPopups);
  1668       delete item;
  1669       break;
  1672     item = item->GetParent();
  1675   HidePopupsInList(popupsToHide, false);
  1678 bool
  1679 nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
  1681   nsMenuChainItem* item = GetTopVisibleMenu();
  1682   while (item && item->Frame() != aPopup) {
  1683     if (item->IsContextMenu())
  1684       return true;
  1685     item = item->GetParent();
  1688   return false;
  1691 void
  1692 nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
  1694   nsMenuChainItem* item = GetTopVisibleMenu();
  1695   if (item && aOldPopup == item->Content())
  1696     return;
  1698   if (mWidget) {
  1699     mWidget->CaptureRollupEvents(nullptr, false);
  1700     mWidget = nullptr;
  1703   if (item) {
  1704     nsMenuPopupFrame* popup = item->Frame();
  1705     mWidget = popup->GetWidget();
  1706     if (mWidget) {
  1707       mWidget->CaptureRollupEvents(nullptr, true);
  1708       popup->AttachedDismissalListener();
  1712   UpdateKeyboardListeners();
  1715 void
  1716 nsXULPopupManager::UpdateKeyboardListeners()
  1718   nsCOMPtr<EventTarget> newTarget;
  1719   bool isForMenu = false;
  1720   nsMenuChainItem* item = GetTopVisibleMenu();
  1721   if (item) {
  1722     if (!item->IgnoreKeys())
  1723       newTarget = item->Content()->GetDocument();
  1724     isForMenu = item->PopupType() == ePopupTypeMenu;
  1726   else if (mActiveMenuBar) {
  1727     newTarget = mActiveMenuBar->GetContent()->GetDocument();
  1728     isForMenu = true;
  1731   if (mKeyListener != newTarget) {
  1732     if (mKeyListener) {
  1733       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
  1734       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
  1735       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
  1736       mKeyListener = nullptr;
  1737       nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
  1740     if (newTarget) {
  1741       newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
  1742       newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
  1743       newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
  1744       nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
  1745       mKeyListener = newTarget;
  1750 void
  1751 nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
  1753   // Walk all of the menu's children, checking to see if any of them has a
  1754   // command attribute. If so, then several attributes must potentially be updated.
  1756   nsCOMPtr<nsIDocument> document = aPopup->GetCurrentDoc();
  1757   if (!document) {
  1758     return;
  1761   for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
  1762        grandChild;
  1763        grandChild = grandChild->GetNextSibling()) {
  1764     if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) {
  1765       // See if we have a command attribute.
  1766       nsAutoString command;
  1767       grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
  1768       if (!command.IsEmpty()) {
  1769         // We do! Look it up in our document
  1770         nsRefPtr<dom::Element> commandElement =
  1771           document->GetElementById(command);
  1772         if (commandElement) {
  1773           nsAutoString commandValue;
  1774           // The menu's disabled state needs to be updated to match the command.
  1775           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
  1776             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true);
  1777           else
  1778             grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
  1780           // The menu's label, accesskey checked and hidden states need to be updated
  1781           // to match the command. Note that unlike the disabled state if the
  1782           // command has *no* value, we assume the menu is supplying its own.
  1783           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
  1784             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true);
  1786           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
  1787             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true);
  1789           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
  1790             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true);
  1792           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue))
  1793             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true);
  1800 // Notify
  1801 //
  1802 // The item selection timer has fired, we might have to readjust the 
  1803 // selected item. There are two cases here that we are trying to deal with:
  1804 //   (1) diagonal movement from a parent menu to a submenu passing briefly over
  1805 //       other items, and
  1806 //   (2) moving out from a submenu to a parent or grandparent menu.
  1807 // In both cases, |mTimerMenu| is the menu item that might have an open submenu and
  1808 // the first item in |mPopups| is the item the mouse is currently over, which could be
  1809 // none of them.
  1810 //
  1811 // case (1):
  1812 //  As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
  1813 //  submenu, it probably passes through one or more sibilings (B). As the mouse passes
  1814 //  through B, it becomes the current menu item and the timer is set and mTimerMenu is 
  1815 //  set to A. Before the timer fires, the mouse leaves the menu containing A and B and
  1816 //  enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
  1817 //  so we have to see if anything in A's children is selected (recall that even disabled
  1818 //  items are selected, the style just doesn't show it). If that is the case, we need to
  1819 //  set the selected item back to A.
  1820 //
  1821 // case (2);
  1822 //  Item A has an open submenu, and in it there is an item (B) which also has an open
  1823 //  submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
  1824 //  submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
  1825 //  the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
  1826 //  the mouse is still within C. The correct behavior is to set the current item to C
  1827 //  and close up the chain parented at A.
  1828 //
  1829 //  This brings up the question of is the logic of case (1) enough? The answer is no,
  1830 //  and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
  1831 //  child, and if it does, set the selected item to A. Because B has a submenu open, it
  1832 //  is selected and as a result, A is set to be the selected item even though the mouse
  1833 //  rests in C -- very wrong. 
  1834 //
  1835 //  The solution is to use the same idea, but instead of only checking one level, 
  1836 //  drill all the way down to the deepest open submenu and check if it has something 
  1837 //  selected. Since the mouse is in a grandparent, it won't, and we know that we can
  1838 //  safely close up A and all its children.
  1839 //
  1840 // The code below melds the two cases together.
  1841 //
  1842 nsresult
  1843 nsXULPopupManager::Notify(nsITimer* aTimer)
  1845   if (aTimer == mCloseTimer)
  1846     KillMenuTimer();
  1848   return NS_OK;
  1851 void
  1852 nsXULPopupManager::KillMenuTimer()
  1854   if (mCloseTimer && mTimerMenu) {
  1855     mCloseTimer->Cancel();
  1856     mCloseTimer = nullptr;
  1858     if (mTimerMenu->IsOpen())
  1859       HidePopup(mTimerMenu->GetContent(), false, false, true, false);
  1862   mTimerMenu = nullptr;
  1865 void
  1866 nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
  1868   if (mCloseTimer && mTimerMenu == aMenuParent) {
  1869     mCloseTimer->Cancel();
  1870     mCloseTimer = nullptr;
  1871     mTimerMenu = nullptr;
  1875 bool
  1876 nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
  1877                                             nsMenuPopupFrame* aFrame)
  1879   nsMenuChainItem* item = GetTopVisibleMenu();
  1880   if (!aFrame && item)
  1881     aFrame = item->Frame();
  1883   if (aFrame) {
  1884     bool action;
  1885     nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
  1886     if (result) {
  1887       aFrame->ChangeMenuItem(result, false);
  1888       if (action) {
  1889         WidgetGUIEvent* evt = aKeyEvent->GetInternalNSEvent()->AsGUIEvent();
  1890         nsMenuFrame* menuToOpen = result->Enter(evt);
  1891         if (menuToOpen) {
  1892           nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
  1893           ShowMenu(content, true, false);
  1896       return true;
  1899     return false;
  1902   if (mActiveMenuBar) {
  1903     nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
  1904     if (result) {
  1905       mActiveMenuBar->SetActive(true);
  1906       result->OpenMenu(true);
  1907       return true;
  1911   return false;
  1915 bool
  1916 nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode)
  1918   // navigate up through the open menus, looking for the topmost one
  1919   // in the same hierarchy
  1920   nsMenuChainItem* item = nullptr;
  1921   nsMenuChainItem* nextitem = GetTopVisibleMenu();
  1923   while (nextitem) {
  1924     item = nextitem;
  1925     nextitem = item->GetParent();
  1927     if (nextitem) {
  1928       // stop if the parent isn't a menu
  1929       if (!nextitem->IsMenu())
  1930         break;
  1932       // check to make sure that the parent is actually the parent menu. It won't
  1933       // be if the parent is in a different frame hierarchy, for example, for a
  1934       // context menu opened on another menu.
  1935       nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
  1936       nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
  1937       if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
  1938         break;
  1943   nsIFrame* itemFrame;
  1944   if (item)
  1945     itemFrame = item->Frame();
  1946   else if (mActiveMenuBar)
  1947     itemFrame = mActiveMenuBar;
  1948   else
  1949     return false;
  1951   nsNavigationDirection theDirection;
  1952   NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END &&
  1953                  aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code");
  1954   theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
  1956   // if a popup is open, first check for navigation within the popup
  1957   if (item && HandleKeyboardNavigationInPopup(item, theDirection))
  1958     return true;
  1960   // no popup handled the key, so check the active menubar, if any
  1961   if (mActiveMenuBar) {
  1962     nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
  1964     if (NS_DIRECTION_IS_INLINE(theDirection)) {
  1965       nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
  1966                               GetNextMenuItem(mActiveMenuBar, currentMenu, false) : 
  1967                               GetPreviousMenuItem(mActiveMenuBar, currentMenu, false);
  1968       mActiveMenuBar->ChangeMenuItem(nextItem, true);
  1969       return true;
  1971     else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
  1972       // Open the menu and select its first item.
  1973       if (currentMenu) {
  1974         nsCOMPtr<nsIContent> content = currentMenu->GetContent();
  1975         ShowMenu(content, true, false);
  1977       return true;
  1981   return false;
  1984 bool
  1985 nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
  1986                                                    nsMenuPopupFrame* aFrame,
  1987                                                    nsNavigationDirection aDir)
  1989   NS_ASSERTION(aFrame, "aFrame is null");
  1990   NS_ASSERTION(!item || item->Frame() == aFrame,
  1991                "aFrame is expected to be equal to item->Frame()");
  1993   nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
  1995   aFrame->ClearIncrementalString();
  1997   // This method only gets called if we're open.
  1998   if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
  1999     // We've been opened, but we haven't had anything selected.
  2000     // We can handle End, but our parent handles Start.
  2001     if (aDir == eNavigationDirection_End) {
  2002       nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true);
  2003       if (nextItem) {
  2004         aFrame->ChangeMenuItem(nextItem, false);
  2005         return true;
  2008     return false;
  2011   bool isContainer = false;
  2012   bool isOpen = false;
  2013   if (currentMenu) {
  2014     isOpen = currentMenu->IsOpen();
  2015     isContainer = currentMenu->IsMenu();
  2016     if (isOpen) {
  2017       // for an open popup, have the child process the event
  2018       nsMenuChainItem* child = item ? item->GetChild() : nullptr;
  2019       if (child && HandleKeyboardNavigationInPopup(child, aDir))
  2020         return true;
  2022     else if (aDir == eNavigationDirection_End &&
  2023              isContainer && !currentMenu->IsDisabled()) {
  2024       // The menu is not yet open. Open it and select the first item.
  2025       nsCOMPtr<nsIContent> content = currentMenu->GetContent();
  2026       ShowMenu(content, true, false);
  2027       return true;
  2031   // For block progression, we can move in either direction
  2032   if (NS_DIRECTION_IS_BLOCK(aDir) ||
  2033       NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
  2034     nsMenuFrame* nextItem;
  2036     if (aDir == eNavigationDirection_Before)
  2037       nextItem = GetPreviousMenuItem(aFrame, currentMenu, true);
  2038     else if (aDir == eNavigationDirection_After)
  2039       nextItem = GetNextMenuItem(aFrame, currentMenu, true);
  2040     else if (aDir == eNavigationDirection_First)
  2041       nextItem = GetNextMenuItem(aFrame, nullptr, true);
  2042     else
  2043       nextItem = GetPreviousMenuItem(aFrame, nullptr, true);
  2045     if (nextItem) {
  2046       aFrame->ChangeMenuItem(nextItem, false);
  2047       return true;
  2050   else if (currentMenu && isContainer && isOpen) {
  2051     if (aDir == eNavigationDirection_Start) {
  2052       // close a submenu when Left is pressed
  2053       nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
  2054       if (popupFrame)
  2055         HidePopup(popupFrame->GetContent(), false, false, false, false);
  2056       return true;
  2060   return false;
  2063 bool
  2064 nsXULPopupManager::HandleKeyboardEventWithKeyCode(
  2065                         nsIDOMKeyEvent* aKeyEvent,
  2066                         nsMenuChainItem* aTopVisibleMenuItem)
  2068   uint32_t keyCode;
  2069   aKeyEvent->GetKeyCode(&keyCode);
  2071   // Escape should close panels, but the other keys should have no effect.
  2072   if (aTopVisibleMenuItem &&
  2073       aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
  2074     if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) {
  2075       HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
  2076       aKeyEvent->StopPropagation();
  2077       aKeyEvent->PreventDefault();
  2079     return true;
  2082   bool consume = (mPopups || mActiveMenuBar);
  2083   switch (keyCode) {
  2084     case nsIDOMKeyEvent::DOM_VK_LEFT:
  2085     case nsIDOMKeyEvent::DOM_VK_RIGHT:
  2086     case nsIDOMKeyEvent::DOM_VK_UP:
  2087     case nsIDOMKeyEvent::DOM_VK_DOWN:
  2088     case nsIDOMKeyEvent::DOM_VK_HOME:
  2089     case nsIDOMKeyEvent::DOM_VK_END:
  2090       HandleKeyboardNavigation(keyCode);
  2091       break;
  2093     case nsIDOMKeyEvent::DOM_VK_ESCAPE:
  2094       // Pressing Escape hides one level of menus only. If no menu is open,
  2095       // check if a menubar is active and inform it that a menu closed. Even
  2096       // though in this latter case, a menu didn't actually close, the effect
  2097       // ends up being the same. Similar for the tab key below.
  2098       if (aTopVisibleMenuItem) {
  2099         HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
  2100       } else if (mActiveMenuBar) {
  2101         mActiveMenuBar->MenuClosed();
  2103       break;
  2105     case nsIDOMKeyEvent::DOM_VK_TAB:
  2106 #ifndef XP_MACOSX
  2107     case nsIDOMKeyEvent::DOM_VK_F10:
  2108 #endif
  2109       // close popups or deactivate menubar when Tab or F10 are pressed
  2110       if (aTopVisibleMenuItem) {
  2111         Rollup(0, nullptr, nullptr);
  2112       } else if (mActiveMenuBar) {
  2113         mActiveMenuBar->MenuClosed();
  2115       break;
  2117     case nsIDOMKeyEvent::DOM_VK_RETURN: {
  2118       // If there is a popup open, check if the current item needs to be opened.
  2119       // Otherwise, tell the active menubar, if any, to activate the menu. The
  2120       // Enter method will return a menu if one needs to be opened as a result.
  2121       nsMenuFrame* menuToOpen = nullptr;
  2122       WidgetGUIEvent* GUIEvent = aKeyEvent->GetInternalNSEvent()->AsGUIEvent();
  2123       if (aTopVisibleMenuItem) {
  2124         menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
  2125       } else if (mActiveMenuBar) {
  2126         menuToOpen = mActiveMenuBar->Enter(GUIEvent);
  2128       if (menuToOpen) {
  2129         nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
  2130         ShowMenu(content, true, false);
  2132       break;
  2135     default:
  2136       return false;
  2139   if (consume) {
  2140     aKeyEvent->StopPropagation();
  2141     aKeyEvent->PreventDefault();
  2143   return true;
  2146 nsMenuFrame*
  2147 nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent,
  2148                                    nsMenuFrame* aStart,
  2149                                    bool aIsPopup)
  2151   nsPresContext* presContext = aParent->PresContext();
  2152   nsIFrame* immediateParent = presContext->PresShell()->
  2153     FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
  2154   if (!immediateParent)
  2155     immediateParent = aParent;
  2157   nsIFrame* currFrame = nullptr;
  2158   if (aStart)
  2159     currFrame = aStart->GetNextSibling();
  2160   else 
  2161     currFrame = immediateParent->GetFirstPrincipalChild();
  2163   while (currFrame) {
  2164     // See if it's a menu item.
  2165     if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
  2166       return do_QueryFrame(currFrame);
  2168     currFrame = currFrame->GetNextSibling();
  2171   currFrame = immediateParent->GetFirstPrincipalChild();
  2173   // Still don't have anything. Try cycling from the beginning.
  2174   while (currFrame && currFrame != aStart) {
  2175     // See if it's a menu item.
  2176     if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
  2177       return do_QueryFrame(currFrame);
  2180     currFrame = currFrame->GetNextSibling();
  2183   // No luck. Just return our start value.
  2184   return aStart;
  2187 nsMenuFrame*
  2188 nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent,
  2189                                        nsMenuFrame* aStart,
  2190                                        bool aIsPopup)
  2192   nsPresContext* presContext = aParent->PresContext();
  2193   nsIFrame* immediateParent = presContext->PresShell()->
  2194     FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
  2195   if (!immediateParent)
  2196     immediateParent = aParent;
  2198   const nsFrameList& frames(immediateParent->PrincipalChildList());
  2200   nsIFrame* currFrame = nullptr;
  2201   if (aStart)
  2202     currFrame = aStart->GetPrevSibling();
  2203   else
  2204     currFrame = frames.LastChild();
  2206   while (currFrame) {
  2207     // See if it's a menu item.
  2208     if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
  2209       return do_QueryFrame(currFrame);
  2211     currFrame = currFrame->GetPrevSibling();
  2214   currFrame = frames.LastChild();
  2216   // Still don't have anything. Try cycling from the end.
  2217   while (currFrame && currFrame != aStart) {
  2218     // See if it's a menu item.
  2219     if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
  2220       return do_QueryFrame(currFrame);
  2223     currFrame = currFrame->GetPrevSibling();
  2226   // No luck. Just return our start value.
  2227   return aStart;
  2230 bool
  2231 nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext,
  2232                                    nsIContent* aContent,
  2233                                    bool aOnPopup)
  2235   int32_t ns = aContent->GetNameSpaceID();
  2236   nsIAtom *tag = aContent->Tag();
  2237   if (ns == kNameSpaceID_XUL) {
  2238     if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem)
  2239       return false;
  2241   else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) {
  2242     return false;
  2245   bool skipNavigatingDisabledMenuItem = true;
  2246   if (aOnPopup) {
  2247     skipNavigatingDisabledMenuItem =
  2248       LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
  2249                           0) != 0;
  2252   return !(skipNavigatingDisabledMenuItem &&
  2253            aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
  2254                                  nsGkAtoms::_true, eCaseMatters));
  2257 nsresult
  2258 nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent)
  2260   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
  2261   NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
  2263   //handlers shouldn't be triggered by non-trusted events.
  2264   bool trustedEvent = false;
  2265   aEvent->GetIsTrusted(&trustedEvent);
  2266   if (!trustedEvent) {
  2267     return NS_OK;
  2270   nsAutoString eventType;
  2271   keyEvent->GetType(eventType);
  2272   if (eventType.EqualsLiteral("keyup")) {
  2273     return KeyUp(keyEvent);
  2275   if (eventType.EqualsLiteral("keydown")) {
  2276     return KeyDown(keyEvent);
  2278   if (eventType.EqualsLiteral("keypress")) {
  2279     return KeyPress(keyEvent);
  2282   NS_ABORT();
  2284   return NS_OK;
  2287 nsresult
  2288 nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent)
  2290   // don't do anything if a menu isn't open or a menubar isn't active
  2291   if (!mActiveMenuBar) {
  2292     nsMenuChainItem* item = GetTopVisibleMenu();
  2293     if (!item || item->PopupType() != ePopupTypeMenu)
  2294       return NS_OK;
  2297   aKeyEvent->StopPropagation();
  2298   aKeyEvent->PreventDefault();
  2300   return NS_OK; // I am consuming event
  2303 nsresult
  2304 nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
  2306   nsMenuChainItem* item = GetTopVisibleMenu();
  2307   if (item && item->Frame()->IsMenuLocked())
  2308     return NS_OK;
  2310   if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
  2311     return NS_OK;
  2314   // don't do anything if a menu isn't open or a menubar isn't active
  2315   if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
  2316     return NS_OK;
  2318   int32_t menuAccessKey = -1;
  2320   // If the key just pressed is the access key (usually Alt),
  2321   // dismiss and unfocus the menu.
  2323   nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
  2324   if (menuAccessKey) {
  2325     uint32_t theChar;
  2326     aKeyEvent->GetKeyCode(&theChar);
  2328     if (theChar == (uint32_t)menuAccessKey) {
  2329       bool ctrl = false;
  2330       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
  2331         aKeyEvent->GetCtrlKey(&ctrl);
  2332       bool alt=false;
  2333       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
  2334         aKeyEvent->GetAltKey(&alt);
  2335       bool shift=false;
  2336       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
  2337         aKeyEvent->GetShiftKey(&shift);
  2338       bool meta=false;
  2339       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META)
  2340         aKeyEvent->GetMetaKey(&meta);
  2341       if (!(ctrl || alt || shift || meta)) {
  2342         // The access key just went down and no other
  2343         // modifiers are already down.
  2344         if (mPopups)
  2345           Rollup(0, nullptr, nullptr);
  2346         else if (mActiveMenuBar)
  2347           mActiveMenuBar->MenuClosed();
  2349       aKeyEvent->PreventDefault();
  2353   // Since a menu was open, stop propagation of the event to keep other event
  2354   // listeners from becoming confused.
  2355   aKeyEvent->StopPropagation();
  2356   return NS_OK;
  2359 nsresult
  2360 nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
  2362   // Don't check prevent default flag -- menus always get first shot at key events.
  2364   nsMenuChainItem* item = GetTopVisibleMenu();
  2365   if (item &&
  2366       (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
  2367     return NS_OK;
  2370   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
  2371   NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
  2372   // if a menu is open or a menubar is active, it consumes the key event
  2373   bool consume = (mPopups || mActiveMenuBar);
  2374   HandleShortcutNavigation(keyEvent, nullptr);
  2375   if (consume) {
  2376     aKeyEvent->StopPropagation();
  2377     aKeyEvent->PreventDefault();
  2380   return NS_OK; // I am consuming event
  2383 NS_IMETHODIMP
  2384 nsXULPopupShowingEvent::Run()
  2386   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  2387   if (pm) {
  2388     pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem);
  2391   return NS_OK;
  2394 NS_IMETHODIMP
  2395 nsXULPopupHidingEvent::Run()
  2397   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  2399   nsIDocument *document = mPopup->GetCurrentDoc();
  2400   if (pm && document) {
  2401     nsIPresShell* presShell = document->GetShell();
  2402     if (presShell) {
  2403       nsPresContext* context = presShell->GetPresContext();
  2404       if (context) {
  2405         pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
  2406                                  context, mPopupType, mDeselectMenu, mIsRollup);
  2411   return NS_OK;
  2414 NS_IMETHODIMP
  2415 nsXULMenuCommandEvent::Run()
  2417   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  2418   if (!pm)
  2419     return NS_OK;
  2421   // The order of the nsViewManager and nsIPresShell COM pointers is
  2422   // important below.  We want the pres shell to get released before the
  2423   // associated view manager on exit from this function.
  2424   // See bug 54233.
  2425   // XXXndeakin is this still needed?
  2427   nsCOMPtr<nsIContent> popup;
  2428   nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
  2429   nsWeakFrame weakFrame(menuFrame);
  2430   if (menuFrame && mFlipChecked) {
  2431     if (menuFrame->IsChecked()) {
  2432       mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
  2433     } else {
  2434       mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
  2435                      NS_LITERAL_STRING("true"), true);
  2439   if (menuFrame && weakFrame.IsAlive()) {
  2440     // Find the popup that the menu is inside. Below, this popup will
  2441     // need to be hidden.
  2442     nsIFrame* frame = menuFrame->GetParent();
  2443     while (frame) {
  2444       nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
  2445       if (popupFrame) {
  2446         popup = popupFrame->GetContent();
  2447         break;
  2449       frame = frame->GetParent();
  2452     nsPresContext* presContext = menuFrame->PresContext();
  2453     nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
  2454     nsRefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
  2456     // Deselect ourselves.
  2457     if (mCloseMenuMode != CloseMenuMode_None)
  2458       menuFrame->SelectMenu(false);
  2460     AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
  2461                                                         shell->GetDocument());
  2462     nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
  2463                                        mControl, mAlt, mShift, mMeta);
  2466   if (popup && mCloseMenuMode != CloseMenuMode_None)
  2467     pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false);
  2469   return NS_OK;

mercurial