LCOV - code coverage report
Current view: top level - layout/xul - nsXULPopupManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 86 1365 6.3 %
Date: 2017-07-14 16:53:18 Functions: 17 99 17.2 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       5             : 
       6             : #include "nsGkAtoms.h"
       7             : #include "nsXULPopupManager.h"
       8             : #include "nsMenuFrame.h"
       9             : #include "nsMenuPopupFrame.h"
      10             : #include "nsMenuBarFrame.h"
      11             : #include "nsMenuBarListener.h"
      12             : #include "nsContentUtils.h"
      13             : #include "nsIDOMDocument.h"
      14             : #include "nsIDOMEvent.h"
      15             : #include "nsXULElement.h"
      16             : #include "nsIDOMXULMenuListElement.h"
      17             : #include "nsIXULDocument.h"
      18             : #include "nsIXULTemplateBuilder.h"
      19             : #include "nsCSSFrameConstructor.h"
      20             : #include "nsGlobalWindow.h"
      21             : #include "nsLayoutUtils.h"
      22             : #include "nsViewManager.h"
      23             : #include "nsIComponentManager.h"
      24             : #include "nsITimer.h"
      25             : #include "nsFocusManager.h"
      26             : #include "nsIDocShell.h"
      27             : #include "nsPIDOMWindow.h"
      28             : #include "nsIInterfaceRequestorUtils.h"
      29             : #include "nsIBaseWindow.h"
      30             : #include "nsIDOMKeyEvent.h"
      31             : #include "nsIDOMMouseEvent.h"
      32             : #include "nsCaret.h"
      33             : #include "nsIDocument.h"
      34             : #include "nsPIWindowRoot.h"
      35             : #include "nsFrameManager.h"
      36             : #include "nsIObserverService.h"
      37             : #include "mozilla/dom/Element.h"
      38             : #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
      39             : #include "mozilla/EventDispatcher.h"
      40             : #include "mozilla/EventStateManager.h"
      41             : #include "mozilla/LookAndFeel.h"
      42             : #include "mozilla/MouseEvents.h"
      43             : #include "mozilla/Services.h"
      44             : #include "mozilla/widget/nsAutoRollup.h"
      45             : 
      46             : using namespace mozilla;
      47             : using namespace mozilla::dom;
      48             : 
      49             : static_assert(nsIDOMKeyEvent::DOM_VK_HOME  == nsIDOMKeyEvent::DOM_VK_END + 1 &&
      50             :               nsIDOMKeyEvent::DOM_VK_LEFT  == nsIDOMKeyEvent::DOM_VK_END + 2 &&
      51             :               nsIDOMKeyEvent::DOM_VK_UP    == nsIDOMKeyEvent::DOM_VK_END + 3 &&
      52             :               nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 &&
      53             :               nsIDOMKeyEvent::DOM_VK_DOWN  == nsIDOMKeyEvent::DOM_VK_END + 5,
      54             :               "nsXULPopupManager assumes some keyCode values are consecutive");
      55             : 
      56             : const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
      57             :   {
      58             :     eNavigationDirection_Last,   // nsIDOMKeyEvent::DOM_VK_END
      59             :     eNavigationDirection_First,  // nsIDOMKeyEvent::DOM_VK_HOME
      60             :     eNavigationDirection_Start,  // nsIDOMKeyEvent::DOM_VK_LEFT
      61             :     eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
      62             :     eNavigationDirection_End,    // nsIDOMKeyEvent::DOM_VK_RIGHT
      63             :     eNavigationDirection_After   // nsIDOMKeyEvent::DOM_VK_DOWN
      64             :   },
      65             :   {
      66             :     eNavigationDirection_Last,   // nsIDOMKeyEvent::DOM_VK_END
      67             :     eNavigationDirection_First,  // nsIDOMKeyEvent::DOM_VK_HOME
      68             :     eNavigationDirection_End,    // nsIDOMKeyEvent::DOM_VK_LEFT
      69             :     eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
      70             :     eNavigationDirection_Start,  // nsIDOMKeyEvent::DOM_VK_RIGHT
      71             :     eNavigationDirection_After   // nsIDOMKeyEvent::DOM_VK_DOWN
      72             :   }
      73             : };
      74             : 
      75             : nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
      76             : 
      77           0 : nsIContent* nsMenuChainItem::Content()
      78             : {
      79           0 :   return mFrame->GetContent();
      80             : }
      81             : 
      82           0 : void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
      83             : {
      84           0 :   if (mParent) {
      85           0 :     NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
      86           0 :     mParent->mChild = nullptr;
      87             :   }
      88           0 :   mParent = aParent;
      89           0 :   if (mParent) {
      90           0 :     if (mParent->mChild)
      91           0 :       mParent->mChild->mParent = nullptr;
      92           0 :     mParent->mChild = this;
      93             :   }
      94           0 : }
      95             : 
      96           0 : void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
      97             : {
      98             :   // If the item has a child, set the child's parent to this item's parent,
      99             :   // effectively removing the item from the chain. If the item has no child,
     100             :   // just set the parent to null.
     101           0 :   if (mChild) {
     102           0 :     NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
     103           0 :     mChild->SetParent(mParent);
     104             :   }
     105             :   else {
     106             :     // An item without a child should be the first item in the chain, so set
     107             :     // the first item pointer, pointed to by aRoot, to the parent.
     108           0 :     NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
     109           0 :     *aRoot = mParent;
     110           0 :     SetParent(nullptr);
     111             :   }
     112           0 : }
     113             : 
     114             : void
     115           0 : nsMenuChainItem::UpdateFollowAnchor()
     116             : {
     117           0 :   mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
     118           0 : }
     119             : 
     120             : void
     121           0 : nsMenuChainItem::CheckForAnchorChange()
     122             : {
     123           0 :   if (mFollowAnchor) {
     124           0 :     mFrame->CheckForAnchorChange(mCurrentRect);
     125             :   }
     126           0 : }
     127             : 
     128             : bool nsXULPopupManager::sDevtoolsDisableAutoHide = false;
     129             : 
     130             : const char* kPrefDevtoolsDisableAutoHide =
     131             :   "ui.popup.disable_autohide";
     132             : 
     133           6 : NS_IMPL_ISUPPORTS(nsXULPopupManager,
     134             :                   nsIDOMEventListener,
     135             :                   nsIObserver)
     136             : 
     137           3 : nsXULPopupManager::nsXULPopupManager() :
     138             :   mRangeOffset(0),
     139             :   mCachedMousePoint(0, 0),
     140             :   mCachedModifiers(0),
     141             :   mActiveMenuBar(nullptr),
     142             :   mPopups(nullptr),
     143           3 :   mTimerMenu(nullptr)
     144             : {
     145           6 :   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     146           3 :   if (obs) {
     147           3 :     obs->AddObserver(this, "xpcom-shutdown", false);
     148             :   }
     149             :   Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide,
     150           3 :                                kPrefDevtoolsDisableAutoHide, false);
     151           3 : }
     152             : 
     153           0 : nsXULPopupManager::~nsXULPopupManager()
     154             : {
     155           0 :   NS_ASSERTION(!mPopups, "XUL popups still open");
     156           0 : }
     157             : 
     158             : nsresult
     159           3 : nsXULPopupManager::Init()
     160             : {
     161           3 :   sInstance = new nsXULPopupManager();
     162           3 :   NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
     163           3 :   NS_ADDREF(sInstance);
     164           3 :   return NS_OK;
     165             : }
     166             : 
     167             : void
     168           0 : nsXULPopupManager::Shutdown()
     169             : {
     170           0 :   NS_IF_RELEASE(sInstance);
     171           0 : }
     172             : 
     173             : NS_IMETHODIMP
     174           0 : nsXULPopupManager::Observe(nsISupports *aSubject,
     175             :                            const char *aTopic,
     176             :                            const char16_t *aData)
     177             : {
     178           0 :   if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
     179           0 :     if (mKeyListener) {
     180           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
     181           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
     182           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
     183           0 :       mKeyListener = nullptr;
     184             :     }
     185           0 :     mRangeParent = nullptr;
     186             :     // mOpeningPopup is cleared explicitly soon after using it.
     187           0 :     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     188           0 :     if (obs) {
     189           0 :       obs->RemoveObserver(this, "xpcom-shutdown");
     190             :     }
     191             :   }
     192             : 
     193           0 :   return NS_OK;
     194             : }
     195             : 
     196             : nsXULPopupManager*
     197          85 : nsXULPopupManager::GetInstance()
     198             : {
     199          85 :   MOZ_ASSERT(sInstance);
     200          85 :   return sInstance;
     201             : }
     202             : 
     203             : bool
     204           0 : nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
     205             :                           const nsIntPoint* pos, nsIContent** aLastRolledUp)
     206             : {
     207           0 :   if (aLastRolledUp) {
     208           0 :     *aLastRolledUp = nullptr;
     209             :   }
     210             : 
     211             :   // We can disable the autohide behavior via a pref to ease debugging.
     212           0 :   if (nsXULPopupManager::sDevtoolsDisableAutoHide) {
     213             :     // Required on linux to allow events to work on other targets.
     214           0 :     if (mWidget) {
     215           0 :       mWidget->CaptureRollupEvents(nullptr, false);
     216             :     }
     217           0 :     return false;
     218             :   }
     219             : 
     220           0 :   bool consume = false;
     221             : 
     222           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
     223           0 :   if (item) {
     224           0 :     if (aLastRolledUp) {
     225             :       // We need to get the popup that will be closed last, so that widget can
     226             :       // keep track of it so it doesn't reopen if a mousedown event is going to
     227             :       // processed. Keep going up the menu chain to get the first level menu of
     228             :       // the same type. If a different type is encountered it means we have,
     229             :       // for example, a menulist or context menu inside a panel, and we want to
     230             :       // treat these as distinct. It's possible that this menu doesn't end up
     231             :       // closing because the popuphiding event was cancelled, but in that case
     232             :       // we don't need to deal with the menu reopening as it will already still
     233             :       // be open.
     234           0 :       nsMenuChainItem* first = item;
     235           0 :       while (first->GetParent()) {
     236           0 :         nsMenuChainItem* parent = first->GetParent();
     237           0 :         if (first->Frame()->PopupType() != parent->Frame()->PopupType() ||
     238           0 :             first->IsContextMenu() != parent->IsContextMenu()) {
     239           0 :           break;
     240             :         }
     241           0 :         first = parent;
     242             :       }
     243             : 
     244             : 
     245           0 :       *aLastRolledUp = first->Content();
     246             :     }
     247             : 
     248           0 :     ConsumeOutsideClicksResult consumeResult = item->Frame()->ConsumeOutsideClicks();
     249           0 :     consume = (consumeResult == ConsumeOutsideClicks_True);
     250             : 
     251           0 :     bool rollup = true;
     252             : 
     253             :     // If norolluponanchor is true, then don't rollup when clicking the anchor.
     254             :     // This would be used to allow adjusting the caret position in an
     255             :     // autocomplete field without hiding the popup for example.
     256           0 :     bool noRollupOnAnchor = (!consume && pos &&
     257           0 :       item->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None,
     258           0 :         nsGkAtoms::norolluponanchor, nsGkAtoms::_true, eCaseMatters));
     259             : 
     260             :     // When ConsumeOutsideClicks_ParentOnly is used, always consume the click
     261             :     // when the click was over the anchor. This way, clicking on a menu doesn't
     262             :     // reopen the menu.
     263           0 :     if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && pos) {
     264           0 :       nsMenuPopupFrame* popupFrame = item->Frame();
     265           0 :       CSSIntRect anchorRect;
     266           0 :       if (popupFrame->IsAnchored()) {
     267             :         // Check if the popup has a screen anchor rectangle. If not, get the rectangle
     268             :         // from the anchor element.
     269           0 :         anchorRect = CSSIntRect::FromUnknownRect(popupFrame->GetScreenAnchorRect());
     270           0 :         if (anchorRect.x == -1 || anchorRect.y == -1) {
     271           0 :           nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor();
     272             : 
     273             :           // Check if the anchor has indicated another node to use for checking
     274             :           // for roll-up. That way, we can anchor a popup on anonymous content or
     275             :           // an individual icon, while clicking elsewhere within a button or other
     276             :           // container doesn't result in us re-opening the popup.
     277           0 :           if (anchor) {
     278           0 :             nsAutoString consumeAnchor;
     279           0 :             anchor->GetAttr(kNameSpaceID_None, nsGkAtoms::consumeanchor,
     280           0 :                             consumeAnchor);
     281           0 :             if (!consumeAnchor.IsEmpty()) {
     282           0 :               nsIDocument* doc = anchor->GetOwnerDocument();
     283           0 :               nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
     284           0 :               if (newAnchor) {
     285           0 :                 anchor = newAnchor;
     286             :               }
     287             :             }
     288             :           }
     289             : 
     290           0 :           if (anchor && anchor->GetPrimaryFrame()) {
     291           0 :             anchorRect = anchor->GetPrimaryFrame()->GetScreenRect();
     292             :           }
     293             :         }
     294             :       }
     295             : 
     296             :       // It's possible that some other element is above the anchor at the same
     297             :       // position, but the only thing that would happen is that the mouse
     298             :       // event will get consumed, so here only a quick coordinates check is
     299             :       // done rather than a slower complete check of what is at that location.
     300           0 :       nsPresContext* presContext = item->Frame()->PresContext();
     301           0 :       CSSIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x),
     302           0 :                                presContext->DevPixelsToIntCSSPixels(pos->y));
     303           0 :       if (anchorRect.Contains(posCSSPixels)) {
     304           0 :         if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
     305           0 :           consume = true;
     306             :         }
     307             : 
     308           0 :         if (noRollupOnAnchor) {
     309           0 :           rollup = false;
     310             :         }
     311             :       }
     312             :     }
     313             : 
     314           0 :     if (rollup) {
     315             :       // if a number of popups to close has been specified, determine the last
     316             :       // popup to close
     317           0 :       nsIContent* lastPopup = nullptr;
     318           0 :       if (aCount != UINT32_MAX) {
     319           0 :         nsMenuChainItem* last = item;
     320           0 :         while (--aCount && last->GetParent()) {
     321           0 :           last = last->GetParent();
     322             :         }
     323           0 :         if (last) {
     324           0 :           lastPopup = last->Content();
     325             :         }
     326             :       }
     327             : 
     328           0 :       nsPresContext* presContext = item->Frame()->PresContext();
     329           0 :       RefPtr<nsViewManager> viewManager = presContext->PresShell()->GetViewManager();
     330             : 
     331           0 :       HidePopup(item->Content(), true, true, false, true, lastPopup);
     332             : 
     333           0 :       if (aFlush) {
     334             :         // The popup's visibility doesn't update until the minimize animation has
     335             :         // finished, so call UpdateWidgetGeometry to update it right away.
     336           0 :         viewManager->UpdateWidgetGeometry();
     337             :       }
     338             :     }
     339             :   }
     340             : 
     341           0 :   return consume;
     342             : }
     343             : 
     344             : ////////////////////////////////////////////////////////////////////////
     345           0 : bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
     346             : {
     347             :   // should rollup only for autocomplete widgets
     348             :   // XXXndeakin this should really be something the popup has more control over
     349             : 
     350           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
     351           0 :   if (!item)
     352           0 :     return false;
     353             : 
     354           0 :   nsIContent* content = item->Frame()->GetContent();
     355           0 :   if (!content)
     356           0 :     return false;
     357             : 
     358           0 :   if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
     359             :                            nsGkAtoms::_true, eCaseMatters))
     360           0 :     return true;
     361             : 
     362           0 :   if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
     363             :                            nsGkAtoms::_false, eCaseMatters))
     364           0 :     return false;
     365             : 
     366           0 :   nsAutoString value;
     367           0 :   content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
     368           0 :   return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
     369             : }
     370             : 
     371           0 : bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
     372             : {
     373           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
     374           0 :   if (!item)
     375           0 :     return false;
     376             : 
     377           0 :   nsMenuPopupFrame* frame = item->Frame();
     378           0 :   if (frame->PopupType() != ePopupTypePanel)
     379           0 :     return true;
     380             : 
     381           0 :   nsIContent* content = frame->GetContent();
     382           0 :   return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
     383           0 :                                            nsGkAtoms::arrow, eCaseMatters));
     384             : }
     385             : 
     386             : // a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
     387           0 : bool nsXULPopupManager::ShouldRollupOnMouseActivate()
     388             : {
     389           0 :   return false;
     390             : }
     391             : 
     392             : uint32_t
     393           0 : nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain)
     394             : {
     395             :   // this method is used by the widget code to determine the list of popups
     396             :   // that are open. If a mouse click occurs outside one of these popups, the
     397             :   // panels will roll up. If the click is inside a popup, they will not roll up
     398           0 :   uint32_t count = 0, sameTypeCount = 0;
     399             : 
     400           0 :   NS_ASSERTION(aWidgetChain, "null parameter");
     401           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
     402           0 :   while (item) {
     403           0 :     nsMenuChainItem* parent = item->GetParent();
     404           0 :     if (!item->IsNoAutoHide()) {
     405           0 :       nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
     406           0 :       NS_ASSERTION(widget, "open popup has no widget");
     407           0 :       aWidgetChain->AppendElement(widget.get());
     408             :       // In the case when a menulist inside a panel is open, clicking in the
     409             :       // panel should still roll up the menu, so if a different type is found,
     410             :       // stop scanning.
     411           0 :       if (!sameTypeCount) {
     412           0 :         count++;
     413           0 :         if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
     414           0 :                        item->IsContextMenu() != parent->IsContextMenu()) {
     415           0 :           sameTypeCount = count;
     416             :         }
     417             :       }
     418             :     }
     419             : 
     420           0 :     item = parent;
     421             :   }
     422             : 
     423           0 :   return sameTypeCount;
     424             : }
     425             : 
     426             : nsIWidget*
     427           1 : nsXULPopupManager::GetRollupWidget()
     428             : {
     429           1 :   nsMenuChainItem* item = GetTopVisibleMenu();
     430           1 :   return item ? item->Frame()->GetWidget() : nullptr;
     431             : }
     432             : 
     433             : void
     434           9 : nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow)
     435             : {
     436             :   // When the parent window is moved, adjust any child popups. Dismissable
     437             :   // menus and panels are expected to roll up when a window is moved, so there
     438             :   // is no need to check these popups, only the noautohide popups.
     439             : 
     440             :   // The items are added to a list so that they can be adjusted bottom to top.
     441          18 :   nsTArray<nsMenuPopupFrame *> list;
     442             : 
     443           9 :   nsMenuChainItem* item = mPopups;
     444           9 :   while (item) {
     445             :     // only move popups that are within the same window and where auto
     446             :     // positioning has not been disabled
     447           0 :     nsMenuPopupFrame* frame = item->Frame();
     448           0 :     if (item->IsNoAutoHide() && frame->GetAutoPosition()) {
     449           0 :       nsIContent* popup = frame->GetContent();
     450           0 :       if (popup) {
     451           0 :         nsIDocument* document = popup->GetUncomposedDoc();
     452           0 :         if (document) {
     453           0 :           if (nsPIDOMWindowOuter* window = document->GetWindow()) {
     454           0 :             window = window->GetPrivateRoot();
     455           0 :             if (window == aWindow) {
     456           0 :               list.AppendElement(frame);
     457             :             }
     458             :           }
     459             :         }
     460             :       }
     461             :     }
     462             : 
     463           0 :     item = item->GetParent();
     464             :   }
     465             : 
     466           9 :   for (int32_t l = list.Length() - 1; l >= 0; l--) {
     467           0 :     list[l]->SetPopupPosition(nullptr, true, false, true);
     468             :   }
     469           9 : }
     470             : 
     471           4 : void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell)
     472             : {
     473           4 :   if (aPresShell->GetDocument()) {
     474           4 :     AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
     475             :   }
     476           4 : }
     477             : 
     478             : static
     479           0 : nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
     480             : {
     481           0 :   nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
     482           0 :   if (!menuPopupFrame)
     483           0 :     return nullptr;
     484             : 
     485             :   // no point moving or resizing hidden popups
     486           0 :   if (!menuPopupFrame->IsVisible())
     487           0 :     return nullptr;
     488             : 
     489           0 :   nsIWidget* widget = menuPopupFrame->GetWidget();
     490           0 :   if (widget && !widget->IsVisible())
     491           0 :     return nullptr;
     492             : 
     493           0 :   return menuPopupFrame;
     494             : }
     495             : 
     496             : void
     497           0 : nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
     498             : {
     499           0 :   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
     500           0 :   if (!menuPopupFrame)
     501           0 :     return;
     502             : 
     503           0 :   nsView* view = menuPopupFrame->GetView();
     504           0 :   if (!view)
     505           0 :     return;
     506             : 
     507             :   // Don't do anything if the popup is already at the specified location. This
     508             :   // prevents recursive calls when a popup is positioned.
     509           0 :   LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
     510           0 :   nsIWidget* widget = menuPopupFrame->GetWidget();
     511           0 :   if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
     512           0 :       (!widget || widget->GetClientOffset() ==
     513           0 :                   menuPopupFrame->GetLastClientOffset())) {
     514           0 :     return;
     515             :   }
     516             : 
     517             :   // Update the popup's position using SetPopupPosition if the popup is
     518             :   // anchored and at the parent level as these maintain their position
     519             :   // relative to the parent window. Otherwise, just update the popup to
     520             :   // the specified screen coordinates.
     521           0 :   if (menuPopupFrame->IsAnchored() &&
     522           0 :       menuPopupFrame->PopupLevel() == ePopupLevelParent) {
     523           0 :     menuPopupFrame->SetPopupPosition(nullptr, true, false, true);
     524             :   }
     525             :   else {
     526           0 :     CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt)
     527           0 :                     / menuPopupFrame->PresContext()->CSSToDevPixelScale();
     528           0 :     menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
     529             :   }
     530             : }
     531             : 
     532             : void
     533           0 : nsXULPopupManager::PopupResized(nsIFrame* aFrame, LayoutDeviceIntSize aSize)
     534             : {
     535           0 :   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
     536           0 :   if (!menuPopupFrame)
     537           0 :     return;
     538             : 
     539           0 :   nsView* view = menuPopupFrame->GetView();
     540           0 :   if (!view)
     541           0 :     return;
     542             : 
     543           0 :   LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
     544             :   // If the size is what we think it is, we have nothing to do.
     545           0 :   if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
     546           0 :     return;
     547             : 
     548           0 :   nsIContent* popup = menuPopupFrame->GetContent();
     549             : 
     550             :   // Only set the width and height if the popup already has these attributes.
     551           0 :   if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) ||
     552           0 :       !popup->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
     553           0 :     return;
     554             :   }
     555             : 
     556             :   // The size is different. Convert the actual size to css pixels and store it
     557             :   // as 'width' and 'height' attributes on the popup.
     558           0 :   nsPresContext* presContext = menuPopupFrame->PresContext();
     559             : 
     560             :   CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
     561           0 :                     presContext->DevPixelsToIntCSSPixels(aSize.height));
     562             : 
     563           0 :   nsAutoString width, height;
     564           0 :   width.AppendInt(newCSS.width);
     565           0 :   height.AppendInt(newCSS.height);
     566           0 :   popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
     567           0 :   popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
     568             : }
     569             : 
     570             : nsMenuPopupFrame*
     571           0 : nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
     572             : {
     573           0 :   if (aShouldFlush) {
     574           0 :     nsIDocument *document = aContent->GetUncomposedDoc();
     575           0 :     if (document) {
     576           0 :       nsCOMPtr<nsIPresShell> presShell = document->GetShell();
     577           0 :       if (presShell)
     578           0 :         presShell->FlushPendingNotifications(FlushType::Layout);
     579             :     }
     580             :   }
     581             : 
     582           0 :   return do_QueryFrame(aContent->GetPrimaryFrame());
     583             : }
     584             : 
     585             : nsMenuChainItem*
     586          23 : nsXULPopupManager::GetTopVisibleMenu()
     587             : {
     588          23 :   nsMenuChainItem* item = mPopups;
     589          23 :   while (item) {
     590           0 :     if (!item->IsNoAutoHide() && item->Frame()->PopupState() != ePopupInvisible) {
     591           0 :       return item;
     592             :     }
     593           0 :     item = item->GetParent();
     594             :   }
     595             : 
     596          23 :   return nullptr;
     597             : }
     598             : 
     599             : void
     600           0 : nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset)
     601             : {
     602           0 :   *aNode = mRangeParent;
     603           0 :   NS_IF_ADDREF(*aNode);
     604           0 :   *aOffset = mRangeOffset;
     605           0 : }
     606             : 
     607             : void
     608           0 : nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
     609             :                                     nsIContent** aTriggerContent)
     610             : {
     611           0 :   mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
     612             : 
     613           0 :   if (aTriggerContent) {
     614           0 :     *aTriggerContent = nullptr;
     615           0 :     if (aEvent) {
     616             :       // get the trigger content from the event
     617             :       nsCOMPtr<nsIContent> target = do_QueryInterface(
     618           0 :         aEvent->InternalDOMEvent()->GetTarget());
     619           0 :       target.forget(aTriggerContent);
     620             :     }
     621             :   }
     622             : 
     623           0 :   mCachedModifiers = 0;
     624             : 
     625           0 :   nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aEvent);
     626           0 :   if (uiEvent) {
     627           0 :     uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
     628           0 :     uiEvent->GetRangeOffset(&mRangeOffset);
     629             : 
     630             :     // get the event coordinates relative to the root frame of the document
     631             :     // containing the popup.
     632           0 :     NS_ASSERTION(aPopup, "Expected a popup node");
     633           0 :     WidgetEvent* event = aEvent->WidgetEventPtr();
     634           0 :     if (event) {
     635           0 :       WidgetInputEvent* inputEvent = event->AsInputEvent();
     636           0 :       if (inputEvent) {
     637           0 :         mCachedModifiers = inputEvent->mModifiers;
     638             :       }
     639           0 :       nsIDocument* doc = aPopup->GetUncomposedDoc();
     640           0 :       if (doc) {
     641           0 :         nsIPresShell* presShell = doc->GetShell();
     642             :         nsPresContext* presContext;
     643           0 :         if (presShell && (presContext = presShell->GetPresContext())) {
     644             :           nsPresContext* rootDocPresContext =
     645           0 :             presContext->GetRootPresContext();
     646           0 :           if (!rootDocPresContext)
     647           0 :             return;
     648             :           nsIFrame* rootDocumentRootFrame = rootDocPresContext->
     649           0 :               PresShell()->FrameManager()->GetRootFrame();
     650           0 :           if ((event->mClass == eMouseEventClass ||
     651           0 :                event->mClass == eMouseScrollEventClass ||
     652           0 :                event->mClass == eWheelEventClass) &&
     653           0 :                !event->AsGUIEvent()->mWidget) {
     654             :             // no widget, so just use the client point if available
     655           0 :             nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
     656           0 :             nsIntPoint clientPt;
     657           0 :             mouseEvent->GetClientX(&clientPt.x);
     658           0 :             mouseEvent->GetClientY(&clientPt.y);
     659             : 
     660             :             // XXX this doesn't handle IFRAMEs in transforms
     661           0 :             nsPoint thisDocToRootDocOffset = presShell->FrameManager()->
     662           0 :               GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
     663             :             // convert to device pixels
     664           0 :             mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
     665           0 :                 nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x);
     666           0 :             mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
     667           0 :                 nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y);
     668             :           }
     669           0 :           else if (rootDocumentRootFrame) {
     670             :             nsPoint pnt =
     671           0 :               nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
     672           0 :             mCachedMousePoint = LayoutDeviceIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
     673             :                                                      rootDocPresContext->AppUnitsToDevPixels(pnt.y));
     674             :           }
     675             :         }
     676             :       }
     677             :     }
     678             :   }
     679             :   else {
     680           0 :     mRangeParent = nullptr;
     681           0 :     mRangeOffset = 0;
     682             :   }
     683             : }
     684             : 
     685             : void
     686           0 : nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
     687             : {
     688           0 :   if (aActivate)
     689           0 :     mActiveMenuBar = aMenuBar;
     690           0 :   else if (mActiveMenuBar == aMenuBar)
     691           0 :     mActiveMenuBar = nullptr;
     692             : 
     693           0 :   UpdateKeyboardListeners();
     694           0 : }
     695             : 
     696             : void
     697           0 : nsXULPopupManager::ShowMenu(nsIContent *aMenu,
     698             :                             bool aSelectFirstItem,
     699             :                             bool aAsynchronous)
     700             : {
     701             :   // generate any template content first. Otherwise, the menupopup may not
     702             :   // have been created yet.
     703           0 :   if (aMenu) {
     704           0 :     nsIContent* element = aMenu;
     705           0 :     do {
     706           0 :       RefPtr<nsXULElement> xulelem = nsXULElement::FromContent(element);
     707           0 :       if (xulelem) {
     708           0 :         nsCOMPtr<nsIXULTemplateBuilder> builder = xulelem->GetBuilder();
     709           0 :         if (builder) {
     710           0 :           builder->CreateContents(aMenu, true);
     711           0 :           break;
     712             :         }
     713             :       }
     714           0 :       element = element->GetParent();
     715           0 :     } while (element);
     716             :   }
     717             : 
     718           0 :   nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
     719           0 :   if (!menuFrame || !menuFrame->IsMenu())
     720           0 :     return;
     721             : 
     722           0 :   nsMenuPopupFrame* popupFrame =  menuFrame->GetPopup();
     723           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     724           0 :     return;
     725             : 
     726             :   // inherit whether or not we're a context menu from the parent
     727           0 :   bool parentIsContextMenu = false;
     728           0 :   bool onMenuBar = false;
     729           0 :   bool onmenu = menuFrame->IsOnMenu();
     730             : 
     731           0 :   nsMenuParent* parent = menuFrame->GetMenuParent();
     732           0 :   if (parent && onmenu) {
     733           0 :     parentIsContextMenu = parent->IsContextMenu();
     734           0 :     onMenuBar = parent->IsMenuBar();
     735             :   }
     736             : 
     737           0 :   nsAutoString position;
     738             : 
     739             : #ifdef XP_MACOSX
     740             :   nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu);
     741             :   bool isNonEditableMenulist = false;
     742             :   if (menulist) {
     743             :     bool editable;
     744             :     menulist->GetEditable(&editable);
     745             :     isNonEditableMenulist = !editable;
     746             :   }
     747             : 
     748             :   if (isNonEditableMenulist) {
     749             :     position.AssignLiteral("selection");
     750             :   }
     751             :   else
     752             : #endif
     753             : 
     754           0 :   if (onMenuBar || !onmenu)
     755           0 :     position.AssignLiteral("after_start");
     756             :   else
     757           0 :     position.AssignLiteral("end_before");
     758             : 
     759             :   // there is no trigger event for menus
     760           0 :   InitTriggerEvent(nullptr, nullptr, nullptr);
     761           0 :   popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0,
     762           0 :                               MenuPopupAnchorType_Node, true);
     763             : 
     764           0 :   if (aAsynchronous) {
     765             :     nsCOMPtr<nsIRunnable> event =
     766           0 :       new nsXULPopupShowingEvent(popupFrame->GetContent(),
     767           0 :                                  parentIsContextMenu, aSelectFirstItem);
     768           0 :     aMenu->OwnerDoc()->Dispatch("nsXULPopupShowingEvent",
     769             :                                 TaskCategory::Other,
     770           0 :                                 event.forget());
     771             :   }
     772             :   else {
     773           0 :     nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
     774           0 :     FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem, nullptr);
     775             :   }
     776             : }
     777             : 
     778             : void
     779           0 : nsXULPopupManager::ShowPopup(nsIContent* aPopup,
     780             :                              nsIContent* aAnchorContent,
     781             :                              const nsAString& aPosition,
     782             :                              int32_t aXPos, int32_t aYPos,
     783             :                              bool aIsContextMenu,
     784             :                              bool aAttributesOverride,
     785             :                              bool aSelectFirstItem,
     786             :                              nsIDOMEvent* aTriggerEvent)
     787             : {
     788           0 :   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
     789           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     790           0 :     return;
     791             : 
     792           0 :   nsCOMPtr<nsIContent> triggerContent;
     793           0 :   InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
     794             : 
     795           0 :   popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
     796           0 :                               aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
     797             : 
     798           0 :   FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem, aTriggerEvent);
     799             : }
     800             : 
     801             : void
     802           0 : nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
     803             :                                      int32_t aXPos, int32_t aYPos,
     804             :                                      bool aIsContextMenu,
     805             :                                      nsIDOMEvent* aTriggerEvent)
     806             : {
     807           0 :   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
     808           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     809           0 :     return;
     810             : 
     811           0 :   nsCOMPtr<nsIContent> triggerContent;
     812           0 :   InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
     813             : 
     814           0 :   popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
     815           0 :   FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
     816             : }
     817             : 
     818             : void
     819           0 : nsXULPopupManager::ShowPopupAtScreenRect(nsIContent* aPopup,
     820             :                                          const nsAString& aPosition,
     821             :                                          const nsIntRect& aRect,
     822             :                                          bool aIsContextMenu,
     823             :                                          bool aAttributesOverride,
     824             :                                          nsIDOMEvent* aTriggerEvent)
     825             : {
     826           0 :   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
     827           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     828           0 :     return;
     829             : 
     830           0 :   nsCOMPtr<nsIContent> triggerContent;
     831           0 :   InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
     832             : 
     833           0 :   popupFrame->InitializePopupAtRect(triggerContent, aPosition,
     834           0 :                                     aRect, aAttributesOverride);
     835             : 
     836           0 :   FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
     837             : }
     838             : 
     839             : void
     840           0 : nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
     841             :                                        nsIContent* aTriggerContent,
     842             :                                        int32_t aXPos, int32_t aYPos)
     843             : {
     844           0 :   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
     845           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     846           0 :     return;
     847             : 
     848           0 :   InitTriggerEvent(nullptr, nullptr, nullptr);
     849             : 
     850           0 :   nsPresContext* pc = popupFrame->PresContext();
     851           0 :   mCachedMousePoint = LayoutDeviceIntPoint(pc->CSSPixelsToDevPixels(aXPos),
     852             :                                            pc->CSSPixelsToDevPixels(aYPos));
     853             : 
     854             :   // coordinates are relative to the root widget
     855           0 :   nsPresContext* rootPresContext = pc->GetRootPresContext();
     856           0 :   if (rootPresContext) {
     857           0 :     nsIWidget *rootWidget = rootPresContext->GetRootWidget();
     858           0 :     if (rootWidget) {
     859           0 :       mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
     860             :     }
     861             :   }
     862             : 
     863           0 :   popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
     864             : 
     865           0 :   FirePopupShowingEvent(aPopup, false, false, nullptr);
     866             : }
     867             : 
     868             : void
     869           0 : nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
     870             :                                             nsIContent* aAnchorContent,
     871             :                                             nsAString& aAnchor,
     872             :                                             nsAString& aAlign,
     873             :                                             int32_t aXPos, int32_t aYPos,
     874             :                                             bool aIsContextMenu)
     875             : {
     876           0 :   nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
     877           0 :   if (!popupFrame || !MayShowPopup(popupFrame))
     878           0 :     return;
     879             : 
     880           0 :   InitTriggerEvent(nullptr, nullptr, nullptr);
     881             : 
     882             :   popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
     883           0 :                                              aAlign, aXPos, aYPos);
     884           0 :   FirePopupShowingEvent(aPopup, aIsContextMenu, false, nullptr);
     885             : }
     886             : 
     887             : static void
     888           0 : CheckCaretDrawingState()
     889             : {
     890             :   // There is 1 caret per document, we need to find the focused
     891             :   // document and erase its caret.
     892           0 :   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
     893           0 :   if (fm) {
     894           0 :     nsCOMPtr<mozIDOMWindowProxy> window;
     895           0 :     fm->GetFocusedWindow(getter_AddRefs(window));
     896           0 :     if (!window)
     897           0 :       return;
     898             : 
     899           0 :     auto* piWindow = nsPIDOMWindowOuter::From(window);
     900           0 :     MOZ_ASSERT(piWindow);
     901             : 
     902           0 :     nsCOMPtr<nsIDocument> focusedDoc = piWindow->GetDoc();
     903           0 :     if (!focusedDoc)
     904           0 :       return;
     905             : 
     906           0 :     nsIPresShell* presShell = focusedDoc->GetShell();
     907           0 :     if (!presShell)
     908           0 :       return;
     909             : 
     910           0 :     RefPtr<nsCaret> caret = presShell->GetCaret();
     911           0 :     if (!caret)
     912           0 :       return;
     913           0 :     caret->SchedulePaint();
     914             :   }
     915             : }
     916             : 
     917             : void
     918           0 : nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
     919             :                                      nsMenuPopupFrame* aPopupFrame,
     920             :                                      bool aIsContextMenu,
     921             :                                      bool aSelectFirstItem)
     922             : {
     923           0 :   nsPopupType popupType = aPopupFrame->PopupType();
     924           0 :   bool ismenu = (popupType == ePopupTypeMenu);
     925             : 
     926             :   // Popups normally hide when an outside click occurs. Panels may use
     927             :   // the noautohide attribute to disable this behaviour. It is expected
     928             :   // that the application will hide these popups manually. The tooltip
     929             :   // listener will handle closing the tooltip also.
     930           0 :   bool isNoAutoHide = aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip;
     931             : 
     932             :   nsMenuChainItem* item =
     933           0 :     new nsMenuChainItem(aPopupFrame, isNoAutoHide, aIsContextMenu, popupType);
     934           0 :   if (!item)
     935           0 :     return;
     936             : 
     937             :   // install keyboard event listeners for navigating menus. For panels, the
     938             :   // escape key may be used to close the panel. However, the ignorekeys
     939             :   // attribute may be used to disable adding these event listeners for popups
     940             :   // that want to handle their own keyboard events.
     941           0 :   nsAutoString ignorekeys;
     942           0 :   aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
     943           0 :   if (ignorekeys.EqualsLiteral("true")) {
     944           0 :     item->SetIgnoreKeys(eIgnoreKeys_True);
     945           0 :   } else if (ignorekeys.EqualsLiteral("shortcuts")) {
     946           0 :     item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
     947             :   }
     948             : 
     949           0 :   if (ismenu) {
     950             :     // if the menu is on a menubar, use the menubar's listener instead
     951           0 :     nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
     952           0 :     if (menuFrame) {
     953           0 :       item->SetOnMenuBar(menuFrame->IsOnMenuBar());
     954             :     }
     955             :   }
     956             : 
     957             :   // use a weak frame as the popup will set an open attribute if it is a menu
     958           0 :   AutoWeakFrame weakFrame(aPopupFrame);
     959           0 :   aPopupFrame->ShowPopup(aIsContextMenu);
     960           0 :   ENSURE_TRUE(weakFrame.IsAlive());
     961             : 
     962             :   // popups normally hide when an outside click occurs. Panels may use
     963             :   // the noautohide attribute to disable this behaviour. It is expected
     964             :   // that the application will hide these popups manually. The tooltip
     965             :   // listener will handle closing the tooltip also.
     966           0 :   nsIContent* oldmenu = nullptr;
     967           0 :   if (mPopups) {
     968           0 :     oldmenu = mPopups->Content();
     969             :   }
     970           0 :   item->SetParent(mPopups);
     971           0 :   mPopups = item;
     972           0 :   SetCaptureState(oldmenu);
     973             : 
     974           0 :   item->UpdateFollowAnchor();
     975             : 
     976           0 :   if (aSelectFirstItem) {
     977           0 :     nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true, false);
     978           0 :     aPopupFrame->SetCurrentMenuItem(next);
     979             :   }
     980             : 
     981           0 :   if (ismenu)
     982           0 :     UpdateMenuItems(aPopup);
     983             : 
     984             :   // Caret visibility may have been affected, ensure that
     985             :   // the caret isn't now drawn when it shouldn't be.
     986           0 :   CheckCaretDrawingState();
     987             : }
     988             : 
     989             : void
     990           1 : nsXULPopupManager::HidePopup(nsIContent* aPopup,
     991             :                              bool aHideChain,
     992             :                              bool aDeselectMenu,
     993             :                              bool aAsynchronous,
     994             :                              bool aIsCancel,
     995             :                              nsIContent* aLastPopup)
     996             : {
     997           1 :   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
     998           1 :   if (!popupFrame) {
     999           2 :     return;
    1000             :   }
    1001             : 
    1002           0 :   nsMenuChainItem* foundPopup = mPopups;
    1003           0 :   while (foundPopup) {
    1004           0 :     if (foundPopup->Content() == aPopup) {
    1005           0 :       break;
    1006             :     }
    1007           0 :     foundPopup = foundPopup->GetParent();
    1008             :   }
    1009             : 
    1010           0 :   bool deselectMenu = false;
    1011           0 :   nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
    1012             : 
    1013           0 :   if (foundPopup) {
    1014           0 :     if (foundPopup->IsNoAutoHide()) {
    1015             :       // If this is a noautohide panel, remove it but don't close any other panels.
    1016           0 :       popupToHide = aPopup;
    1017             :     } else {
    1018             :       // At this point, foundPopup will be set to the found item in the list. If
    1019             :       // foundPopup is the topmost menu, the one to remove, then there are no other
    1020             :       // popups to hide. If foundPopup is not the topmost menu, then there may be
    1021             :       // open submenus below it. In this case, we need to make sure that those
    1022             :       // submenus are closed up first. To do this, we scan up the menu list to
    1023             :       // find the topmost popup with only menus between it and foundPopup and
    1024             :       // close that menu first. In synchronous mode, the FirePopupHidingEvent
    1025             :       // method will be called which in turn calls HidePopupCallback to close up
    1026             :       // the next popup in the chain. These two methods will be called in
    1027             :       // sequence recursively to close up all the necessary popups. In
    1028             :       // asynchronous mode, a similar process occurs except that the
    1029             :       // FirePopupHidingEvent method is called asynchronously. In either case,
    1030             :       // nextPopup is set to the content node of the next popup to close, and
    1031             :       // lastPopup is set to the last popup in the chain to close, which will be
    1032             :       // aPopup, or null to close up all menus.
    1033             : 
    1034           0 :       nsMenuChainItem* topMenu = foundPopup;
    1035             :       // Use IsMenu to ensure that foundPopup is a menu and scan down the child
    1036             :       // list until a non-menu is found. If foundPopup isn't a menu at all, don't
    1037             :       // scan and just close up this menu.
    1038           0 :       if (foundPopup->IsMenu()) {
    1039           0 :         nsMenuChainItem* child = foundPopup->GetChild();
    1040           0 :         while (child && child->IsMenu()) {
    1041           0 :           topMenu = child;
    1042           0 :           child = child->GetChild();
    1043             :         }
    1044             :       }
    1045             : 
    1046           0 :       deselectMenu = aDeselectMenu;
    1047           0 :       popupToHide = topMenu->Content();
    1048           0 :       popupFrame = topMenu->Frame();
    1049             : 
    1050             :       // Close up another popup if there is one, and we are either hiding the
    1051             :       // entire chain or the item to hide isn't the topmost popup.
    1052           0 :       nsMenuChainItem* parent = topMenu->GetParent();
    1053           0 :       if (parent && (aHideChain || topMenu != foundPopup)) {
    1054           0 :         while (parent && parent->IsNoAutoHide()) {
    1055           0 :           parent = parent->GetParent();
    1056             :         }
    1057             : 
    1058           0 :         if (parent) {
    1059           0 :           nextPopup = parent->Content();
    1060             :         }
    1061             :       }
    1062             : 
    1063           0 :       lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
    1064             :     }
    1065           0 :   } else if (popupFrame->PopupState() == ePopupPositioning) {
    1066             :     // When the popup is in the popuppositioning state, it will not be in the
    1067             :     // mPopups list. We need another way to find it and make sure it does not
    1068             :     // continue the popup showing process.
    1069           0 :     deselectMenu = aDeselectMenu;
    1070           0 :     popupToHide = aPopup;
    1071             :   }
    1072             : 
    1073           0 :   if (popupToHide) {
    1074           0 :     nsPopupState state = popupFrame->PopupState();
    1075             :     // If the popup is already being hidden, don't attempt to hide it again
    1076           0 :     if (state == ePopupHiding) {
    1077           0 :       return;
    1078             :     }
    1079             : 
    1080             :     // Change the popup state to hiding. Don't set the hiding state if the
    1081             :     // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
    1082             :     // run again. In the invisible state, we just want the events to fire.
    1083           0 :     if (state != ePopupInvisible) {
    1084           0 :       popupFrame->SetPopupState(ePopupHiding);
    1085             :     }
    1086             : 
    1087             :     // For menus, popupToHide is always the frontmost item in the list to hide.
    1088           0 :     if (aAsynchronous) {
    1089             :       nsCOMPtr<nsIRunnable> event =
    1090             :         new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
    1091           0 :                                   popupFrame->PopupType(), deselectMenu, aIsCancel);
    1092           0 :         aPopup->OwnerDoc()->Dispatch("nsXULPopupHidingEvent",
    1093             :                                      TaskCategory::Other,
    1094           0 :                                      event.forget());
    1095             :     }
    1096             :     else {
    1097           0 :       FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
    1098             :                            popupFrame->PresContext(), popupFrame->PopupType(),
    1099           0 :                            deselectMenu, aIsCancel);
    1100             :     }
    1101             :   }
    1102             : }
    1103             : 
    1104             : // This is used to hide the popup after a transition finishes.
    1105             : class TransitionEnder : public nsIDOMEventListener
    1106             : {
    1107             : protected:
    1108           0 :   virtual ~TransitionEnder() { }
    1109             : 
    1110             : public:
    1111             : 
    1112             :   nsCOMPtr<nsIContent> mContent;
    1113             :   bool mDeselectMenu;
    1114             : 
    1115             :   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    1116           0 :   NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
    1117             : 
    1118             :   TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
    1119             :     : mContent(aContent), mDeselectMenu(aDeselectMenu)
    1120             :   {
    1121             :   }
    1122             : 
    1123           0 :   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
    1124             :   {
    1125           0 :     mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
    1126             : 
    1127           0 :     nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
    1128             : 
    1129             :     // Now hide the popup. There could be other properties transitioning, but
    1130             :     // we'll assume they all end at the same time and just hide the popup upon
    1131             :     // the first one ending.
    1132           0 :     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    1133           0 :     if (pm && popupFrame) {
    1134           0 :       pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
    1135           0 :                             popupFrame->PopupType(), mDeselectMenu);
    1136             :     }
    1137             : 
    1138           0 :     return NS_OK;
    1139             :   }
    1140             : };
    1141             : 
    1142           0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
    1143           0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
    1144           0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
    1145           0 :   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
    1146           0 :   NS_INTERFACE_MAP_ENTRY(nsISupports)
    1147           0 : NS_INTERFACE_MAP_END
    1148             : 
    1149           0 : NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
    1150             : 
    1151             : void
    1152           0 : nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
    1153             :                                      nsMenuPopupFrame* aPopupFrame,
    1154             :                                      nsIContent* aNextPopup,
    1155             :                                      nsIContent* aLastPopup,
    1156             :                                      nsPopupType aPopupType,
    1157             :                                      bool aDeselectMenu)
    1158             : {
    1159           0 :   if (mCloseTimer && mTimerMenu == aPopupFrame) {
    1160           0 :     mCloseTimer->Cancel();
    1161           0 :     mCloseTimer = nullptr;
    1162           0 :     mTimerMenu = nullptr;
    1163             :   }
    1164             : 
    1165             :   // The popup to hide is aPopup. Search the list again to find the item that
    1166             :   // corresponds to the popup to hide aPopup. This is done because it's
    1167             :   // possible someone added another item (attempted to open another popup)
    1168             :   // or removed a popup frame during the event processing so the item isn't at
    1169             :   // the front anymore.
    1170           0 :   nsMenuChainItem* item = mPopups;
    1171           0 :   while (item) {
    1172           0 :     if (item->Content() == aPopup) {
    1173           0 :       item->Detach(&mPopups);
    1174           0 :       SetCaptureState(aPopup);
    1175           0 :       break;
    1176             :     }
    1177           0 :     item = item->GetParent();
    1178             :   }
    1179             : 
    1180           0 :   delete item;
    1181             : 
    1182           0 :   AutoWeakFrame weakFrame(aPopupFrame);
    1183           0 :   aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
    1184           0 :   ENSURE_TRUE(weakFrame.IsAlive());
    1185             : 
    1186             :   // send the popuphidden event synchronously. This event has no default
    1187             :   // behaviour.
    1188           0 :   nsEventStatus status = nsEventStatus_eIgnore;
    1189             :   WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
    1190           0 :                          WidgetMouseEvent::eReal);
    1191           0 :   EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
    1192           0 :                             &event, nullptr, &status);
    1193           0 :   ENSURE_TRUE(weakFrame.IsAlive());
    1194             : 
    1195             :   // Force any popups that might be anchored on elements within this popup to update.
    1196           0 :   UpdatePopupPositions(aPopupFrame->PresContext()->RefreshDriver());
    1197             : 
    1198             :   // if there are more popups to close, look for the next one
    1199           0 :   if (aNextPopup && aPopup != aLastPopup) {
    1200           0 :     nsMenuChainItem* foundMenu = nullptr;
    1201           0 :     nsMenuChainItem* item = mPopups;
    1202           0 :     while (item) {
    1203           0 :       if (item->Content() == aNextPopup) {
    1204           0 :         foundMenu = item;
    1205           0 :         break;
    1206             :       }
    1207           0 :       item = item->GetParent();
    1208             :     }
    1209             : 
    1210             :     // continue hiding the chain of popups until the last popup aLastPopup
    1211             :     // is reached, or until a popup of a different type is reached. This
    1212             :     // last check is needed so that a menulist inside a non-menu panel only
    1213             :     // closes the menu and not the panel as well.
    1214           0 :     if (foundMenu &&
    1215           0 :         (aLastPopup || aPopupType == foundMenu->PopupType())) {
    1216             : 
    1217           0 :       nsCOMPtr<nsIContent> popupToHide = item->Content();
    1218           0 :       nsMenuChainItem* parent = item->GetParent();
    1219             : 
    1220           0 :       nsCOMPtr<nsIContent> nextPopup;
    1221           0 :       if (parent && popupToHide != aLastPopup)
    1222           0 :         nextPopup = parent->Content();
    1223             : 
    1224           0 :       nsMenuPopupFrame* popupFrame = item->Frame();
    1225           0 :       nsPopupState state = popupFrame->PopupState();
    1226           0 :       if (state == ePopupHiding)
    1227           0 :         return;
    1228           0 :       if (state != ePopupInvisible)
    1229           0 :         popupFrame->SetPopupState(ePopupHiding);
    1230             : 
    1231           0 :       FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
    1232             :                            popupFrame->PresContext(),
    1233           0 :                            foundMenu->PopupType(), aDeselectMenu, false);
    1234             :     }
    1235             :   }
    1236             : }
    1237             : 
    1238             : void
    1239           0 : nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
    1240             : {
    1241             :   // Don't close up immediately.
    1242             :   // Kick off a close timer.
    1243           0 :   KillMenuTimer();
    1244             : 
    1245             :   int32_t menuDelay =
    1246           0 :     LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
    1247             : 
    1248             :   // Kick off the timer.
    1249           0 :   mCloseTimer = do_CreateInstance("@mozilla.org/timer;1");
    1250           0 :   nsIContent* content = aPopup->GetContent();
    1251           0 :   if (content) {
    1252           0 :     mCloseTimer->SetTarget(
    1253           0 :         content->OwnerDoc()->EventTargetFor(TaskCategory::Other));
    1254             :   }
    1255           0 :   mCloseTimer->InitWithNamedFuncCallback([](nsITimer* aTimer, void* aClosure) {
    1256           0 :     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    1257           0 :     if (pm) {
    1258           0 :       pm->KillMenuTimer();
    1259             :     }
    1260           0 :   }, nullptr, menuDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer");
    1261             : 
    1262             :   // the popup will call PopupDestroyed if it is destroyed, which checks if it
    1263             :   // is set to mTimerMenu, so it should be safe to keep a reference to it
    1264           0 :   mTimerMenu = aPopup;
    1265           0 : }
    1266             : 
    1267             : void
    1268          11 : nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames)
    1269             : {
    1270             :   // Create a weak frame list. This is done in a separate array with the
    1271             :   // right capacity predetermined to avoid multiple allocations.
    1272          22 :   nsTArray<WeakFrame> weakPopups(aFrames.Length());
    1273             :   uint32_t f;
    1274          11 :   for (f = 0; f < aFrames.Length(); f++) {
    1275           0 :     WeakFrame* wframe = weakPopups.AppendElement();
    1276           0 :     if (wframe)
    1277           0 :       *wframe = aFrames[f];
    1278             :   }
    1279             : 
    1280          11 :   for (f = 0; f < weakPopups.Length(); f++) {
    1281             :     // check to ensure that the frame is still alive before hiding it.
    1282           0 :     if (weakPopups[f].IsAlive()) {
    1283             :       nsMenuPopupFrame* frame =
    1284           0 :         static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame());
    1285           0 :       frame->HidePopup(true, ePopupInvisible);
    1286             :     }
    1287             :   }
    1288             : 
    1289          11 :   SetCaptureState(nullptr);
    1290          11 : }
    1291             : 
    1292             : void
    1293           0 : nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup)
    1294             : {
    1295             : #ifndef MOZ_GTK
    1296           0 :   nsMenuChainItem* item = mPopups;
    1297           0 :   while (item) {
    1298           0 :     if (item->Content() == aPopup) {
    1299           0 :       nsIContent* oldmenu = nullptr;
    1300           0 :       if (mPopups) {
    1301           0 :         oldmenu = mPopups->Content();
    1302             :       }
    1303             : 
    1304           0 :       item->SetNoAutoHide(!aShouldRollup);
    1305           0 :       SetCaptureState(oldmenu);
    1306           0 :       return;
    1307             :     }
    1308           0 :     item = item->GetParent();
    1309             :   }
    1310             : #endif
    1311             : }
    1312             : 
    1313             : bool
    1314           0 : nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
    1315             : {
    1316           0 :   nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
    1317           0 :   while(docShellItem) {
    1318           0 :     if (docShellItem == aExpected)
    1319           0 :       return true;
    1320             : 
    1321           0 :     nsCOMPtr<nsIDocShellTreeItem> parent;
    1322           0 :     docShellItem->GetParent(getter_AddRefs(parent));
    1323           0 :     docShellItem = parent;
    1324             :   }
    1325             : 
    1326           0 :   return false;
    1327             : }
    1328             : 
    1329             : void
    1330           4 : nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
    1331             : {
    1332           8 :   nsTArray<nsMenuPopupFrame *> popupsToHide;
    1333             : 
    1334             :   // iterate to get the set of popup frames to hide
    1335           4 :   nsMenuChainItem* item = mPopups;
    1336           4 :   while (item) {
    1337           0 :     nsMenuChainItem* parent = item->GetParent();
    1338           0 :     if (item->Frame()->PopupState() != ePopupInvisible &&
    1339           0 :         IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
    1340           0 :       nsMenuPopupFrame* frame = item->Frame();
    1341           0 :       item->Detach(&mPopups);
    1342           0 :       delete item;
    1343           0 :       popupsToHide.AppendElement(frame);
    1344             :     }
    1345           0 :     item = parent;
    1346             :   }
    1347             : 
    1348           4 :   HidePopupsInList(popupsToHide);
    1349           4 : }
    1350             : 
    1351             : void
    1352          42 : nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver)
    1353             : {
    1354          42 :   nsMenuChainItem* item = mPopups;
    1355          42 :   while (item) {
    1356           0 :     if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
    1357           0 :       item->CheckForAnchorChange();
    1358             :     }
    1359             : 
    1360           0 :     item = item->GetParent();
    1361             :   }
    1362          42 : }
    1363             : 
    1364             : void
    1365           0 : nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup)
    1366             : {
    1367           0 :   nsMenuChainItem* item = mPopups;
    1368           0 :   while (item) {
    1369           0 :     if (item->Frame() == aPopup) {
    1370           0 :       item->UpdateFollowAnchor();
    1371           0 :       break;
    1372             :     }
    1373             : 
    1374           0 :     item = item->GetParent();
    1375             :   }
    1376           0 : }
    1377             : 
    1378             : void
    1379           0 : nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
    1380             : {
    1381           0 :   CloseMenuMode cmm = CloseMenuMode_Auto;
    1382             : 
    1383             :   static nsIContent::AttrValuesArray strings[] =
    1384             :     {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
    1385             : 
    1386           0 :   switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
    1387           0 :                                  strings, eCaseMatters)) {
    1388             :     case 0:
    1389           0 :       cmm = CloseMenuMode_None;
    1390           0 :       break;
    1391             :     case 1:
    1392           0 :       cmm = CloseMenuMode_Single;
    1393           0 :       break;
    1394             :     default:
    1395           0 :       break;
    1396             :   }
    1397             : 
    1398             :   // When a menuitem is selected to be executed, first hide all the open
    1399             :   // popups, but don't remove them yet. This is needed when a menu command
    1400             :   // opens a modal dialog. The views associated with the popups needed to be
    1401             :   // hidden and the accesibility events fired before the command executes, but
    1402             :   // the popuphiding/popuphidden events are fired afterwards.
    1403           0 :   nsTArray<nsMenuPopupFrame *> popupsToHide;
    1404           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    1405           0 :   if (cmm != CloseMenuMode_None) {
    1406           0 :     while (item) {
    1407             :       // if it isn't a <menupopup>, don't close it automatically
    1408           0 :       if (!item->IsMenu())
    1409           0 :         break;
    1410           0 :       nsMenuChainItem* next = item->GetParent();
    1411           0 :       popupsToHide.AppendElement(item->Frame());
    1412           0 :       if (cmm == CloseMenuMode_Single) // only close one level of menu
    1413           0 :         break;
    1414           0 :       item = next;
    1415             :     }
    1416             : 
    1417             :     // Now hide the popups. If the closemenu mode is auto, deselect the menu,
    1418             :     // otherwise only one popup is closing, so keep the parent menu selected.
    1419           0 :     HidePopupsInList(popupsToHide);
    1420             :   }
    1421             : 
    1422           0 :   aEvent->SetCloseMenuMode(cmm);
    1423           0 :   nsCOMPtr<nsIRunnable> event = aEvent;
    1424           0 :   aMenu->OwnerDoc()->Dispatch("nsXULMenuCommandEvent",
    1425             :                               TaskCategory::Other,
    1426           0 :                               event.forget());
    1427           0 : }
    1428             : 
    1429             : void
    1430           0 : nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
    1431             :                                          bool aIsContextMenu,
    1432             :                                          bool aSelectFirstItem,
    1433             :                                          nsIDOMEvent* aTriggerEvent)
    1434             : {
    1435           0 :   nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
    1436             : 
    1437           0 :   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
    1438           0 :   if (!popupFrame)
    1439           0 :     return;
    1440             : 
    1441           0 :   nsPresContext *presContext = popupFrame->PresContext();
    1442           0 :   nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
    1443           0 :   nsPopupType popupType = popupFrame->PopupType();
    1444             : 
    1445             :   // generate the child frames if they have not already been generated
    1446           0 :   if (!popupFrame->HasGeneratedChildren()) {
    1447           0 :     popupFrame->SetGeneratedChildren();
    1448           0 :     presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
    1449             :   }
    1450             : 
    1451             :   // get the frame again
    1452           0 :   nsIFrame* frame = aPopup->GetPrimaryFrame();
    1453           0 :   if (!frame)
    1454           0 :     return;
    1455             : 
    1456           0 :   presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
    1457           0 :                               NS_FRAME_HAS_DIRTY_CHILDREN);
    1458             : 
    1459             :   // cache the popup so that document.popupNode can retrieve the trigger node
    1460             :   // during the popupshowing event. It will be cleared below after the event
    1461             :   // has fired.
    1462           0 :   mOpeningPopup = aPopup;
    1463             : 
    1464           0 :   nsEventStatus status = nsEventStatus_eIgnore;
    1465             :   WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
    1466           0 :                          WidgetMouseEvent::eReal);
    1467             : 
    1468             :   // coordinates are relative to the root widget
    1469             :   nsPresContext* rootPresContext =
    1470           0 :     presShell->GetPresContext()->GetRootPresContext();
    1471           0 :   if (rootPresContext) {
    1472             :     rootPresContext->PresShell()->GetViewManager()->
    1473           0 :       GetRootWidget(getter_AddRefs(event.mWidget));
    1474             :   }
    1475             :   else {
    1476           0 :     event.mWidget = nullptr;
    1477             :   }
    1478             : 
    1479           0 :   if (aTriggerEvent) {
    1480             :     WidgetMouseEventBase* mouseEvent =
    1481           0 :       aTriggerEvent->WidgetEventPtr()->AsMouseEventBase();
    1482           0 :     if (mouseEvent) {
    1483           0 :       event.inputSource = mouseEvent->inputSource;
    1484             :     }
    1485             :   }
    1486             : 
    1487           0 :   event.mRefPoint = mCachedMousePoint;
    1488           0 :   event.mModifiers = mCachedModifiers;
    1489           0 :   EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
    1490             : 
    1491           0 :   mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
    1492           0 :   mOpeningPopup = nullptr;
    1493             : 
    1494           0 :   mCachedModifiers = 0;
    1495             : 
    1496             :   // if a panel, blur whatever has focus so that the panel can take the focus.
    1497             :   // This is done after the popupshowing event in case that event is cancelled.
    1498             :   // Using noautofocus="true" will disable this behaviour, which is needed for
    1499             :   // the autocomplete widget as it manages focus itself.
    1500           0 :   if (popupType == ePopupTypePanel &&
    1501           0 :       !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
    1502             :                            nsGkAtoms::_true, eCaseMatters)) {
    1503           0 :     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
    1504           0 :     if (fm) {
    1505           0 :       nsIDocument* doc = popup->GetUncomposedDoc();
    1506             : 
    1507             :       // Only remove the focus if the currently focused item is ouside the
    1508             :       // popup. It isn't a big deal if the current focus is in a child popup
    1509             :       // inside the popup as that shouldn't be visible. This check ensures that
    1510             :       // a node inside the popup that is focused during a popupshowing event
    1511             :       // remains focused.
    1512           0 :       nsCOMPtr<nsIDOMElement> currentFocusElement;
    1513           0 :       fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
    1514           0 :       nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
    1515           0 :       if (doc && currentFocus &&
    1516           0 :           !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
    1517           0 :         fm->ClearFocus(doc->GetWindow());
    1518             :       }
    1519             :     }
    1520             :   }
    1521             : 
    1522             :   // clear these as they are no longer valid
    1523           0 :   mRangeParent = nullptr;
    1524           0 :   mRangeOffset = 0;
    1525             : 
    1526             :   // get the frame again in case it went away
    1527           0 :   popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
    1528           0 :   if (popupFrame) {
    1529             :     // if the event was cancelled, don't open the popup, reset its state back
    1530             :     // to closed and clear its trigger content.
    1531           0 :     if (status == nsEventStatus_eConsumeNoDefault) {
    1532           0 :       popupFrame->SetPopupState(ePopupClosed);
    1533           0 :       popupFrame->ClearTriggerContent();
    1534             :     }
    1535             :     else {
    1536             :       // Now check if we need to fire the popuppositioned event. If not, call
    1537             :       // ShowPopupCallback directly.
    1538             : 
    1539             :       // The popuppositioned event only fires on arrow panels for now.
    1540           0 :       if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    1541             :                           nsGkAtoms::arrow, eCaseMatters)) {
    1542           0 :         popupFrame->ShowWithPositionedEvent();
    1543           0 :         presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange,
    1544           0 :                                     NS_FRAME_HAS_DIRTY_CHILDREN);
    1545             :       }
    1546             :       else {
    1547           0 :         ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
    1548             :       }
    1549             :     }
    1550             :   }
    1551             : }
    1552             : 
    1553             : void
    1554           0 : nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
    1555             :                                         nsIContent* aNextPopup,
    1556             :                                         nsIContent* aLastPopup,
    1557             :                                         nsPresContext *aPresContext,
    1558             :                                         nsPopupType aPopupType,
    1559             :                                         bool aDeselectMenu,
    1560             :                                         bool aIsCancel)
    1561             : {
    1562           0 :   nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
    1563             :   mozilla::Unused << presShell; // This presShell may be keeping things alive on non GTK platforms
    1564             : 
    1565           0 :   nsEventStatus status = nsEventStatus_eIgnore;
    1566             :   WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
    1567           0 :                          WidgetMouseEvent::eReal);
    1568           0 :   EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
    1569             : 
    1570             :   // when a panel is closed, blur whatever has focus inside the popup
    1571           0 :   if (aPopupType == ePopupTypePanel &&
    1572           0 :       !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
    1573             :                            nsGkAtoms::_true, eCaseMatters)) {
    1574           0 :     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
    1575           0 :     if (fm) {
    1576           0 :       nsIDocument* doc = aPopup->GetUncomposedDoc();
    1577             : 
    1578             :       // Remove the focus from the focused node only if it is inside the popup.
    1579           0 :       nsCOMPtr<nsIDOMElement> currentFocusElement;
    1580           0 :       fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
    1581           0 :       nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
    1582           0 :       if (doc && currentFocus &&
    1583           0 :           nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
    1584           0 :         fm->ClearFocus(doc->GetWindow());
    1585             :       }
    1586             :     }
    1587             :   }
    1588             : 
    1589             :   // get frame again in case it went away
    1590           0 :   nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
    1591           0 :   if (popupFrame) {
    1592             :     // if the event was cancelled, don't hide the popup, and reset its
    1593             :     // state back to open. Only popups in chrome shells can prevent a popup
    1594             :     // from hiding.
    1595           0 :     if (status == nsEventStatus_eConsumeNoDefault &&
    1596           0 :         !popupFrame->IsInContentShell()) {
    1597             :       // XXXndeakin
    1598             :       // If an attempt was made to hide this popup before the popupshown event
    1599             :       // fired, then ePopupShown is set here even though it should be
    1600             :       // ePopupVisible. This probably isn't worth the hassle of handling.
    1601           0 :       popupFrame->SetPopupState(ePopupShown);
    1602             :     }
    1603             :     else {
    1604             :       // If the popup has an animate attribute and it is not set to false, check
    1605             :       // if it has a closing transition and wait for it to finish. The transition
    1606             :       // may still occur either way, but the view will be hidden and you won't be
    1607             :       // able to see it. If there is a next popup, indicating that mutliple popups
    1608             :       // are rolling up, don't wait and hide the popup right away since the effect
    1609             :       // would likely be undesirable. Transitions are currently disabled on Linux
    1610             :       // due to rendering issues on certain configurations.
    1611             : #ifndef MOZ_WIDGET_GTK
    1612             :       if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) {
    1613             :         // If animate="false" then don't transition at all. If animate="cancel",
    1614             :         // only show the transition if cancelling the popup or rolling up.
    1615             :         // Otherwise, always show the transition.
    1616             :         nsAutoString animate;
    1617             :         aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate);
    1618             : 
    1619             :         if (!animate.EqualsLiteral("false") &&
    1620             :             (!animate.EqualsLiteral("cancel") || aIsCancel)) {
    1621             :           presShell->FlushPendingNotifications(FlushType::Layout);
    1622             : 
    1623             :           // Get the frame again in case the flush caused it to go away
    1624             :           popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
    1625             :           if (!popupFrame)
    1626             :             return;
    1627             : 
    1628             :           if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) {
    1629             :             RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
    1630             :             aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
    1631             :                                            ender, false, false);
    1632             :             return;
    1633             :           }
    1634             :         }
    1635             :       }
    1636             : #endif
    1637             : 
    1638           0 :       HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
    1639           0 :                         aPopupType, aDeselectMenu);
    1640             :     }
    1641             :   }
    1642           0 : }
    1643             : 
    1644             : bool
    1645           0 : nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
    1646             : {
    1647             :   // a popup is open if it is in the open list. The assertions ensure that the
    1648             :   // frame is in the correct state. If the popup is in the hiding or invisible
    1649             :   // state, it will still be in the open popup list until it is closed.
    1650           0 :   nsMenuChainItem* item = mPopups;
    1651           0 :   while (item) {
    1652           0 :     if (item->Content() == aPopup) {
    1653           0 :       NS_ASSERTION(item->Frame()->IsOpen() ||
    1654             :                    item->Frame()->PopupState() == ePopupHiding ||
    1655             :                    item->Frame()->PopupState() == ePopupInvisible,
    1656             :                    "popup in open list not actually open");
    1657           0 :       return true;
    1658             :     }
    1659           0 :     item = item->GetParent();
    1660             :   }
    1661             : 
    1662           0 :   return false;
    1663             : }
    1664             : 
    1665             : bool
    1666           0 : nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
    1667             : {
    1668           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    1669           0 :   while (item) {
    1670           0 :     nsMenuPopupFrame* popup = item->Frame();
    1671           0 :     if (popup && popup->IsOpen()) {
    1672           0 :       nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
    1673           0 :       if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
    1674           0 :         return true;
    1675             :       }
    1676             :     }
    1677           0 :     item = item->GetParent();
    1678             :   }
    1679             : 
    1680           0 :   return false;
    1681             : }
    1682             : 
    1683             : nsIFrame*
    1684           0 : nsXULPopupManager::GetTopPopup(nsPopupType aType)
    1685             : {
    1686           0 :   nsMenuChainItem* item = mPopups;
    1687           0 :   while (item) {
    1688           0 :     if (item->Frame()->IsVisible() &&
    1689           0 :         (item->PopupType() == aType || aType == ePopupTypeAny)) {
    1690           0 :       return item->Frame();
    1691             :     }
    1692             : 
    1693           0 :     item = item->GetParent();
    1694             :   }
    1695             : 
    1696           0 :   return nullptr;
    1697             : }
    1698             : 
    1699             : void
    1700          10 : nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
    1701             : {
    1702          10 :   aPopups.Clear();
    1703             : 
    1704          10 :   nsMenuChainItem* item = mPopups;
    1705          10 :   while (item) {
    1706             :     // Skip panels which are not visible as well as popups that
    1707             :     // are transparent to mouse events.
    1708           0 :     if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
    1709           0 :       aPopups.AppendElement(item->Frame());
    1710             :     }
    1711             : 
    1712           0 :     item = item->GetParent();
    1713             :   }
    1714          10 : }
    1715             : 
    1716             : already_AddRefed<nsIDOMNode>
    1717          11 : nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
    1718             : {
    1719          11 :   if (!aDocument)
    1720           0 :     return nullptr;
    1721             : 
    1722          22 :   nsCOMPtr<nsIDOMNode> node;
    1723             : 
    1724             :   // if mOpeningPopup is set, it means that a popupshowing event is being
    1725             :   // fired. In this case, just use the cached node, as the popup is not yet in
    1726             :   // the list of open popups.
    1727          11 :   if (mOpeningPopup && mOpeningPopup->GetUncomposedDoc() == aDocument &&
    1728           0 :       aIsTooltip == mOpeningPopup->IsXULElement(nsGkAtoms::tooltip)) {
    1729           0 :     node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false)));
    1730             :   }
    1731             :   else {
    1732          11 :     nsMenuChainItem* item = mPopups;
    1733          11 :     while (item) {
    1734             :       // look for a popup of the same type and document.
    1735           0 :       if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
    1736           0 :           item->Content()->GetUncomposedDoc() == aDocument) {
    1737           0 :         node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame()));
    1738           0 :         if (node)
    1739           0 :           break;
    1740             :       }
    1741           0 :       item = item->GetParent();
    1742             :     }
    1743             :   }
    1744             : 
    1745          11 :   return node.forget();
    1746             : }
    1747             : 
    1748             : bool
    1749           0 : nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
    1750             : {
    1751             :   // if a popup's IsOpen method returns true, then the popup must always be in
    1752             :   // the popup chain scanned in IsPopupOpen.
    1753           0 :   NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
    1754             :                "popup frame state doesn't match XULPopupManager open state");
    1755             : 
    1756           0 :   nsPopupState state = aPopup->PopupState();
    1757             : 
    1758             :   // if the popup is not in the open popup chain, then it must have a state that
    1759             :   // is either closed, in the process of being shown, or invisible.
    1760           0 :   NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
    1761             :                state == ePopupShowing || state == ePopupPositioning ||
    1762             :                state == ePopupInvisible,
    1763             :                "popup not in XULPopupManager open list is open");
    1764             : 
    1765             :   // don't show popups unless they are closed or invisible
    1766           0 :   if (state != ePopupClosed && state != ePopupInvisible)
    1767           0 :     return false;
    1768             : 
    1769             :   // Don't show popups that we already have in our popup chain
    1770           0 :   if (IsPopupOpen(aPopup->GetContent())) {
    1771           0 :     NS_WARNING("Refusing to show duplicate popup");
    1772           0 :     return false;
    1773             :   }
    1774             : 
    1775             :   // if the popup was just rolled up, don't reopen it
    1776           0 :   if (mozilla::widget::nsAutoRollup::GetLastRollup() == aPopup->GetContent())
    1777           0 :       return false;
    1778             : 
    1779           0 :   nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
    1780           0 :   nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
    1781           0 :   if (!baseWin)
    1782           0 :     return false;
    1783             : 
    1784           0 :   nsCOMPtr<nsIDocShellTreeItem> root;
    1785           0 :   dsti->GetRootTreeItem(getter_AddRefs(root));
    1786           0 :   if (!root) {
    1787           0 :     return false;
    1788             :   }
    1789             : 
    1790           0 :   nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow();
    1791             : 
    1792             :   // chrome shells can always open popups, but other types of shells can only
    1793             :   // open popups when they are focused and visible
    1794           0 :   if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
    1795             :     // only allow popups in active windows
    1796           0 :     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
    1797           0 :     if (!fm || !rootWin)
    1798           0 :       return false;
    1799             : 
    1800           0 :     nsCOMPtr<mozIDOMWindowProxy> activeWindow;
    1801           0 :     fm->GetActiveWindow(getter_AddRefs(activeWindow));
    1802           0 :     if (activeWindow != rootWin)
    1803           0 :       return false;
    1804             : 
    1805             :     // only allow popups in visible frames
    1806             :     bool visible;
    1807           0 :     baseWin->GetVisibility(&visible);
    1808           0 :     if (!visible)
    1809           0 :       return false;
    1810             :   }
    1811             : 
    1812             :   // platforms respond differently when an popup is opened in a minimized
    1813             :   // window, so this is always disabled.
    1814           0 :   nsCOMPtr<nsIWidget> mainWidget;
    1815           0 :   baseWin->GetMainWidget(getter_AddRefs(mainWidget));
    1816           0 :   if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
    1817           0 :     return false;
    1818             :   }
    1819             : 
    1820             : #ifdef XP_MACOSX
    1821             :   if (rootWin) {
    1822             :     auto globalWin = nsGlobalWindow::Cast(rootWin.get());
    1823             :     if (globalWin->IsInModalState()) {
    1824             :       return false;
    1825             :     }
    1826             :   }
    1827             : #endif
    1828             : 
    1829             :   // cannot open a popup that is a submenu of a menupopup that isn't open.
    1830           0 :   nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
    1831           0 :   if (menuFrame) {
    1832           0 :     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
    1833           0 :     if (parentPopup && !parentPopup->IsOpen())
    1834           0 :       return false;
    1835             :   }
    1836             : 
    1837           0 :   return true;
    1838             : }
    1839             : 
    1840             : void
    1841           7 : nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
    1842             : {
    1843             :   // when a popup frame is destroyed, just unhook it from the list of popups
    1844           7 :   if (mTimerMenu == aPopup) {
    1845           0 :     if (mCloseTimer) {
    1846           0 :       mCloseTimer->Cancel();
    1847           0 :       mCloseTimer = nullptr;
    1848             :     }
    1849           0 :     mTimerMenu = nullptr;
    1850             :   }
    1851             : 
    1852          14 :   nsTArray<nsMenuPopupFrame *> popupsToHide;
    1853             : 
    1854           7 :   nsMenuChainItem* item = mPopups;
    1855           7 :   while (item) {
    1856           0 :     nsMenuPopupFrame* frame = item->Frame();
    1857           0 :     if (frame == aPopup) {
    1858             :       // XXXndeakin shouldn't this only happen for menus?
    1859           0 :       if (!item->IsNoAutoHide() && frame->PopupState() != ePopupInvisible) {
    1860             :         // Iterate through any child menus and hide them as well, since the
    1861             :         // parent is going away. We won't remove them from the list yet, just
    1862             :         // hide them, as they will be removed from the list when this function
    1863             :         // gets called for that child frame.
    1864           0 :         nsMenuChainItem* child = item->GetChild();
    1865           0 :         while (child) {
    1866             :           // if the popup is a child frame of the menu that was destroyed, add
    1867             :           // it to the list of popups to hide. Don't bother with the events
    1868             :           // since the frames are going away. If the child menu is not a child
    1869             :           // frame, for example, a context menu, use HidePopup instead, but call
    1870             :           // it asynchronously since we are in the middle of frame destruction.
    1871           0 :           nsMenuPopupFrame* childframe = child->Frame();
    1872           0 :           if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
    1873           0 :             popupsToHide.AppendElement(childframe);
    1874             :           }
    1875             :           else {
    1876             :             // HidePopup will take care of hiding any of its children, so
    1877             :             // break out afterwards
    1878           0 :             HidePopup(child->Content(), false, false, true, false);
    1879           0 :             break;
    1880             :           }
    1881             : 
    1882           0 :           child = child->GetChild();
    1883             :         }
    1884             :       }
    1885             : 
    1886           0 :       item->Detach(&mPopups);
    1887           0 :       delete item;
    1888           0 :       break;
    1889             :     }
    1890             : 
    1891           0 :     item = item->GetParent();
    1892             :   }
    1893             : 
    1894           7 :   HidePopupsInList(popupsToHide);
    1895           7 : }
    1896             : 
    1897             : bool
    1898           0 : nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
    1899             : {
    1900           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    1901           0 :   while (item && item->Frame() != aPopup) {
    1902           0 :     if (item->IsContextMenu())
    1903           0 :       return true;
    1904           0 :     item = item->GetParent();
    1905             :   }
    1906             : 
    1907           0 :   return false;
    1908             : }
    1909             : 
    1910             : void
    1911          11 : nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
    1912             : {
    1913          11 :   nsMenuChainItem* item = GetTopVisibleMenu();
    1914          11 :   if (item && aOldPopup == item->Content())
    1915           0 :     return;
    1916             : 
    1917          11 :   if (mWidget) {
    1918           0 :     mWidget->CaptureRollupEvents(nullptr, false);
    1919           0 :     mWidget = nullptr;
    1920             :   }
    1921             : 
    1922          11 :   if (item) {
    1923           0 :     nsMenuPopupFrame* popup = item->Frame();
    1924           0 :     mWidget = popup->GetWidget();
    1925           0 :     if (mWidget) {
    1926           0 :       mWidget->CaptureRollupEvents(nullptr, true);
    1927           0 :       popup->AttachedDismissalListener();
    1928             :     }
    1929             :   }
    1930             : 
    1931          11 :   UpdateKeyboardListeners();
    1932             : }
    1933             : 
    1934             : void
    1935          11 : nsXULPopupManager::UpdateKeyboardListeners()
    1936             : {
    1937          22 :   nsCOMPtr<EventTarget> newTarget;
    1938          11 :   bool isForMenu = false;
    1939          11 :   nsMenuChainItem* item = GetTopVisibleMenu();
    1940          11 :   if (item) {
    1941           0 :     if (item->IgnoreKeys() != eIgnoreKeys_True) {
    1942           0 :       newTarget = item->Content()->GetComposedDoc();
    1943             :     }
    1944           0 :     isForMenu = item->PopupType() == ePopupTypeMenu;
    1945             :   }
    1946          11 :   else if (mActiveMenuBar) {
    1947           0 :     newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
    1948           0 :     isForMenu = true;
    1949             :   }
    1950             : 
    1951          11 :   if (mKeyListener != newTarget) {
    1952           0 :     if (mKeyListener) {
    1953           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
    1954           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
    1955           0 :       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
    1956           0 :       mKeyListener = nullptr;
    1957           0 :       nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
    1958             :     }
    1959             : 
    1960           0 :     if (newTarget) {
    1961           0 :       newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
    1962           0 :       newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
    1963           0 :       newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
    1964           0 :       nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
    1965           0 :       mKeyListener = newTarget;
    1966             :     }
    1967             :   }
    1968          11 : }
    1969             : 
    1970             : void
    1971           0 : nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
    1972             : {
    1973             :   // Walk all of the menu's children, checking to see if any of them has a
    1974             :   // command attribute. If so, then several attributes must potentially be updated.
    1975             : 
    1976           0 :   nsCOMPtr<nsIDocument> document = aPopup->GetUncomposedDoc();
    1977           0 :   if (!document) {
    1978           0 :     return;
    1979             :   }
    1980             : 
    1981           0 :   for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
    1982             :        grandChild;
    1983           0 :        grandChild = grandChild->GetNextSibling()) {
    1984           0 :     if (grandChild->IsXULElement(nsGkAtoms::menugroup)) {
    1985           0 :       if (grandChild->GetChildCount() == 0) {
    1986           0 :         continue;
    1987             :       }
    1988           0 :       grandChild = grandChild->GetFirstChild();
    1989             :     }
    1990           0 :     if (grandChild->IsXULElement(nsGkAtoms::menuitem)) {
    1991             :       // See if we have a command attribute.
    1992           0 :       nsAutoString command;
    1993           0 :       grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
    1994           0 :       if (!command.IsEmpty()) {
    1995             :         // We do! Look it up in our document
    1996             :         RefPtr<dom::Element> commandElement =
    1997           0 :           document->GetElementById(command);
    1998           0 :         if (commandElement) {
    1999           0 :           nsAutoString commandValue;
    2000             :           // The menu's disabled state needs to be updated to match the command.
    2001           0 :           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
    2002           0 :             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true);
    2003             :           else
    2004           0 :             grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
    2005             : 
    2006             :           // The menu's label, accesskey checked and hidden states need to be updated
    2007             :           // to match the command. Note that unlike the disabled state if the
    2008             :           // command has *no* value, we assume the menu is supplying its own.
    2009           0 :           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
    2010           0 :             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true);
    2011             : 
    2012           0 :           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
    2013           0 :             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true);
    2014             : 
    2015           0 :           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
    2016           0 :             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true);
    2017             : 
    2018           0 :           if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue))
    2019           0 :             grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true);
    2020             :         }
    2021             :       }
    2022             :     }
    2023           0 :     if (!grandChild->GetNextSibling() &&
    2024           0 :         grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
    2025           0 :       grandChild = grandChild->GetParent();
    2026             :     }
    2027             :   }
    2028             : }
    2029             : 
    2030             : // Notify
    2031             : //
    2032             : // The item selection timer has fired, we might have to readjust the
    2033             : // selected item. There are two cases here that we are trying to deal with:
    2034             : //   (1) diagonal movement from a parent menu to a submenu passing briefly over
    2035             : //       other items, and
    2036             : //   (2) moving out from a submenu to a parent or grandparent menu.
    2037             : // In both cases, |mTimerMenu| is the menu item that might have an open submenu and
    2038             : // the first item in |mPopups| is the item the mouse is currently over, which could be
    2039             : // none of them.
    2040             : //
    2041             : // case (1):
    2042             : //  As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
    2043             : //  submenu, it probably passes through one or more sibilings (B). As the mouse passes
    2044             : //  through B, it becomes the current menu item and the timer is set and mTimerMenu is
    2045             : //  set to A. Before the timer fires, the mouse leaves the menu containing A and B and
    2046             : //  enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
    2047             : //  so we have to see if anything in A's children is selected (recall that even disabled
    2048             : //  items are selected, the style just doesn't show it). If that is the case, we need to
    2049             : //  set the selected item back to A.
    2050             : //
    2051             : // case (2);
    2052             : //  Item A has an open submenu, and in it there is an item (B) which also has an open
    2053             : //  submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
    2054             : //  submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
    2055             : //  the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
    2056             : //  the mouse is still within C. The correct behavior is to set the current item to C
    2057             : //  and close up the chain parented at A.
    2058             : //
    2059             : //  This brings up the question of is the logic of case (1) enough? The answer is no,
    2060             : //  and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
    2061             : //  child, and if it does, set the selected item to A. Because B has a submenu open, it
    2062             : //  is selected and as a result, A is set to be the selected item even though the mouse
    2063             : //  rests in C -- very wrong.
    2064             : //
    2065             : //  The solution is to use the same idea, but instead of only checking one level,
    2066             : //  drill all the way down to the deepest open submenu and check if it has something
    2067             : //  selected. Since the mouse is in a grandparent, it won't, and we know that we can
    2068             : //  safely close up A and all its children.
    2069             : //
    2070             : // The code below melds the two cases together.
    2071             : //
    2072             : void
    2073           0 : nsXULPopupManager::KillMenuTimer()
    2074             : {
    2075           0 :   if (mCloseTimer && mTimerMenu) {
    2076           0 :     mCloseTimer->Cancel();
    2077           0 :     mCloseTimer = nullptr;
    2078             : 
    2079           0 :     if (mTimerMenu->IsOpen())
    2080           0 :       HidePopup(mTimerMenu->GetContent(), false, false, true, false);
    2081             :   }
    2082             : 
    2083           0 :   mTimerMenu = nullptr;
    2084           0 : }
    2085             : 
    2086             : void
    2087           0 : nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
    2088             : {
    2089           0 :   if (mCloseTimer && mTimerMenu == aMenuParent) {
    2090           0 :     mCloseTimer->Cancel();
    2091           0 :     mCloseTimer = nullptr;
    2092           0 :     mTimerMenu = nullptr;
    2093             :   }
    2094           0 : }
    2095             : 
    2096             : bool
    2097           0 : nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
    2098             :                                             nsMenuPopupFrame* aFrame)
    2099             : {
    2100             :   // On Windows, don't check shortcuts when the accelerator key is down.
    2101             : #ifdef XP_WIN
    2102             :   WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
    2103             :   if (evt && evt->IsAccel()) {
    2104             :     return false;
    2105             :   }
    2106             : #endif
    2107             : 
    2108           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    2109           0 :   if (!aFrame && item)
    2110           0 :     aFrame = item->Frame();
    2111             : 
    2112           0 :   if (aFrame) {
    2113             :     bool action;
    2114           0 :     nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
    2115           0 :     if (result) {
    2116           0 :       aFrame->ChangeMenuItem(result, false, true);
    2117           0 :       if (action) {
    2118           0 :         WidgetGUIEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsGUIEvent();
    2119           0 :         nsMenuFrame* menuToOpen = result->Enter(evt);
    2120           0 :         if (menuToOpen) {
    2121           0 :           nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
    2122           0 :           ShowMenu(content, true, false);
    2123             :         }
    2124             :       }
    2125           0 :       return true;
    2126             :     }
    2127             : 
    2128           0 :     return false;
    2129             :   }
    2130             : 
    2131           0 :   if (mActiveMenuBar) {
    2132           0 :     nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
    2133           0 :     if (result) {
    2134           0 :       mActiveMenuBar->SetActive(true);
    2135           0 :       result->OpenMenu(true);
    2136           0 :       return true;
    2137             :     }
    2138             :   }
    2139             : 
    2140           0 :   return false;
    2141             : }
    2142             : 
    2143             : 
    2144             : bool
    2145           0 : nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode)
    2146             : {
    2147             :   // navigate up through the open menus, looking for the topmost one
    2148             :   // in the same hierarchy
    2149           0 :   nsMenuChainItem* item = nullptr;
    2150           0 :   nsMenuChainItem* nextitem = GetTopVisibleMenu();
    2151             : 
    2152           0 :   while (nextitem) {
    2153           0 :     item = nextitem;
    2154           0 :     nextitem = item->GetParent();
    2155             : 
    2156           0 :     if (nextitem) {
    2157             :       // stop if the parent isn't a menu
    2158           0 :       if (!nextitem->IsMenu())
    2159           0 :         break;
    2160             : 
    2161             :       // check to make sure that the parent is actually the parent menu. It won't
    2162             :       // be if the parent is in a different frame hierarchy, for example, for a
    2163             :       // context menu opened on another menu.
    2164           0 :       nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
    2165           0 :       nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
    2166           0 :       if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
    2167           0 :         break;
    2168             :       }
    2169             :     }
    2170             :   }
    2171             : 
    2172             :   nsIFrame* itemFrame;
    2173           0 :   if (item)
    2174           0 :     itemFrame = item->Frame();
    2175           0 :   else if (mActiveMenuBar)
    2176           0 :     itemFrame = mActiveMenuBar;
    2177             :   else
    2178           0 :     return false;
    2179             : 
    2180             :   nsNavigationDirection theDirection;
    2181           0 :   NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END &&
    2182             :                  aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code");
    2183           0 :   theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
    2184             : 
    2185             :   // if a popup is open, first check for navigation within the popup
    2186           0 :   if (item && HandleKeyboardNavigationInPopup(item, theDirection))
    2187           0 :     return true;
    2188             : 
    2189             :   // no popup handled the key, so check the active menubar, if any
    2190           0 :   if (mActiveMenuBar) {
    2191           0 :     nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
    2192             : 
    2193           0 :     if (NS_DIRECTION_IS_INLINE(theDirection)) {
    2194           0 :       nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
    2195           0 :                               GetNextMenuItem(mActiveMenuBar, currentMenu, false, true) :
    2196           0 :                               GetPreviousMenuItem(mActiveMenuBar, currentMenu, false, true);
    2197           0 :       mActiveMenuBar->ChangeMenuItem(nextItem, true, true);
    2198           0 :       return true;
    2199             :     }
    2200           0 :     else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
    2201             :       // Open the menu and select its first item.
    2202           0 :       if (currentMenu) {
    2203           0 :         nsCOMPtr<nsIContent> content = currentMenu->GetContent();
    2204           0 :         ShowMenu(content, true, false);
    2205             :       }
    2206           0 :       return true;
    2207             :     }
    2208             :   }
    2209             : 
    2210           0 :   return false;
    2211             : }
    2212             : 
    2213             : bool
    2214           0 : nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
    2215             :                                                    nsMenuPopupFrame* aFrame,
    2216             :                                                    nsNavigationDirection aDir)
    2217             : {
    2218           0 :   NS_ASSERTION(aFrame, "aFrame is null");
    2219           0 :   NS_ASSERTION(!item || item->Frame() == aFrame,
    2220             :                "aFrame is expected to be equal to item->Frame()");
    2221             : 
    2222           0 :   nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
    2223             : 
    2224           0 :   aFrame->ClearIncrementalString();
    2225             : 
    2226             :   // This method only gets called if we're open.
    2227           0 :   if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
    2228             :     // We've been opened, but we haven't had anything selected.
    2229             :     // We can handle End, but our parent handles Start.
    2230           0 :     if (aDir == eNavigationDirection_End) {
    2231           0 :       nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
    2232           0 :       if (nextItem) {
    2233           0 :         aFrame->ChangeMenuItem(nextItem, false, true);
    2234           0 :         return true;
    2235             :       }
    2236             :     }
    2237           0 :     return false;
    2238             :   }
    2239             : 
    2240           0 :   bool isContainer = false;
    2241           0 :   bool isOpen = false;
    2242           0 :   if (currentMenu) {
    2243           0 :     isOpen = currentMenu->IsOpen();
    2244           0 :     isContainer = currentMenu->IsMenu();
    2245           0 :     if (isOpen) {
    2246             :       // for an open popup, have the child process the event
    2247           0 :       nsMenuChainItem* child = item ? item->GetChild() : nullptr;
    2248           0 :       if (child && HandleKeyboardNavigationInPopup(child, aDir))
    2249           0 :         return true;
    2250             :     }
    2251           0 :     else if (aDir == eNavigationDirection_End &&
    2252           0 :              isContainer && !currentMenu->IsDisabled()) {
    2253             :       // The menu is not yet open. Open it and select the first item.
    2254           0 :       nsCOMPtr<nsIContent> content = currentMenu->GetContent();
    2255           0 :       ShowMenu(content, true, false);
    2256           0 :       return true;
    2257             :     }
    2258             :   }
    2259             : 
    2260             :   // For block progression, we can move in either direction
    2261           0 :   if (NS_DIRECTION_IS_BLOCK(aDir) ||
    2262           0 :       NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
    2263             :     nsMenuFrame* nextItem;
    2264             : 
    2265           0 :     if (aDir == eNavigationDirection_Before ||
    2266             :         aDir == eNavigationDirection_After) {
    2267             :       // Cursor navigation does not wrap on Mac or for menulists on Windows.
    2268             :       bool wrap =
    2269             : #ifdef XP_WIN
    2270             :         aFrame->IsMenuList() ? false : true;
    2271             : #elif defined XP_MACOSX
    2272             :         false;
    2273             : #else
    2274           0 :         true;
    2275             : #endif
    2276             : 
    2277           0 :       if (aDir == eNavigationDirection_Before) {
    2278           0 :         nextItem = GetPreviousMenuItem(aFrame, currentMenu, true, wrap);
    2279             :       } else {
    2280           0 :         nextItem = GetNextMenuItem(aFrame, currentMenu, true, wrap);
    2281           0 :       }
    2282           0 :     } else if (aDir == eNavigationDirection_First) {
    2283           0 :       nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
    2284             :     } else {
    2285           0 :       nextItem = GetPreviousMenuItem(aFrame, nullptr, true, false);
    2286             :     }
    2287             : 
    2288           0 :     if (nextItem) {
    2289           0 :       aFrame->ChangeMenuItem(nextItem, false, true);
    2290           0 :       return true;
    2291           0 :     }
    2292             :   }
    2293           0 :   else if (currentMenu && isContainer && isOpen) {
    2294           0 :     if (aDir == eNavigationDirection_Start) {
    2295             :       // close a submenu when Left is pressed
    2296           0 :       nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
    2297           0 :       if (popupFrame)
    2298           0 :         HidePopup(popupFrame->GetContent(), false, false, false, false);
    2299           0 :       return true;
    2300             :     }
    2301             :   }
    2302             : 
    2303           0 :   return false;
    2304             : }
    2305             : 
    2306             : bool
    2307           0 : nsXULPopupManager::HandleKeyboardEventWithKeyCode(
    2308             :                         nsIDOMKeyEvent* aKeyEvent,
    2309             :                         nsMenuChainItem* aTopVisibleMenuItem)
    2310             : {
    2311             :   uint32_t keyCode;
    2312           0 :   aKeyEvent->GetKeyCode(&keyCode);
    2313             : 
    2314             :   // Escape should close panels, but the other keys should have no effect.
    2315           0 :   if (aTopVisibleMenuItem &&
    2316           0 :       aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
    2317           0 :     if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) {
    2318           0 :       HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
    2319           0 :       aKeyEvent->AsEvent()->StopPropagation();
    2320           0 :       aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2321           0 :       aKeyEvent->AsEvent()->PreventDefault();
    2322             :     }
    2323           0 :     return true;
    2324             :   }
    2325             : 
    2326           0 :   bool consume = (aTopVisibleMenuItem || mActiveMenuBar);
    2327           0 :   switch (keyCode) {
    2328             :     case nsIDOMKeyEvent::DOM_VK_UP:
    2329             :     case nsIDOMKeyEvent::DOM_VK_DOWN:
    2330             : #ifndef XP_MACOSX
    2331             :       // roll up the popup when alt+up/down are pressed within a menulist.
    2332             :       bool alt;
    2333           0 :       aKeyEvent->GetAltKey(&alt);
    2334           0 :       if (alt && aTopVisibleMenuItem && aTopVisibleMenuItem->Frame()->IsMenuList()) {
    2335           0 :         Rollup(0, false, nullptr, nullptr);
    2336           0 :         break;
    2337             :       }
    2338             :       MOZ_FALLTHROUGH;
    2339             : #endif
    2340             : 
    2341             :     case nsIDOMKeyEvent::DOM_VK_LEFT:
    2342             :     case nsIDOMKeyEvent::DOM_VK_RIGHT:
    2343             :     case nsIDOMKeyEvent::DOM_VK_HOME:
    2344             :     case nsIDOMKeyEvent::DOM_VK_END:
    2345           0 :       HandleKeyboardNavigation(keyCode);
    2346           0 :       break;
    2347             : 
    2348             :     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
    2349             :     case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
    2350           0 :       if (aTopVisibleMenuItem) {
    2351           0 :         aTopVisibleMenuItem->Frame()->ChangeByPage(keyCode == nsIDOMKeyEvent::DOM_VK_PAGE_UP);
    2352             :       }
    2353           0 :       break;
    2354             : 
    2355             :     case nsIDOMKeyEvent::DOM_VK_ESCAPE:
    2356             :       // Pressing Escape hides one level of menus only. If no menu is open,
    2357             :       // check if a menubar is active and inform it that a menu closed. Even
    2358             :       // though in this latter case, a menu didn't actually close, the effect
    2359             :       // ends up being the same. Similar for the tab key below.
    2360           0 :       if (aTopVisibleMenuItem) {
    2361           0 :         HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
    2362           0 :       } else if (mActiveMenuBar) {
    2363           0 :         mActiveMenuBar->MenuClosed();
    2364             :       }
    2365           0 :       break;
    2366             : 
    2367             :     case nsIDOMKeyEvent::DOM_VK_TAB:
    2368             : #ifndef XP_MACOSX
    2369             :     case nsIDOMKeyEvent::DOM_VK_F10:
    2370             : #endif
    2371           0 :       if (aTopVisibleMenuItem &&
    2372           0 :           !aTopVisibleMenuItem->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None,
    2373             :            nsGkAtoms::activateontab, nsGkAtoms::_true, eCaseMatters)) {
    2374             :         // close popups or deactivate menubar when Tab or F10 are pressed
    2375           0 :         Rollup(0, false, nullptr, nullptr);
    2376           0 :         break;
    2377           0 :       } else if (mActiveMenuBar) {
    2378           0 :         mActiveMenuBar->MenuClosed();
    2379           0 :         break;
    2380             :       }
    2381             :       // Intentional fall-through to RETURN case
    2382             :       MOZ_FALLTHROUGH;
    2383             : 
    2384             :     case nsIDOMKeyEvent::DOM_VK_RETURN: {
    2385             :       // If there is a popup open, check if the current item needs to be opened.
    2386             :       // Otherwise, tell the active menubar, if any, to activate the menu. The
    2387             :       // Enter method will return a menu if one needs to be opened as a result.
    2388           0 :       nsMenuFrame* menuToOpen = nullptr;
    2389           0 :       WidgetGUIEvent* GUIEvent = aKeyEvent->AsEvent()->
    2390           0 :         WidgetEventPtr()->AsGUIEvent();
    2391             : 
    2392           0 :       if (aTopVisibleMenuItem) {
    2393           0 :         menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
    2394           0 :       } else if (mActiveMenuBar) {
    2395           0 :         menuToOpen = mActiveMenuBar->Enter(GUIEvent);
    2396             :       }
    2397           0 :       if (menuToOpen) {
    2398           0 :         nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
    2399           0 :         ShowMenu(content, true, false);
    2400             :       }
    2401           0 :       break;
    2402             :     }
    2403             : 
    2404             :     default:
    2405           0 :       return false;
    2406             :   }
    2407             : 
    2408           0 :   if (consume) {
    2409           0 :     aKeyEvent->AsEvent()->StopPropagation();
    2410           0 :     aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2411           0 :     aKeyEvent->AsEvent()->PreventDefault();
    2412             :   }
    2413           0 :   return true;
    2414             : }
    2415             : 
    2416             : nsMenuFrame*
    2417           0 : nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent,
    2418             :                                    nsMenuFrame* aStart,
    2419             :                                    bool aIsPopup,
    2420             :                                    bool aWrap)
    2421             : {
    2422           0 :   nsPresContext* presContext = aParent->PresContext();
    2423             :   auto insertion = presContext->PresShell()->
    2424           0 :     FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
    2425           0 :   nsContainerFrame* immediateParent = insertion.mParentFrame;
    2426           0 :   if (!immediateParent)
    2427           0 :     immediateParent = aParent;
    2428             : 
    2429           0 :   nsIFrame* currFrame = nullptr;
    2430           0 :   if (aStart) {
    2431           0 :     if (aStart->GetNextSibling())
    2432           0 :       currFrame = aStart->GetNextSibling();
    2433           0 :     else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2434           0 :       currFrame = aStart->GetParent()->GetNextSibling();
    2435             :   }
    2436             :   else
    2437           0 :     currFrame = immediateParent->PrincipalChildList().FirstChild();
    2438             : 
    2439           0 :   while (currFrame) {
    2440             :     // See if it's a menu item.
    2441           0 :     nsIContent* currFrameContent = currFrame->GetContent();
    2442           0 :     if (IsValidMenuItem(currFrameContent, aIsPopup)) {
    2443           0 :       return do_QueryFrame(currFrame);
    2444             :     }
    2445           0 :     if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
    2446           0 :         currFrameContent->GetChildCount() > 0)
    2447           0 :       currFrame = currFrame->PrincipalChildList().FirstChild();
    2448           0 :     else if (!currFrame->GetNextSibling() &&
    2449           0 :              currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2450           0 :       currFrame = currFrame->GetParent()->GetNextSibling();
    2451             :     else
    2452           0 :       currFrame = currFrame->GetNextSibling();
    2453             :   }
    2454             : 
    2455           0 :   if (!aWrap) {
    2456           0 :     return aStart;
    2457             :   }
    2458             : 
    2459           0 :   currFrame = immediateParent->PrincipalChildList().FirstChild();
    2460             : 
    2461             :   // Still don't have anything. Try cycling from the beginning.
    2462           0 :   while (currFrame && currFrame != aStart) {
    2463             :     // See if it's a menu item.
    2464           0 :     nsIContent* currFrameContent = currFrame->GetContent();
    2465           0 :     if (IsValidMenuItem(currFrameContent, aIsPopup)) {
    2466           0 :       return do_QueryFrame(currFrame);
    2467             :     }
    2468           0 :     if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
    2469           0 :         currFrameContent->GetChildCount() > 0)
    2470           0 :       currFrame = currFrame->PrincipalChildList().FirstChild();
    2471           0 :     else if (!currFrame->GetNextSibling() &&
    2472           0 :              currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2473           0 :       currFrame = currFrame->GetParent()->GetNextSibling();
    2474             :     else
    2475           0 :       currFrame = currFrame->GetNextSibling();
    2476             :   }
    2477             : 
    2478             :   // No luck. Just return our start value.
    2479           0 :   return aStart;
    2480             : }
    2481             : 
    2482             : nsMenuFrame*
    2483           0 : nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent,
    2484             :                                        nsMenuFrame* aStart,
    2485             :                                        bool aIsPopup,
    2486             :                                        bool aWrap)
    2487             : {
    2488           0 :   nsPresContext* presContext = aParent->PresContext();
    2489             :   auto insertion = presContext->PresShell()->
    2490           0 :     FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
    2491           0 :   nsContainerFrame* immediateParent = insertion.mParentFrame;
    2492           0 :   if (!immediateParent)
    2493           0 :     immediateParent = aParent;
    2494             : 
    2495           0 :   const nsFrameList& frames(immediateParent->PrincipalChildList());
    2496             : 
    2497           0 :   nsIFrame* currFrame = nullptr;
    2498           0 :   if (aStart) {
    2499           0 :     if (aStart->GetPrevSibling())
    2500           0 :       currFrame = aStart->GetPrevSibling();
    2501           0 :     else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2502           0 :       currFrame = aStart->GetParent()->GetPrevSibling();
    2503             :   }
    2504             :   else
    2505           0 :     currFrame = frames.LastChild();
    2506             : 
    2507           0 :   while (currFrame) {
    2508             :     // See if it's a menu item.
    2509           0 :     nsIContent* currFrameContent = currFrame->GetContent();
    2510           0 :     if (IsValidMenuItem(currFrameContent, aIsPopup)) {
    2511           0 :       return do_QueryFrame(currFrame);
    2512             :     }
    2513           0 :     if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
    2514           0 :         currFrameContent->GetChildCount() > 0) {
    2515           0 :       const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
    2516           0 :       currFrame = menugroupFrames.LastChild();
    2517             :     }
    2518           0 :     else if (!currFrame->GetPrevSibling() &&
    2519           0 :              currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2520           0 :       currFrame = currFrame->GetParent()->GetPrevSibling();
    2521             :     else
    2522           0 :       currFrame = currFrame->GetPrevSibling();
    2523             :   }
    2524             : 
    2525           0 :   if (!aWrap) {
    2526           0 :     return aStart;
    2527             :   }
    2528             : 
    2529           0 :   currFrame = frames.LastChild();
    2530             : 
    2531             :   // Still don't have anything. Try cycling from the end.
    2532           0 :   while (currFrame && currFrame != aStart) {
    2533             :     // See if it's a menu item.
    2534           0 :     nsIContent* currFrameContent = currFrame->GetContent();
    2535           0 :     if (IsValidMenuItem(currFrameContent, aIsPopup)) {
    2536           0 :       return do_QueryFrame(currFrame);
    2537             :     }
    2538           0 :     if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
    2539           0 :         currFrameContent->GetChildCount() > 0) {
    2540           0 :       const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
    2541           0 :       currFrame = menugroupFrames.LastChild();
    2542             :     }
    2543           0 :     else if (!currFrame->GetPrevSibling() &&
    2544           0 :              currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
    2545           0 :       currFrame = currFrame->GetParent()->GetPrevSibling();
    2546             :     else
    2547           0 :       currFrame = currFrame->GetPrevSibling();
    2548             :   }
    2549             : 
    2550             :   // No luck. Just return our start value.
    2551           0 :   return aStart;
    2552             : }
    2553             : 
    2554             : bool
    2555           0 : nsXULPopupManager::IsValidMenuItem(nsIContent* aContent, bool aOnPopup)
    2556             : {
    2557           0 :   if (aContent->IsXULElement()) {
    2558           0 :     if (!aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
    2559           0 :       return false;
    2560             :     }
    2561             :   }
    2562           0 :   else if (!aOnPopup || !aContent->IsHTMLElement(nsGkAtoms::option)) {
    2563           0 :     return false;
    2564             :   }
    2565             : 
    2566           0 :   nsMenuFrame* menuFrame = do_QueryFrame(aContent->GetPrimaryFrame());
    2567             : 
    2568           0 :   bool skipNavigatingDisabledMenuItem = true;
    2569           0 :   if (aOnPopup && (!menuFrame || menuFrame->GetParentMenuListType() == eNotMenuList)) {
    2570           0 :     skipNavigatingDisabledMenuItem =
    2571           0 :       LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
    2572             :                           0) != 0;
    2573             :   }
    2574             : 
    2575           0 :   return !(skipNavigatingDisabledMenuItem &&
    2576           0 :            aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
    2577           0 :                                  nsGkAtoms::_true, eCaseMatters));
    2578             : }
    2579             : 
    2580             : nsresult
    2581           0 : nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent)
    2582             : {
    2583           0 :   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
    2584           0 :   NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
    2585             : 
    2586             :   //handlers shouldn't be triggered by non-trusted events.
    2587           0 :   bool trustedEvent = false;
    2588           0 :   aEvent->GetIsTrusted(&trustedEvent);
    2589           0 :   if (!trustedEvent) {
    2590           0 :     return NS_OK;
    2591             :   }
    2592             : 
    2593           0 :   nsAutoString eventType;
    2594           0 :   aEvent->GetType(eventType);
    2595           0 :   if (eventType.EqualsLiteral("keyup")) {
    2596           0 :     return KeyUp(keyEvent);
    2597             :   }
    2598           0 :   if (eventType.EqualsLiteral("keydown")) {
    2599           0 :     return KeyDown(keyEvent);
    2600             :   }
    2601           0 :   if (eventType.EqualsLiteral("keypress")) {
    2602           0 :     return KeyPress(keyEvent);
    2603             :   }
    2604             : 
    2605           0 :   NS_ABORT();
    2606             : 
    2607           0 :   return NS_OK;
    2608             : }
    2609             : 
    2610             : nsresult
    2611           0 : nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys)
    2612             : {
    2613           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    2614           0 :   if (item) {
    2615           0 :     item->SetIgnoreKeys(aIgnoreKeys ? eIgnoreKeys_True : eIgnoreKeys_Shortcuts);
    2616             :   }
    2617           0 :   UpdateKeyboardListeners();
    2618           0 :   return NS_OK;
    2619             : }
    2620             : 
    2621             : nsresult
    2622           0 : nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent)
    2623             : {
    2624             :   // don't do anything if a menu isn't open or a menubar isn't active
    2625           0 :   if (!mActiveMenuBar) {
    2626           0 :     nsMenuChainItem* item = GetTopVisibleMenu();
    2627           0 :     if (!item || item->PopupType() != ePopupTypeMenu)
    2628           0 :       return NS_OK;
    2629             : 
    2630           0 :     if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) {
    2631           0 :       aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2632           0 :       return NS_OK;
    2633             :     }
    2634             :   }
    2635             : 
    2636           0 :   aKeyEvent->AsEvent()->StopPropagation();
    2637           0 :   aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2638           0 :   aKeyEvent->AsEvent()->PreventDefault();
    2639             : 
    2640           0 :   return NS_OK; // I am consuming event
    2641             : }
    2642             : 
    2643             : nsresult
    2644           0 : nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
    2645             : {
    2646           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    2647           0 :   if (item && item->Frame()->IsMenuLocked())
    2648           0 :     return NS_OK;
    2649             : 
    2650           0 :   if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
    2651           0 :     return NS_OK;
    2652             :   }
    2653             : 
    2654             :   // don't do anything if a menu isn't open or a menubar isn't active
    2655           0 :   if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
    2656           0 :     return NS_OK;
    2657             : 
    2658             :   // Since a menu was open, stop propagation of the event to keep other event
    2659             :   // listeners from becoming confused.
    2660           0 :   if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) {
    2661           0 :     aKeyEvent->AsEvent()->StopPropagation();
    2662             :   }
    2663             : 
    2664           0 :   int32_t menuAccessKey = -1;
    2665             : 
    2666             :   // If the key just pressed is the access key (usually Alt),
    2667             :   // dismiss and unfocus the menu.
    2668             : 
    2669           0 :   nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
    2670           0 :   if (menuAccessKey) {
    2671             :     uint32_t theChar;
    2672           0 :     aKeyEvent->GetKeyCode(&theChar);
    2673             : 
    2674           0 :     if (theChar == (uint32_t)menuAccessKey) {
    2675           0 :       bool ctrl = false;
    2676           0 :       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
    2677           0 :         aKeyEvent->GetCtrlKey(&ctrl);
    2678           0 :       bool alt=false;
    2679           0 :       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
    2680           0 :         aKeyEvent->GetAltKey(&alt);
    2681           0 :       bool shift=false;
    2682           0 :       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
    2683           0 :         aKeyEvent->GetShiftKey(&shift);
    2684           0 :       bool meta=false;
    2685           0 :       if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META)
    2686           0 :         aKeyEvent->GetMetaKey(&meta);
    2687           0 :       if (!(ctrl || alt || shift || meta)) {
    2688             :         // The access key just went down and no other
    2689             :         // modifiers are already down.
    2690           0 :         nsMenuChainItem* item = GetTopVisibleMenu();
    2691           0 :         if (item && !item->Frame()->IsMenuList()) {
    2692           0 :           Rollup(0, false, nullptr, nullptr);
    2693           0 :         } else if (mActiveMenuBar) {
    2694           0 :           mActiveMenuBar->MenuClosed();
    2695             :         }
    2696             : 
    2697             :         // Clear the item to avoid bugs as it may have been deleted during rollup.
    2698           0 :         item = nullptr;
    2699             :       }
    2700           0 :       aKeyEvent->AsEvent()->StopPropagation();
    2701           0 :       aKeyEvent->AsEvent()->PreventDefault();
    2702             :     }
    2703             :   }
    2704             : 
    2705           0 :   aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2706           0 :   return NS_OK;
    2707             : }
    2708             : 
    2709             : nsresult
    2710           0 : nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
    2711             : {
    2712             :   // Don't check prevent default flag -- menus always get first shot at key events.
    2713             : 
    2714           0 :   nsMenuChainItem* item = GetTopVisibleMenu();
    2715           0 :   if (item &&
    2716           0 :       (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
    2717           0 :     return NS_OK;
    2718             :   }
    2719             : 
    2720           0 :   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
    2721           0 :   NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
    2722             :   // if a menu is open or a menubar is active, it consumes the key event
    2723           0 :   bool consume = (item || mActiveMenuBar);
    2724             : 
    2725           0 :   WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
    2726           0 :   bool isAccel = evt && evt->IsAccel();
    2727             : 
    2728             :   // When ignorekeys="shortcuts" is used, we don't call preventDefault on the
    2729             :   // key event when the accelerator key is pressed. This allows another
    2730             :   // listener to handle keys. For instance, this allows global shortcuts to
    2731             :   // still apply while a menu is open.
    2732           0 :   if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) {
    2733           0 :     consume = false;
    2734             :   }
    2735             : 
    2736           0 :   HandleShortcutNavigation(keyEvent, nullptr);
    2737             : 
    2738           0 :   aKeyEvent->AsEvent()->StopCrossProcessForwarding();
    2739           0 :   if (consume) {
    2740           0 :     aKeyEvent->AsEvent()->StopPropagation();
    2741           0 :     aKeyEvent->AsEvent()->PreventDefault();
    2742             :   }
    2743             : 
    2744           0 :   return NS_OK; // I am consuming event
    2745             : }
    2746             : 
    2747             : NS_IMETHODIMP
    2748           0 : nsXULPopupShowingEvent::Run()
    2749             : {
    2750           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    2751           0 :   if (pm) {
    2752           0 :     pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem, nullptr);
    2753             :   }
    2754             : 
    2755           0 :   return NS_OK;
    2756             : }
    2757             : 
    2758             : NS_IMETHODIMP
    2759           0 : nsXULPopupHidingEvent::Run()
    2760             : {
    2761           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    2762             : 
    2763           0 :   nsIDocument *document = mPopup->GetUncomposedDoc();
    2764           0 :   if (pm && document) {
    2765           0 :     nsIPresShell* presShell = document->GetShell();
    2766           0 :     if (presShell) {
    2767           0 :       nsPresContext* context = presShell->GetPresContext();
    2768           0 :       if (context) {
    2769           0 :         pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
    2770           0 :                                  context, mPopupType, mDeselectMenu, mIsRollup);
    2771             :       }
    2772             :     }
    2773             :   }
    2774             : 
    2775           0 :   return NS_OK;
    2776             : }
    2777             : 
    2778             : bool
    2779           0 : nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent *aPopup,
    2780             :                                             bool aIsContextMenu,
    2781             :                                             bool aSelectFirstItem)
    2782             : {
    2783             :   // The popuppositioned event only fires on arrow panels for now.
    2784           0 :   if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    2785             :                           nsGkAtoms::arrow, eCaseMatters)) {
    2786             :     nsCOMPtr<nsIRunnable> event =
    2787           0 :       new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem);
    2788           0 :     aPopup->OwnerDoc()->Dispatch("nsXULPopupPositionedEvent",
    2789             :                                  TaskCategory::Other,
    2790           0 :                                  event.forget());
    2791             : 
    2792           0 :     return true;
    2793             :   }
    2794             : 
    2795           0 :   return false;
    2796             : }
    2797             : 
    2798             : NS_IMETHODIMP
    2799           0 : nsXULPopupPositionedEvent::Run()
    2800             : {
    2801           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    2802           0 :   if (pm) {
    2803           0 :     nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
    2804           0 :     if (popupFrame) {
    2805             :       // At this point, hidePopup may have been called but it currently has no
    2806             :       // way to stop this event. However, if hidePopup was called, the popup
    2807             :       // will now be in the hiding or closed state. If we are in the shown or
    2808             :       // positioning state instead, we can assume that we are still clear to
    2809             :       // open/move the popup
    2810           0 :       nsPopupState state = popupFrame->PopupState();
    2811           0 :       if (state != ePopupPositioning && state != ePopupShown) {
    2812           0 :         return NS_OK;
    2813             :       }
    2814           0 :       nsEventStatus status = nsEventStatus_eIgnore;
    2815             :       WidgetMouseEvent event(true, eXULPopupPositioned, nullptr,
    2816           0 :                              WidgetMouseEvent::eReal);
    2817           0 :       EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(),
    2818           0 :                                 &event, nullptr, &status);
    2819             : 
    2820             :       // Get the popup frame and make sure it is still in the positioning
    2821             :       // state. If it isn't, someone may have tried to reshow or hide it
    2822             :       // during the popuppositioned event.
    2823             :       // Alternately, this event may have been fired in reponse to moving the
    2824             :       // popup rather than opening it. In that case, we are done.
    2825           0 :       nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
    2826           0 :       if (popupFrame && popupFrame->PopupState() == ePopupPositioning) {
    2827           0 :         pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu, mSelectFirstItem);
    2828             :       }
    2829             :     }
    2830             :   }
    2831             : 
    2832           0 :   return NS_OK;
    2833             : }
    2834             : 
    2835             : NS_IMETHODIMP
    2836           0 : nsXULMenuCommandEvent::Run()
    2837             : {
    2838           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    2839           0 :   if (!pm)
    2840           0 :     return NS_OK;
    2841             : 
    2842             :   // The order of the nsViewManager and nsIPresShell COM pointers is
    2843             :   // important below.  We want the pres shell to get released before the
    2844             :   // associated view manager on exit from this function.
    2845             :   // See bug 54233.
    2846             :   // XXXndeakin is this still needed?
    2847             : 
    2848           0 :   nsCOMPtr<nsIContent> popup;
    2849           0 :   nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
    2850           0 :   AutoWeakFrame weakFrame(menuFrame);
    2851           0 :   if (menuFrame && mFlipChecked) {
    2852           0 :     if (menuFrame->IsChecked()) {
    2853           0 :       mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
    2854             :     } else {
    2855           0 :       mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
    2856           0 :                      NS_LITERAL_STRING("true"), true);
    2857             :     }
    2858             :   }
    2859             : 
    2860           0 :   if (menuFrame && weakFrame.IsAlive()) {
    2861             :     // Find the popup that the menu is inside. Below, this popup will
    2862             :     // need to be hidden.
    2863           0 :     nsIFrame* frame = menuFrame->GetParent();
    2864           0 :     while (frame) {
    2865           0 :       nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
    2866           0 :       if (popupFrame) {
    2867           0 :         popup = popupFrame->GetContent();
    2868           0 :         break;
    2869             :       }
    2870           0 :       frame = frame->GetParent();
    2871             :     }
    2872             : 
    2873           0 :     nsPresContext* presContext = menuFrame->PresContext();
    2874           0 :     nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
    2875           0 :     RefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
    2876             :     mozilla::Unused << kungFuDeathGrip; // Not referred to directly within this function
    2877             : 
    2878             :     // Deselect ourselves.
    2879           0 :     if (mCloseMenuMode != CloseMenuMode_None)
    2880           0 :       menuFrame->SelectMenu(false);
    2881             : 
    2882           0 :     AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
    2883           0 :                                                         shell->GetDocument());
    2884           0 :     nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
    2885           0 :                                        mControl, mAlt, mShift, mMeta);
    2886             :   }
    2887             : 
    2888           0 :   if (popup && mCloseMenuMode != CloseMenuMode_None)
    2889           0 :     pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false);
    2890             : 
    2891           0 :   return NS_OK;
    2892             : }

Generated by: LCOV version 1.13