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