michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsXULPopupManager.h" michael@0: #include "nsMenuFrame.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsMenuBarFrame.h" michael@0: #include "nsIPopupBoxObject.h" michael@0: #include "nsMenuBarListener.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMXULElement.h" michael@0: #include "nsIXULDocument.h" michael@0: #include "nsIXULTemplateBuilder.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsITimer.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsCaret.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsPIWindowRoot.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 && michael@0: nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 && michael@0: nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 && michael@0: nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 && michael@0: nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5, michael@0: "nsXULPopupManager assumes some keyCode values are consecutive"); michael@0: michael@0: const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = { michael@0: { michael@0: eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END michael@0: eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME michael@0: eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_LEFT michael@0: eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP michael@0: eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_RIGHT michael@0: eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN michael@0: }, michael@0: { michael@0: eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END michael@0: eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME michael@0: eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_LEFT michael@0: eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP michael@0: eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_RIGHT michael@0: eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN michael@0: } michael@0: }; michael@0: michael@0: nsXULPopupManager* nsXULPopupManager::sInstance = nullptr; michael@0: michael@0: nsIContent* nsMenuChainItem::Content() michael@0: { michael@0: return mFrame->GetContent(); michael@0: } michael@0: michael@0: void nsMenuChainItem::SetParent(nsMenuChainItem* aParent) michael@0: { michael@0: if (mParent) { michael@0: NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this"); michael@0: mParent->mChild = nullptr; michael@0: } michael@0: mParent = aParent; michael@0: if (mParent) { michael@0: if (mParent->mChild) michael@0: mParent->mChild->mParent = nullptr; michael@0: mParent->mChild = this; michael@0: } michael@0: } michael@0: michael@0: void nsMenuChainItem::Detach(nsMenuChainItem** aRoot) michael@0: { michael@0: // If the item has a child, set the child's parent to this item's parent, michael@0: // effectively removing the item from the chain. If the item has no child, michael@0: // just set the parent to null. michael@0: if (mChild) { michael@0: NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain"); michael@0: mChild->SetParent(mParent); michael@0: } michael@0: else { michael@0: // An item without a child should be the first item in the chain, so set michael@0: // the first item pointer, pointed to by aRoot, to the parent. michael@0: NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain"); michael@0: *aRoot = mParent; michael@0: SetParent(nullptr); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXULPopupManager, michael@0: nsIDOMEventListener, michael@0: nsITimerCallback, michael@0: nsIObserver) michael@0: michael@0: nsXULPopupManager::nsXULPopupManager() : michael@0: mRangeOffset(0), michael@0: mCachedMousePoint(0, 0), michael@0: mCachedModifiers(0), michael@0: mActiveMenuBar(nullptr), michael@0: mPopups(nullptr), michael@0: mNoHidePanels(nullptr), michael@0: mTimerMenu(nullptr) michael@0: { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(this, "xpcom-shutdown", false); michael@0: } michael@0: } michael@0: michael@0: nsXULPopupManager::~nsXULPopupManager() michael@0: { michael@0: NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open"); michael@0: } michael@0: michael@0: nsresult michael@0: nsXULPopupManager::Init() michael@0: { michael@0: sInstance = new nsXULPopupManager(); michael@0: NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); michael@0: NS_ADDREF(sInstance); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::Shutdown() michael@0: { michael@0: NS_IF_RELEASE(sInstance); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXULPopupManager::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { michael@0: if (mKeyListener) { michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); michael@0: mKeyListener = nullptr; michael@0: } michael@0: mRangeParent = nullptr; michael@0: // mOpeningPopup is cleared explicitly soon after using it. michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->RemoveObserver(this, "xpcom-shutdown"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsXULPopupManager* michael@0: nsXULPopupManager::GetInstance() michael@0: { michael@0: MOZ_ASSERT(sInstance); michael@0: return sInstance; michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp) michael@0: { michael@0: bool consume = false; michael@0: michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (item) { michael@0: if (aLastRolledUp) { michael@0: // we need to get the popup that will be closed last, so that michael@0: // widget can keep track of it so it doesn't reopen if a mouse michael@0: // down event is going to processed. michael@0: // Keep going up the menu chain to get the first level menu. This will michael@0: // be the one that closes up last. It's possible that this menu doesn't michael@0: // end up closing because the popuphiding event was cancelled, but in michael@0: // that case we don't need to deal with the menu reopening as it will michael@0: // already still be open. michael@0: nsMenuChainItem* first = item; michael@0: while (first->GetParent()) michael@0: first = first->GetParent(); michael@0: *aLastRolledUp = first->Content(); michael@0: } michael@0: michael@0: consume = item->Frame()->ConsumeOutsideClicks(); michael@0: // If the click was over the anchor, always consume the click. This way, michael@0: // clicking on a menu doesn't reopen the menu. michael@0: if (!consume && pos) { michael@0: nsCOMPtr anchor = item->Frame()->GetAnchor(); michael@0: if (anchor && anchor->GetPrimaryFrame()) { michael@0: // It's possible that some other element is above the anchor at the same michael@0: // position, but the only thing that would happen is that the mouse michael@0: // event will get consumed, so here only a quick coordinates check is michael@0: // done rather than a slower complete check of what is at that location. michael@0: if (anchor->GetPrimaryFrame()->GetScreenRect().Contains(*pos)) { michael@0: consume = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // if a number of popups to close has been specified, determine the last michael@0: // popup to close michael@0: nsIContent* lastPopup = nullptr; michael@0: if (aCount != UINT32_MAX) { michael@0: nsMenuChainItem* last = item; michael@0: while (--aCount && last->GetParent()) { michael@0: last = last->GetParent(); michael@0: } michael@0: if (last) { michael@0: lastPopup = last->Content(); michael@0: } michael@0: } michael@0: michael@0: HidePopup(item->Content(), true, true, false, true, lastPopup); michael@0: } michael@0: michael@0: return consume; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() michael@0: { michael@0: // should rollup only for autocomplete widgets michael@0: // XXXndeakin this should really be something the popup has more control over michael@0: michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (!item) michael@0: return false; michael@0: michael@0: nsIContent* content = item->Frame()->GetContent(); michael@0: if (!content) michael@0: return false; michael@0: michael@0: if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return true; michael@0: michael@0: if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, michael@0: nsGkAtoms::_false, eCaseMatters)) michael@0: return false; michael@0: michael@0: nsAutoString value; michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value); michael@0: return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete")); michael@0: } michael@0: michael@0: bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (!item) michael@0: return false; michael@0: michael@0: nsMenuPopupFrame* frame = item->Frame(); michael@0: if (frame->PopupType() != ePopupTypePanel) michael@0: return true; michael@0: michael@0: nsIContent* content = frame->GetContent(); michael@0: return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::arrow, eCaseMatters)); michael@0: } michael@0: michael@0: // a menu should not roll up if activated by a mouse activate message (eg. X-mouse) michael@0: bool nsXULPopupManager::ShouldRollupOnMouseActivate() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t michael@0: nsXULPopupManager::GetSubmenuWidgetChain(nsTArray *aWidgetChain) michael@0: { michael@0: // this method is used by the widget code to determine the list of popups michael@0: // that are open. If a mouse click occurs outside one of these popups, the michael@0: // panels will roll up. If the click is inside a popup, they will not roll up michael@0: uint32_t count = 0, sameTypeCount = 0; michael@0: michael@0: NS_ASSERTION(aWidgetChain, "null parameter"); michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: while (item) { michael@0: nsCOMPtr widget = item->Frame()->GetWidget(); michael@0: NS_ASSERTION(widget, "open popup has no widget"); michael@0: aWidgetChain->AppendElement(widget.get()); michael@0: // In the case when a menulist inside a panel is open, clicking in the michael@0: // panel should still roll up the menu, so if a different type is found, michael@0: // stop scanning. michael@0: nsMenuChainItem* parent = item->GetParent(); michael@0: if (!sameTypeCount) { michael@0: count++; michael@0: if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() || michael@0: item->IsContextMenu() != parent->IsContextMenu()) { michael@0: sameTypeCount = count; michael@0: } michael@0: } michael@0: item = parent; michael@0: } michael@0: michael@0: return sameTypeCount; michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsXULPopupManager::GetRollupWidget() michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: return item ? item->Frame()->GetWidget() : nullptr; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow) michael@0: { michael@0: // When the parent window is moved, adjust any child popups. Dismissable michael@0: // menus and panels are expected to roll up when a window is moved, so there michael@0: // is no need to check these popups, only the noautohide popups. michael@0: michael@0: // The items are added to a list so that they can be adjusted bottom to top. michael@0: nsTArray list; michael@0: michael@0: nsMenuChainItem* item = mNoHidePanels; michael@0: while (item) { michael@0: // only move popups that are within the same window and where auto michael@0: // positioning has not been disabled michael@0: nsMenuPopupFrame* frame = item->Frame(); michael@0: if (frame->GetAutoPosition()) { michael@0: nsIContent* popup = frame->GetContent(); michael@0: if (popup) { michael@0: nsIDocument* document = popup->GetCurrentDoc(); michael@0: if (document) { michael@0: nsPIDOMWindow* window = document->GetWindow(); michael@0: if (window) { michael@0: window = window->GetPrivateRoot(); michael@0: if (window == aWindow) { michael@0: list.AppendElement(frame); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: for (int32_t l = list.Length() - 1; l >= 0; l--) { michael@0: list[l]->SetPopupPosition(nullptr, true, false); michael@0: } michael@0: } michael@0: michael@0: void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell) michael@0: { michael@0: if (aPresShell->GetDocument()) { michael@0: AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow()); michael@0: } michael@0: } michael@0: michael@0: static michael@0: nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) michael@0: { michael@0: nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame); michael@0: if (!menuPopupFrame) michael@0: return nullptr; michael@0: michael@0: // no point moving or resizing hidden popups michael@0: if (menuPopupFrame->PopupState() != ePopupOpenAndVisible) michael@0: return nullptr; michael@0: michael@0: nsIWidget* widget = menuPopupFrame->GetWidget(); michael@0: if (widget && !widget->IsVisible()) michael@0: return nullptr; michael@0: michael@0: return menuPopupFrame; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) michael@0: { michael@0: nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); michael@0: if (!menuPopupFrame) michael@0: return; michael@0: michael@0: nsView* view = menuPopupFrame->GetView(); michael@0: if (!view) michael@0: return; michael@0: michael@0: // Don't do anything if the popup is already at the specified location. This michael@0: // prevents recursive calls when a popup is positioned. michael@0: nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); michael@0: nsIWidget* widget = menuPopupFrame->GetWidget(); michael@0: if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y && michael@0: (!widget || widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) { michael@0: return; michael@0: } michael@0: michael@0: // Update the popup's position using SetPopupPosition if the popup is michael@0: // anchored and at the parent level as these maintain their position michael@0: // relative to the parent window. Otherwise, just update the popup to michael@0: // the specified screen coordinates. michael@0: if (menuPopupFrame->IsAnchored() && michael@0: menuPopupFrame->PopupLevel() == ePopupLevelParent) { michael@0: menuPopupFrame->SetPopupPosition(nullptr, true, false); michael@0: } michael@0: else { michael@0: nsPresContext* presContext = menuPopupFrame->PresContext(); michael@0: aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x); michael@0: aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y); michael@0: menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize) michael@0: { michael@0: nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); michael@0: if (!menuPopupFrame) michael@0: return; michael@0: michael@0: nsView* view = menuPopupFrame->GetView(); michael@0: if (!view) michael@0: return; michael@0: michael@0: nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); michael@0: // If the size is what we think it is, we have nothing to do. michael@0: if (curDevSize.width == aSize.width && curDevSize.height == aSize.height) michael@0: return; michael@0: michael@0: // The size is different. Convert the actual size to css pixels and store it michael@0: // as 'width' and 'height' attributes on the popup. michael@0: nsPresContext* presContext = menuPopupFrame->PresContext(); michael@0: michael@0: nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width), michael@0: presContext->DevPixelsToIntCSSPixels(aSize.height)); michael@0: michael@0: nsIContent* popup = menuPopupFrame->GetContent(); michael@0: nsAutoString width, height; michael@0: width.AppendInt(newCSS.width); michael@0: height.AppendInt(newCSS.height); michael@0: popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false); michael@0: popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true); michael@0: } michael@0: michael@0: nsMenuPopupFrame* michael@0: nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush) michael@0: { michael@0: if (aShouldFlush) { michael@0: nsIDocument *document = aContent->GetCurrentDoc(); michael@0: if (document) { michael@0: nsCOMPtr presShell = document->GetShell(); michael@0: if (presShell) michael@0: presShell->FlushPendingNotifications(Flush_Layout); michael@0: } michael@0: } michael@0: michael@0: return do_QueryFrame(aContent->GetPrimaryFrame()); michael@0: } michael@0: michael@0: nsMenuChainItem* michael@0: nsXULPopupManager::GetTopVisibleMenu() michael@0: { michael@0: nsMenuChainItem* item = mPopups; michael@0: while (item && item->Frame()->PopupState() == ePopupInvisible) michael@0: item = item->GetParent(); michael@0: return item; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset) michael@0: { michael@0: *aNode = mRangeParent; michael@0: NS_IF_ADDREF(*aNode); michael@0: *aOffset = mRangeOffset; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, michael@0: nsIContent** aTriggerContent) michael@0: { michael@0: mCachedMousePoint = nsIntPoint(0, 0); michael@0: michael@0: if (aTriggerContent) { michael@0: *aTriggerContent = nullptr; michael@0: if (aEvent) { michael@0: // get the trigger content from the event michael@0: nsCOMPtr target = do_QueryInterface( michael@0: aEvent->InternalDOMEvent()->GetTarget()); michael@0: target.forget(aTriggerContent); michael@0: } michael@0: } michael@0: michael@0: mCachedModifiers = 0; michael@0: michael@0: nsCOMPtr uiEvent = do_QueryInterface(aEvent); michael@0: if (uiEvent) { michael@0: uiEvent->GetRangeParent(getter_AddRefs(mRangeParent)); michael@0: uiEvent->GetRangeOffset(&mRangeOffset); michael@0: michael@0: // get the event coordinates relative to the root frame of the document michael@0: // containing the popup. michael@0: NS_ASSERTION(aPopup, "Expected a popup node"); michael@0: WidgetEvent* event = aEvent->GetInternalNSEvent(); michael@0: if (event) { michael@0: WidgetInputEvent* inputEvent = event->AsInputEvent(); michael@0: if (inputEvent) { michael@0: mCachedModifiers = inputEvent->modifiers; michael@0: } michael@0: nsIDocument* doc = aPopup->GetCurrentDoc(); michael@0: if (doc) { michael@0: nsIPresShell* presShell = doc->GetShell(); michael@0: nsPresContext* presContext; michael@0: if (presShell && (presContext = presShell->GetPresContext())) { michael@0: nsPresContext* rootDocPresContext = michael@0: presContext->GetRootPresContext(); michael@0: if (!rootDocPresContext) michael@0: return; michael@0: nsIFrame* rootDocumentRootFrame = rootDocPresContext-> michael@0: PresShell()->FrameManager()->GetRootFrame(); michael@0: if ((event->eventStructType == NS_MOUSE_EVENT || michael@0: event->eventStructType == NS_MOUSE_SCROLL_EVENT || michael@0: event->eventStructType == NS_WHEEL_EVENT) && michael@0: !event->AsGUIEvent()->widget) { michael@0: // no widget, so just use the client point if available michael@0: nsCOMPtr mouseEvent = do_QueryInterface(aEvent); michael@0: nsIntPoint clientPt; michael@0: mouseEvent->GetClientX(&clientPt.x); michael@0: mouseEvent->GetClientY(&clientPt.y); michael@0: michael@0: // XXX this doesn't handle IFRAMEs in transforms michael@0: nsPoint thisDocToRootDocOffset = presShell->FrameManager()-> michael@0: GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame); michael@0: // convert to device pixels michael@0: mCachedMousePoint.x = presContext->AppUnitsToDevPixels( michael@0: nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x); michael@0: mCachedMousePoint.y = presContext->AppUnitsToDevPixels( michael@0: nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y); michael@0: } michael@0: else if (rootDocumentRootFrame) { michael@0: nsPoint pnt = michael@0: nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame); michael@0: mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x), michael@0: rootDocPresContext->AppUnitsToDevPixels(pnt.y)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: mRangeParent = nullptr; michael@0: mRangeOffset = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate) michael@0: { michael@0: if (aActivate) michael@0: mActiveMenuBar = aMenuBar; michael@0: else if (mActiveMenuBar == aMenuBar) michael@0: mActiveMenuBar = nullptr; michael@0: michael@0: UpdateKeyboardListeners(); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowMenu(nsIContent *aMenu, michael@0: bool aSelectFirstItem, michael@0: bool aAsynchronous) michael@0: { michael@0: // generate any template content first. Otherwise, the menupopup may not michael@0: // have been created yet. michael@0: if (aMenu) { michael@0: nsIContent* element = aMenu; michael@0: do { michael@0: nsCOMPtr xulelem = do_QueryInterface(element); michael@0: if (xulelem) { michael@0: nsCOMPtr builder; michael@0: xulelem->GetBuilder(getter_AddRefs(builder)); michael@0: if (builder) { michael@0: builder->CreateContents(aMenu, true); michael@0: break; michael@0: } michael@0: } michael@0: element = element->GetParent(); michael@0: } while (element); michael@0: } michael@0: michael@0: nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame()); michael@0: if (!menuFrame || !menuFrame->IsMenu()) michael@0: return; michael@0: michael@0: nsMenuPopupFrame* popupFrame = menuFrame->GetPopup(); michael@0: if (!popupFrame || !MayShowPopup(popupFrame)) michael@0: return; michael@0: michael@0: // inherit whether or not we're a context menu from the parent michael@0: bool parentIsContextMenu = false; michael@0: bool onMenuBar = false; michael@0: bool onmenu = menuFrame->IsOnMenu(); michael@0: michael@0: nsMenuParent* parent = menuFrame->GetMenuParent(); michael@0: if (parent && onmenu) { michael@0: parentIsContextMenu = parent->IsContextMenu(); michael@0: onMenuBar = parent->IsMenuBar(); michael@0: } michael@0: michael@0: nsAutoString position; michael@0: if (onMenuBar || !onmenu) michael@0: position.AssignLiteral("after_start"); michael@0: else michael@0: position.AssignLiteral("end_before"); michael@0: michael@0: // there is no trigger event for menus michael@0: InitTriggerEvent(nullptr, nullptr, nullptr); michael@0: popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true); michael@0: michael@0: if (aAsynchronous) { michael@0: nsCOMPtr event = michael@0: new nsXULPopupShowingEvent(popupFrame->GetContent(), michael@0: parentIsContextMenu, aSelectFirstItem); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: else { michael@0: nsCOMPtr popupContent = popupFrame->GetContent(); michael@0: FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowPopup(nsIContent* aPopup, michael@0: nsIContent* aAnchorContent, michael@0: const nsAString& aPosition, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aIsContextMenu, michael@0: bool aAttributesOverride, michael@0: bool aSelectFirstItem, michael@0: nsIDOMEvent* aTriggerEvent) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); michael@0: if (!popupFrame || !MayShowPopup(popupFrame)) michael@0: return; michael@0: michael@0: nsCOMPtr triggerContent; michael@0: InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); michael@0: michael@0: popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, michael@0: aXPos, aYPos, aAttributesOverride); michael@0: michael@0: FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aIsContextMenu, michael@0: nsIDOMEvent* aTriggerEvent) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); michael@0: if (!popupFrame || !MayShowPopup(popupFrame)) michael@0: return; michael@0: michael@0: nsCOMPtr triggerContent; michael@0: InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); michael@0: michael@0: popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu); michael@0: FirePopupShowingEvent(aPopup, aIsContextMenu, false); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup, michael@0: nsIContent* aTriggerContent, michael@0: int32_t aXPos, int32_t aYPos) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); michael@0: if (!popupFrame || !MayShowPopup(popupFrame)) michael@0: return; michael@0: michael@0: InitTriggerEvent(nullptr, nullptr, nullptr); michael@0: michael@0: nsPresContext* pc = popupFrame->PresContext(); michael@0: mCachedMousePoint = nsIntPoint(pc->CSSPixelsToDevPixels(aXPos), michael@0: pc->CSSPixelsToDevPixels(aYPos)); michael@0: michael@0: // coordinates are relative to the root widget michael@0: nsPresContext* rootPresContext = pc->GetRootPresContext(); michael@0: if (rootPresContext) { michael@0: nsIWidget *rootWidget = rootPresContext->GetRootWidget(); michael@0: if (rootWidget) { michael@0: mCachedMousePoint -= rootWidget->WidgetToScreenOffset(); michael@0: } michael@0: } michael@0: michael@0: popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false); michael@0: michael@0: FirePopupShowingEvent(aPopup, false, false); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup, michael@0: nsIContent* aAnchorContent, michael@0: nsAString& aAnchor, michael@0: nsAString& aAlign, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aIsContextMenu) michael@0: { michael@0: nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); michael@0: if (!popupFrame || !MayShowPopup(popupFrame)) michael@0: return; michael@0: michael@0: InitTriggerEvent(nullptr, nullptr, nullptr); michael@0: michael@0: popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor, michael@0: aAlign, aXPos, aYPos); michael@0: FirePopupShowingEvent(aPopup, aIsContextMenu, false); michael@0: } michael@0: michael@0: static void michael@0: CheckCaretDrawingState() { michael@0: michael@0: // There is 1 caret per document, we need to find the focused michael@0: // document and erase its caret. michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (fm) { michael@0: nsCOMPtr window; michael@0: fm->GetFocusedWindow(getter_AddRefs(window)); michael@0: if (!window) michael@0: return; michael@0: michael@0: nsCOMPtr domDoc; michael@0: nsCOMPtr focusedDoc; michael@0: window->GetDocument(getter_AddRefs(domDoc)); michael@0: focusedDoc = do_QueryInterface(domDoc); michael@0: if (!focusedDoc) michael@0: return; michael@0: michael@0: nsIPresShell* presShell = focusedDoc->GetShell(); michael@0: if (!presShell) michael@0: return; michael@0: michael@0: nsRefPtr caret = presShell->GetCaret(); michael@0: if (!caret) michael@0: return; michael@0: caret->CheckCaretDrawingState(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup, michael@0: nsMenuPopupFrame* aPopupFrame, michael@0: bool aIsContextMenu, michael@0: bool aSelectFirstItem) michael@0: { michael@0: nsPopupType popupType = aPopupFrame->PopupType(); michael@0: bool ismenu = (popupType == ePopupTypeMenu); michael@0: michael@0: nsMenuChainItem* item = michael@0: new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType); michael@0: if (!item) michael@0: return; michael@0: michael@0: // install keyboard event listeners for navigating menus. For panels, the michael@0: // escape key may be used to close the panel. However, the ignorekeys michael@0: // attribute may be used to disable adding these event listeners for popups michael@0: // that want to handle their own keyboard events. michael@0: if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: item->SetIgnoreKeys(true); michael@0: michael@0: if (ismenu) { michael@0: // if the menu is on a menubar, use the menubar's listener instead michael@0: nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent()); michael@0: if (menuFrame) { michael@0: item->SetOnMenuBar(menuFrame->IsOnMenuBar()); michael@0: } michael@0: } michael@0: michael@0: // use a weak frame as the popup will set an open attribute if it is a menu michael@0: nsWeakFrame weakFrame(aPopupFrame); michael@0: aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // popups normally hide when an outside click occurs. Panels may use michael@0: // the noautohide attribute to disable this behaviour. It is expected michael@0: // that the application will hide these popups manually. The tooltip michael@0: // listener will handle closing the tooltip also. michael@0: if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) { michael@0: item->SetParent(mNoHidePanels); michael@0: mNoHidePanels = item; michael@0: } michael@0: else { michael@0: nsIContent* oldmenu = nullptr; michael@0: if (mPopups) michael@0: oldmenu = mPopups->Content(); michael@0: item->SetParent(mPopups); michael@0: mPopups = item; michael@0: SetCaptureState(oldmenu); michael@0: } michael@0: michael@0: if (aSelectFirstItem) { michael@0: nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true); michael@0: aPopupFrame->SetCurrentMenuItem(next); michael@0: } michael@0: michael@0: if (ismenu) michael@0: UpdateMenuItems(aPopup); michael@0: michael@0: // Caret visibility may have been affected, ensure that michael@0: // the caret isn't now drawn when it shouldn't be. michael@0: CheckCaretDrawingState(); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopup(nsIContent* aPopup, michael@0: bool aHideChain, michael@0: bool aDeselectMenu, michael@0: bool aAsynchronous, michael@0: bool aIsRollup, michael@0: nsIContent* aLastPopup) michael@0: { michael@0: // if the popup is on the nohide panels list, remove it but don't close any michael@0: // other panels michael@0: nsMenuPopupFrame* popupFrame = nullptr; michael@0: bool foundPanel = false; michael@0: nsMenuChainItem* item = mNoHidePanels; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: foundPanel = true; michael@0: popupFrame = item->Frame(); michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: // when removing a menu, all of the child popups must be closed michael@0: nsMenuChainItem* foundMenu = nullptr; michael@0: item = mPopups; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: foundMenu = item; michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: nsPopupType type = ePopupTypePanel; michael@0: bool deselectMenu = false; michael@0: nsCOMPtr popupToHide, nextPopup, lastPopup; michael@0: if (foundMenu) { michael@0: // at this point, foundMenu will be set to the found item in the list. If michael@0: // foundMenu is the topmost menu, the one to remove, then there are no other michael@0: // popups to hide. If foundMenu is not the topmost menu, then there may be michael@0: // open submenus below it. In this case, we need to make sure that those michael@0: // submenus are closed up first. To do this, we scan up the menu list to michael@0: // find the topmost popup with only menus between it and foundMenu and michael@0: // close that menu first. In synchronous mode, the FirePopupHidingEvent michael@0: // method will be called which in turn calls HidePopupCallback to close up michael@0: // the next popup in the chain. These two methods will be called in michael@0: // sequence recursively to close up all the necessary popups. In michael@0: // asynchronous mode, a similar process occurs except that the michael@0: // FirePopupHidingEvent method is called asynchronously. In either case, michael@0: // nextPopup is set to the content node of the next popup to close, and michael@0: // lastPopup is set to the last popup in the chain to close, which will be michael@0: // aPopup, or null to close up all menus. michael@0: michael@0: nsMenuChainItem* topMenu = foundMenu; michael@0: // Use IsMenu to ensure that foundMenu is a menu and scan down the child michael@0: // list until a non-menu is found. If foundMenu isn't a menu at all, don't michael@0: // scan and just close up this menu. michael@0: if (foundMenu->IsMenu()) { michael@0: item = topMenu->GetChild(); michael@0: while (item && item->IsMenu()) { michael@0: topMenu = item; michael@0: item = item->GetChild(); michael@0: } michael@0: } michael@0: michael@0: deselectMenu = aDeselectMenu; michael@0: popupToHide = topMenu->Content(); michael@0: popupFrame = topMenu->Frame(); michael@0: type = popupFrame->PopupType(); michael@0: michael@0: nsMenuChainItem* parent = topMenu->GetParent(); michael@0: michael@0: // close up another popup if there is one, and we are either hiding the michael@0: // entire chain or the item to hide isn't the topmost popup. michael@0: if (parent && (aHideChain || topMenu != foundMenu)) michael@0: nextPopup = parent->Content(); michael@0: michael@0: lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup); michael@0: } michael@0: else if (foundPanel) { michael@0: popupToHide = aPopup; michael@0: } michael@0: michael@0: if (popupFrame) { michael@0: nsPopupState state = popupFrame->PopupState(); michael@0: // if the popup is already being hidden, don't attempt to hide it again michael@0: if (state == ePopupHiding) michael@0: return; michael@0: // change the popup state to hiding. Don't set the hiding state if the michael@0: // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will michael@0: // run again. In the invisible state, we just want the events to fire. michael@0: if (state != ePopupInvisible) michael@0: popupFrame->SetPopupState(ePopupHiding); michael@0: michael@0: // for menus, popupToHide is always the frontmost item in the list to hide. michael@0: if (aAsynchronous) { michael@0: nsCOMPtr event = michael@0: new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup, michael@0: type, deselectMenu, aIsRollup); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: else { michael@0: FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, michael@0: popupFrame->PresContext(), type, deselectMenu, aIsRollup); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This is used to hide the popup after a transition finishes. michael@0: class TransitionEnder : public nsIDOMEventListener michael@0: { michael@0: public: michael@0: michael@0: nsCOMPtr mContent; michael@0: bool mDeselectMenu; michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder) michael@0: michael@0: TransitionEnder(nsIContent* aContent, bool aDeselectMenu) michael@0: : mContent(aContent), mDeselectMenu(aDeselectMenu) michael@0: { michael@0: } michael@0: michael@0: virtual ~TransitionEnder() { } michael@0: michael@0: NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE michael@0: { michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false); michael@0: michael@0: nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame()); michael@0: michael@0: // Now hide the popup. There could be other properties transitioning, but michael@0: // we'll assume they all end at the same time and just hide the popup upon michael@0: // the first one ending. michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && popupFrame) { michael@0: pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr, michael@0: popupFrame->PopupType(), mDeselectMenu); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder) michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent); michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopupCallback(nsIContent* aPopup, michael@0: nsMenuPopupFrame* aPopupFrame, michael@0: nsIContent* aNextPopup, michael@0: nsIContent* aLastPopup, michael@0: nsPopupType aPopupType, michael@0: bool aDeselectMenu) michael@0: { michael@0: if (mCloseTimer && mTimerMenu == aPopupFrame) { michael@0: mCloseTimer->Cancel(); michael@0: mCloseTimer = nullptr; michael@0: mTimerMenu = nullptr; michael@0: } michael@0: michael@0: // The popup to hide is aPopup. Search the list again to find the item that michael@0: // corresponds to the popup to hide aPopup. This is done because it's michael@0: // possible someone added another item (attempted to open another popup) michael@0: // or removed a popup frame during the event processing so the item isn't at michael@0: // the front anymore. michael@0: nsMenuChainItem* item = mNoHidePanels; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: item->Detach(&mNoHidePanels); michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: if (!item) { michael@0: item = mPopups; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: item->Detach(&mPopups); michael@0: SetCaptureState(aPopup); michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: } michael@0: michael@0: delete item; michael@0: michael@0: nsWeakFrame weakFrame(aPopupFrame); michael@0: aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // send the popuphidden event synchronously. This event has no default michael@0: // behaviour. michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr, michael@0: WidgetMouseEvent::eReal); michael@0: EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(), michael@0: &event, nullptr, &status); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // if there are more popups to close, look for the next one michael@0: if (aNextPopup && aPopup != aLastPopup) { michael@0: nsMenuChainItem* foundMenu = nullptr; michael@0: nsMenuChainItem* item = mPopups; michael@0: while (item) { michael@0: if (item->Content() == aNextPopup) { michael@0: foundMenu = item; michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: // continue hiding the chain of popups until the last popup aLastPopup michael@0: // is reached, or until a popup of a different type is reached. This michael@0: // last check is needed so that a menulist inside a non-menu panel only michael@0: // closes the menu and not the panel as well. michael@0: if (foundMenu && michael@0: (aLastPopup || aPopupType == foundMenu->PopupType())) { michael@0: michael@0: nsCOMPtr popupToHide = item->Content(); michael@0: nsMenuChainItem* parent = item->GetParent(); michael@0: michael@0: nsCOMPtr nextPopup; michael@0: if (parent && popupToHide != aLastPopup) michael@0: nextPopup = parent->Content(); michael@0: michael@0: nsMenuPopupFrame* popupFrame = item->Frame(); michael@0: nsPopupState state = popupFrame->PopupState(); michael@0: if (state == ePopupHiding) michael@0: return; michael@0: if (state != ePopupInvisible) michael@0: popupFrame->SetPopupState(ePopupHiding); michael@0: michael@0: FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, michael@0: popupFrame->PresContext(), michael@0: foundMenu->PopupType(), aDeselectMenu, false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopup(nsIFrame* aFrame) michael@0: { michael@0: nsMenuPopupFrame* popup = do_QueryFrame(aFrame); michael@0: if (popup) michael@0: HidePopup(aFrame->GetContent(), false, true, false, false); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup) michael@0: { michael@0: // Don't close up immediately. michael@0: // Kick off a close timer. michael@0: KillMenuTimer(); michael@0: michael@0: int32_t menuDelay = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms michael@0: michael@0: // Kick off the timer. michael@0: mCloseTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: // the popup will call PopupDestroyed if it is destroyed, which checks if it michael@0: // is set to mTimerMenu, so it should be safe to keep a reference to it michael@0: mTimerMenu = aPopup; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopupsInList(const nsTArray &aFrames, michael@0: bool aDeselectMenu) michael@0: { michael@0: // Create a weak frame list. This is done in a separate array with the michael@0: // right capacity predetermined, otherwise the array would get resized and michael@0: // move the weak frame pointers around. michael@0: nsTArray weakPopups(aFrames.Length()); michael@0: uint32_t f; michael@0: for (f = 0; f < aFrames.Length(); f++) { michael@0: nsWeakFrame* wframe = weakPopups.AppendElement(); michael@0: if (wframe) michael@0: *wframe = aFrames[f]; michael@0: } michael@0: michael@0: for (f = 0; f < weakPopups.Length(); f++) { michael@0: // check to ensure that the frame is still alive before hiding it. michael@0: if (weakPopups[f].IsAlive()) { michael@0: nsMenuPopupFrame* frame = michael@0: static_cast(weakPopups[f].GetFrame()); michael@0: frame->HidePopup(true, ePopupInvisible); michael@0: } michael@0: } michael@0: michael@0: SetCaptureState(nullptr); michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected) michael@0: { michael@0: nsCOMPtr docShellItem(aDoc->GetDocShell()); michael@0: while(docShellItem) { michael@0: if (docShellItem == aExpected) michael@0: return true; michael@0: michael@0: nsCOMPtr parent; michael@0: docShellItem->GetParent(getter_AddRefs(parent)); michael@0: docShellItem = parent; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide) michael@0: { michael@0: nsTArray popupsToHide; michael@0: michael@0: // iterate to get the set of popup frames to hide michael@0: nsMenuChainItem* item = mPopups; michael@0: while (item) { michael@0: nsMenuChainItem* parent = item->GetParent(); michael@0: if (item->Frame()->PopupState() != ePopupInvisible && michael@0: IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { michael@0: nsMenuPopupFrame* frame = item->Frame(); michael@0: item->Detach(&mPopups); michael@0: delete item; michael@0: popupsToHide.AppendElement(frame); michael@0: } michael@0: item = parent; michael@0: } michael@0: michael@0: // now look for panels to hide michael@0: item = mNoHidePanels; michael@0: while (item) { michael@0: nsMenuChainItem* parent = item->GetParent(); michael@0: if (item->Frame()->PopupState() != ePopupInvisible && michael@0: IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { michael@0: nsMenuPopupFrame* frame = item->Frame(); michael@0: item->Detach(&mNoHidePanels); michael@0: delete item; michael@0: popupsToHide.AppendElement(frame); michael@0: } michael@0: item = parent; michael@0: } michael@0: michael@0: HidePopupsInList(popupsToHide, true); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent) michael@0: { michael@0: CloseMenuMode cmm = CloseMenuMode_Auto; michael@0: michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::none, &nsGkAtoms::single, nullptr}; michael@0: michael@0: switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu, michael@0: strings, eCaseMatters)) { michael@0: case 0: michael@0: cmm = CloseMenuMode_None; michael@0: break; michael@0: case 1: michael@0: cmm = CloseMenuMode_Single; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // When a menuitem is selected to be executed, first hide all the open michael@0: // popups, but don't remove them yet. This is needed when a menu command michael@0: // opens a modal dialog. The views associated with the popups needed to be michael@0: // hidden and the accesibility events fired before the command executes, but michael@0: // the popuphiding/popuphidden events are fired afterwards. michael@0: nsTArray popupsToHide; michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (cmm != CloseMenuMode_None) { michael@0: while (item) { michael@0: // if it isn't a , don't close it automatically michael@0: if (!item->IsMenu()) michael@0: break; michael@0: nsMenuChainItem* next = item->GetParent(); michael@0: popupsToHide.AppendElement(item->Frame()); michael@0: if (cmm == CloseMenuMode_Single) // only close one level of menu michael@0: break; michael@0: item = next; michael@0: } michael@0: michael@0: // Now hide the popups. If the closemenu mode is auto, deselect the menu, michael@0: // otherwise only one popup is closing, so keep the parent menu selected. michael@0: HidePopupsInList(popupsToHide, cmm == CloseMenuMode_Auto); michael@0: } michael@0: michael@0: aEvent->SetCloseMenuMode(cmm); michael@0: nsCOMPtr event = aEvent; michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup, michael@0: bool aIsContextMenu, michael@0: bool aSelectFirstItem) michael@0: { michael@0: nsCOMPtr popup = aPopup; // keep a strong reference to the popup michael@0: michael@0: nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); michael@0: if (!popupFrame) michael@0: return; michael@0: michael@0: nsPresContext *presContext = popupFrame->PresContext(); michael@0: nsCOMPtr presShell = presContext->PresShell(); michael@0: nsPopupType popupType = popupFrame->PopupType(); michael@0: michael@0: // generate the child frames if they have not already been generated michael@0: if (!popupFrame->HasGeneratedChildren()) { michael@0: popupFrame->SetGeneratedChildren(); michael@0: presShell->FrameConstructor()->GenerateChildFrames(popupFrame); michael@0: } michael@0: michael@0: // get the frame again michael@0: nsIFrame* frame = aPopup->GetPrimaryFrame(); michael@0: if (!frame) michael@0: return; michael@0: michael@0: presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: michael@0: // cache the popup so that document.popupNode can retrieve the trigger node michael@0: // during the popupshowing event. It will be cleared below after the event michael@0: // has fired. michael@0: mOpeningPopup = aPopup; michael@0: michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr, michael@0: WidgetMouseEvent::eReal); michael@0: michael@0: // coordinates are relative to the root widget michael@0: nsPresContext* rootPresContext = michael@0: presShell->GetPresContext()->GetRootPresContext(); michael@0: if (rootPresContext) { michael@0: rootPresContext->PresShell()->GetViewManager()-> michael@0: GetRootWidget(getter_AddRefs(event.widget)); michael@0: } michael@0: else { michael@0: event.widget = nullptr; michael@0: } michael@0: michael@0: event.refPoint = LayoutDeviceIntPoint::FromUntyped(mCachedMousePoint); michael@0: event.modifiers = mCachedModifiers; michael@0: EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status); michael@0: michael@0: mCachedMousePoint = nsIntPoint(0, 0); michael@0: mOpeningPopup = nullptr; michael@0: michael@0: mCachedModifiers = 0; michael@0: michael@0: // if a panel, blur whatever has focus so that the panel can take the focus. michael@0: // This is done after the popupshowing event in case that event is cancelled. michael@0: // Using noautofocus="true" will disable this behaviour, which is needed for michael@0: // the autocomplete widget as it manages focus itself. michael@0: if (popupType == ePopupTypePanel && michael@0: !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (fm) { michael@0: nsIDocument* doc = popup->GetCurrentDoc(); michael@0: michael@0: // Only remove the focus if the currently focused item is ouside the michael@0: // popup. It isn't a big deal if the current focus is in a child popup michael@0: // inside the popup as that shouldn't be visible. This check ensures that michael@0: // a node inside the popup that is focused during a popupshowing event michael@0: // remains focused. michael@0: nsCOMPtr currentFocusElement; michael@0: fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); michael@0: nsCOMPtr currentFocus = do_QueryInterface(currentFocusElement); michael@0: if (doc && currentFocus && michael@0: !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) { michael@0: fm->ClearFocus(doc->GetWindow()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // clear these as they are no longer valid michael@0: mRangeParent = nullptr; michael@0: mRangeOffset = 0; michael@0: michael@0: // get the frame again in case it went away michael@0: popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); michael@0: if (popupFrame) { michael@0: // if the event was cancelled, don't open the popup, reset its state back michael@0: // to closed and clear its trigger content. michael@0: if (status == nsEventStatus_eConsumeNoDefault) { michael@0: popupFrame->SetPopupState(ePopupClosed); michael@0: popupFrame->ClearTriggerContent(); michael@0: } michael@0: else { michael@0: ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup, michael@0: nsIContent* aNextPopup, michael@0: nsIContent* aLastPopup, michael@0: nsPresContext *aPresContext, michael@0: nsPopupType aPopupType, michael@0: bool aDeselectMenu, michael@0: bool aIsRollup) michael@0: { michael@0: nsCOMPtr presShell = aPresContext->PresShell(); michael@0: michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr, michael@0: WidgetMouseEvent::eReal); michael@0: EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status); michael@0: michael@0: // when a panel is closed, blur whatever has focus inside the popup michael@0: if (aPopupType == ePopupTypePanel && michael@0: !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (fm) { michael@0: nsIDocument* doc = aPopup->GetCurrentDoc(); michael@0: michael@0: // Remove the focus from the focused node only if it is inside the popup. michael@0: nsCOMPtr currentFocusElement; michael@0: fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); michael@0: nsCOMPtr currentFocus = do_QueryInterface(currentFocusElement); michael@0: if (doc && currentFocus && michael@0: nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) { michael@0: fm->ClearFocus(doc->GetWindow()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // get frame again in case it went away michael@0: nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); michael@0: if (popupFrame) { michael@0: // if the event was cancelled, don't hide the popup, and reset its michael@0: // state back to open. Only popups in chrome shells can prevent a popup michael@0: // from hiding. michael@0: if (status == nsEventStatus_eConsumeNoDefault && michael@0: !popupFrame->IsInContentShell()) { michael@0: popupFrame->SetPopupState(ePopupOpenAndVisible); michael@0: } michael@0: else { michael@0: // If the popup has an animate attribute and it is not set to false, assume michael@0: // that it has a closing transition and wait for it to finish. The transition michael@0: // may still occur either way, but the view will be hidden and you won't be michael@0: // able to see it. If there is a next popup, indicating that mutliple popups michael@0: // are rolling up, don't wait and hide the popup right away since the effect michael@0: // would likely be undesirable. This also does a quick check to see if the michael@0: // popup has a transition defined, and skips the wait if not. Transitions michael@0: // are currently disabled on Linux due to rendering issues on certain michael@0: // configurations. michael@0: #ifndef MOZ_WIDGET_GTK michael@0: if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate) && michael@0: popupFrame->StyleDisplay()->mTransitionPropertyCount > 0) { michael@0: nsAutoString animate; michael@0: aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate); michael@0: michael@0: // If animate="false" then don't transition at all. If animate="cancel", michael@0: // only show the transition if cancelling the popup or rolling up. michael@0: // Otherwise, always show the transition. michael@0: if (!animate.EqualsLiteral("false") && michael@0: (!animate.EqualsLiteral("cancel") || aIsRollup)) { michael@0: nsCOMPtr ender = new TransitionEnder(aPopup, aDeselectMenu); michael@0: aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"), michael@0: ender, false, false); michael@0: return; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, michael@0: aPopupType, aDeselectMenu); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) michael@0: { michael@0: // a popup is open if it is in the open list. The assertions ensure that the michael@0: // frame is in the correct state. If the popup is in the hiding or invisible michael@0: // state, it will still be in the open popup list until it is closed. michael@0: nsMenuChainItem* item = mPopups; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: NS_ASSERTION(item->Frame()->IsOpen() || michael@0: item->Frame()->PopupState() == ePopupHiding || michael@0: item->Frame()->PopupState() == ePopupInvisible, michael@0: "popup in open list not actually open"); michael@0: return true; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: item = mNoHidePanels; michael@0: while (item) { michael@0: if (item->Content() == aPopup) { michael@0: NS_ASSERTION(item->Frame()->IsOpen() || michael@0: item->Frame()->PopupState() == ePopupHiding || michael@0: item->Frame()->PopupState() == ePopupInvisible, michael@0: "popup in open list not actually open"); michael@0: return true; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent) michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: while (item) { michael@0: nsMenuPopupFrame* popup = item->Frame(); michael@0: if (popup && popup->IsOpen()) { michael@0: nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent()); michael@0: if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) { michael@0: return true; michael@0: } michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsXULPopupManager::GetTopPopup(nsPopupType aType) michael@0: { michael@0: if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels) michael@0: return mNoHidePanels->Frame(); michael@0: michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: while (item) { michael@0: if (item->PopupType() == aType || aType == ePopupTypeAny) michael@0: return item->Frame(); michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::GetVisiblePopups(nsTArray& aPopups) michael@0: { michael@0: aPopups.Clear(); michael@0: michael@0: // Iterate over both lists of popups michael@0: nsMenuChainItem* item = mPopups; michael@0: for (int32_t list = 0; list < 2; list++) { michael@0: while (item) { michael@0: // Skip panels which are not open and visible as well as popups that michael@0: // are transparent to mouse events. michael@0: if (item->Frame()->PopupState() == ePopupOpenAndVisible && michael@0: !item->Frame()->IsMouseTransparent()) { michael@0: aPopups.AppendElement(item->Frame()); michael@0: } michael@0: michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: item = mNoHidePanels; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip) michael@0: { michael@0: if (!aDocument) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr node; michael@0: michael@0: // if mOpeningPopup is set, it means that a popupshowing event is being michael@0: // fired. In this case, just use the cached node, as the popup is not yet in michael@0: // the list of open popups. michael@0: if (mOpeningPopup && mOpeningPopup->GetCurrentDoc() == aDocument && michael@0: aIsTooltip == (mOpeningPopup->Tag() == nsGkAtoms::tooltip)) { michael@0: node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false))); michael@0: } michael@0: else { michael@0: nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups; michael@0: while (item) { michael@0: // look for a popup of the same type and document. michael@0: if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip && michael@0: item->Content()->GetCurrentDoc() == aDocument) { michael@0: node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame())); michael@0: if (node) michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: } michael@0: michael@0: return node.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) michael@0: { michael@0: // if a popup's IsOpen method returns true, then the popup must always be in michael@0: // the popup chain scanned in IsPopupOpen. michael@0: NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()), michael@0: "popup frame state doesn't match XULPopupManager open state"); michael@0: michael@0: nsPopupState state = aPopup->PopupState(); michael@0: michael@0: // if the popup is not in the open popup chain, then it must have a state that michael@0: // is either closed, in the process of being shown, or invisible. michael@0: NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed || michael@0: state == ePopupShowing || state == ePopupInvisible, michael@0: "popup not in XULPopupManager open list is open"); michael@0: michael@0: // don't show popups unless they are closed or invisible michael@0: if (state != ePopupClosed && state != ePopupInvisible) michael@0: return false; michael@0: michael@0: // Don't show popups that we already have in our popup chain michael@0: if (IsPopupOpen(aPopup->GetContent())) { michael@0: NS_WARNING("Refusing to show duplicate popup"); michael@0: return false; michael@0: } michael@0: michael@0: // if the popup was just rolled up, don't reopen it michael@0: nsCOMPtr widget = aPopup->GetWidget(); michael@0: if (widget && widget->GetLastRollup() == aPopup->GetContent()) michael@0: return false; michael@0: michael@0: nsCOMPtr dsti = aPopup->PresContext()->GetDocShell(); michael@0: nsCOMPtr baseWin = do_QueryInterface(dsti); michael@0: if (!baseWin) michael@0: return false; michael@0: michael@0: // chrome shells can always open popups, but other types of shells can only michael@0: // open popups when they are focused and visible michael@0: if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { michael@0: // only allow popups in active windows michael@0: nsCOMPtr root; michael@0: dsti->GetRootTreeItem(getter_AddRefs(root)); michael@0: nsCOMPtr rootWin = do_GetInterface(root); michael@0: michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (!fm || !rootWin) michael@0: return false; michael@0: michael@0: nsCOMPtr activeWindow; michael@0: fm->GetActiveWindow(getter_AddRefs(activeWindow)); michael@0: if (activeWindow != rootWin) michael@0: return false; michael@0: michael@0: // only allow popups in visible frames michael@0: bool visible; michael@0: baseWin->GetVisibility(&visible); michael@0: if (!visible) michael@0: return false; michael@0: } michael@0: michael@0: // platforms respond differently when an popup is opened in a minimized michael@0: // window, so this is always disabled. michael@0: nsCOMPtr mainWidget; michael@0: baseWin->GetMainWidget(getter_AddRefs(mainWidget)); michael@0: if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) { michael@0: return false; michael@0: } michael@0: michael@0: // cannot open a popup that is a submenu of a menupopup that isn't open. michael@0: nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent()); michael@0: if (menuFrame) { michael@0: nsMenuParent* parentPopup = menuFrame->GetMenuParent(); michael@0: if (parentPopup && !parentPopup->IsOpen()) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) michael@0: { michael@0: // when a popup frame is destroyed, just unhook it from the list of popups michael@0: if (mTimerMenu == aPopup) { michael@0: if (mCloseTimer) { michael@0: mCloseTimer->Cancel(); michael@0: mCloseTimer = nullptr; michael@0: } michael@0: mTimerMenu = nullptr; michael@0: } michael@0: michael@0: nsMenuChainItem* item = mNoHidePanels; michael@0: while (item) { michael@0: if (item->Frame() == aPopup) { michael@0: item->Detach(&mNoHidePanels); michael@0: delete item; michael@0: break; michael@0: } michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: nsTArray popupsToHide; michael@0: michael@0: item = mPopups; michael@0: while (item) { michael@0: nsMenuPopupFrame* frame = item->Frame(); michael@0: if (frame == aPopup) { michael@0: if (frame->PopupState() != ePopupInvisible) { michael@0: // Iterate through any child menus and hide them as well, since the michael@0: // parent is going away. We won't remove them from the list yet, just michael@0: // hide them, as they will be removed from the list when this function michael@0: // gets called for that child frame. michael@0: nsMenuChainItem* child = item->GetChild(); michael@0: while (child) { michael@0: // if the popup is a child frame of the menu that was destroyed, add michael@0: // it to the list of popups to hide. Don't bother with the events michael@0: // since the frames are going away. If the child menu is not a child michael@0: // frame, for example, a context menu, use HidePopup instead, but call michael@0: // it asynchronously since we are in the middle of frame destruction. michael@0: nsMenuPopupFrame* childframe = child->Frame(); michael@0: if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) { michael@0: popupsToHide.AppendElement(childframe); michael@0: } michael@0: else { michael@0: // HidePopup will take care of hiding any of its children, so michael@0: // break out afterwards michael@0: HidePopup(child->Content(), false, false, true, false); michael@0: break; michael@0: } michael@0: michael@0: child = child->GetChild(); michael@0: } michael@0: } michael@0: michael@0: item->Detach(&mPopups); michael@0: delete item; michael@0: break; michael@0: } michael@0: michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: HidePopupsInList(popupsToHide, false); michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: while (item && item->Frame() != aPopup) { michael@0: if (item->IsContextMenu()) michael@0: return true; michael@0: item = item->GetParent(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (item && aOldPopup == item->Content()) michael@0: return; michael@0: michael@0: if (mWidget) { michael@0: mWidget->CaptureRollupEvents(nullptr, false); michael@0: mWidget = nullptr; michael@0: } michael@0: michael@0: if (item) { michael@0: nsMenuPopupFrame* popup = item->Frame(); michael@0: mWidget = popup->GetWidget(); michael@0: if (mWidget) { michael@0: mWidget->CaptureRollupEvents(nullptr, true); michael@0: popup->AttachedDismissalListener(); michael@0: } michael@0: } michael@0: michael@0: UpdateKeyboardListeners(); michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::UpdateKeyboardListeners() michael@0: { michael@0: nsCOMPtr newTarget; michael@0: bool isForMenu = false; michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (item) { michael@0: if (!item->IgnoreKeys()) michael@0: newTarget = item->Content()->GetDocument(); michael@0: isForMenu = item->PopupType() == ePopupTypeMenu; michael@0: } michael@0: else if (mActiveMenuBar) { michael@0: newTarget = mActiveMenuBar->GetContent()->GetDocument(); michael@0: isForMenu = true; michael@0: } michael@0: michael@0: if (mKeyListener != newTarget) { michael@0: if (mKeyListener) { michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); michael@0: mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); michael@0: mKeyListener = nullptr; michael@0: nsContentUtils::NotifyInstalledMenuKeyboardListener(false); michael@0: } michael@0: michael@0: if (newTarget) { michael@0: newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true); michael@0: newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true); michael@0: newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true); michael@0: nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu); michael@0: mKeyListener = newTarget; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup) michael@0: { michael@0: // Walk all of the menu's children, checking to see if any of them has a michael@0: // command attribute. If so, then several attributes must potentially be updated. michael@0: michael@0: nsCOMPtr document = aPopup->GetCurrentDoc(); michael@0: if (!document) { michael@0: return; michael@0: } michael@0: michael@0: for (nsCOMPtr grandChild = aPopup->GetFirstChild(); michael@0: grandChild; michael@0: grandChild = grandChild->GetNextSibling()) { michael@0: if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) { michael@0: // See if we have a command attribute. michael@0: nsAutoString command; michael@0: grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); michael@0: if (!command.IsEmpty()) { michael@0: // We do! Look it up in our document michael@0: nsRefPtr commandElement = michael@0: document->GetElementById(command); michael@0: if (commandElement) { michael@0: nsAutoString commandValue; michael@0: // The menu's disabled state needs to be updated to match the command. michael@0: if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue)) michael@0: grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true); michael@0: else michael@0: grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); michael@0: michael@0: // The menu's label, accesskey checked and hidden states need to be updated michael@0: // to match the command. Note that unlike the disabled state if the michael@0: // command has *no* value, we assume the menu is supplying its own. michael@0: if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue)) michael@0: grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true); michael@0: michael@0: if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue)) michael@0: grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true); michael@0: michael@0: if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue)) michael@0: grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true); michael@0: michael@0: if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue)) michael@0: grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Notify michael@0: // michael@0: // The item selection timer has fired, we might have to readjust the michael@0: // selected item. There are two cases here that we are trying to deal with: michael@0: // (1) diagonal movement from a parent menu to a submenu passing briefly over michael@0: // other items, and michael@0: // (2) moving out from a submenu to a parent or grandparent menu. michael@0: // In both cases, |mTimerMenu| is the menu item that might have an open submenu and michael@0: // the first item in |mPopups| is the item the mouse is currently over, which could be michael@0: // none of them. michael@0: // michael@0: // case (1): michael@0: // As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the michael@0: // submenu, it probably passes through one or more sibilings (B). As the mouse passes michael@0: // through B, it becomes the current menu item and the timer is set and mTimerMenu is michael@0: // set to A. Before the timer fires, the mouse leaves the menu containing A and B and michael@0: // enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|) michael@0: // so we have to see if anything in A's children is selected (recall that even disabled michael@0: // items are selected, the style just doesn't show it). If that is the case, we need to michael@0: // set the selected item back to A. michael@0: // michael@0: // case (2); michael@0: // Item A has an open submenu, and in it there is an item (B) which also has an open michael@0: // submenu (so there are 3 menus displayed right now). The mouse then leaves B's child michael@0: // submenu and selects an item that is a sibling of A, call it C. When the mouse enters C, michael@0: // the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires, michael@0: // the mouse is still within C. The correct behavior is to set the current item to C michael@0: // and close up the chain parented at A. michael@0: // michael@0: // This brings up the question of is the logic of case (1) enough? The answer is no, michael@0: // and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected michael@0: // child, and if it does, set the selected item to A. Because B has a submenu open, it michael@0: // is selected and as a result, A is set to be the selected item even though the mouse michael@0: // rests in C -- very wrong. michael@0: // michael@0: // The solution is to use the same idea, but instead of only checking one level, michael@0: // drill all the way down to the deepest open submenu and check if it has something michael@0: // selected. Since the mouse is in a grandparent, it won't, and we know that we can michael@0: // safely close up A and all its children. michael@0: // michael@0: // The code below melds the two cases together. michael@0: // michael@0: nsresult michael@0: nsXULPopupManager::Notify(nsITimer* aTimer) michael@0: { michael@0: if (aTimer == mCloseTimer) michael@0: KillMenuTimer(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::KillMenuTimer() michael@0: { michael@0: if (mCloseTimer && mTimerMenu) { michael@0: mCloseTimer->Cancel(); michael@0: mCloseTimer = nullptr; michael@0: michael@0: if (mTimerMenu->IsOpen()) michael@0: HidePopup(mTimerMenu->GetContent(), false, false, true, false); michael@0: } michael@0: michael@0: mTimerMenu = nullptr; michael@0: } michael@0: michael@0: void michael@0: nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent) michael@0: { michael@0: if (mCloseTimer && mTimerMenu == aMenuParent) { michael@0: mCloseTimer->Cancel(); michael@0: mCloseTimer = nullptr; michael@0: mTimerMenu = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, michael@0: nsMenuPopupFrame* aFrame) michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (!aFrame && item) michael@0: aFrame = item->Frame(); michael@0: michael@0: if (aFrame) { michael@0: bool action; michael@0: nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action); michael@0: if (result) { michael@0: aFrame->ChangeMenuItem(result, false); michael@0: if (action) { michael@0: WidgetGUIEvent* evt = aKeyEvent->GetInternalNSEvent()->AsGUIEvent(); michael@0: nsMenuFrame* menuToOpen = result->Enter(evt); michael@0: if (menuToOpen) { michael@0: nsCOMPtr content = menuToOpen->GetContent(); michael@0: ShowMenu(content, true, false); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: if (mActiveMenuBar) { michael@0: nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent); michael@0: if (result) { michael@0: mActiveMenuBar->SetActive(true); michael@0: result->OpenMenu(true); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode) michael@0: { michael@0: // navigate up through the open menus, looking for the topmost one michael@0: // in the same hierarchy michael@0: nsMenuChainItem* item = nullptr; michael@0: nsMenuChainItem* nextitem = GetTopVisibleMenu(); michael@0: michael@0: while (nextitem) { michael@0: item = nextitem; michael@0: nextitem = item->GetParent(); michael@0: michael@0: if (nextitem) { michael@0: // stop if the parent isn't a menu michael@0: if (!nextitem->IsMenu()) michael@0: break; michael@0: michael@0: // check to make sure that the parent is actually the parent menu. It won't michael@0: // be if the parent is in a different frame hierarchy, for example, for a michael@0: // context menu opened on another menu. michael@0: nsMenuParent* expectedParent = static_cast(nextitem->Frame()); michael@0: nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent()); michael@0: if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIFrame* itemFrame; michael@0: if (item) michael@0: itemFrame = item->Frame(); michael@0: else if (mActiveMenuBar) michael@0: itemFrame = mActiveMenuBar; michael@0: else michael@0: return false; michael@0: michael@0: nsNavigationDirection theDirection; michael@0: NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END && michael@0: aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code"); michael@0: theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode); michael@0: michael@0: // if a popup is open, first check for navigation within the popup michael@0: if (item && HandleKeyboardNavigationInPopup(item, theDirection)) michael@0: return true; michael@0: michael@0: // no popup handled the key, so check the active menubar, if any michael@0: if (mActiveMenuBar) { michael@0: nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem(); michael@0: michael@0: if (NS_DIRECTION_IS_INLINE(theDirection)) { michael@0: nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ? michael@0: GetNextMenuItem(mActiveMenuBar, currentMenu, false) : michael@0: GetPreviousMenuItem(mActiveMenuBar, currentMenu, false); michael@0: mActiveMenuBar->ChangeMenuItem(nextItem, true); michael@0: return true; michael@0: } michael@0: else if (NS_DIRECTION_IS_BLOCK(theDirection)) { michael@0: // Open the menu and select its first item. michael@0: if (currentMenu) { michael@0: nsCOMPtr content = currentMenu->GetContent(); michael@0: ShowMenu(content, true, false); michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item, michael@0: nsMenuPopupFrame* aFrame, michael@0: nsNavigationDirection aDir) michael@0: { michael@0: NS_ASSERTION(aFrame, "aFrame is null"); michael@0: NS_ASSERTION(!item || item->Frame() == aFrame, michael@0: "aFrame is expected to be equal to item->Frame()"); michael@0: michael@0: nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem(); michael@0: michael@0: aFrame->ClearIncrementalString(); michael@0: michael@0: // This method only gets called if we're open. michael@0: if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) { michael@0: // We've been opened, but we haven't had anything selected. michael@0: // We can handle End, but our parent handles Start. michael@0: if (aDir == eNavigationDirection_End) { michael@0: nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true); michael@0: if (nextItem) { michael@0: aFrame->ChangeMenuItem(nextItem, false); michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool isContainer = false; michael@0: bool isOpen = false; michael@0: if (currentMenu) { michael@0: isOpen = currentMenu->IsOpen(); michael@0: isContainer = currentMenu->IsMenu(); michael@0: if (isOpen) { michael@0: // for an open popup, have the child process the event michael@0: nsMenuChainItem* child = item ? item->GetChild() : nullptr; michael@0: if (child && HandleKeyboardNavigationInPopup(child, aDir)) michael@0: return true; michael@0: } michael@0: else if (aDir == eNavigationDirection_End && michael@0: isContainer && !currentMenu->IsDisabled()) { michael@0: // The menu is not yet open. Open it and select the first item. michael@0: nsCOMPtr content = currentMenu->GetContent(); michael@0: ShowMenu(content, true, false); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // For block progression, we can move in either direction michael@0: if (NS_DIRECTION_IS_BLOCK(aDir) || michael@0: NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) { michael@0: nsMenuFrame* nextItem; michael@0: michael@0: if (aDir == eNavigationDirection_Before) michael@0: nextItem = GetPreviousMenuItem(aFrame, currentMenu, true); michael@0: else if (aDir == eNavigationDirection_After) michael@0: nextItem = GetNextMenuItem(aFrame, currentMenu, true); michael@0: else if (aDir == eNavigationDirection_First) michael@0: nextItem = GetNextMenuItem(aFrame, nullptr, true); michael@0: else michael@0: nextItem = GetPreviousMenuItem(aFrame, nullptr, true); michael@0: michael@0: if (nextItem) { michael@0: aFrame->ChangeMenuItem(nextItem, false); michael@0: return true; michael@0: } michael@0: } michael@0: else if (currentMenu && isContainer && isOpen) { michael@0: if (aDir == eNavigationDirection_Start) { michael@0: // close a submenu when Left is pressed michael@0: nsMenuPopupFrame* popupFrame = currentMenu->GetPopup(); michael@0: if (popupFrame) michael@0: HidePopup(popupFrame->GetContent(), false, false, false, false); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::HandleKeyboardEventWithKeyCode( michael@0: nsIDOMKeyEvent* aKeyEvent, michael@0: nsMenuChainItem* aTopVisibleMenuItem) michael@0: { michael@0: uint32_t keyCode; michael@0: aKeyEvent->GetKeyCode(&keyCode); michael@0: michael@0: // Escape should close panels, but the other keys should have no effect. michael@0: if (aTopVisibleMenuItem && michael@0: aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) { michael@0: if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) { michael@0: HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool consume = (mPopups || mActiveMenuBar); michael@0: switch (keyCode) { michael@0: case nsIDOMKeyEvent::DOM_VK_LEFT: michael@0: case nsIDOMKeyEvent::DOM_VK_RIGHT: michael@0: case nsIDOMKeyEvent::DOM_VK_UP: michael@0: case nsIDOMKeyEvent::DOM_VK_DOWN: michael@0: case nsIDOMKeyEvent::DOM_VK_HOME: michael@0: case nsIDOMKeyEvent::DOM_VK_END: michael@0: HandleKeyboardNavigation(keyCode); michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_ESCAPE: michael@0: // Pressing Escape hides one level of menus only. If no menu is open, michael@0: // check if a menubar is active and inform it that a menu closed. Even michael@0: // though in this latter case, a menu didn't actually close, the effect michael@0: // ends up being the same. Similar for the tab key below. michael@0: if (aTopVisibleMenuItem) { michael@0: HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); michael@0: } else if (mActiveMenuBar) { michael@0: mActiveMenuBar->MenuClosed(); michael@0: } michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_TAB: michael@0: #ifndef XP_MACOSX michael@0: case nsIDOMKeyEvent::DOM_VK_F10: michael@0: #endif michael@0: // close popups or deactivate menubar when Tab or F10 are pressed michael@0: if (aTopVisibleMenuItem) { michael@0: Rollup(0, nullptr, nullptr); michael@0: } else if (mActiveMenuBar) { michael@0: mActiveMenuBar->MenuClosed(); michael@0: } michael@0: break; michael@0: michael@0: case nsIDOMKeyEvent::DOM_VK_RETURN: { michael@0: // If there is a popup open, check if the current item needs to be opened. michael@0: // Otherwise, tell the active menubar, if any, to activate the menu. The michael@0: // Enter method will return a menu if one needs to be opened as a result. michael@0: nsMenuFrame* menuToOpen = nullptr; michael@0: WidgetGUIEvent* GUIEvent = aKeyEvent->GetInternalNSEvent()->AsGUIEvent(); michael@0: if (aTopVisibleMenuItem) { michael@0: menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent); michael@0: } else if (mActiveMenuBar) { michael@0: menuToOpen = mActiveMenuBar->Enter(GUIEvent); michael@0: } michael@0: if (menuToOpen) { michael@0: nsCOMPtr content = menuToOpen->GetContent(); michael@0: ShowMenu(content, true, false); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: if (consume) { michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent, michael@0: nsMenuFrame* aStart, michael@0: bool aIsPopup) michael@0: { michael@0: nsPresContext* presContext = aParent->PresContext(); michael@0: nsIFrame* immediateParent = presContext->PresShell()-> michael@0: FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); michael@0: if (!immediateParent) michael@0: immediateParent = aParent; michael@0: michael@0: nsIFrame* currFrame = nullptr; michael@0: if (aStart) michael@0: currFrame = aStart->GetNextSibling(); michael@0: else michael@0: currFrame = immediateParent->GetFirstPrincipalChild(); michael@0: michael@0: while (currFrame) { michael@0: // See if it's a menu item. michael@0: if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { michael@0: return do_QueryFrame(currFrame); michael@0: } michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } michael@0: michael@0: currFrame = immediateParent->GetFirstPrincipalChild(); michael@0: michael@0: // Still don't have anything. Try cycling from the beginning. michael@0: while (currFrame && currFrame != aStart) { michael@0: // See if it's a menu item. michael@0: if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { michael@0: return do_QueryFrame(currFrame); michael@0: } michael@0: michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } michael@0: michael@0: // No luck. Just return our start value. michael@0: return aStart; michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent, michael@0: nsMenuFrame* aStart, michael@0: bool aIsPopup) michael@0: { michael@0: nsPresContext* presContext = aParent->PresContext(); michael@0: nsIFrame* immediateParent = presContext->PresShell()-> michael@0: FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); michael@0: if (!immediateParent) michael@0: immediateParent = aParent; michael@0: michael@0: const nsFrameList& frames(immediateParent->PrincipalChildList()); michael@0: michael@0: nsIFrame* currFrame = nullptr; michael@0: if (aStart) michael@0: currFrame = aStart->GetPrevSibling(); michael@0: else michael@0: currFrame = frames.LastChild(); michael@0: michael@0: while (currFrame) { michael@0: // See if it's a menu item. michael@0: if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { michael@0: return do_QueryFrame(currFrame); michael@0: } michael@0: currFrame = currFrame->GetPrevSibling(); michael@0: } michael@0: michael@0: currFrame = frames.LastChild(); michael@0: michael@0: // Still don't have anything. Try cycling from the end. michael@0: while (currFrame && currFrame != aStart) { michael@0: // See if it's a menu item. michael@0: if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { michael@0: return do_QueryFrame(currFrame); michael@0: } michael@0: michael@0: currFrame = currFrame->GetPrevSibling(); michael@0: } michael@0: michael@0: // No luck. Just return our start value. michael@0: return aStart; michael@0: } michael@0: michael@0: bool michael@0: nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext, michael@0: nsIContent* aContent, michael@0: bool aOnPopup) michael@0: { michael@0: int32_t ns = aContent->GetNameSpaceID(); michael@0: nsIAtom *tag = aContent->Tag(); michael@0: if (ns == kNameSpaceID_XUL) { michael@0: if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem) michael@0: return false; michael@0: } michael@0: else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) { michael@0: return false; michael@0: } michael@0: michael@0: bool skipNavigatingDisabledMenuItem = true; michael@0: if (aOnPopup) { michael@0: skipNavigatingDisabledMenuItem = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, michael@0: 0) != 0; michael@0: } michael@0: michael@0: return !(skipNavigatingDisabledMenuItem && michael@0: aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters)); michael@0: } michael@0: michael@0: nsresult michael@0: nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr keyEvent = do_QueryInterface(aEvent); michael@0: NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); michael@0: michael@0: //handlers shouldn't be triggered by non-trusted events. michael@0: bool trustedEvent = false; michael@0: aEvent->GetIsTrusted(&trustedEvent); michael@0: if (!trustedEvent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString eventType; michael@0: keyEvent->GetType(eventType); michael@0: if (eventType.EqualsLiteral("keyup")) { michael@0: return KeyUp(keyEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("keydown")) { michael@0: return KeyDown(keyEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("keypress")) { michael@0: return KeyPress(keyEvent); michael@0: } michael@0: michael@0: NS_ABORT(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: // don't do anything if a menu isn't open or a menubar isn't active michael@0: if (!mActiveMenuBar) { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (!item || item->PopupType() != ePopupTypeMenu) michael@0: return NS_OK; michael@0: } michael@0: michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: michael@0: return NS_OK; // I am consuming event michael@0: } michael@0: michael@0: nsresult michael@0: nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (item && item->Frame()->IsMenuLocked()) michael@0: return NS_OK; michael@0: michael@0: if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // don't do anything if a menu isn't open or a menubar isn't active michael@0: if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu)) michael@0: return NS_OK; michael@0: michael@0: int32_t menuAccessKey = -1; michael@0: michael@0: // If the key just pressed is the access key (usually Alt), michael@0: // dismiss and unfocus the menu. michael@0: michael@0: nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); michael@0: if (menuAccessKey) { michael@0: uint32_t theChar; michael@0: aKeyEvent->GetKeyCode(&theChar); michael@0: michael@0: if (theChar == (uint32_t)menuAccessKey) { michael@0: bool ctrl = false; michael@0: if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL) michael@0: aKeyEvent->GetCtrlKey(&ctrl); michael@0: bool alt=false; michael@0: if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT) michael@0: aKeyEvent->GetAltKey(&alt); michael@0: bool shift=false; michael@0: if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT) michael@0: aKeyEvent->GetShiftKey(&shift); michael@0: bool meta=false; michael@0: if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META) michael@0: aKeyEvent->GetMetaKey(&meta); michael@0: if (!(ctrl || alt || shift || meta)) { michael@0: // The access key just went down and no other michael@0: // modifiers are already down. michael@0: if (mPopups) michael@0: Rollup(0, nullptr, nullptr); michael@0: else if (mActiveMenuBar) michael@0: mActiveMenuBar->MenuClosed(); michael@0: } michael@0: aKeyEvent->PreventDefault(); michael@0: } michael@0: } michael@0: michael@0: // Since a menu was open, stop propagation of the event to keep other event michael@0: // listeners from becoming confused. michael@0: aKeyEvent->StopPropagation(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: // Don't check prevent default flag -- menus always get first shot at key events. michael@0: michael@0: nsMenuChainItem* item = GetTopVisibleMenu(); michael@0: if (item && michael@0: (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr keyEvent = do_QueryInterface(aKeyEvent); michael@0: NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); michael@0: // if a menu is open or a menubar is active, it consumes the key event michael@0: bool consume = (mPopups || mActiveMenuBar); michael@0: HandleShortcutNavigation(keyEvent, nullptr); michael@0: if (consume) { michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: } michael@0: michael@0: return NS_OK; // I am consuming event michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXULPopupShowingEvent::Run() michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) { michael@0: pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXULPopupHidingEvent::Run() michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: michael@0: nsIDocument *document = mPopup->GetCurrentDoc(); michael@0: if (pm && document) { michael@0: nsIPresShell* presShell = document->GetShell(); michael@0: if (presShell) { michael@0: nsPresContext* context = presShell->GetPresContext(); michael@0: if (context) { michael@0: pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup, michael@0: context, mPopupType, mDeselectMenu, mIsRollup); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXULMenuCommandEvent::Run() michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (!pm) michael@0: return NS_OK; michael@0: michael@0: // The order of the nsViewManager and nsIPresShell COM pointers is michael@0: // important below. We want the pres shell to get released before the michael@0: // associated view manager on exit from this function. michael@0: // See bug 54233. michael@0: // XXXndeakin is this still needed? michael@0: michael@0: nsCOMPtr popup; michael@0: nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame()); michael@0: nsWeakFrame weakFrame(menuFrame); michael@0: if (menuFrame && mFlipChecked) { michael@0: if (menuFrame->IsChecked()) { michael@0: mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); michael@0: } else { michael@0: mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, michael@0: NS_LITERAL_STRING("true"), true); michael@0: } michael@0: } michael@0: michael@0: if (menuFrame && weakFrame.IsAlive()) { michael@0: // Find the popup that the menu is inside. Below, this popup will michael@0: // need to be hidden. michael@0: nsIFrame* frame = menuFrame->GetParent(); michael@0: while (frame) { michael@0: nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); michael@0: if (popupFrame) { michael@0: popup = popupFrame->GetContent(); michael@0: break; michael@0: } michael@0: frame = frame->GetParent(); michael@0: } michael@0: michael@0: nsPresContext* presContext = menuFrame->PresContext(); michael@0: nsCOMPtr shell = presContext->PresShell(); michael@0: nsRefPtr kungFuDeathGrip = shell->GetViewManager(); michael@0: michael@0: // Deselect ourselves. michael@0: if (mCloseMenuMode != CloseMenuMode_None) michael@0: menuFrame->SelectMenu(false); michael@0: michael@0: AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr, michael@0: shell->GetDocument()); michael@0: nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell, michael@0: mControl, mAlt, mShift, mMeta); michael@0: } michael@0: michael@0: if (popup && mCloseMenuMode != CloseMenuMode_None) michael@0: pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false); michael@0: michael@0: return NS_OK; michael@0: }