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/. */
7 #include "nsXULPopupManager.h"
8 #include "nsMenuFrame.h"
9 #include "nsMenuPopupFrame.h"
10 #include "nsMenuBarFrame.h"
11 #include "nsIPopupBoxObject.h"
12 #include "nsMenuBarListener.h"
13 #include "nsContentUtils.h"
14 #include "nsIDOMDocument.h"
15 #include "nsIDOMEvent.h"
16 #include "nsIDOMXULElement.h"
17 #include "nsIXULDocument.h"
18 #include "nsIXULTemplateBuilder.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsLayoutUtils.h"
21 #include "nsViewManager.h"
22 #include "nsIComponentManager.h"
24 #include "nsFocusManager.h"
25 #include "nsIDocShell.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsIBaseWindow.h"
29 #include "nsIDOMKeyEvent.h"
30 #include "nsIDOMMouseEvent.h"
32 #include "nsIDocument.h"
33 #include "nsPIWindowRoot.h"
34 #include "nsFrameManager.h"
35 #include "nsIObserverService.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
38 #include "mozilla/EventDispatcher.h"
39 #include "mozilla/EventStateManager.h"
40 #include "mozilla/LookAndFeel.h"
41 #include "mozilla/MouseEvents.h"
42 #include "mozilla/Services.h"
44 using namespace mozilla
;
45 using namespace mozilla::dom
;
47 static_assert(nsIDOMKeyEvent::DOM_VK_HOME
== nsIDOMKeyEvent::DOM_VK_END
+ 1 &&
48 nsIDOMKeyEvent::DOM_VK_LEFT
== nsIDOMKeyEvent::DOM_VK_END
+ 2 &&
49 nsIDOMKeyEvent::DOM_VK_UP
== nsIDOMKeyEvent::DOM_VK_END
+ 3 &&
50 nsIDOMKeyEvent::DOM_VK_RIGHT
== nsIDOMKeyEvent::DOM_VK_END
+ 4 &&
51 nsIDOMKeyEvent::DOM_VK_DOWN
== nsIDOMKeyEvent::DOM_VK_END
+ 5,
52 "nsXULPopupManager assumes some keyCode values are consecutive");
54 const nsNavigationDirection DirectionFromKeyCodeTable
[2][6] = {
56 eNavigationDirection_Last
, // nsIDOMKeyEvent::DOM_VK_END
57 eNavigationDirection_First
, // nsIDOMKeyEvent::DOM_VK_HOME
58 eNavigationDirection_Start
, // nsIDOMKeyEvent::DOM_VK_LEFT
59 eNavigationDirection_Before
, // nsIDOMKeyEvent::DOM_VK_UP
60 eNavigationDirection_End
, // nsIDOMKeyEvent::DOM_VK_RIGHT
61 eNavigationDirection_After
// nsIDOMKeyEvent::DOM_VK_DOWN
64 eNavigationDirection_Last
, // nsIDOMKeyEvent::DOM_VK_END
65 eNavigationDirection_First
, // nsIDOMKeyEvent::DOM_VK_HOME
66 eNavigationDirection_End
, // nsIDOMKeyEvent::DOM_VK_LEFT
67 eNavigationDirection_Before
, // nsIDOMKeyEvent::DOM_VK_UP
68 eNavigationDirection_Start
, // nsIDOMKeyEvent::DOM_VK_RIGHT
69 eNavigationDirection_After
// nsIDOMKeyEvent::DOM_VK_DOWN
73 nsXULPopupManager
* nsXULPopupManager::sInstance
= nullptr;
75 nsIContent
* nsMenuChainItem::Content()
77 return mFrame
->GetContent();
80 void nsMenuChainItem::SetParent(nsMenuChainItem
* aParent
)
83 NS_ASSERTION(mParent
->mChild
== this, "Unexpected - parent's child not set to this");
84 mParent
->mChild
= nullptr;
89 mParent
->mChild
->mParent
= nullptr;
90 mParent
->mChild
= this;
94 void nsMenuChainItem::Detach(nsMenuChainItem
** aRoot
)
96 // If the item has a child, set the child's parent to this item's parent,
97 // effectively removing the item from the chain. If the item has no child,
98 // just set the parent to null.
100 NS_ASSERTION(this != *aRoot
, "Unexpected - popup with child at end of chain");
101 mChild
->SetParent(mParent
);
104 // An item without a child should be the first item in the chain, so set
105 // the first item pointer, pointed to by aRoot, to the parent.
106 NS_ASSERTION(this == *aRoot
, "Unexpected - popup with no child not at end of chain");
112 NS_IMPL_ISUPPORTS(nsXULPopupManager
,
117 nsXULPopupManager::nsXULPopupManager() :
119 mCachedMousePoint(0, 0),
121 mActiveMenuBar(nullptr),
123 mNoHidePanels(nullptr),
126 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
128 obs
->AddObserver(this, "xpcom-shutdown", false);
132 nsXULPopupManager::~nsXULPopupManager()
134 NS_ASSERTION(!mPopups
&& !mNoHidePanels
, "XUL popups still open");
138 nsXULPopupManager::Init()
140 sInstance
= new nsXULPopupManager();
141 NS_ENSURE_TRUE(sInstance
, NS_ERROR_OUT_OF_MEMORY
);
142 NS_ADDREF(sInstance
);
147 nsXULPopupManager::Shutdown()
149 NS_IF_RELEASE(sInstance
);
153 nsXULPopupManager::Observe(nsISupports
*aSubject
,
155 const char16_t
*aData
)
157 if (!nsCRT::strcmp(aTopic
, "xpcom-shutdown")) {
159 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
160 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
161 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
162 mKeyListener
= nullptr;
164 mRangeParent
= nullptr;
165 // mOpeningPopup is cleared explicitly soon after using it.
166 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
168 obs
->RemoveObserver(this, "xpcom-shutdown");
176 nsXULPopupManager::GetInstance()
178 MOZ_ASSERT(sInstance
);
183 nsXULPopupManager::Rollup(uint32_t aCount
, const nsIntPoint
* pos
, nsIContent
** aLastRolledUp
)
185 bool consume
= false;
187 nsMenuChainItem
* item
= GetTopVisibleMenu();
190 // we need to get the popup that will be closed last, so that
191 // widget can keep track of it so it doesn't reopen if a mouse
192 // down event is going to processed.
193 // Keep going up the menu chain to get the first level menu. This will
194 // be the one that closes up last. It's possible that this menu doesn't
195 // end up closing because the popuphiding event was cancelled, but in
196 // that case we don't need to deal with the menu reopening as it will
197 // already still be open.
198 nsMenuChainItem
* first
= item
;
199 while (first
->GetParent())
200 first
= first
->GetParent();
201 *aLastRolledUp
= first
->Content();
204 consume
= item
->Frame()->ConsumeOutsideClicks();
205 // If the click was over the anchor, always consume the click. This way,
206 // clicking on a menu doesn't reopen the menu.
207 if (!consume
&& pos
) {
208 nsCOMPtr
<nsIContent
> anchor
= item
->Frame()->GetAnchor();
210 // Check if the anchor has indicated another node to use for checking
211 // for roll-up. That way, we can anchor a popup on anonymous content or
212 // an individual icon, while clicking elsewhere within a button or other
213 // container doesn't result in us re-opening the popup.
215 nsAutoString consumeAnchor
;
216 anchor
->GetAttr(kNameSpaceID_None
, nsGkAtoms::consumeanchor
,
218 if (!consumeAnchor
.IsEmpty()) {
219 nsIDocument
* doc
= anchor
->GetOwnerDocument();
220 nsIContent
* newAnchor
= doc
->GetElementById(consumeAnchor
);
227 if (anchor
&& anchor
->GetPrimaryFrame()) {
228 // It's possible that some other element is above the anchor at the same
229 // position, but the only thing that would happen is that the mouse
230 // event will get consumed, so here only a quick coordinates check is
231 // done rather than a slower complete check of what is at that location.
232 if (anchor
->GetPrimaryFrame()->GetScreenRect().Contains(*pos
)) {
238 // if a number of popups to close has been specified, determine the last
240 nsIContent
* lastPopup
= nullptr;
241 if (aCount
!= UINT32_MAX
) {
242 nsMenuChainItem
* last
= item
;
243 while (--aCount
&& last
->GetParent()) {
244 last
= last
->GetParent();
247 lastPopup
= last
->Content();
251 HidePopup(item
->Content(), true, true, false, true, lastPopup
);
257 ////////////////////////////////////////////////////////////////////////
258 bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
260 // should rollup only for autocomplete widgets
261 // XXXndeakin this should really be something the popup has more control over
263 nsMenuChainItem
* item
= GetTopVisibleMenu();
267 nsIContent
* content
= item
->Frame()->GetContent();
271 if (content
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::rolluponmousewheel
,
272 nsGkAtoms::_true
, eCaseMatters
))
275 if (content
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::rolluponmousewheel
,
276 nsGkAtoms::_false
, eCaseMatters
))
280 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, value
);
281 return StringBeginsWith(value
, NS_LITERAL_STRING("autocomplete"));
284 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
286 nsMenuChainItem
* item
= GetTopVisibleMenu();
290 nsMenuPopupFrame
* frame
= item
->Frame();
291 if (frame
->PopupType() != ePopupTypePanel
)
294 nsIContent
* content
= frame
->GetContent();
295 return !(content
&& content
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
296 nsGkAtoms::arrow
, eCaseMatters
));
299 // a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
300 bool nsXULPopupManager::ShouldRollupOnMouseActivate()
306 nsXULPopupManager::GetSubmenuWidgetChain(nsTArray
<nsIWidget
*> *aWidgetChain
)
308 // this method is used by the widget code to determine the list of popups
309 // that are open. If a mouse click occurs outside one of these popups, the
310 // panels will roll up. If the click is inside a popup, they will not roll up
311 uint32_t count
= 0, sameTypeCount
= 0;
313 NS_ASSERTION(aWidgetChain
, "null parameter");
314 nsMenuChainItem
* item
= GetTopVisibleMenu();
316 nsCOMPtr
<nsIWidget
> widget
= item
->Frame()->GetWidget();
317 NS_ASSERTION(widget
, "open popup has no widget");
318 aWidgetChain
->AppendElement(widget
.get());
319 // In the case when a menulist inside a panel is open, clicking in the
320 // panel should still roll up the menu, so if a different type is found,
322 nsMenuChainItem
* parent
= item
->GetParent();
323 if (!sameTypeCount
) {
325 if (!parent
|| item
->Frame()->PopupType() != parent
->Frame()->PopupType() ||
326 item
->IsContextMenu() != parent
->IsContextMenu()) {
327 sameTypeCount
= count
;
333 return sameTypeCount
;
337 nsXULPopupManager::GetRollupWidget()
339 nsMenuChainItem
* item
= GetTopVisibleMenu();
340 return item
? item
->Frame()->GetWidget() : nullptr;
344 nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow
* aWindow
)
346 // When the parent window is moved, adjust any child popups. Dismissable
347 // menus and panels are expected to roll up when a window is moved, so there
348 // is no need to check these popups, only the noautohide popups.
350 // The items are added to a list so that they can be adjusted bottom to top.
351 nsTArray
<nsMenuPopupFrame
*> list
;
353 nsMenuChainItem
* item
= mNoHidePanels
;
355 // only move popups that are within the same window and where auto
356 // positioning has not been disabled
357 nsMenuPopupFrame
* frame
= item
->Frame();
358 if (frame
->GetAutoPosition()) {
359 nsIContent
* popup
= frame
->GetContent();
361 nsIDocument
* document
= popup
->GetCurrentDoc();
363 nsPIDOMWindow
* window
= document
->GetWindow();
365 window
= window
->GetPrivateRoot();
366 if (window
== aWindow
) {
367 list
.AppendElement(frame
);
374 item
= item
->GetParent();
377 for (int32_t l
= list
.Length() - 1; l
>= 0; l
--) {
378 list
[l
]->SetPopupPosition(nullptr, true, false);
382 void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell
* aPresShell
)
384 if (aPresShell
->GetDocument()) {
385 AdjustPopupsOnWindowChange(aPresShell
->GetDocument()->GetWindow());
390 nsMenuPopupFrame
* GetPopupToMoveOrResize(nsIFrame
* aFrame
)
392 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(aFrame
);
396 // no point moving or resizing hidden popups
397 if (!menuPopupFrame
->IsVisible())
400 nsIWidget
* widget
= menuPopupFrame
->GetWidget();
401 if (widget
&& !widget
->IsVisible())
404 return menuPopupFrame
;
408 nsXULPopupManager::PopupMoved(nsIFrame
* aFrame
, nsIntPoint aPnt
)
410 nsMenuPopupFrame
* menuPopupFrame
= GetPopupToMoveOrResize(aFrame
);
414 nsView
* view
= menuPopupFrame
->GetView();
418 // Don't do anything if the popup is already at the specified location. This
419 // prevents recursive calls when a popup is positioned.
420 nsIntRect curDevSize
= view
->CalcWidgetBounds(eWindowType_popup
);
421 nsIWidget
* widget
= menuPopupFrame
->GetWidget();
422 if (curDevSize
.x
== aPnt
.x
&& curDevSize
.y
== aPnt
.y
&&
423 (!widget
|| widget
->GetClientOffset() == menuPopupFrame
->GetLastClientOffset())) {
427 // Update the popup's position using SetPopupPosition if the popup is
428 // anchored and at the parent level as these maintain their position
429 // relative to the parent window. Otherwise, just update the popup to
430 // the specified screen coordinates.
431 if (menuPopupFrame
->IsAnchored() &&
432 menuPopupFrame
->PopupLevel() == ePopupLevelParent
) {
433 menuPopupFrame
->SetPopupPosition(nullptr, true, false);
436 nsPresContext
* presContext
= menuPopupFrame
->PresContext();
437 aPnt
.x
= presContext
->DevPixelsToIntCSSPixels(aPnt
.x
);
438 aPnt
.y
= presContext
->DevPixelsToIntCSSPixels(aPnt
.y
);
439 menuPopupFrame
->MoveTo(aPnt
.x
, aPnt
.y
, false);
444 nsXULPopupManager::PopupResized(nsIFrame
* aFrame
, nsIntSize aSize
)
446 nsMenuPopupFrame
* menuPopupFrame
= GetPopupToMoveOrResize(aFrame
);
450 nsView
* view
= menuPopupFrame
->GetView();
454 nsIntRect curDevSize
= view
->CalcWidgetBounds(eWindowType_popup
);
455 // If the size is what we think it is, we have nothing to do.
456 if (curDevSize
.width
== aSize
.width
&& curDevSize
.height
== aSize
.height
)
459 // The size is different. Convert the actual size to css pixels and store it
460 // as 'width' and 'height' attributes on the popup.
461 nsPresContext
* presContext
= menuPopupFrame
->PresContext();
463 nsIntSize
newCSS(presContext
->DevPixelsToIntCSSPixels(aSize
.width
),
464 presContext
->DevPixelsToIntCSSPixels(aSize
.height
));
466 nsIContent
* popup
= menuPopupFrame
->GetContent();
467 nsAutoString width
, height
;
468 width
.AppendInt(newCSS
.width
);
469 height
.AppendInt(newCSS
.height
);
470 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::width
, width
, false);
471 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::height
, height
, true);
475 nsXULPopupManager::GetPopupFrameForContent(nsIContent
* aContent
, bool aShouldFlush
)
478 nsIDocument
*document
= aContent
->GetCurrentDoc();
480 nsCOMPtr
<nsIPresShell
> presShell
= document
->GetShell();
482 presShell
->FlushPendingNotifications(Flush_Layout
);
486 return do_QueryFrame(aContent
->GetPrimaryFrame());
490 nsXULPopupManager::GetTopVisibleMenu()
492 nsMenuChainItem
* item
= mPopups
;
493 while (item
&& item
->Frame()->PopupState() == ePopupInvisible
)
494 item
= item
->GetParent();
499 nsXULPopupManager::GetMouseLocation(nsIDOMNode
** aNode
, int32_t* aOffset
)
501 *aNode
= mRangeParent
;
502 NS_IF_ADDREF(*aNode
);
503 *aOffset
= mRangeOffset
;
507 nsXULPopupManager::InitTriggerEvent(nsIDOMEvent
* aEvent
, nsIContent
* aPopup
,
508 nsIContent
** aTriggerContent
)
510 mCachedMousePoint
= nsIntPoint(0, 0);
512 if (aTriggerContent
) {
513 *aTriggerContent
= nullptr;
515 // get the trigger content from the event
516 nsCOMPtr
<nsIContent
> target
= do_QueryInterface(
517 aEvent
->InternalDOMEvent()->GetTarget());
518 target
.forget(aTriggerContent
);
522 mCachedModifiers
= 0;
524 nsCOMPtr
<nsIDOMUIEvent
> uiEvent
= do_QueryInterface(aEvent
);
526 uiEvent
->GetRangeParent(getter_AddRefs(mRangeParent
));
527 uiEvent
->GetRangeOffset(&mRangeOffset
);
529 // get the event coordinates relative to the root frame of the document
530 // containing the popup.
531 NS_ASSERTION(aPopup
, "Expected a popup node");
532 WidgetEvent
* event
= aEvent
->GetInternalNSEvent();
534 WidgetInputEvent
* inputEvent
= event
->AsInputEvent();
536 mCachedModifiers
= inputEvent
->modifiers
;
538 nsIDocument
* doc
= aPopup
->GetCurrentDoc();
540 nsIPresShell
* presShell
= doc
->GetShell();
541 nsPresContext
* presContext
;
542 if (presShell
&& (presContext
= presShell
->GetPresContext())) {
543 nsPresContext
* rootDocPresContext
=
544 presContext
->GetRootPresContext();
545 if (!rootDocPresContext
)
547 nsIFrame
* rootDocumentRootFrame
= rootDocPresContext
->
548 PresShell()->FrameManager()->GetRootFrame();
549 if ((event
->mClass
== eMouseEventClass
||
550 event
->mClass
== eMouseScrollEventClass
||
551 event
->mClass
== eWheelEventClass
) &&
552 !event
->AsGUIEvent()->widget
) {
553 // no widget, so just use the client point if available
554 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
556 mouseEvent
->GetClientX(&clientPt
.x
);
557 mouseEvent
->GetClientY(&clientPt
.y
);
559 // XXX this doesn't handle IFRAMEs in transforms
560 nsPoint thisDocToRootDocOffset
= presShell
->FrameManager()->
561 GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame
);
562 // convert to device pixels
563 mCachedMousePoint
.x
= presContext
->AppUnitsToDevPixels(
564 nsPresContext::CSSPixelsToAppUnits(clientPt
.x
) + thisDocToRootDocOffset
.x
);
565 mCachedMousePoint
.y
= presContext
->AppUnitsToDevPixels(
566 nsPresContext::CSSPixelsToAppUnits(clientPt
.y
) + thisDocToRootDocOffset
.y
);
568 else if (rootDocumentRootFrame
) {
570 nsLayoutUtils::GetEventCoordinatesRelativeTo(event
, rootDocumentRootFrame
);
571 mCachedMousePoint
= nsIntPoint(rootDocPresContext
->AppUnitsToDevPixels(pnt
.x
),
572 rootDocPresContext
->AppUnitsToDevPixels(pnt
.y
));
579 mRangeParent
= nullptr;
585 nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame
* aMenuBar
, bool aActivate
)
588 mActiveMenuBar
= aMenuBar
;
589 else if (mActiveMenuBar
== aMenuBar
)
590 mActiveMenuBar
= nullptr;
592 UpdateKeyboardListeners();
596 nsXULPopupManager::ShowMenu(nsIContent
*aMenu
,
597 bool aSelectFirstItem
,
600 // generate any template content first. Otherwise, the menupopup may not
601 // have been created yet.
603 nsIContent
* element
= aMenu
;
605 nsCOMPtr
<nsIDOMXULElement
> xulelem
= do_QueryInterface(element
);
607 nsCOMPtr
<nsIXULTemplateBuilder
> builder
;
608 xulelem
->GetBuilder(getter_AddRefs(builder
));
610 builder
->CreateContents(aMenu
, true);
614 element
= element
->GetParent();
618 nsMenuFrame
* menuFrame
= do_QueryFrame(aMenu
->GetPrimaryFrame());
619 if (!menuFrame
|| !menuFrame
->IsMenu())
622 nsMenuPopupFrame
* popupFrame
= menuFrame
->GetPopup();
623 if (!popupFrame
|| !MayShowPopup(popupFrame
))
626 // inherit whether or not we're a context menu from the parent
627 bool parentIsContextMenu
= false;
628 bool onMenuBar
= false;
629 bool onmenu
= menuFrame
->IsOnMenu();
631 nsMenuParent
* parent
= menuFrame
->GetMenuParent();
632 if (parent
&& onmenu
) {
633 parentIsContextMenu
= parent
->IsContextMenu();
634 onMenuBar
= parent
->IsMenuBar();
637 nsAutoString position
;
638 if (onMenuBar
|| !onmenu
)
639 position
.AssignLiteral("after_start");
641 position
.AssignLiteral("end_before");
643 // there is no trigger event for menus
644 InitTriggerEvent(nullptr, nullptr, nullptr);
645 popupFrame
->InitializePopup(menuFrame
->GetAnchor(), nullptr, position
, 0, 0, true);
648 nsCOMPtr
<nsIRunnable
> event
=
649 new nsXULPopupShowingEvent(popupFrame
->GetContent(),
650 parentIsContextMenu
, aSelectFirstItem
);
651 NS_DispatchToCurrentThread(event
);
654 nsCOMPtr
<nsIContent
> popupContent
= popupFrame
->GetContent();
655 FirePopupShowingEvent(popupContent
, parentIsContextMenu
, aSelectFirstItem
);
660 nsXULPopupManager::ShowPopup(nsIContent
* aPopup
,
661 nsIContent
* aAnchorContent
,
662 const nsAString
& aPosition
,
663 int32_t aXPos
, int32_t aYPos
,
665 bool aAttributesOverride
,
666 bool aSelectFirstItem
,
667 nsIDOMEvent
* aTriggerEvent
)
669 nsMenuPopupFrame
* popupFrame
= GetPopupFrameForContent(aPopup
, true);
670 if (!popupFrame
|| !MayShowPopup(popupFrame
))
673 nsCOMPtr
<nsIContent
> triggerContent
;
674 InitTriggerEvent(aTriggerEvent
, aPopup
, getter_AddRefs(triggerContent
));
676 popupFrame
->InitializePopup(aAnchorContent
, triggerContent
, aPosition
,
677 aXPos
, aYPos
, aAttributesOverride
);
679 FirePopupShowingEvent(aPopup
, aIsContextMenu
, aSelectFirstItem
);
683 nsXULPopupManager::ShowPopupAtScreen(nsIContent
* aPopup
,
684 int32_t aXPos
, int32_t aYPos
,
686 nsIDOMEvent
* aTriggerEvent
)
688 nsMenuPopupFrame
* popupFrame
= GetPopupFrameForContent(aPopup
, true);
689 if (!popupFrame
|| !MayShowPopup(popupFrame
))
692 nsCOMPtr
<nsIContent
> triggerContent
;
693 InitTriggerEvent(aTriggerEvent
, aPopup
, getter_AddRefs(triggerContent
));
695 popupFrame
->InitializePopupAtScreen(triggerContent
, aXPos
, aYPos
, aIsContextMenu
);
696 FirePopupShowingEvent(aPopup
, aIsContextMenu
, false);
700 nsXULPopupManager::ShowTooltipAtScreen(nsIContent
* aPopup
,
701 nsIContent
* aTriggerContent
,
702 int32_t aXPos
, int32_t aYPos
)
704 nsMenuPopupFrame
* popupFrame
= GetPopupFrameForContent(aPopup
, true);
705 if (!popupFrame
|| !MayShowPopup(popupFrame
))
708 InitTriggerEvent(nullptr, nullptr, nullptr);
710 nsPresContext
* pc
= popupFrame
->PresContext();
711 mCachedMousePoint
= nsIntPoint(pc
->CSSPixelsToDevPixels(aXPos
),
712 pc
->CSSPixelsToDevPixels(aYPos
));
714 // coordinates are relative to the root widget
715 nsPresContext
* rootPresContext
= pc
->GetRootPresContext();
716 if (rootPresContext
) {
717 nsIWidget
*rootWidget
= rootPresContext
->GetRootWidget();
719 mCachedMousePoint
-= rootWidget
->WidgetToScreenOffset();
723 popupFrame
->InitializePopupAtScreen(aTriggerContent
, aXPos
, aYPos
, false);
725 FirePopupShowingEvent(aPopup
, false, false);
729 nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent
* aPopup
,
730 nsIContent
* aAnchorContent
,
733 int32_t aXPos
, int32_t aYPos
,
736 nsMenuPopupFrame
* popupFrame
= GetPopupFrameForContent(aPopup
, true);
737 if (!popupFrame
|| !MayShowPopup(popupFrame
))
740 InitTriggerEvent(nullptr, nullptr, nullptr);
742 popupFrame
->InitializePopupWithAnchorAlign(aAnchorContent
, aAnchor
,
743 aAlign
, aXPos
, aYPos
);
744 FirePopupShowingEvent(aPopup
, aIsContextMenu
, false);
748 CheckCaretDrawingState()
750 // There is 1 caret per document, we need to find the focused
751 // document and erase its caret.
752 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
754 nsCOMPtr
<nsIDOMWindow
> window
;
755 fm
->GetFocusedWindow(getter_AddRefs(window
));
759 nsCOMPtr
<nsIDOMDocument
> domDoc
;
760 nsCOMPtr
<nsIDocument
> focusedDoc
;
761 window
->GetDocument(getter_AddRefs(domDoc
));
762 focusedDoc
= do_QueryInterface(domDoc
);
766 nsIPresShell
* presShell
= focusedDoc
->GetShell();
770 nsRefPtr
<nsCaret
> caret
= presShell
->GetCaret();
773 caret
->SchedulePaint();
778 nsXULPopupManager::ShowPopupCallback(nsIContent
* aPopup
,
779 nsMenuPopupFrame
* aPopupFrame
,
781 bool aSelectFirstItem
)
783 nsPopupType popupType
= aPopupFrame
->PopupType();
784 bool ismenu
= (popupType
== ePopupTypeMenu
);
786 nsMenuChainItem
* item
=
787 new nsMenuChainItem(aPopupFrame
, aIsContextMenu
, popupType
);
791 // install keyboard event listeners for navigating menus. For panels, the
792 // escape key may be used to close the panel. However, the ignorekeys
793 // attribute may be used to disable adding these event listeners for popups
794 // that want to handle their own keyboard events.
795 if (aPopup
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::ignorekeys
,
796 nsGkAtoms::_true
, eCaseMatters
))
797 item
->SetIgnoreKeys(true);
800 // if the menu is on a menubar, use the menubar's listener instead
801 nsMenuFrame
* menuFrame
= do_QueryFrame(aPopupFrame
->GetParent());
803 item
->SetOnMenuBar(menuFrame
->IsOnMenuBar());
807 // use a weak frame as the popup will set an open attribute if it is a menu
808 nsWeakFrame
weakFrame(aPopupFrame
);
809 aPopupFrame
->ShowPopup(aIsContextMenu
, aSelectFirstItem
);
810 ENSURE_TRUE(weakFrame
.IsAlive());
812 // popups normally hide when an outside click occurs. Panels may use
813 // the noautohide attribute to disable this behaviour. It is expected
814 // that the application will hide these popups manually. The tooltip
815 // listener will handle closing the tooltip also.
816 if (aPopupFrame
->IsNoAutoHide() || popupType
== ePopupTypeTooltip
) {
817 item
->SetParent(mNoHidePanels
);
818 mNoHidePanels
= item
;
821 nsIContent
* oldmenu
= nullptr;
823 oldmenu
= mPopups
->Content();
824 item
->SetParent(mPopups
);
826 SetCaptureState(oldmenu
);
829 if (aSelectFirstItem
) {
830 nsMenuFrame
* next
= GetNextMenuItem(aPopupFrame
, nullptr, true);
831 aPopupFrame
->SetCurrentMenuItem(next
);
835 UpdateMenuItems(aPopup
);
837 // Caret visibility may have been affected, ensure that
838 // the caret isn't now drawn when it shouldn't be.
839 CheckCaretDrawingState();
843 nsXULPopupManager::HidePopup(nsIContent
* aPopup
,
848 nsIContent
* aLastPopup
)
850 // if the popup is on the nohide panels list, remove it but don't close any
852 nsMenuPopupFrame
* popupFrame
= nullptr;
853 bool foundPanel
= false;
854 nsMenuChainItem
* item
= mNoHidePanels
;
856 if (item
->Content() == aPopup
) {
858 popupFrame
= item
->Frame();
861 item
= item
->GetParent();
864 // when removing a menu, all of the child popups must be closed
865 nsMenuChainItem
* foundMenu
= nullptr;
868 if (item
->Content() == aPopup
) {
872 item
= item
->GetParent();
875 nsPopupType type
= ePopupTypePanel
;
876 bool deselectMenu
= false;
877 nsCOMPtr
<nsIContent
> popupToHide
, nextPopup
, lastPopup
;
879 // at this point, foundMenu will be set to the found item in the list. If
880 // foundMenu is the topmost menu, the one to remove, then there are no other
881 // popups to hide. If foundMenu is not the topmost menu, then there may be
882 // open submenus below it. In this case, we need to make sure that those
883 // submenus are closed up first. To do this, we scan up the menu list to
884 // find the topmost popup with only menus between it and foundMenu and
885 // close that menu first. In synchronous mode, the FirePopupHidingEvent
886 // method will be called which in turn calls HidePopupCallback to close up
887 // the next popup in the chain. These two methods will be called in
888 // sequence recursively to close up all the necessary popups. In
889 // asynchronous mode, a similar process occurs except that the
890 // FirePopupHidingEvent method is called asynchronously. In either case,
891 // nextPopup is set to the content node of the next popup to close, and
892 // lastPopup is set to the last popup in the chain to close, which will be
893 // aPopup, or null to close up all menus.
895 nsMenuChainItem
* topMenu
= foundMenu
;
896 // Use IsMenu to ensure that foundMenu is a menu and scan down the child
897 // list until a non-menu is found. If foundMenu isn't a menu at all, don't
898 // scan and just close up this menu.
899 if (foundMenu
->IsMenu()) {
900 item
= topMenu
->GetChild();
901 while (item
&& item
->IsMenu()) {
903 item
= item
->GetChild();
907 deselectMenu
= aDeselectMenu
;
908 popupToHide
= topMenu
->Content();
909 popupFrame
= topMenu
->Frame();
910 type
= popupFrame
->PopupType();
912 nsMenuChainItem
* parent
= topMenu
->GetParent();
914 // close up another popup if there is one, and we are either hiding the
915 // entire chain or the item to hide isn't the topmost popup.
916 if (parent
&& (aHideChain
|| topMenu
!= foundMenu
))
917 nextPopup
= parent
->Content();
919 lastPopup
= aLastPopup
? aLastPopup
: (aHideChain
? nullptr : aPopup
);
921 else if (foundPanel
) {
922 popupToHide
= aPopup
;
926 nsPopupState state
= popupFrame
->PopupState();
927 // if the popup is already being hidden, don't attempt to hide it again
928 if (state
== ePopupHiding
)
930 // change the popup state to hiding. Don't set the hiding state if the
931 // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
932 // run again. In the invisible state, we just want the events to fire.
933 if (state
!= ePopupInvisible
)
934 popupFrame
->SetPopupState(ePopupHiding
);
936 // for menus, popupToHide is always the frontmost item in the list to hide.
938 nsCOMPtr
<nsIRunnable
> event
=
939 new nsXULPopupHidingEvent(popupToHide
, nextPopup
, lastPopup
,
940 type
, deselectMenu
, aIsRollup
);
941 NS_DispatchToCurrentThread(event
);
944 FirePopupHidingEvent(popupToHide
, nextPopup
, lastPopup
,
945 popupFrame
->PresContext(), type
, deselectMenu
, aIsRollup
);
950 // This is used to hide the popup after a transition finishes.
951 class TransitionEnder
: public nsIDOMEventListener
954 virtual ~TransitionEnder() { }
958 nsCOMPtr
<nsIContent
> mContent
;
961 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
962 NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder
)
964 TransitionEnder(nsIContent
* aContent
, bool aDeselectMenu
)
965 : mContent(aContent
), mDeselectMenu(aDeselectMenu
)
969 NS_IMETHOD
HandleEvent(nsIDOMEvent
* aEvent
) MOZ_OVERRIDE
971 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
973 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(mContent
->GetPrimaryFrame());
975 // Now hide the popup. There could be other properties transitioning, but
976 // we'll assume they all end at the same time and just hide the popup upon
977 // the first one ending.
978 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
979 if (pm
&& popupFrame
) {
980 pm
->HidePopupCallback(mContent
, popupFrame
, nullptr, nullptr,
981 popupFrame
->PopupType(), mDeselectMenu
);
988 NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder
)
989 NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder
)
990 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder
)
991 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
992 NS_INTERFACE_MAP_ENTRY(nsISupports
)
995 NS_IMPL_CYCLE_COLLECTION(TransitionEnder
, mContent
);
998 nsXULPopupManager::HidePopupCallback(nsIContent
* aPopup
,
999 nsMenuPopupFrame
* aPopupFrame
,
1000 nsIContent
* aNextPopup
,
1001 nsIContent
* aLastPopup
,
1002 nsPopupType aPopupType
,
1005 if (mCloseTimer
&& mTimerMenu
== aPopupFrame
) {
1006 mCloseTimer
->Cancel();
1007 mCloseTimer
= nullptr;
1008 mTimerMenu
= nullptr;
1011 // The popup to hide is aPopup. Search the list again to find the item that
1012 // corresponds to the popup to hide aPopup. This is done because it's
1013 // possible someone added another item (attempted to open another popup)
1014 // or removed a popup frame during the event processing so the item isn't at
1015 // the front anymore.
1016 nsMenuChainItem
* item
= mNoHidePanels
;
1018 if (item
->Content() == aPopup
) {
1019 item
->Detach(&mNoHidePanels
);
1022 item
= item
->GetParent();
1028 if (item
->Content() == aPopup
) {
1029 item
->Detach(&mPopups
);
1030 SetCaptureState(aPopup
);
1033 item
= item
->GetParent();
1039 nsWeakFrame
weakFrame(aPopupFrame
);
1040 aPopupFrame
->HidePopup(aDeselectMenu
, ePopupClosed
);
1041 ENSURE_TRUE(weakFrame
.IsAlive());
1043 // send the popuphidden event synchronously. This event has no default
1045 nsEventStatus status
= nsEventStatus_eIgnore
;
1046 WidgetMouseEvent
event(true, NS_XUL_POPUP_HIDDEN
, nullptr,
1047 WidgetMouseEvent::eReal
);
1048 EventDispatcher::Dispatch(aPopup
, aPopupFrame
->PresContext(),
1049 &event
, nullptr, &status
);
1050 ENSURE_TRUE(weakFrame
.IsAlive());
1052 // if there are more popups to close, look for the next one
1053 if (aNextPopup
&& aPopup
!= aLastPopup
) {
1054 nsMenuChainItem
* foundMenu
= nullptr;
1055 nsMenuChainItem
* item
= mPopups
;
1057 if (item
->Content() == aNextPopup
) {
1061 item
= item
->GetParent();
1064 // continue hiding the chain of popups until the last popup aLastPopup
1065 // is reached, or until a popup of a different type is reached. This
1066 // last check is needed so that a menulist inside a non-menu panel only
1067 // closes the menu and not the panel as well.
1069 (aLastPopup
|| aPopupType
== foundMenu
->PopupType())) {
1071 nsCOMPtr
<nsIContent
> popupToHide
= item
->Content();
1072 nsMenuChainItem
* parent
= item
->GetParent();
1074 nsCOMPtr
<nsIContent
> nextPopup
;
1075 if (parent
&& popupToHide
!= aLastPopup
)
1076 nextPopup
= parent
->Content();
1078 nsMenuPopupFrame
* popupFrame
= item
->Frame();
1079 nsPopupState state
= popupFrame
->PopupState();
1080 if (state
== ePopupHiding
)
1082 if (state
!= ePopupInvisible
)
1083 popupFrame
->SetPopupState(ePopupHiding
);
1085 FirePopupHidingEvent(popupToHide
, nextPopup
, aLastPopup
,
1086 popupFrame
->PresContext(),
1087 foundMenu
->PopupType(), aDeselectMenu
, false);
1093 nsXULPopupManager::HidePopup(nsIFrame
* aFrame
)
1095 nsMenuPopupFrame
* popup
= do_QueryFrame(aFrame
);
1097 HidePopup(aFrame
->GetContent(), false, true, false, false);
1101 nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame
* aPopup
)
1103 // Don't close up immediately.
1104 // Kick off a close timer.
1108 LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay
, 300); // ms
1110 // Kick off the timer.
1111 mCloseTimer
= do_CreateInstance("@mozilla.org/timer;1");
1112 mCloseTimer
->InitWithCallback(this, menuDelay
, nsITimer::TYPE_ONE_SHOT
);
1114 // the popup will call PopupDestroyed if it is destroyed, which checks if it
1115 // is set to mTimerMenu, so it should be safe to keep a reference to it
1116 mTimerMenu
= aPopup
;
1120 nsXULPopupManager::HidePopupsInList(const nsTArray
<nsMenuPopupFrame
*> &aFrames
,
1123 // Create a weak frame list. This is done in a separate array with the
1124 // right capacity predetermined, otherwise the array would get resized and
1125 // move the weak frame pointers around.
1126 nsTArray
<nsWeakFrame
> weakPopups(aFrames
.Length());
1128 for (f
= 0; f
< aFrames
.Length(); f
++) {
1129 nsWeakFrame
* wframe
= weakPopups
.AppendElement();
1131 *wframe
= aFrames
[f
];
1134 for (f
= 0; f
< weakPopups
.Length(); f
++) {
1135 // check to ensure that the frame is still alive before hiding it.
1136 if (weakPopups
[f
].IsAlive()) {
1137 nsMenuPopupFrame
* frame
=
1138 static_cast<nsMenuPopupFrame
*>(weakPopups
[f
].GetFrame());
1139 frame
->HidePopup(true, ePopupInvisible
);
1143 SetCaptureState(nullptr);
1147 nsXULPopupManager::IsChildOfDocShell(nsIDocument
* aDoc
, nsIDocShellTreeItem
* aExpected
)
1149 nsCOMPtr
<nsIDocShellTreeItem
> docShellItem(aDoc
->GetDocShell());
1150 while(docShellItem
) {
1151 if (docShellItem
== aExpected
)
1154 nsCOMPtr
<nsIDocShellTreeItem
> parent
;
1155 docShellItem
->GetParent(getter_AddRefs(parent
));
1156 docShellItem
= parent
;
1163 nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem
* aDocShellToHide
)
1165 nsTArray
<nsMenuPopupFrame
*> popupsToHide
;
1167 // iterate to get the set of popup frames to hide
1168 nsMenuChainItem
* item
= mPopups
;
1170 nsMenuChainItem
* parent
= item
->GetParent();
1171 if (item
->Frame()->PopupState() != ePopupInvisible
&&
1172 IsChildOfDocShell(item
->Content()->OwnerDoc(), aDocShellToHide
)) {
1173 nsMenuPopupFrame
* frame
= item
->Frame();
1174 item
->Detach(&mPopups
);
1176 popupsToHide
.AppendElement(frame
);
1181 // now look for panels to hide
1182 item
= mNoHidePanels
;
1184 nsMenuChainItem
* parent
= item
->GetParent();
1185 if (item
->Frame()->PopupState() != ePopupInvisible
&&
1186 IsChildOfDocShell(item
->Content()->OwnerDoc(), aDocShellToHide
)) {
1187 nsMenuPopupFrame
* frame
= item
->Frame();
1188 item
->Detach(&mNoHidePanels
);
1190 popupsToHide
.AppendElement(frame
);
1195 HidePopupsInList(popupsToHide
, true);
1199 nsXULPopupManager::ExecuteMenu(nsIContent
* aMenu
, nsXULMenuCommandEvent
* aEvent
)
1201 CloseMenuMode cmm
= CloseMenuMode_Auto
;
1203 static nsIContent::AttrValuesArray strings
[] =
1204 {&nsGkAtoms::none
, &nsGkAtoms::single
, nullptr};
1206 switch (aMenu
->FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::closemenu
,
1207 strings
, eCaseMatters
)) {
1209 cmm
= CloseMenuMode_None
;
1212 cmm
= CloseMenuMode_Single
;
1218 // When a menuitem is selected to be executed, first hide all the open
1219 // popups, but don't remove them yet. This is needed when a menu command
1220 // opens a modal dialog. The views associated with the popups needed to be
1221 // hidden and the accesibility events fired before the command executes, but
1222 // the popuphiding/popuphidden events are fired afterwards.
1223 nsTArray
<nsMenuPopupFrame
*> popupsToHide
;
1224 nsMenuChainItem
* item
= GetTopVisibleMenu();
1225 if (cmm
!= CloseMenuMode_None
) {
1227 // if it isn't a <menupopup>, don't close it automatically
1228 if (!item
->IsMenu())
1230 nsMenuChainItem
* next
= item
->GetParent();
1231 popupsToHide
.AppendElement(item
->Frame());
1232 if (cmm
== CloseMenuMode_Single
) // only close one level of menu
1237 // Now hide the popups. If the closemenu mode is auto, deselect the menu,
1238 // otherwise only one popup is closing, so keep the parent menu selected.
1239 HidePopupsInList(popupsToHide
, cmm
== CloseMenuMode_Auto
);
1242 aEvent
->SetCloseMenuMode(cmm
);
1243 nsCOMPtr
<nsIRunnable
> event
= aEvent
;
1244 NS_DispatchToCurrentThread(event
);
1248 nsXULPopupManager::FirePopupShowingEvent(nsIContent
* aPopup
,
1249 bool aIsContextMenu
,
1250 bool aSelectFirstItem
)
1252 nsCOMPtr
<nsIContent
> popup
= aPopup
; // keep a strong reference to the popup
1254 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(aPopup
->GetPrimaryFrame());
1258 nsPresContext
*presContext
= popupFrame
->PresContext();
1259 nsCOMPtr
<nsIPresShell
> presShell
= presContext
->PresShell();
1260 nsPopupType popupType
= popupFrame
->PopupType();
1262 // generate the child frames if they have not already been generated
1263 if (!popupFrame
->HasGeneratedChildren()) {
1264 popupFrame
->SetGeneratedChildren();
1265 presShell
->FrameConstructor()->GenerateChildFrames(popupFrame
);
1268 // get the frame again
1269 nsIFrame
* frame
= aPopup
->GetPrimaryFrame();
1273 presShell
->FrameNeedsReflow(frame
, nsIPresShell::eTreeChange
,
1274 NS_FRAME_HAS_DIRTY_CHILDREN
);
1276 // cache the popup so that document.popupNode can retrieve the trigger node
1277 // during the popupshowing event. It will be cleared below after the event
1279 mOpeningPopup
= aPopup
;
1281 nsEventStatus status
= nsEventStatus_eIgnore
;
1282 WidgetMouseEvent
event(true, NS_XUL_POPUP_SHOWING
, nullptr,
1283 WidgetMouseEvent::eReal
);
1285 // coordinates are relative to the root widget
1286 nsPresContext
* rootPresContext
=
1287 presShell
->GetPresContext()->GetRootPresContext();
1288 if (rootPresContext
) {
1289 rootPresContext
->PresShell()->GetViewManager()->
1290 GetRootWidget(getter_AddRefs(event
.widget
));
1293 event
.widget
= nullptr;
1296 event
.refPoint
= LayoutDeviceIntPoint::FromUntyped(mCachedMousePoint
);
1297 event
.modifiers
= mCachedModifiers
;
1298 EventDispatcher::Dispatch(popup
, presContext
, &event
, nullptr, &status
);
1300 mCachedMousePoint
= nsIntPoint(0, 0);
1301 mOpeningPopup
= nullptr;
1303 mCachedModifiers
= 0;
1305 // if a panel, blur whatever has focus so that the panel can take the focus.
1306 // This is done after the popupshowing event in case that event is cancelled.
1307 // Using noautofocus="true" will disable this behaviour, which is needed for
1308 // the autocomplete widget as it manages focus itself.
1309 if (popupType
== ePopupTypePanel
&&
1310 !popup
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::noautofocus
,
1311 nsGkAtoms::_true
, eCaseMatters
)) {
1312 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
1314 nsIDocument
* doc
= popup
->GetCurrentDoc();
1316 // Only remove the focus if the currently focused item is ouside the
1317 // popup. It isn't a big deal if the current focus is in a child popup
1318 // inside the popup as that shouldn't be visible. This check ensures that
1319 // a node inside the popup that is focused during a popupshowing event
1321 nsCOMPtr
<nsIDOMElement
> currentFocusElement
;
1322 fm
->GetFocusedElement(getter_AddRefs(currentFocusElement
));
1323 nsCOMPtr
<nsIContent
> currentFocus
= do_QueryInterface(currentFocusElement
);
1324 if (doc
&& currentFocus
&&
1325 !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus
, popup
)) {
1326 fm
->ClearFocus(doc
->GetWindow());
1331 // clear these as they are no longer valid
1332 mRangeParent
= nullptr;
1335 // get the frame again in case it went away
1336 popupFrame
= do_QueryFrame(aPopup
->GetPrimaryFrame());
1338 // if the event was cancelled, don't open the popup, reset its state back
1339 // to closed and clear its trigger content.
1340 if (status
== nsEventStatus_eConsumeNoDefault
) {
1341 popupFrame
->SetPopupState(ePopupClosed
);
1342 popupFrame
->ClearTriggerContent();
1345 ShowPopupCallback(aPopup
, popupFrame
, aIsContextMenu
, aSelectFirstItem
);
1351 nsXULPopupManager::FirePopupHidingEvent(nsIContent
* aPopup
,
1352 nsIContent
* aNextPopup
,
1353 nsIContent
* aLastPopup
,
1354 nsPresContext
*aPresContext
,
1355 nsPopupType aPopupType
,
1359 nsCOMPtr
<nsIPresShell
> presShell
= aPresContext
->PresShell();
1361 nsEventStatus status
= nsEventStatus_eIgnore
;
1362 WidgetMouseEvent
event(true, NS_XUL_POPUP_HIDING
, nullptr,
1363 WidgetMouseEvent::eReal
);
1364 EventDispatcher::Dispatch(aPopup
, aPresContext
, &event
, nullptr, &status
);
1366 // when a panel is closed, blur whatever has focus inside the popup
1367 if (aPopupType
== ePopupTypePanel
&&
1368 !aPopup
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::noautofocus
,
1369 nsGkAtoms::_true
, eCaseMatters
)) {
1370 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
1372 nsIDocument
* doc
= aPopup
->GetCurrentDoc();
1374 // Remove the focus from the focused node only if it is inside the popup.
1375 nsCOMPtr
<nsIDOMElement
> currentFocusElement
;
1376 fm
->GetFocusedElement(getter_AddRefs(currentFocusElement
));
1377 nsCOMPtr
<nsIContent
> currentFocus
= do_QueryInterface(currentFocusElement
);
1378 if (doc
&& currentFocus
&&
1379 nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus
, aPopup
)) {
1380 fm
->ClearFocus(doc
->GetWindow());
1385 // get frame again in case it went away
1386 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(aPopup
->GetPrimaryFrame());
1388 // if the event was cancelled, don't hide the popup, and reset its
1389 // state back to open. Only popups in chrome shells can prevent a popup
1391 if (status
== nsEventStatus_eConsumeNoDefault
&&
1392 !popupFrame
->IsInContentShell()) {
1394 // If an attempt was made to hide this popup before the popupshown event
1395 // fired, then ePopupShown is set here even though it should be
1396 // ePopupVisible. This probably isn't worth the hassle of handling.
1397 popupFrame
->SetPopupState(ePopupShown
);
1400 // If the popup has an animate attribute and it is not set to false, check
1401 // if it has a closing transition and wait for it to finish. The transition
1402 // may still occur either way, but the view will be hidden and you won't be
1403 // able to see it. If there is a next popup, indicating that mutliple popups
1404 // are rolling up, don't wait and hide the popup right away since the effect
1405 // would likely be undesirable. Transitions are currently disabled on Linux
1406 // due to rendering issues on certain configurations.
1407 #ifndef MOZ_WIDGET_GTK
1408 if (!aNextPopup
&& aPopup
->HasAttr(kNameSpaceID_None
, nsGkAtoms::animate
)) {
1409 // If animate="false" then don't transition at all. If animate="cancel",
1410 // only show the transition if cancelling the popup or rolling up.
1411 // Otherwise, always show the transition.
1412 nsAutoString animate
;
1413 aPopup
->GetAttr(kNameSpaceID_None
, nsGkAtoms::animate
, animate
);
1415 if (!animate
.EqualsLiteral("false") &&
1416 (!animate
.EqualsLiteral("cancel") || aIsRollup
)) {
1417 presShell
->FlushPendingNotifications(Flush_Layout
);
1419 // Get the frame again in case the flush caused it to go away
1420 popupFrame
= do_QueryFrame(aPopup
->GetPrimaryFrame());
1424 if (nsLayoutUtils::HasCurrentAnimations(aPopup
, nsGkAtoms::transitionsProperty
, aPresContext
)) {
1425 nsRefPtr
<TransitionEnder
> ender
= new TransitionEnder(aPopup
, aDeselectMenu
);
1426 aPopup
->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
1427 ender
, false, false);
1434 HidePopupCallback(aPopup
, popupFrame
, aNextPopup
, aLastPopup
,
1435 aPopupType
, aDeselectMenu
);
1441 nsXULPopupManager::IsPopupOpen(nsIContent
* aPopup
)
1443 // a popup is open if it is in the open list. The assertions ensure that the
1444 // frame is in the correct state. If the popup is in the hiding or invisible
1445 // state, it will still be in the open popup list until it is closed.
1446 nsMenuChainItem
* item
= mPopups
;
1448 if (item
->Content() == aPopup
) {
1449 NS_ASSERTION(item
->Frame()->IsOpen() ||
1450 item
->Frame()->PopupState() == ePopupHiding
||
1451 item
->Frame()->PopupState() == ePopupInvisible
,
1452 "popup in open list not actually open");
1455 item
= item
->GetParent();
1458 item
= mNoHidePanels
;
1460 if (item
->Content() == aPopup
) {
1461 NS_ASSERTION(item
->Frame()->IsOpen() ||
1462 item
->Frame()->PopupState() == ePopupHiding
||
1463 item
->Frame()->PopupState() == ePopupInvisible
,
1464 "popup in open list not actually open");
1467 item
= item
->GetParent();
1474 nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent
* aMenuParent
)
1476 nsMenuChainItem
* item
= GetTopVisibleMenu();
1478 nsMenuPopupFrame
* popup
= item
->Frame();
1479 if (popup
&& popup
->IsOpen()) {
1480 nsMenuFrame
* menuFrame
= do_QueryFrame(popup
->GetParent());
1481 if (menuFrame
&& menuFrame
->GetMenuParent() == aMenuParent
) {
1485 item
= item
->GetParent();
1492 nsXULPopupManager::GetTopPopup(nsPopupType aType
)
1494 if ((aType
== ePopupTypePanel
|| aType
== ePopupTypeTooltip
) && mNoHidePanels
)
1495 return mNoHidePanels
->Frame();
1497 nsMenuChainItem
* item
= GetTopVisibleMenu();
1499 if (item
->PopupType() == aType
|| aType
== ePopupTypeAny
)
1500 return item
->Frame();
1501 item
= item
->GetParent();
1508 nsXULPopupManager::GetVisiblePopups(nsTArray
<nsIFrame
*>& aPopups
)
1512 // Iterate over both lists of popups
1513 nsMenuChainItem
* item
= mPopups
;
1514 for (int32_t list
= 0; list
< 2; list
++) {
1516 // Skip panels which are not visible as well as popups that
1517 // are transparent to mouse events.
1518 if (item
->Frame()->IsVisible() && !item
->Frame()->IsMouseTransparent()) {
1519 aPopups
.AppendElement(item
->Frame());
1522 item
= item
->GetParent();
1525 item
= mNoHidePanels
;
1529 already_AddRefed
<nsIDOMNode
>
1530 nsXULPopupManager::GetLastTriggerNode(nsIDocument
* aDocument
, bool aIsTooltip
)
1535 nsCOMPtr
<nsIDOMNode
> node
;
1537 // if mOpeningPopup is set, it means that a popupshowing event is being
1538 // fired. In this case, just use the cached node, as the popup is not yet in
1539 // the list of open popups.
1540 if (mOpeningPopup
&& mOpeningPopup
->GetCurrentDoc() == aDocument
&&
1541 aIsTooltip
== (mOpeningPopup
->Tag() == nsGkAtoms::tooltip
)) {
1542 node
= do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup
, false)));
1545 nsMenuChainItem
* item
= aIsTooltip
? mNoHidePanels
: mPopups
;
1547 // look for a popup of the same type and document.
1548 if ((item
->PopupType() == ePopupTypeTooltip
) == aIsTooltip
&&
1549 item
->Content()->GetCurrentDoc() == aDocument
) {
1550 node
= do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item
->Frame()));
1554 item
= item
->GetParent();
1558 return node
.forget();
1562 nsXULPopupManager::MayShowPopup(nsMenuPopupFrame
* aPopup
)
1564 // if a popup's IsOpen method returns true, then the popup must always be in
1565 // the popup chain scanned in IsPopupOpen.
1566 NS_ASSERTION(!aPopup
->IsOpen() || IsPopupOpen(aPopup
->GetContent()),
1567 "popup frame state doesn't match XULPopupManager open state");
1569 nsPopupState state
= aPopup
->PopupState();
1571 // if the popup is not in the open popup chain, then it must have a state that
1572 // is either closed, in the process of being shown, or invisible.
1573 NS_ASSERTION(IsPopupOpen(aPopup
->GetContent()) || state
== ePopupClosed
||
1574 state
== ePopupShowing
|| state
== ePopupInvisible
,
1575 "popup not in XULPopupManager open list is open");
1577 // don't show popups unless they are closed or invisible
1578 if (state
!= ePopupClosed
&& state
!= ePopupInvisible
)
1581 // Don't show popups that we already have in our popup chain
1582 if (IsPopupOpen(aPopup
->GetContent())) {
1583 NS_WARNING("Refusing to show duplicate popup");
1587 // if the popup was just rolled up, don't reopen it
1588 nsCOMPtr
<nsIWidget
> widget
= aPopup
->GetWidget();
1589 if (widget
&& widget
->GetLastRollup() == aPopup
->GetContent())
1592 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= aPopup
->PresContext()->GetDocShell();
1593 nsCOMPtr
<nsIBaseWindow
> baseWin
= do_QueryInterface(dsti
);
1597 // chrome shells can always open popups, but other types of shells can only
1598 // open popups when they are focused and visible
1599 if (dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
1600 // only allow popups in active windows
1601 nsCOMPtr
<nsIDocShellTreeItem
> root
;
1602 dsti
->GetRootTreeItem(getter_AddRefs(root
));
1607 nsCOMPtr
<nsIDOMWindow
> rootWin
= root
->GetWindow();
1609 nsIFocusManager
* fm
= nsFocusManager::GetFocusManager();
1610 if (!fm
|| !rootWin
)
1613 nsCOMPtr
<nsIDOMWindow
> activeWindow
;
1614 fm
->GetActiveWindow(getter_AddRefs(activeWindow
));
1615 if (activeWindow
!= rootWin
)
1618 // only allow popups in visible frames
1620 baseWin
->GetVisibility(&visible
);
1625 // platforms respond differently when an popup is opened in a minimized
1626 // window, so this is always disabled.
1627 nsCOMPtr
<nsIWidget
> mainWidget
;
1628 baseWin
->GetMainWidget(getter_AddRefs(mainWidget
));
1629 if (mainWidget
&& mainWidget
->SizeMode() == nsSizeMode_Minimized
) {
1633 // cannot open a popup that is a submenu of a menupopup that isn't open.
1634 nsMenuFrame
* menuFrame
= do_QueryFrame(aPopup
->GetParent());
1636 nsMenuParent
* parentPopup
= menuFrame
->GetMenuParent();
1637 if (parentPopup
&& !parentPopup
->IsOpen())
1645 nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame
* aPopup
)
1647 // when a popup frame is destroyed, just unhook it from the list of popups
1648 if (mTimerMenu
== aPopup
) {
1650 mCloseTimer
->Cancel();
1651 mCloseTimer
= nullptr;
1653 mTimerMenu
= nullptr;
1656 nsMenuChainItem
* item
= mNoHidePanels
;
1658 if (item
->Frame() == aPopup
) {
1659 item
->Detach(&mNoHidePanels
);
1663 item
= item
->GetParent();
1666 nsTArray
<nsMenuPopupFrame
*> popupsToHide
;
1670 nsMenuPopupFrame
* frame
= item
->Frame();
1671 if (frame
== aPopup
) {
1672 if (frame
->PopupState() != ePopupInvisible
) {
1673 // Iterate through any child menus and hide them as well, since the
1674 // parent is going away. We won't remove them from the list yet, just
1675 // hide them, as they will be removed from the list when this function
1676 // gets called for that child frame.
1677 nsMenuChainItem
* child
= item
->GetChild();
1679 // if the popup is a child frame of the menu that was destroyed, add
1680 // it to the list of popups to hide. Don't bother with the events
1681 // since the frames are going away. If the child menu is not a child
1682 // frame, for example, a context menu, use HidePopup instead, but call
1683 // it asynchronously since we are in the middle of frame destruction.
1684 nsMenuPopupFrame
* childframe
= child
->Frame();
1685 if (nsLayoutUtils::IsProperAncestorFrame(frame
, childframe
)) {
1686 popupsToHide
.AppendElement(childframe
);
1689 // HidePopup will take care of hiding any of its children, so
1690 // break out afterwards
1691 HidePopup(child
->Content(), false, false, true, false);
1695 child
= child
->GetChild();
1699 item
->Detach(&mPopups
);
1704 item
= item
->GetParent();
1707 HidePopupsInList(popupsToHide
, false);
1711 nsXULPopupManager::HasContextMenu(nsMenuPopupFrame
* aPopup
)
1713 nsMenuChainItem
* item
= GetTopVisibleMenu();
1714 while (item
&& item
->Frame() != aPopup
) {
1715 if (item
->IsContextMenu())
1717 item
= item
->GetParent();
1724 nsXULPopupManager::SetCaptureState(nsIContent
* aOldPopup
)
1726 nsMenuChainItem
* item
= GetTopVisibleMenu();
1727 if (item
&& aOldPopup
== item
->Content())
1731 mWidget
->CaptureRollupEvents(nullptr, false);
1736 nsMenuPopupFrame
* popup
= item
->Frame();
1737 mWidget
= popup
->GetWidget();
1739 mWidget
->CaptureRollupEvents(nullptr, true);
1740 popup
->AttachedDismissalListener();
1744 UpdateKeyboardListeners();
1748 nsXULPopupManager::UpdateKeyboardListeners()
1750 nsCOMPtr
<EventTarget
> newTarget
;
1751 bool isForMenu
= false;
1752 nsMenuChainItem
* item
= GetTopVisibleMenu();
1754 if (!item
->IgnoreKeys())
1755 newTarget
= item
->Content()->GetComposedDoc();
1756 isForMenu
= item
->PopupType() == ePopupTypeMenu
;
1758 else if (mActiveMenuBar
) {
1759 newTarget
= mActiveMenuBar
->GetContent()->GetComposedDoc();
1763 if (mKeyListener
!= newTarget
) {
1765 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
1766 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
1767 mKeyListener
->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
1768 mKeyListener
= nullptr;
1769 nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
1773 newTarget
->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
1774 newTarget
->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
1775 newTarget
->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
1776 nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu
);
1777 mKeyListener
= newTarget
;
1783 nsXULPopupManager::UpdateMenuItems(nsIContent
* aPopup
)
1785 // Walk all of the menu's children, checking to see if any of them has a
1786 // command attribute. If so, then several attributes must potentially be updated.
1788 nsCOMPtr
<nsIDocument
> document
= aPopup
->GetCurrentDoc();
1793 for (nsCOMPtr
<nsIContent
> grandChild
= aPopup
->GetFirstChild();
1795 grandChild
= grandChild
->GetNextSibling()) {
1796 if (grandChild
->IsXUL(nsGkAtoms::menugroup
)) {
1797 if (grandChild
->GetChildCount() == 0) {
1800 grandChild
= grandChild
->GetFirstChild();
1802 if (grandChild
->IsXUL(nsGkAtoms::menuitem
)) {
1803 // See if we have a command attribute.
1804 nsAutoString command
;
1805 grandChild
->GetAttr(kNameSpaceID_None
, nsGkAtoms::command
, command
);
1806 if (!command
.IsEmpty()) {
1807 // We do! Look it up in our document
1808 nsRefPtr
<dom::Element
> commandElement
=
1809 document
->GetElementById(command
);
1810 if (commandElement
) {
1811 nsAutoString commandValue
;
1812 // The menu's disabled state needs to be updated to match the command.
1813 if (commandElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, commandValue
))
1814 grandChild
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, commandValue
, true);
1816 grandChild
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, true);
1818 // The menu's label, accesskey checked and hidden states need to be updated
1819 // to match the command. Note that unlike the disabled state if the
1820 // command has *no* value, we assume the menu is supplying its own.
1821 if (commandElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
, commandValue
))
1822 grandChild
->SetAttr(kNameSpaceID_None
, nsGkAtoms::label
, commandValue
, true);
1824 if (commandElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
, commandValue
))
1825 grandChild
->SetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
, commandValue
, true);
1827 if (commandElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::checked
, commandValue
))
1828 grandChild
->SetAttr(kNameSpaceID_None
, nsGkAtoms::checked
, commandValue
, true);
1830 if (commandElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::hidden
, commandValue
))
1831 grandChild
->SetAttr(kNameSpaceID_None
, nsGkAtoms::hidden
, commandValue
, true);
1835 if (!grandChild
->GetNextSibling() &&
1836 grandChild
->GetParent()->IsXUL(nsGkAtoms::menugroup
)) {
1837 grandChild
= grandChild
->GetParent();
1844 // The item selection timer has fired, we might have to readjust the
1845 // selected item. There are two cases here that we are trying to deal with:
1846 // (1) diagonal movement from a parent menu to a submenu passing briefly over
1848 // (2) moving out from a submenu to a parent or grandparent menu.
1849 // In both cases, |mTimerMenu| is the menu item that might have an open submenu and
1850 // the first item in |mPopups| is the item the mouse is currently over, which could be
1854 // As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
1855 // submenu, it probably passes through one or more sibilings (B). As the mouse passes
1856 // through B, it becomes the current menu item and the timer is set and mTimerMenu is
1857 // set to A. Before the timer fires, the mouse leaves the menu containing A and B and
1858 // enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
1859 // so we have to see if anything in A's children is selected (recall that even disabled
1860 // items are selected, the style just doesn't show it). If that is the case, we need to
1861 // set the selected item back to A.
1864 // Item A has an open submenu, and in it there is an item (B) which also has an open
1865 // submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
1866 // submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
1867 // the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
1868 // the mouse is still within C. The correct behavior is to set the current item to C
1869 // and close up the chain parented at A.
1871 // This brings up the question of is the logic of case (1) enough? The answer is no,
1872 // and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
1873 // child, and if it does, set the selected item to A. Because B has a submenu open, it
1874 // is selected and as a result, A is set to be the selected item even though the mouse
1875 // rests in C -- very wrong.
1877 // The solution is to use the same idea, but instead of only checking one level,
1878 // drill all the way down to the deepest open submenu and check if it has something
1879 // selected. Since the mouse is in a grandparent, it won't, and we know that we can
1880 // safely close up A and all its children.
1882 // The code below melds the two cases together.
1885 nsXULPopupManager::Notify(nsITimer
* aTimer
)
1887 if (aTimer
== mCloseTimer
)
1894 nsXULPopupManager::KillMenuTimer()
1896 if (mCloseTimer
&& mTimerMenu
) {
1897 mCloseTimer
->Cancel();
1898 mCloseTimer
= nullptr;
1900 if (mTimerMenu
->IsOpen())
1901 HidePopup(mTimerMenu
->GetContent(), false, false, true, false);
1904 mTimerMenu
= nullptr;
1908 nsXULPopupManager::CancelMenuTimer(nsMenuParent
* aMenuParent
)
1910 if (mCloseTimer
&& mTimerMenu
== aMenuParent
) {
1911 mCloseTimer
->Cancel();
1912 mCloseTimer
= nullptr;
1913 mTimerMenu
= nullptr;
1918 nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent
* aKeyEvent
,
1919 nsMenuPopupFrame
* aFrame
)
1921 nsMenuChainItem
* item
= GetTopVisibleMenu();
1922 if (!aFrame
&& item
)
1923 aFrame
= item
->Frame();
1927 nsMenuFrame
* result
= aFrame
->FindMenuWithShortcut(aKeyEvent
, action
);
1929 aFrame
->ChangeMenuItem(result
, false);
1931 WidgetGUIEvent
* evt
= aKeyEvent
->GetInternalNSEvent()->AsGUIEvent();
1932 nsMenuFrame
* menuToOpen
= result
->Enter(evt
);
1934 nsCOMPtr
<nsIContent
> content
= menuToOpen
->GetContent();
1935 ShowMenu(content
, true, false);
1944 if (mActiveMenuBar
) {
1945 nsMenuFrame
* result
= mActiveMenuBar
->FindMenuWithShortcut(aKeyEvent
);
1947 mActiveMenuBar
->SetActive(true);
1948 result
->OpenMenu(true);
1958 nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode
)
1960 // navigate up through the open menus, looking for the topmost one
1961 // in the same hierarchy
1962 nsMenuChainItem
* item
= nullptr;
1963 nsMenuChainItem
* nextitem
= GetTopVisibleMenu();
1967 nextitem
= item
->GetParent();
1970 // stop if the parent isn't a menu
1971 if (!nextitem
->IsMenu())
1974 // check to make sure that the parent is actually the parent menu. It won't
1975 // be if the parent is in a different frame hierarchy, for example, for a
1976 // context menu opened on another menu.
1977 nsMenuParent
* expectedParent
= static_cast<nsMenuParent
*>(nextitem
->Frame());
1978 nsMenuFrame
* menuFrame
= do_QueryFrame(item
->Frame()->GetParent());
1979 if (!menuFrame
|| menuFrame
->GetMenuParent() != expectedParent
) {
1985 nsIFrame
* itemFrame
;
1987 itemFrame
= item
->Frame();
1988 else if (mActiveMenuBar
)
1989 itemFrame
= mActiveMenuBar
;
1993 nsNavigationDirection theDirection
;
1994 NS_ASSERTION(aKeyCode
>= nsIDOMKeyEvent::DOM_VK_END
&&
1995 aKeyCode
<= nsIDOMKeyEvent::DOM_VK_DOWN
, "Illegal key code");
1996 theDirection
= NS_DIRECTION_FROM_KEY_CODE(itemFrame
, aKeyCode
);
1998 // if a popup is open, first check for navigation within the popup
1999 if (item
&& HandleKeyboardNavigationInPopup(item
, theDirection
))
2002 // no popup handled the key, so check the active menubar, if any
2003 if (mActiveMenuBar
) {
2004 nsMenuFrame
* currentMenu
= mActiveMenuBar
->GetCurrentMenuItem();
2006 if (NS_DIRECTION_IS_INLINE(theDirection
)) {
2007 nsMenuFrame
* nextItem
= (theDirection
== eNavigationDirection_End
) ?
2008 GetNextMenuItem(mActiveMenuBar
, currentMenu
, false) :
2009 GetPreviousMenuItem(mActiveMenuBar
, currentMenu
, false);
2010 mActiveMenuBar
->ChangeMenuItem(nextItem
, true);
2013 else if (NS_DIRECTION_IS_BLOCK(theDirection
)) {
2014 // Open the menu and select its first item.
2016 nsCOMPtr
<nsIContent
> content
= currentMenu
->GetContent();
2017 ShowMenu(content
, true, false);
2027 nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem
* item
,
2028 nsMenuPopupFrame
* aFrame
,
2029 nsNavigationDirection aDir
)
2031 NS_ASSERTION(aFrame
, "aFrame is null");
2032 NS_ASSERTION(!item
|| item
->Frame() == aFrame
,
2033 "aFrame is expected to be equal to item->Frame()");
2035 nsMenuFrame
* currentMenu
= aFrame
->GetCurrentMenuItem();
2037 aFrame
->ClearIncrementalString();
2039 // This method only gets called if we're open.
2040 if (!currentMenu
&& NS_DIRECTION_IS_INLINE(aDir
)) {
2041 // We've been opened, but we haven't had anything selected.
2042 // We can handle End, but our parent handles Start.
2043 if (aDir
== eNavigationDirection_End
) {
2044 nsMenuFrame
* nextItem
= GetNextMenuItem(aFrame
, nullptr, true);
2046 aFrame
->ChangeMenuItem(nextItem
, false);
2053 bool isContainer
= false;
2054 bool isOpen
= false;
2056 isOpen
= currentMenu
->IsOpen();
2057 isContainer
= currentMenu
->IsMenu();
2059 // for an open popup, have the child process the event
2060 nsMenuChainItem
* child
= item
? item
->GetChild() : nullptr;
2061 if (child
&& HandleKeyboardNavigationInPopup(child
, aDir
))
2064 else if (aDir
== eNavigationDirection_End
&&
2065 isContainer
&& !currentMenu
->IsDisabled()) {
2066 // The menu is not yet open. Open it and select the first item.
2067 nsCOMPtr
<nsIContent
> content
= currentMenu
->GetContent();
2068 ShowMenu(content
, true, false);
2073 // For block progression, we can move in either direction
2074 if (NS_DIRECTION_IS_BLOCK(aDir
) ||
2075 NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir
)) {
2076 nsMenuFrame
* nextItem
;
2078 if (aDir
== eNavigationDirection_Before
)
2079 nextItem
= GetPreviousMenuItem(aFrame
, currentMenu
, true);
2080 else if (aDir
== eNavigationDirection_After
)
2081 nextItem
= GetNextMenuItem(aFrame
, currentMenu
, true);
2082 else if (aDir
== eNavigationDirection_First
)
2083 nextItem
= GetNextMenuItem(aFrame
, nullptr, true);
2085 nextItem
= GetPreviousMenuItem(aFrame
, nullptr, true);
2088 aFrame
->ChangeMenuItem(nextItem
, false);
2092 else if (currentMenu
&& isContainer
&& isOpen
) {
2093 if (aDir
== eNavigationDirection_Start
) {
2094 // close a submenu when Left is pressed
2095 nsMenuPopupFrame
* popupFrame
= currentMenu
->GetPopup();
2097 HidePopup(popupFrame
->GetContent(), false, false, false, false);
2106 nsXULPopupManager::HandleKeyboardEventWithKeyCode(
2107 nsIDOMKeyEvent
* aKeyEvent
,
2108 nsMenuChainItem
* aTopVisibleMenuItem
)
2111 aKeyEvent
->GetKeyCode(&keyCode
);
2113 // Escape should close panels, but the other keys should have no effect.
2114 if (aTopVisibleMenuItem
&&
2115 aTopVisibleMenuItem
->PopupType() != ePopupTypeMenu
) {
2116 if (keyCode
== nsIDOMKeyEvent::DOM_VK_ESCAPE
) {
2117 HidePopup(aTopVisibleMenuItem
->Content(), false, false, false, true);
2118 aKeyEvent
->StopPropagation();
2119 aKeyEvent
->PreventDefault();
2124 bool consume
= (mPopups
|| mActiveMenuBar
);
2126 case nsIDOMKeyEvent::DOM_VK_LEFT
:
2127 case nsIDOMKeyEvent::DOM_VK_RIGHT
:
2128 case nsIDOMKeyEvent::DOM_VK_UP
:
2129 case nsIDOMKeyEvent::DOM_VK_DOWN
:
2130 case nsIDOMKeyEvent::DOM_VK_HOME
:
2131 case nsIDOMKeyEvent::DOM_VK_END
:
2132 HandleKeyboardNavigation(keyCode
);
2135 case nsIDOMKeyEvent::DOM_VK_ESCAPE
:
2136 // Pressing Escape hides one level of menus only. If no menu is open,
2137 // check if a menubar is active and inform it that a menu closed. Even
2138 // though in this latter case, a menu didn't actually close, the effect
2139 // ends up being the same. Similar for the tab key below.
2140 if (aTopVisibleMenuItem
) {
2141 HidePopup(aTopVisibleMenuItem
->Content(), false, false, false, true);
2142 } else if (mActiveMenuBar
) {
2143 mActiveMenuBar
->MenuClosed();
2147 case nsIDOMKeyEvent::DOM_VK_TAB
:
2149 case nsIDOMKeyEvent::DOM_VK_F10
:
2151 // close popups or deactivate menubar when Tab or F10 are pressed
2152 if (aTopVisibleMenuItem
) {
2153 Rollup(0, nullptr, nullptr);
2154 } else if (mActiveMenuBar
) {
2155 mActiveMenuBar
->MenuClosed();
2159 case nsIDOMKeyEvent::DOM_VK_RETURN
: {
2160 // If there is a popup open, check if the current item needs to be opened.
2161 // Otherwise, tell the active menubar, if any, to activate the menu. The
2162 // Enter method will return a menu if one needs to be opened as a result.
2163 nsMenuFrame
* menuToOpen
= nullptr;
2164 WidgetGUIEvent
* GUIEvent
= aKeyEvent
->GetInternalNSEvent()->AsGUIEvent();
2165 if (aTopVisibleMenuItem
) {
2166 menuToOpen
= aTopVisibleMenuItem
->Frame()->Enter(GUIEvent
);
2167 } else if (mActiveMenuBar
) {
2168 menuToOpen
= mActiveMenuBar
->Enter(GUIEvent
);
2171 nsCOMPtr
<nsIContent
> content
= menuToOpen
->GetContent();
2172 ShowMenu(content
, true, false);
2182 aKeyEvent
->StopPropagation();
2183 aKeyEvent
->PreventDefault();
2189 nsXULPopupManager::GetNextMenuItem(nsContainerFrame
* aParent
,
2190 nsMenuFrame
* aStart
,
2193 nsPresContext
* presContext
= aParent
->PresContext();
2194 nsContainerFrame
* immediateParent
= presContext
->PresShell()->
2195 FrameConstructor()->GetInsertionPoint(aParent
->GetContent(), nullptr);
2196 if (!immediateParent
)
2197 immediateParent
= aParent
;
2199 nsIFrame
* currFrame
= nullptr;
2201 if (aStart
->GetNextSibling())
2202 currFrame
= aStart
->GetNextSibling();
2203 else if (aStart
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2204 currFrame
= aStart
->GetParent()->GetNextSibling();
2207 currFrame
= immediateParent
->GetFirstPrincipalChild();
2210 // See if it's a menu item.
2211 nsIContent
* currFrameContent
= currFrame
->GetContent();
2212 if (IsValidMenuItem(presContext
, currFrameContent
, aIsPopup
)) {
2213 return do_QueryFrame(currFrame
);
2215 if (currFrameContent
->IsXUL(nsGkAtoms::menugroup
) &&
2216 currFrameContent
->GetChildCount() > 0)
2217 currFrame
= currFrame
->GetFirstPrincipalChild();
2218 else if (!currFrame
->GetNextSibling() &&
2219 currFrame
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2220 currFrame
= currFrame
->GetParent()->GetNextSibling();
2222 currFrame
= currFrame
->GetNextSibling();
2225 currFrame
= immediateParent
->GetFirstPrincipalChild();
2227 // Still don't have anything. Try cycling from the beginning.
2228 while (currFrame
&& currFrame
!= aStart
) {
2229 // See if it's a menu item.
2230 nsIContent
* currFrameContent
= currFrame
->GetContent();
2231 if (IsValidMenuItem(presContext
, currFrameContent
, aIsPopup
)) {
2232 return do_QueryFrame(currFrame
);
2234 if (currFrameContent
->IsXUL(nsGkAtoms::menugroup
) &&
2235 currFrameContent
->GetChildCount() > 0)
2236 currFrame
= currFrame
->GetFirstPrincipalChild();
2237 else if (!currFrame
->GetNextSibling() &&
2238 currFrame
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2239 currFrame
= currFrame
->GetParent()->GetNextSibling();
2241 currFrame
= currFrame
->GetNextSibling();
2244 // No luck. Just return our start value.
2249 nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame
* aParent
,
2250 nsMenuFrame
* aStart
,
2253 nsPresContext
* presContext
= aParent
->PresContext();
2254 nsContainerFrame
* immediateParent
= presContext
->PresShell()->
2255 FrameConstructor()->GetInsertionPoint(aParent
->GetContent(), nullptr);
2256 if (!immediateParent
)
2257 immediateParent
= aParent
;
2259 const nsFrameList
& frames(immediateParent
->PrincipalChildList());
2261 nsIFrame
* currFrame
= nullptr;
2263 if (aStart
->GetPrevSibling())
2264 currFrame
= aStart
->GetPrevSibling();
2265 else if (aStart
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2266 currFrame
= aStart
->GetParent()->GetPrevSibling();
2269 currFrame
= frames
.LastChild();
2272 // See if it's a menu item.
2273 nsIContent
* currFrameContent
= currFrame
->GetContent();
2274 if (IsValidMenuItem(presContext
, currFrameContent
, aIsPopup
)) {
2275 return do_QueryFrame(currFrame
);
2277 if (currFrameContent
->IsXUL(nsGkAtoms::menugroup
) &&
2278 currFrameContent
->GetChildCount() > 0) {
2279 const nsFrameList
& menugroupFrames(currFrame
->PrincipalChildList());
2280 currFrame
= menugroupFrames
.LastChild();
2282 else if (!currFrame
->GetPrevSibling() &&
2283 currFrame
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2284 currFrame
= currFrame
->GetParent()->GetPrevSibling();
2286 currFrame
= currFrame
->GetPrevSibling();
2289 currFrame
= frames
.LastChild();
2291 // Still don't have anything. Try cycling from the end.
2292 while (currFrame
&& currFrame
!= aStart
) {
2293 // See if it's a menu item.
2294 nsIContent
* currFrameContent
= currFrame
->GetContent();
2295 if (IsValidMenuItem(presContext
, currFrameContent
, aIsPopup
)) {
2296 return do_QueryFrame(currFrame
);
2298 if (currFrameContent
->IsXUL(nsGkAtoms::menugroup
) &&
2299 currFrameContent
->GetChildCount() > 0) {
2300 const nsFrameList
& menugroupFrames(currFrame
->PrincipalChildList());
2301 currFrame
= menugroupFrames
.LastChild();
2303 else if (!currFrame
->GetPrevSibling() &&
2304 currFrame
->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup
))
2305 currFrame
= currFrame
->GetParent()->GetPrevSibling();
2307 currFrame
= currFrame
->GetPrevSibling();
2310 // No luck. Just return our start value.
2315 nsXULPopupManager::IsValidMenuItem(nsPresContext
* aPresContext
,
2316 nsIContent
* aContent
,
2319 int32_t ns
= aContent
->GetNameSpaceID();
2320 nsIAtom
*tag
= aContent
->Tag();
2321 if (ns
== kNameSpaceID_XUL
) {
2322 if (tag
!= nsGkAtoms::menu
&& tag
!= nsGkAtoms::menuitem
)
2325 else if (ns
!= kNameSpaceID_XHTML
|| !aOnPopup
|| tag
!= nsGkAtoms::option
) {
2329 bool skipNavigatingDisabledMenuItem
= true;
2331 skipNavigatingDisabledMenuItem
=
2332 LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem
,
2336 return !(skipNavigatingDisabledMenuItem
&&
2337 aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
2338 nsGkAtoms::_true
, eCaseMatters
));
2342 nsXULPopupManager::HandleEvent(nsIDOMEvent
* aEvent
)
2344 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aEvent
);
2345 NS_ENSURE_TRUE(keyEvent
, NS_ERROR_UNEXPECTED
);
2347 //handlers shouldn't be triggered by non-trusted events.
2348 bool trustedEvent
= false;
2349 aEvent
->GetIsTrusted(&trustedEvent
);
2350 if (!trustedEvent
) {
2354 nsAutoString eventType
;
2355 keyEvent
->GetType(eventType
);
2356 if (eventType
.EqualsLiteral("keyup")) {
2357 return KeyUp(keyEvent
);
2359 if (eventType
.EqualsLiteral("keydown")) {
2360 return KeyDown(keyEvent
);
2362 if (eventType
.EqualsLiteral("keypress")) {
2363 return KeyPress(keyEvent
);
2372 nsXULPopupManager::KeyUp(nsIDOMKeyEvent
* aKeyEvent
)
2374 // don't do anything if a menu isn't open or a menubar isn't active
2375 if (!mActiveMenuBar
) {
2376 nsMenuChainItem
* item
= GetTopVisibleMenu();
2377 if (!item
|| item
->PopupType() != ePopupTypeMenu
)
2381 aKeyEvent
->StopPropagation();
2382 aKeyEvent
->PreventDefault();
2384 return NS_OK
; // I am consuming event
2388 nsXULPopupManager::KeyDown(nsIDOMKeyEvent
* aKeyEvent
)
2390 nsMenuChainItem
* item
= GetTopVisibleMenu();
2391 if (item
&& item
->Frame()->IsMenuLocked())
2394 if (HandleKeyboardEventWithKeyCode(aKeyEvent
, item
)) {
2398 // don't do anything if a menu isn't open or a menubar isn't active
2399 if (!mActiveMenuBar
&& (!item
|| item
->PopupType() != ePopupTypeMenu
))
2402 int32_t menuAccessKey
= -1;
2404 // If the key just pressed is the access key (usually Alt),
2405 // dismiss and unfocus the menu.
2407 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey
);
2408 if (menuAccessKey
) {
2410 aKeyEvent
->GetKeyCode(&theChar
);
2412 if (theChar
== (uint32_t)menuAccessKey
) {
2414 if (menuAccessKey
!= nsIDOMKeyEvent::DOM_VK_CONTROL
)
2415 aKeyEvent
->GetCtrlKey(&ctrl
);
2417 if (menuAccessKey
!= nsIDOMKeyEvent::DOM_VK_ALT
)
2418 aKeyEvent
->GetAltKey(&alt
);
2420 if (menuAccessKey
!= nsIDOMKeyEvent::DOM_VK_SHIFT
)
2421 aKeyEvent
->GetShiftKey(&shift
);
2423 if (menuAccessKey
!= nsIDOMKeyEvent::DOM_VK_META
)
2424 aKeyEvent
->GetMetaKey(&meta
);
2425 if (!(ctrl
|| alt
|| shift
|| meta
)) {
2426 // The access key just went down and no other
2427 // modifiers are already down.
2429 Rollup(0, nullptr, nullptr);
2430 else if (mActiveMenuBar
)
2431 mActiveMenuBar
->MenuClosed();
2433 aKeyEvent
->PreventDefault();
2437 // Since a menu was open, stop propagation of the event to keep other event
2438 // listeners from becoming confused.
2439 aKeyEvent
->StopPropagation();
2444 nsXULPopupManager::KeyPress(nsIDOMKeyEvent
* aKeyEvent
)
2446 // Don't check prevent default flag -- menus always get first shot at key events.
2448 nsMenuChainItem
* item
= GetTopVisibleMenu();
2450 (item
->Frame()->IsMenuLocked() || item
->PopupType() != ePopupTypeMenu
)) {
2454 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aKeyEvent
);
2455 NS_ENSURE_TRUE(keyEvent
, NS_ERROR_UNEXPECTED
);
2456 // if a menu is open or a menubar is active, it consumes the key event
2457 bool consume
= (mPopups
|| mActiveMenuBar
);
2458 HandleShortcutNavigation(keyEvent
, nullptr);
2460 aKeyEvent
->StopPropagation();
2461 aKeyEvent
->PreventDefault();
2464 return NS_OK
; // I am consuming event
2468 nsXULPopupShowingEvent::Run()
2470 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2472 pm
->FirePopupShowingEvent(mPopup
, mIsContextMenu
, mSelectFirstItem
);
2479 nsXULPopupHidingEvent::Run()
2481 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2483 nsIDocument
*document
= mPopup
->GetCurrentDoc();
2484 if (pm
&& document
) {
2485 nsIPresShell
* presShell
= document
->GetShell();
2487 nsPresContext
* context
= presShell
->GetPresContext();
2489 pm
->FirePopupHidingEvent(mPopup
, mNextPopup
, mLastPopup
,
2490 context
, mPopupType
, mDeselectMenu
, mIsRollup
);
2499 nsXULMenuCommandEvent::Run()
2501 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2505 // The order of the nsViewManager and nsIPresShell COM pointers is
2506 // important below. We want the pres shell to get released before the
2507 // associated view manager on exit from this function.
2509 // XXXndeakin is this still needed?
2511 nsCOMPtr
<nsIContent
> popup
;
2512 nsMenuFrame
* menuFrame
= do_QueryFrame(mMenu
->GetPrimaryFrame());
2513 nsWeakFrame
weakFrame(menuFrame
);
2514 if (menuFrame
&& mFlipChecked
) {
2515 if (menuFrame
->IsChecked()) {
2516 mMenu
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::checked
, true);
2518 mMenu
->SetAttr(kNameSpaceID_None
, nsGkAtoms::checked
,
2519 NS_LITERAL_STRING("true"), true);
2523 if (menuFrame
&& weakFrame
.IsAlive()) {
2524 // Find the popup that the menu is inside. Below, this popup will
2525 // need to be hidden.
2526 nsIFrame
* frame
= menuFrame
->GetParent();
2528 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
2530 popup
= popupFrame
->GetContent();
2533 frame
= frame
->GetParent();
2536 nsPresContext
* presContext
= menuFrame
->PresContext();
2537 nsCOMPtr
<nsIPresShell
> shell
= presContext
->PresShell();
2538 nsRefPtr
<nsViewManager
> kungFuDeathGrip
= shell
->GetViewManager();
2540 // Deselect ourselves.
2541 if (mCloseMenuMode
!= CloseMenuMode_None
)
2542 menuFrame
->SelectMenu(false);
2544 AutoHandlingUserInputStatePusher
userInpStatePusher(mUserInput
, nullptr,
2545 shell
->GetDocument());
2546 nsContentUtils::DispatchXULCommand(mMenu
, mIsTrusted
, nullptr, shell
,
2547 mControl
, mAlt
, mShift
, mMeta
);
2550 if (popup
&& mCloseMenuMode
!= CloseMenuMode_None
)
2551 pm
->HidePopup(popup
, mCloseMenuMode
== CloseMenuMode_Auto
, true, false, false);