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