Bumping manifests a=b2g-bump
[gecko.git] / layout / xul / nsXULPopupManager.cpp
blob54a35990a5c6d56557f232c3ef0a788838497343
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsGkAtoms.h"
7 #include "nsXULPopupManager.h"
8 #include "nsMenuFrame.h"
9 #include "nsMenuPopupFrame.h"
10 #include "nsMenuBarFrame.h"
11 #include "nsIPopupBoxObject.h"
12 #include "nsMenuBarListener.h"
13 #include "nsContentUtils.h"
14 #include "nsIDOMDocument.h"
15 #include "nsIDOMEvent.h"
16 #include "nsIDOMXULElement.h"
17 #include "nsIXULDocument.h"
18 #include "nsIXULTemplateBuilder.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsLayoutUtils.h"
21 #include "nsViewManager.h"
22 #include "nsIComponentManager.h"
23 #include "nsITimer.h"
24 #include "nsFocusManager.h"
25 #include "nsIDocShell.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsIBaseWindow.h"
29 #include "nsIDOMKeyEvent.h"
30 #include "nsIDOMMouseEvent.h"
31 #include "nsCaret.h"
32 #include "nsIDocument.h"
33 #include "nsPIWindowRoot.h"
34 #include "nsFrameManager.h"
35 #include "nsIObserverService.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
38 #include "mozilla/EventDispatcher.h"
39 #include "mozilla/EventStateManager.h"
40 #include "mozilla/LookAndFeel.h"
41 #include "mozilla/MouseEvents.h"
42 #include "mozilla/Services.h"
44 using namespace mozilla;
45 using namespace mozilla::dom;
47 static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 &&
48 nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 &&
49 nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 &&
50 nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 &&
51 nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5,
52 "nsXULPopupManager assumes some keyCode values are consecutive");
54 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
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)
82 if (mParent) {
83 NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
84 mParent->mChild = nullptr;
86 mParent = aParent;
87 if (mParent) {
88 if (mParent->mChild)
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.
99 if (mChild) {
100 NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
101 mChild->SetParent(mParent);
103 else {
104 // An item without a child should be the first item in the chain, so set
105 // the first item pointer, pointed to by aRoot, to the parent.
106 NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
107 *aRoot = mParent;
108 SetParent(nullptr);
112 NS_IMPL_ISUPPORTS(nsXULPopupManager,
113 nsIDOMEventListener,
114 nsITimerCallback,
115 nsIObserver)
117 nsXULPopupManager::nsXULPopupManager() :
118 mRangeOffset(0),
119 mCachedMousePoint(0, 0),
120 mCachedModifiers(0),
121 mActiveMenuBar(nullptr),
122 mPopups(nullptr),
123 mNoHidePanels(nullptr),
124 mTimerMenu(nullptr)
126 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
127 if (obs) {
128 obs->AddObserver(this, "xpcom-shutdown", false);
132 nsXULPopupManager::~nsXULPopupManager()
134 NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
137 nsresult
138 nsXULPopupManager::Init()
140 sInstance = new nsXULPopupManager();
141 NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
142 NS_ADDREF(sInstance);
143 return NS_OK;
146 void
147 nsXULPopupManager::Shutdown()
149 NS_IF_RELEASE(sInstance);
152 NS_IMETHODIMP
153 nsXULPopupManager::Observe(nsISupports *aSubject,
154 const char *aTopic,
155 const char16_t *aData)
157 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
158 if (mKeyListener) {
159 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
160 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
161 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
162 mKeyListener = nullptr;
164 mRangeParent = nullptr;
165 // mOpeningPopup is cleared explicitly soon after using it.
166 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
167 if (obs) {
168 obs->RemoveObserver(this, "xpcom-shutdown");
172 return NS_OK;
175 nsXULPopupManager*
176 nsXULPopupManager::GetInstance()
178 MOZ_ASSERT(sInstance);
179 return sInstance;
182 bool
183 nsXULPopupManager::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
185 bool consume = false;
187 nsMenuChainItem* item = GetTopVisibleMenu();
188 if (item) {
189 if (aLastRolledUp) {
190 // we need to get the popup that will be closed last, so that
191 // widget can keep track of it so it doesn't reopen if a mouse
192 // down event is going to processed.
193 // Keep going up the menu chain to get the first level menu. This will
194 // be the one that closes up last. It's possible that this menu doesn't
195 // end up closing because the popuphiding event was cancelled, but in
196 // that case we don't need to deal with the menu reopening as it will
197 // already still be open.
198 nsMenuChainItem* first = item;
199 while (first->GetParent())
200 first = first->GetParent();
201 *aLastRolledUp = first->Content();
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.
214 if (anchor) {
215 nsAutoString consumeAnchor;
216 anchor->GetAttr(kNameSpaceID_None, nsGkAtoms::consumeanchor,
217 consumeAnchor);
218 if (!consumeAnchor.IsEmpty()) {
219 nsIDocument* doc = anchor->GetOwnerDocument();
220 nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
221 if (newAnchor) {
222 anchor = newAnchor;
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)) {
233 consume = true;
238 // if a number of popups to close has been specified, determine the last
239 // popup to close
240 nsIContent* lastPopup = nullptr;
241 if (aCount != UINT32_MAX) {
242 nsMenuChainItem* last = item;
243 while (--aCount && last->GetParent()) {
244 last = last->GetParent();
246 if (last) {
247 lastPopup = last->Content();
251 HidePopup(item->Content(), true, true, false, true, lastPopup);
254 return consume;
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();
264 if (!item)
265 return false;
267 nsIContent* content = item->Frame()->GetContent();
268 if (!content)
269 return false;
271 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
272 nsGkAtoms::_true, eCaseMatters))
273 return true;
275 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
276 nsGkAtoms::_false, eCaseMatters))
277 return false;
279 nsAutoString value;
280 content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
281 return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
284 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
286 nsMenuChainItem* item = GetTopVisibleMenu();
287 if (!item)
288 return false;
290 nsMenuPopupFrame* frame = item->Frame();
291 if (frame->PopupType() != ePopupTypePanel)
292 return true;
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()
302 return false;
305 uint32_t
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();
315 while (item) {
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,
321 // stop scanning.
322 nsMenuChainItem* parent = item->GetParent();
323 if (!sameTypeCount) {
324 count++;
325 if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
326 item->IsContextMenu() != parent->IsContextMenu()) {
327 sameTypeCount = count;
330 item = parent;
333 return sameTypeCount;
336 nsIWidget*
337 nsXULPopupManager::GetRollupWidget()
339 nsMenuChainItem* item = GetTopVisibleMenu();
340 return item ? item->Frame()->GetWidget() : nullptr;
343 void
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;
354 while (item) {
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();
360 if (popup) {
361 nsIDocument* document = popup->GetCurrentDoc();
362 if (document) {
363 nsPIDOMWindow* window = document->GetWindow();
364 if (window) {
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());
389 static
390 nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
392 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
393 if (!menuPopupFrame)
394 return nullptr;
396 // no point moving or resizing hidden popups
397 if (!menuPopupFrame->IsVisible())
398 return nullptr;
400 nsIWidget* widget = menuPopupFrame->GetWidget();
401 if (widget && !widget->IsVisible())
402 return nullptr;
404 return menuPopupFrame;
407 void
408 nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
410 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
411 if (!menuPopupFrame)
412 return;
414 nsView* view = menuPopupFrame->GetView();
415 if (!view)
416 return;
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())) {
424 return;
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);
435 else {
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);
443 void
444 nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize)
446 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
447 if (!menuPopupFrame)
448 return;
450 nsView* view = menuPopupFrame->GetView();
451 if (!view)
452 return;
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)
457 return;
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);
474 nsMenuPopupFrame*
475 nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
477 if (aShouldFlush) {
478 nsIDocument *document = aContent->GetCurrentDoc();
479 if (document) {
480 nsCOMPtr<nsIPresShell> presShell = document->GetShell();
481 if (presShell)
482 presShell->FlushPendingNotifications(Flush_Layout);
486 return do_QueryFrame(aContent->GetPrimaryFrame());
489 nsMenuChainItem*
490 nsXULPopupManager::GetTopVisibleMenu()
492 nsMenuChainItem* item = mPopups;
493 while (item && item->Frame()->PopupState() == ePopupInvisible)
494 item = item->GetParent();
495 return item;
498 void
499 nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset)
501 *aNode = mRangeParent;
502 NS_IF_ADDREF(*aNode);
503 *aOffset = mRangeOffset;
506 void
507 nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
508 nsIContent** aTriggerContent)
510 mCachedMousePoint = nsIntPoint(0, 0);
512 if (aTriggerContent) {
513 *aTriggerContent = nullptr;
514 if (aEvent) {
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);
525 if (uiEvent) {
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();
533 if (event) {
534 WidgetInputEvent* inputEvent = event->AsInputEvent();
535 if (inputEvent) {
536 mCachedModifiers = inputEvent->modifiers;
538 nsIDocument* doc = aPopup->GetCurrentDoc();
539 if (doc) {
540 nsIPresShell* presShell = doc->GetShell();
541 nsPresContext* presContext;
542 if (presShell && (presContext = presShell->GetPresContext())) {
543 nsPresContext* rootDocPresContext =
544 presContext->GetRootPresContext();
545 if (!rootDocPresContext)
546 return;
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);
555 nsIntPoint clientPt;
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) {
569 nsPoint pnt =
570 nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
571 mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
572 rootDocPresContext->AppUnitsToDevPixels(pnt.y));
578 else {
579 mRangeParent = nullptr;
580 mRangeOffset = 0;
584 void
585 nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
587 if (aActivate)
588 mActiveMenuBar = aMenuBar;
589 else if (mActiveMenuBar == aMenuBar)
590 mActiveMenuBar = nullptr;
592 UpdateKeyboardListeners();
595 void
596 nsXULPopupManager::ShowMenu(nsIContent *aMenu,
597 bool aSelectFirstItem,
598 bool aAsynchronous)
600 // generate any template content first. Otherwise, the menupopup may not
601 // have been created yet.
602 if (aMenu) {
603 nsIContent* element = aMenu;
604 do {
605 nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element);
606 if (xulelem) {
607 nsCOMPtr<nsIXULTemplateBuilder> builder;
608 xulelem->GetBuilder(getter_AddRefs(builder));
609 if (builder) {
610 builder->CreateContents(aMenu, true);
611 break;
614 element = element->GetParent();
615 } while (element);
618 nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
619 if (!menuFrame || !menuFrame->IsMenu())
620 return;
622 nsMenuPopupFrame* popupFrame = menuFrame->GetPopup();
623 if (!popupFrame || !MayShowPopup(popupFrame))
624 return;
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");
640 else
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);
647 if (aAsynchronous) {
648 nsCOMPtr<nsIRunnable> event =
649 new nsXULPopupShowingEvent(popupFrame->GetContent(),
650 parentIsContextMenu, aSelectFirstItem);
651 NS_DispatchToCurrentThread(event);
653 else {
654 nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
655 FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem);
659 void
660 nsXULPopupManager::ShowPopup(nsIContent* aPopup,
661 nsIContent* aAnchorContent,
662 const nsAString& aPosition,
663 int32_t aXPos, int32_t aYPos,
664 bool aIsContextMenu,
665 bool aAttributesOverride,
666 bool aSelectFirstItem,
667 nsIDOMEvent* aTriggerEvent)
669 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
670 if (!popupFrame || !MayShowPopup(popupFrame))
671 return;
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);
682 void
683 nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
684 int32_t aXPos, int32_t aYPos,
685 bool aIsContextMenu,
686 nsIDOMEvent* aTriggerEvent)
688 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
689 if (!popupFrame || !MayShowPopup(popupFrame))
690 return;
692 nsCOMPtr<nsIContent> triggerContent;
693 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
695 popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
696 FirePopupShowingEvent(aPopup, aIsContextMenu, false);
699 void
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))
706 return;
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();
718 if (rootWidget) {
719 mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
723 popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
725 FirePopupShowingEvent(aPopup, false, false);
728 void
729 nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
730 nsIContent* aAnchorContent,
731 nsAString& aAnchor,
732 nsAString& aAlign,
733 int32_t aXPos, int32_t aYPos,
734 bool aIsContextMenu)
736 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
737 if (!popupFrame || !MayShowPopup(popupFrame))
738 return;
740 InitTriggerEvent(nullptr, nullptr, nullptr);
742 popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
743 aAlign, aXPos, aYPos);
744 FirePopupShowingEvent(aPopup, aIsContextMenu, false);
747 static void
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();
753 if (fm) {
754 nsCOMPtr<nsIDOMWindow> window;
755 fm->GetFocusedWindow(getter_AddRefs(window));
756 if (!window)
757 return;
759 nsCOMPtr<nsIDOMDocument> domDoc;
760 nsCOMPtr<nsIDocument> focusedDoc;
761 window->GetDocument(getter_AddRefs(domDoc));
762 focusedDoc = do_QueryInterface(domDoc);
763 if (!focusedDoc)
764 return;
766 nsIPresShell* presShell = focusedDoc->GetShell();
767 if (!presShell)
768 return;
770 nsRefPtr<nsCaret> caret = presShell->GetCaret();
771 if (!caret)
772 return;
773 caret->SchedulePaint();
777 void
778 nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
779 nsMenuPopupFrame* aPopupFrame,
780 bool aIsContextMenu,
781 bool aSelectFirstItem)
783 nsPopupType popupType = aPopupFrame->PopupType();
784 bool ismenu = (popupType == ePopupTypeMenu);
786 nsMenuChainItem* item =
787 new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
788 if (!item)
789 return;
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);
799 if (ismenu) {
800 // if the menu is on a menubar, use the menubar's listener instead
801 nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
802 if (menuFrame) {
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;
820 else {
821 nsIContent* oldmenu = nullptr;
822 if (mPopups)
823 oldmenu = mPopups->Content();
824 item->SetParent(mPopups);
825 mPopups = item;
826 SetCaptureState(oldmenu);
829 if (aSelectFirstItem) {
830 nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true);
831 aPopupFrame->SetCurrentMenuItem(next);
834 if (ismenu)
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();
842 void
843 nsXULPopupManager::HidePopup(nsIContent* aPopup,
844 bool aHideChain,
845 bool aDeselectMenu,
846 bool aAsynchronous,
847 bool aIsRollup,
848 nsIContent* aLastPopup)
850 // if the popup is on the nohide panels list, remove it but don't close any
851 // other panels
852 nsMenuPopupFrame* popupFrame = nullptr;
853 bool foundPanel = false;
854 nsMenuChainItem* item = mNoHidePanels;
855 while (item) {
856 if (item->Content() == aPopup) {
857 foundPanel = true;
858 popupFrame = item->Frame();
859 break;
861 item = item->GetParent();
864 // when removing a menu, all of the child popups must be closed
865 nsMenuChainItem* foundMenu = nullptr;
866 item = mPopups;
867 while (item) {
868 if (item->Content() == aPopup) {
869 foundMenu = item;
870 break;
872 item = item->GetParent();
875 nsPopupType type = ePopupTypePanel;
876 bool deselectMenu = false;
877 nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
878 if (foundMenu) {
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()) {
902 topMenu = item;
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;
925 if (popupFrame) {
926 nsPopupState state = popupFrame->PopupState();
927 // if the popup is already being hidden, don't attempt to hide it again
928 if (state == ePopupHiding)
929 return;
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.
937 if (aAsynchronous) {
938 nsCOMPtr<nsIRunnable> event =
939 new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
940 type, deselectMenu, aIsRollup);
941 NS_DispatchToCurrentThread(event);
943 else {
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
953 protected:
954 virtual ~TransitionEnder() { }
956 public:
958 nsCOMPtr<nsIContent> mContent;
959 bool mDeselectMenu;
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);
984 return NS_OK;
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)
993 NS_INTERFACE_MAP_END
995 NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
997 void
998 nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
999 nsMenuPopupFrame* aPopupFrame,
1000 nsIContent* aNextPopup,
1001 nsIContent* aLastPopup,
1002 nsPopupType aPopupType,
1003 bool aDeselectMenu)
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;
1017 while (item) {
1018 if (item->Content() == aPopup) {
1019 item->Detach(&mNoHidePanels);
1020 break;
1022 item = item->GetParent();
1025 if (!item) {
1026 item = mPopups;
1027 while (item) {
1028 if (item->Content() == aPopup) {
1029 item->Detach(&mPopups);
1030 SetCaptureState(aPopup);
1031 break;
1033 item = item->GetParent();
1037 delete item;
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
1044 // behaviour.
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;
1056 while (item) {
1057 if (item->Content() == aNextPopup) {
1058 foundMenu = item;
1059 break;
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.
1068 if (foundMenu &&
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)
1081 return;
1082 if (state != ePopupInvisible)
1083 popupFrame->SetPopupState(ePopupHiding);
1085 FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
1086 popupFrame->PresContext(),
1087 foundMenu->PopupType(), aDeselectMenu, false);
1092 void
1093 nsXULPopupManager::HidePopup(nsIFrame* aFrame)
1095 nsMenuPopupFrame* popup = do_QueryFrame(aFrame);
1096 if (popup)
1097 HidePopup(aFrame->GetContent(), false, true, false, false);
1100 void
1101 nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
1103 // Don't close up immediately.
1104 // Kick off a close timer.
1105 KillMenuTimer();
1107 int32_t menuDelay =
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;
1119 void
1120 nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames,
1121 bool aDeselectMenu)
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());
1127 uint32_t f;
1128 for (f = 0; f < aFrames.Length(); f++) {
1129 nsWeakFrame* wframe = weakPopups.AppendElement();
1130 if (wframe)
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);
1146 bool
1147 nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
1149 nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
1150 while(docShellItem) {
1151 if (docShellItem == aExpected)
1152 return true;
1154 nsCOMPtr<nsIDocShellTreeItem> parent;
1155 docShellItem->GetParent(getter_AddRefs(parent));
1156 docShellItem = parent;
1159 return false;
1162 void
1163 nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
1165 nsTArray<nsMenuPopupFrame *> popupsToHide;
1167 // iterate to get the set of popup frames to hide
1168 nsMenuChainItem* item = mPopups;
1169 while (item) {
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);
1175 delete item;
1176 popupsToHide.AppendElement(frame);
1178 item = parent;
1181 // now look for panels to hide
1182 item = mNoHidePanels;
1183 while (item) {
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);
1189 delete item;
1190 popupsToHide.AppendElement(frame);
1192 item = parent;
1195 HidePopupsInList(popupsToHide, true);
1198 void
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)) {
1208 case 0:
1209 cmm = CloseMenuMode_None;
1210 break;
1211 case 1:
1212 cmm = CloseMenuMode_Single;
1213 break;
1214 default:
1215 break;
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) {
1226 while (item) {
1227 // if it isn't a <menupopup>, don't close it automatically
1228 if (!item->IsMenu())
1229 break;
1230 nsMenuChainItem* next = item->GetParent();
1231 popupsToHide.AppendElement(item->Frame());
1232 if (cmm == CloseMenuMode_Single) // only close one level of menu
1233 break;
1234 item = next;
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);
1247 void
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());
1255 if (!popupFrame)
1256 return;
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();
1270 if (!frame)
1271 return;
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
1278 // has fired.
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));
1292 else {
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();
1313 if (fm) {
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
1320 // remains focused.
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;
1333 mRangeOffset = 0;
1335 // get the frame again in case it went away
1336 popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1337 if (popupFrame) {
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();
1344 else {
1345 ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem);
1350 void
1351 nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
1352 nsIContent* aNextPopup,
1353 nsIContent* aLastPopup,
1354 nsPresContext *aPresContext,
1355 nsPopupType aPopupType,
1356 bool aDeselectMenu,
1357 bool aIsRollup)
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();
1371 if (fm) {
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());
1387 if (popupFrame) {
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
1390 // from hiding.
1391 if (status == nsEventStatus_eConsumeNoDefault &&
1392 !popupFrame->IsInContentShell()) {
1393 // XXXndeakin
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);
1399 else {
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());
1421 if (!popupFrame)
1422 return;
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);
1428 return;
1432 #endif
1434 HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
1435 aPopupType, aDeselectMenu);
1440 bool
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;
1447 while (item) {
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");
1453 return true;
1455 item = item->GetParent();
1458 item = mNoHidePanels;
1459 while (item) {
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");
1465 return true;
1467 item = item->GetParent();
1470 return false;
1473 bool
1474 nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
1476 nsMenuChainItem* item = GetTopVisibleMenu();
1477 while (item) {
1478 nsMenuPopupFrame* popup = item->Frame();
1479 if (popup && popup->IsOpen()) {
1480 nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
1481 if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
1482 return true;
1485 item = item->GetParent();
1488 return false;
1491 nsIFrame*
1492 nsXULPopupManager::GetTopPopup(nsPopupType aType)
1494 if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
1495 return mNoHidePanels->Frame();
1497 nsMenuChainItem* item = GetTopVisibleMenu();
1498 while (item) {
1499 if (item->PopupType() == aType || aType == ePopupTypeAny)
1500 return item->Frame();
1501 item = item->GetParent();
1504 return nullptr;
1507 void
1508 nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
1510 aPopups.Clear();
1512 // Iterate over both lists of popups
1513 nsMenuChainItem* item = mPopups;
1514 for (int32_t list = 0; list < 2; list++) {
1515 while (item) {
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)
1532 if (!aDocument)
1533 return nullptr;
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)));
1544 else {
1545 nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
1546 while (item) {
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()));
1551 if (node)
1552 break;
1554 item = item->GetParent();
1558 return node.forget();
1561 bool
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)
1579 return false;
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");
1584 return false;
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())
1590 return false;
1592 nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
1593 nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
1594 if (!baseWin)
1595 return false;
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));
1603 if (!root) {
1604 return false;
1607 nsCOMPtr<nsIDOMWindow> rootWin = root->GetWindow();
1609 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1610 if (!fm || !rootWin)
1611 return false;
1613 nsCOMPtr<nsIDOMWindow> activeWindow;
1614 fm->GetActiveWindow(getter_AddRefs(activeWindow));
1615 if (activeWindow != rootWin)
1616 return false;
1618 // only allow popups in visible frames
1619 bool visible;
1620 baseWin->GetVisibility(&visible);
1621 if (!visible)
1622 return false;
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) {
1630 return false;
1633 // cannot open a popup that is a submenu of a menupopup that isn't open.
1634 nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
1635 if (menuFrame) {
1636 nsMenuParent* parentPopup = menuFrame->GetMenuParent();
1637 if (parentPopup && !parentPopup->IsOpen())
1638 return false;
1641 return true;
1644 void
1645 nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
1647 // when a popup frame is destroyed, just unhook it from the list of popups
1648 if (mTimerMenu == aPopup) {
1649 if (mCloseTimer) {
1650 mCloseTimer->Cancel();
1651 mCloseTimer = nullptr;
1653 mTimerMenu = nullptr;
1656 nsMenuChainItem* item = mNoHidePanels;
1657 while (item) {
1658 if (item->Frame() == aPopup) {
1659 item->Detach(&mNoHidePanels);
1660 delete item;
1661 break;
1663 item = item->GetParent();
1666 nsTArray<nsMenuPopupFrame *> popupsToHide;
1668 item = mPopups;
1669 while (item) {
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();
1678 while (child) {
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);
1688 else {
1689 // HidePopup will take care of hiding any of its children, so
1690 // break out afterwards
1691 HidePopup(child->Content(), false, false, true, false);
1692 break;
1695 child = child->GetChild();
1699 item->Detach(&mPopups);
1700 delete item;
1701 break;
1704 item = item->GetParent();
1707 HidePopupsInList(popupsToHide, false);
1710 bool
1711 nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
1713 nsMenuChainItem* item = GetTopVisibleMenu();
1714 while (item && item->Frame() != aPopup) {
1715 if (item->IsContextMenu())
1716 return true;
1717 item = item->GetParent();
1720 return false;
1723 void
1724 nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
1726 nsMenuChainItem* item = GetTopVisibleMenu();
1727 if (item && aOldPopup == item->Content())
1728 return;
1730 if (mWidget) {
1731 mWidget->CaptureRollupEvents(nullptr, false);
1732 mWidget = nullptr;
1735 if (item) {
1736 nsMenuPopupFrame* popup = item->Frame();
1737 mWidget = popup->GetWidget();
1738 if (mWidget) {
1739 mWidget->CaptureRollupEvents(nullptr, true);
1740 popup->AttachedDismissalListener();
1744 UpdateKeyboardListeners();
1747 void
1748 nsXULPopupManager::UpdateKeyboardListeners()
1750 nsCOMPtr<EventTarget> newTarget;
1751 bool isForMenu = false;
1752 nsMenuChainItem* item = GetTopVisibleMenu();
1753 if (item) {
1754 if (!item->IgnoreKeys())
1755 newTarget = item->Content()->GetComposedDoc();
1756 isForMenu = item->PopupType() == ePopupTypeMenu;
1758 else if (mActiveMenuBar) {
1759 newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
1760 isForMenu = true;
1763 if (mKeyListener != newTarget) {
1764 if (mKeyListener) {
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);
1772 if (newTarget) {
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;
1782 void
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();
1789 if (!document) {
1790 return;
1793 for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
1794 grandChild;
1795 grandChild = grandChild->GetNextSibling()) {
1796 if (grandChild->IsXUL(nsGkAtoms::menugroup)) {
1797 if (grandChild->GetChildCount() == 0) {
1798 continue;
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);
1815 else
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();
1842 // Notify
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
1847 // other items, and
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
1851 // none of them.
1853 // case (1):
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.
1863 // case (2);
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.
1884 nsresult
1885 nsXULPopupManager::Notify(nsITimer* aTimer)
1887 if (aTimer == mCloseTimer)
1888 KillMenuTimer();
1890 return NS_OK;
1893 void
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;
1907 void
1908 nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
1910 if (mCloseTimer && mTimerMenu == aMenuParent) {
1911 mCloseTimer->Cancel();
1912 mCloseTimer = nullptr;
1913 mTimerMenu = nullptr;
1917 bool
1918 nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
1919 nsMenuPopupFrame* aFrame)
1921 nsMenuChainItem* item = GetTopVisibleMenu();
1922 if (!aFrame && item)
1923 aFrame = item->Frame();
1925 if (aFrame) {
1926 bool action;
1927 nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
1928 if (result) {
1929 aFrame->ChangeMenuItem(result, false);
1930 if (action) {
1931 WidgetGUIEvent* evt = aKeyEvent->GetInternalNSEvent()->AsGUIEvent();
1932 nsMenuFrame* menuToOpen = result->Enter(evt);
1933 if (menuToOpen) {
1934 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
1935 ShowMenu(content, true, false);
1938 return true;
1941 return false;
1944 if (mActiveMenuBar) {
1945 nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
1946 if (result) {
1947 mActiveMenuBar->SetActive(true);
1948 result->OpenMenu(true);
1949 return true;
1953 return false;
1957 bool
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();
1965 while (nextitem) {
1966 item = nextitem;
1967 nextitem = item->GetParent();
1969 if (nextitem) {
1970 // stop if the parent isn't a menu
1971 if (!nextitem->IsMenu())
1972 break;
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) {
1980 break;
1985 nsIFrame* itemFrame;
1986 if (item)
1987 itemFrame = item->Frame();
1988 else if (mActiveMenuBar)
1989 itemFrame = mActiveMenuBar;
1990 else
1991 return false;
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))
2000 return true;
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);
2011 return true;
2013 else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
2014 // Open the menu and select its first item.
2015 if (currentMenu) {
2016 nsCOMPtr<nsIContent> content = currentMenu->GetContent();
2017 ShowMenu(content, true, false);
2019 return true;
2023 return false;
2026 bool
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);
2045 if (nextItem) {
2046 aFrame->ChangeMenuItem(nextItem, false);
2047 return true;
2050 return false;
2053 bool isContainer = false;
2054 bool isOpen = false;
2055 if (currentMenu) {
2056 isOpen = currentMenu->IsOpen();
2057 isContainer = currentMenu->IsMenu();
2058 if (isOpen) {
2059 // for an open popup, have the child process the event
2060 nsMenuChainItem* child = item ? item->GetChild() : nullptr;
2061 if (child && HandleKeyboardNavigationInPopup(child, aDir))
2062 return true;
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);
2069 return true;
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);
2084 else
2085 nextItem = GetPreviousMenuItem(aFrame, nullptr, true);
2087 if (nextItem) {
2088 aFrame->ChangeMenuItem(nextItem, false);
2089 return true;
2092 else if (currentMenu && isContainer && isOpen) {
2093 if (aDir == eNavigationDirection_Start) {
2094 // close a submenu when Left is pressed
2095 nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
2096 if (popupFrame)
2097 HidePopup(popupFrame->GetContent(), false, false, false, false);
2098 return true;
2102 return false;
2105 bool
2106 nsXULPopupManager::HandleKeyboardEventWithKeyCode(
2107 nsIDOMKeyEvent* aKeyEvent,
2108 nsMenuChainItem* aTopVisibleMenuItem)
2110 uint32_t keyCode;
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();
2121 return true;
2124 bool consume = (mPopups || mActiveMenuBar);
2125 switch (keyCode) {
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);
2133 break;
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();
2145 break;
2147 case nsIDOMKeyEvent::DOM_VK_TAB:
2148 #ifndef XP_MACOSX
2149 case nsIDOMKeyEvent::DOM_VK_F10:
2150 #endif
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();
2157 break;
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);
2170 if (menuToOpen) {
2171 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
2172 ShowMenu(content, true, false);
2174 break;
2177 default:
2178 return false;
2181 if (consume) {
2182 aKeyEvent->StopPropagation();
2183 aKeyEvent->PreventDefault();
2185 return true;
2188 nsMenuFrame*
2189 nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent,
2190 nsMenuFrame* aStart,
2191 bool aIsPopup)
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;
2200 if (aStart) {
2201 if (aStart->GetNextSibling())
2202 currFrame = aStart->GetNextSibling();
2203 else if (aStart->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup))
2204 currFrame = aStart->GetParent()->GetNextSibling();
2206 else
2207 currFrame = immediateParent->GetFirstPrincipalChild();
2209 while (currFrame) {
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();
2221 else
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();
2240 else
2241 currFrame = currFrame->GetNextSibling();
2244 // No luck. Just return our start value.
2245 return aStart;
2248 nsMenuFrame*
2249 nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent,
2250 nsMenuFrame* aStart,
2251 bool aIsPopup)
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;
2262 if (aStart) {
2263 if (aStart->GetPrevSibling())
2264 currFrame = aStart->GetPrevSibling();
2265 else if (aStart->GetParent()->GetContent()->IsXUL(nsGkAtoms::menugroup))
2266 currFrame = aStart->GetParent()->GetPrevSibling();
2268 else
2269 currFrame = frames.LastChild();
2271 while (currFrame) {
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();
2285 else
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();
2306 else
2307 currFrame = currFrame->GetPrevSibling();
2310 // No luck. Just return our start value.
2311 return aStart;
2314 bool
2315 nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext,
2316 nsIContent* aContent,
2317 bool aOnPopup)
2319 int32_t ns = aContent->GetNameSpaceID();
2320 nsIAtom *tag = aContent->Tag();
2321 if (ns == kNameSpaceID_XUL) {
2322 if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem)
2323 return false;
2325 else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) {
2326 return false;
2329 bool skipNavigatingDisabledMenuItem = true;
2330 if (aOnPopup) {
2331 skipNavigatingDisabledMenuItem =
2332 LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
2333 0) != 0;
2336 return !(skipNavigatingDisabledMenuItem &&
2337 aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
2338 nsGkAtoms::_true, eCaseMatters));
2341 nsresult
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) {
2351 return NS_OK;
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);
2366 NS_ABORT();
2368 return NS_OK;
2371 nsresult
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)
2378 return NS_OK;
2381 aKeyEvent->StopPropagation();
2382 aKeyEvent->PreventDefault();
2384 return NS_OK; // I am consuming event
2387 nsresult
2388 nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
2390 nsMenuChainItem* item = GetTopVisibleMenu();
2391 if (item && item->Frame()->IsMenuLocked())
2392 return NS_OK;
2394 if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
2395 return NS_OK;
2398 // don't do anything if a menu isn't open or a menubar isn't active
2399 if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
2400 return NS_OK;
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) {
2409 uint32_t theChar;
2410 aKeyEvent->GetKeyCode(&theChar);
2412 if (theChar == (uint32_t)menuAccessKey) {
2413 bool ctrl = false;
2414 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
2415 aKeyEvent->GetCtrlKey(&ctrl);
2416 bool alt=false;
2417 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
2418 aKeyEvent->GetAltKey(&alt);
2419 bool shift=false;
2420 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
2421 aKeyEvent->GetShiftKey(&shift);
2422 bool meta=false;
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.
2428 if (mPopups)
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();
2440 return NS_OK;
2443 nsresult
2444 nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
2446 // Don't check prevent default flag -- menus always get first shot at key events.
2448 nsMenuChainItem* item = GetTopVisibleMenu();
2449 if (item &&
2450 (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
2451 return NS_OK;
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);
2459 if (consume) {
2460 aKeyEvent->StopPropagation();
2461 aKeyEvent->PreventDefault();
2464 return NS_OK; // I am consuming event
2467 NS_IMETHODIMP
2468 nsXULPopupShowingEvent::Run()
2470 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2471 if (pm) {
2472 pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem);
2475 return NS_OK;
2478 NS_IMETHODIMP
2479 nsXULPopupHidingEvent::Run()
2481 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2483 nsIDocument *document = mPopup->GetCurrentDoc();
2484 if (pm && document) {
2485 nsIPresShell* presShell = document->GetShell();
2486 if (presShell) {
2487 nsPresContext* context = presShell->GetPresContext();
2488 if (context) {
2489 pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
2490 context, mPopupType, mDeselectMenu, mIsRollup);
2495 return NS_OK;
2498 NS_IMETHODIMP
2499 nsXULMenuCommandEvent::Run()
2501 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2502 if (!pm)
2503 return NS_OK;
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.
2508 // See bug 54233.
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);
2517 } else {
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();
2527 while (frame) {
2528 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
2529 if (popupFrame) {
2530 popup = popupFrame->GetContent();
2531 break;
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);
2553 return NS_OK;