michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ 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 "nsMenuPopupFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsMenuFrame.h" michael@0: #include "nsMenuBarFrame.h" michael@0: #include "nsPopupSetFrame.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMScreen.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsRect.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsIRootBox.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsIPopupBoxObject.h" michael@0: #include "nsPIWindowRoot.h" michael@0: #include "nsIReflowCallback.h" michael@0: #include "nsBindingManager.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsISound.h" michael@0: #include "nsIScreenManager.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsThemeConstants.h" michael@0: #include "nsDisplayList.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1; michael@0: michael@0: // NS_NewMenuPopupFrame michael@0: // michael@0: // Wrapper for creating a new menu popup container michael@0: // michael@0: nsIFrame* michael@0: NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsMenuPopupFrame (aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsMenuPopupFrame) michael@0: NS_QUERYFRAME_ENTRY(nsMenuPopupFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) michael@0: michael@0: // michael@0: // nsMenuPopupFrame ctor michael@0: // michael@0: nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext) michael@0: :nsBoxFrame(aShell, aContext), michael@0: mCurrentMenu(nullptr), michael@0: mPrefSize(-1, -1), michael@0: mLastClientOffset(0, 0), michael@0: mPopupType(ePopupTypePanel), michael@0: mPopupState(ePopupClosed), michael@0: mPopupAlignment(POPUPALIGNMENT_NONE), michael@0: mPopupAnchor(POPUPALIGNMENT_NONE), michael@0: mPosition(POPUPPOSITION_UNKNOWN), michael@0: mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT), michael@0: mFlip(FlipType_Default), michael@0: mIsOpenChanged(false), michael@0: mIsContextMenu(false), michael@0: mAdjustOffsetForContextMenu(false), michael@0: mGeneratedChildren(false), michael@0: mMenuCanOverlapOSBar(false), michael@0: mShouldAutoPosition(true), michael@0: mInContentShell(true), michael@0: mIsMenuLocked(false), michael@0: mMouseTransparent(false), michael@0: mHFlip(false), michael@0: mVFlip(false) michael@0: { michael@0: // the preference name is backwards here. True means that the 'top' level is michael@0: // the default, and false means that the 'parent' level is the default. michael@0: if (sDefaultLevelIsTop >= 0) michael@0: return; michael@0: sDefaultLevelIsTop = michael@0: Preferences::GetBool("ui.panel.default_level_parent", false); michael@0: } // ctor michael@0: michael@0: michael@0: void michael@0: nsMenuPopupFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the michael@0: // look&feel object michael@0: mMenuCanOverlapOSBar = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0; michael@0: michael@0: CreatePopupView(); michael@0: michael@0: // XXX Hack. The popup's view should float above all other views, michael@0: // so we use the nsView::SetFloating() to tell the view manager michael@0: // about that constraint. michael@0: nsView* ourView = GetView(); michael@0: nsViewManager* viewManager = ourView->GetViewManager(); michael@0: viewManager->SetViewFloating(ourView, true); michael@0: michael@0: mPopupType = ePopupTypePanel; michael@0: nsIDocument* doc = aContent->OwnerDoc(); michael@0: int32_t namespaceID; michael@0: nsCOMPtr tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID); michael@0: if (namespaceID == kNameSpaceID_XUL) { michael@0: if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup) michael@0: mPopupType = ePopupTypeMenu; michael@0: else if (tag == nsGkAtoms::tooltip) michael@0: mPopupType = ePopupTypeTooltip; michael@0: } michael@0: michael@0: nsCOMPtr dsti = PresContext()->GetDocShell(); michael@0: if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) { michael@0: mInContentShell = false; michael@0: } michael@0: michael@0: // To improve performance, create the widget for the popup only if it is not michael@0: // a leaf. Leaf popups such as menus will create their widgets later when michael@0: // the popup opens. michael@0: if (!IsLeaf() && !ourView->HasWidget()) { michael@0: CreateWidgetForView(ourView); michael@0: } michael@0: michael@0: if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) && michael@0: aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default, michael@0: nsGkAtoms::_true, eIgnoreCase)) { michael@0: nsIRootBox* rootBox = michael@0: nsIRootBox::GetRootBox(PresContext()->GetPresShell()); michael@0: if (rootBox) { michael@0: rootBox->SetDefaultTooltip(aContent); michael@0: } michael@0: } michael@0: michael@0: AddStateBits(NS_FRAME_IN_POPUP); michael@0: } michael@0: michael@0: bool michael@0: nsMenuPopupFrame::IsNoAutoHide() const michael@0: { michael@0: // Panels with noautohide="true" don't hide when the mouse is clicked michael@0: // outside of them, or when another application is made active. Non-autohide michael@0: // panels cannot be used in content windows. michael@0: return (!mInContentShell && mPopupType == ePopupTypePanel && michael@0: mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide, michael@0: nsGkAtoms::_true, eIgnoreCase)); michael@0: } michael@0: michael@0: nsPopupLevel michael@0: nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const michael@0: { michael@0: // The popup level is determined as follows, in this order: michael@0: // 1. non-panels (menus and tooltips) are always topmost michael@0: // 2. any specified level attribute michael@0: // 3. if a titlebar attribute is set, use the 'floating' level michael@0: // 4. if this is a noautohide panel, use the 'parent' level michael@0: // 5. use the platform-specific default level michael@0: michael@0: // If this is not a panel, this is always a top-most popup. michael@0: if (mPopupType != ePopupTypePanel) michael@0: return ePopupLevelTop; michael@0: michael@0: // If the level attribute has been set, use that. michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr}; michael@0: switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level, michael@0: strings, eCaseMatters)) { michael@0: case 0: michael@0: return ePopupLevelTop; michael@0: case 1: michael@0: return ePopupLevelParent; michael@0: case 2: michael@0: return ePopupLevelFloating; michael@0: } michael@0: michael@0: // Panels with titlebars most likely want to be floating popups. michael@0: if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar)) michael@0: return ePopupLevelFloating; michael@0: michael@0: // If this panel is a noautohide panel, the default is the parent level. michael@0: if (aIsNoAutoHide) michael@0: return ePopupLevelParent; michael@0: michael@0: // Otherwise, the result depends on the platform. michael@0: return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::EnsureWidget() michael@0: { michael@0: nsView* ourView = GetView(); michael@0: if (!ourView->HasWidget()) { michael@0: NS_ASSERTION(!mGeneratedChildren && !GetFirstPrincipalChild(), michael@0: "Creating widget for MenuPopupFrame with children"); michael@0: CreateWidgetForView(ourView); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuPopupFrame::CreateWidgetForView(nsView* aView) michael@0: { michael@0: // Create a widget for ourselves. michael@0: nsWidgetInitData widgetData; michael@0: widgetData.mWindowType = eWindowType_popup; michael@0: widgetData.mBorderStyle = eBorderStyle_default; michael@0: widgetData.clipSiblings = true; michael@0: widgetData.mPopupHint = mPopupType; michael@0: widgetData.mNoAutoHide = IsNoAutoHide(); michael@0: michael@0: if (!mInContentShell) { michael@0: // A drag popup may be used for non-static translucent drag feedback michael@0: if (mPopupType == ePopupTypePanel && michael@0: mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::drag, eIgnoreCase)) { michael@0: widgetData.mIsDragPopup = true; michael@0: } michael@0: michael@0: // If mousethrough="always" is set directly on the popup, then the widget michael@0: // should ignore mouse events, passing them through to the content behind. michael@0: mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS; michael@0: widgetData.mMouseTransparent = mMouseTransparent; michael@0: } michael@0: michael@0: nsAutoString title; michael@0: if (mContent && widgetData.mNoAutoHide) { michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar, michael@0: nsGkAtoms::normal, eCaseMatters)) { michael@0: widgetData.mBorderStyle = eBorderStyle_title; michael@0: michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title); michael@0: michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: widgetData.mBorderStyle = michael@0: static_cast(widgetData.mBorderStyle | eBorderStyle_close); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this); michael@0: nsIContent* parentContent = GetContent()->GetParent(); michael@0: nsIAtom *tag = nullptr; michael@0: if (parentContent) michael@0: tag = parentContent->Tag(); michael@0: widgetData.mSupportTranslucency = mode == eTransparencyTransparent; michael@0: widgetData.mDropShadow = !(mode == eTransparencyTransparent || tag == nsGkAtoms::menulist); michael@0: widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide); michael@0: michael@0: // panels which have a parent level need a parent widget. This allows them to michael@0: // always appear in front of the parent window but behind other windows that michael@0: // should be in front of it. michael@0: nsCOMPtr parentWidget; michael@0: if (widgetData.mPopupLevel != ePopupLevelTop) { michael@0: nsCOMPtr dsti = PresContext()->GetDocShell(); michael@0: if (!dsti) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr treeOwner; michael@0: dsti->GetTreeOwner(getter_AddRefs(treeOwner)); michael@0: if (!treeOwner) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr baseWindow(do_QueryInterface(treeOwner)); michael@0: if (baseWindow) michael@0: baseWindow->GetMainWidget(getter_AddRefs(parentWidget)); michael@0: } michael@0: michael@0: nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget, michael@0: true, true); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsIWidget* widget = aView->GetWidget(); michael@0: widget->SetTransparencyMode(mode); michael@0: widget->SetWindowShadowStyle(GetShadowStyle()); michael@0: michael@0: // most popups don't have a title so avoid setting the title if there isn't one michael@0: if (!title.IsEmpty()) { michael@0: widget->SetTitle(title); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint8_t michael@0: nsMenuPopupFrame::GetShadowStyle() michael@0: { michael@0: uint8_t shadow = StyleUIReset()->mWindowShadow; michael@0: if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT) michael@0: return shadow; michael@0: michael@0: switch (StyleDisplay()->mAppearance) { michael@0: case NS_THEME_TOOLTIP: michael@0: return NS_STYLE_WINDOW_SHADOW_TOOLTIP; michael@0: case NS_THEME_MENUPOPUP: michael@0: return NS_STYLE_WINDOW_SHADOW_MENU; michael@0: } michael@0: return NS_STYLE_WINDOW_SHADOW_DEFAULT; michael@0: } michael@0: michael@0: // this class is used for dispatching popupshown events asynchronously. michael@0: class nsXULPopupShownEvent : public nsRunnable michael@0: { michael@0: public: michael@0: nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext) michael@0: : mPopup(aPopup), mPresContext(aPresContext) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWN, nullptr, michael@0: WidgetMouseEvent::eReal); michael@0: return EventDispatcher::Dispatch(mPopup, mPresContext, &event); michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mPopup; michael@0: nsRefPtr mPresContext; michael@0: }; michael@0: michael@0: nsresult michael@0: nsMenuPopupFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: // unless the list is empty, indicate that children have been generated. michael@0: if (aChildList.NotEmpty()) michael@0: mGeneratedChildren = true; michael@0: return nsBoxFrame::SetInitialChildList(aListID, aChildList); michael@0: } michael@0: michael@0: bool michael@0: nsMenuPopupFrame::IsLeaf() const michael@0: { michael@0: if (mGeneratedChildren) michael@0: return false; michael@0: michael@0: if (mPopupType != ePopupTypeMenu) { michael@0: // any panel with a type attribute, such as the autocomplete popup, michael@0: // is always generated right away. michael@0: return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type); michael@0: } michael@0: michael@0: // menu popups generate their child frames lazily only when opened, so michael@0: // behave like a leaf frame. However, generate child frames normally if michael@0: // the parent menu has a sizetopopup attribute. In this case the size of michael@0: // the parent menu is dependent on the size of the popup, so the frames michael@0: // need to exist in order to calculate this size. michael@0: nsIContent* parentContent = mContent->GetParent(); michael@0: return (parentContent && michael@0: !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup)); michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, michael@0: nsIFrame* aAnchor, bool aSizedToPopup) michael@0: { michael@0: if (!mGeneratedChildren) michael@0: return; michael@0: michael@0: SchedulePaint(); michael@0: michael@0: bool shouldPosition = true; michael@0: bool isOpen = IsOpen(); michael@0: if (!isOpen) { michael@0: // if the popup is not open, only do layout while showing or if the menu michael@0: // is sized to the popup michael@0: shouldPosition = (mPopupState == ePopupShowing); michael@0: if (!shouldPosition && !aSizedToPopup) { michael@0: RemoveStateBits(NS_FRAME_FIRST_REFLOW); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // if the popup has just been opened, make sure the scrolled window is at 0,0 michael@0: if (mIsOpenChanged) { michael@0: nsIScrollableFrame *scrollframe = do_QueryFrame(GetChildBox()); michael@0: if (scrollframe) { michael@0: nsWeakFrame weakFrame(this); michael@0: scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // get the preferred, minimum and maximum size. If the menu is sized to the michael@0: // popup, then the popup's width is the menu's width. michael@0: nsSize prefSize = GetPrefSize(aState); michael@0: nsSize minSize = GetMinSize(aState); michael@0: nsSize maxSize = GetMaxSize(aState); michael@0: michael@0: if (aSizedToPopup) { michael@0: prefSize.width = aParentMenu->GetRect().width; michael@0: } michael@0: prefSize = BoundsCheck(minSize, prefSize, maxSize); michael@0: michael@0: // if the size changed then set the bounds to be the preferred size michael@0: bool sizeChanged = (mPrefSize != prefSize); michael@0: if (sizeChanged) { michael@0: SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false); michael@0: mPrefSize = prefSize; michael@0: } michael@0: michael@0: if (shouldPosition) { michael@0: SetPopupPosition(aAnchor, false, aSizedToPopup); michael@0: } michael@0: michael@0: nsRect bounds(GetRect()); michael@0: Layout(aState); michael@0: michael@0: // if the width or height changed, readjust the popup position. This is a michael@0: // special case for tooltips where the preferred height doesn't include the michael@0: // real height for its inline element, but does once it is laid out. michael@0: // This is bug 228673 which doesn't have a simple fix. michael@0: if (!aParentMenu) { michael@0: nsSize newsize = GetSize(); michael@0: if (newsize.width > bounds.width || newsize.height > bounds.height) { michael@0: // the size after layout was larger than the preferred size, michael@0: // so set the preferred size accordingly michael@0: mPrefSize = newsize; michael@0: if (isOpen) { michael@0: SetPopupPosition(nullptr, false, aSizedToPopup); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsPresContext* pc = PresContext(); michael@0: nsView* view = GetView(); michael@0: michael@0: if (sizeChanged) { michael@0: // If the size of the popup changed, apply any size constraints. michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (widget) { michael@0: SetSizeConstraints(pc, widget, minSize, maxSize); michael@0: } michael@0: } michael@0: michael@0: if (isOpen) { michael@0: nsViewManager* viewManager = view->GetViewManager(); michael@0: nsRect rect = GetRect(); michael@0: rect.x = rect.y = 0; michael@0: viewManager->ResizeView(view, rect); michael@0: michael@0: viewManager->SetViewVisibility(view, nsViewVisibility_kShow); michael@0: mPopupState = ePopupOpenAndVisible; michael@0: nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0); michael@0: } michael@0: michael@0: // finally, if the popup just opened, send a popupshown event michael@0: if (mIsOpenChanged) { michael@0: mIsOpenChanged = false; michael@0: nsCOMPtr event = new nsXULPopupShownEvent(GetContent(), pc); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: } michael@0: michael@0: nsIContent* michael@0: nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame) michael@0: { michael@0: while (aMenuPopupFrame) { michael@0: if (aMenuPopupFrame->mTriggerContent) michael@0: return aMenuPopupFrame->mTriggerContent; michael@0: michael@0: // check up the menu hierarchy until a popup with a trigger node is found michael@0: nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent()); michael@0: if (!menuFrame) michael@0: break; michael@0: michael@0: nsMenuParent* parentPopup = menuFrame->GetMenuParent(); michael@0: if (!parentPopup || !parentPopup->IsMenu()) michael@0: break; michael@0: michael@0: aMenuPopupFrame = static_cast(parentPopup); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor, michael@0: const nsAString& aAlign) michael@0: { michael@0: mTriggerContent = nullptr; michael@0: michael@0: if (aAnchor.EqualsLiteral("topleft")) michael@0: mPopupAnchor = POPUPALIGNMENT_TOPLEFT; michael@0: else if (aAnchor.EqualsLiteral("topright")) michael@0: mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; michael@0: else if (aAnchor.EqualsLiteral("bottomleft")) michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; michael@0: else if (aAnchor.EqualsLiteral("bottomright")) michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: else if (aAnchor.EqualsLiteral("leftcenter")) michael@0: mPopupAnchor = POPUPALIGNMENT_LEFTCENTER; michael@0: else if (aAnchor.EqualsLiteral("rightcenter")) michael@0: mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER; michael@0: else if (aAnchor.EqualsLiteral("topcenter")) michael@0: mPopupAnchor = POPUPALIGNMENT_TOPCENTER; michael@0: else if (aAnchor.EqualsLiteral("bottomcenter")) michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER; michael@0: else michael@0: mPopupAnchor = POPUPALIGNMENT_NONE; michael@0: michael@0: if (aAlign.EqualsLiteral("topleft")) michael@0: mPopupAlignment = POPUPALIGNMENT_TOPLEFT; michael@0: else if (aAlign.EqualsLiteral("topright")) michael@0: mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; michael@0: else if (aAlign.EqualsLiteral("bottomleft")) michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; michael@0: else if (aAlign.EqualsLiteral("bottomright")) michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: else michael@0: mPopupAlignment = POPUPALIGNMENT_NONE; michael@0: michael@0: mPosition = POPUPPOSITION_UNKNOWN; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent, michael@0: nsIContent* aTriggerContent, michael@0: const nsAString& aPosition, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aAttributesOverride) michael@0: { michael@0: EnsureWidget(); michael@0: michael@0: mPopupState = ePopupShowing; michael@0: mAnchorContent = aAnchorContent; michael@0: mTriggerContent = aTriggerContent; michael@0: mXPos = aXPos; michael@0: mYPos = aYPos; michael@0: mAdjustOffsetForContextMenu = false; michael@0: mVFlip = false; michael@0: mHFlip = false; michael@0: mAlignmentOffset = 0; michael@0: michael@0: // if aAttributesOverride is true, then the popupanchor, popupalign and michael@0: // position attributes on the override those values passed in. michael@0: // If false, those attributes are only used if the values passed in are empty michael@0: if (aAnchorContent) { michael@0: nsAutoString anchor, align, position, flip; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor); michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align); michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position); michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip); michael@0: michael@0: if (aAttributesOverride) { michael@0: // if the attributes are set, clear the offset position. Otherwise, michael@0: // the offset is used to adjust the position from the anchor point michael@0: if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty()) michael@0: position.Assign(aPosition); michael@0: else michael@0: mXPos = mYPos = 0; michael@0: } michael@0: else if (!aPosition.IsEmpty()) { michael@0: position.Assign(aPosition); michael@0: } michael@0: michael@0: if (flip.EqualsLiteral("none")) { michael@0: mFlip = FlipType_None; michael@0: } else if (flip.EqualsLiteral("both")) { michael@0: mFlip = FlipType_Both; michael@0: } else if (flip.EqualsLiteral("slide")) { michael@0: mFlip = FlipType_Slide; michael@0: } michael@0: michael@0: position.CompressWhitespace(); michael@0: int32_t spaceIdx = position.FindChar(' '); michael@0: // if there is a space in the position, assume it is the anchor and michael@0: // alignment as two separate tokens. michael@0: if (spaceIdx >= 0) { michael@0: InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1)); michael@0: } michael@0: else if (position.EqualsLiteral("before_start")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; michael@0: mPosition = POPUPPOSITION_BEFORESTART; michael@0: } michael@0: else if (position.EqualsLiteral("before_end")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: mPosition = POPUPPOSITION_BEFOREEND; michael@0: } michael@0: else if (position.EqualsLiteral("after_start")) { michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPLEFT; michael@0: mPosition = POPUPPOSITION_AFTERSTART; michael@0: } michael@0: else if (position.EqualsLiteral("after_end")) { michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; michael@0: mPosition = POPUPPOSITION_AFTEREND; michael@0: } michael@0: else if (position.EqualsLiteral("start_before")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; michael@0: mPosition = POPUPPOSITION_STARTBEFORE; michael@0: } michael@0: else if (position.EqualsLiteral("start_after")) { michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: mPosition = POPUPPOSITION_STARTAFTER; michael@0: } michael@0: else if (position.EqualsLiteral("end_before")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPLEFT; michael@0: mPosition = POPUPPOSITION_ENDBEFORE; michael@0: } michael@0: else if (position.EqualsLiteral("end_after")) { michael@0: mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; michael@0: mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; michael@0: mPosition = POPUPPOSITION_ENDAFTER; michael@0: } michael@0: else if (position.EqualsLiteral("overlap")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPLEFT; michael@0: mPosition = POPUPPOSITION_OVERLAP; michael@0: } michael@0: else if (position.EqualsLiteral("after_pointer")) { michael@0: mPopupAnchor = POPUPALIGNMENT_TOPLEFT; michael@0: mPopupAlignment = POPUPALIGNMENT_TOPLEFT; michael@0: mPosition = POPUPPOSITION_AFTERPOINTER; michael@0: // XXXndeakin this is supposed to anchor vertically after, but with the michael@0: // horizontal position as the mouse pointer. michael@0: mYPos += 21; michael@0: } michael@0: else { michael@0: InitPositionFromAnchorAlign(anchor, align); michael@0: } michael@0: } michael@0: michael@0: mScreenXPos = -1; michael@0: mScreenYPos = -1; michael@0: michael@0: if (aAttributesOverride) { michael@0: // Use |left| and |top| dimension attributes to position the popup if michael@0: // present, as they may have been persisted. michael@0: nsAutoString left, top; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); michael@0: michael@0: nsresult err; michael@0: if (!left.IsEmpty()) { michael@0: int32_t x = left.ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) michael@0: mScreenXPos = x; michael@0: } michael@0: if (!top.IsEmpty()) { michael@0: int32_t y = top.ToInteger(&err); michael@0: if (NS_SUCCEEDED(err)) michael@0: mScreenYPos = y; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aIsContextMenu) michael@0: { michael@0: EnsureWidget(); michael@0: michael@0: mPopupState = ePopupShowing; michael@0: mAnchorContent = nullptr; michael@0: mTriggerContent = aTriggerContent; michael@0: mScreenXPos = aXPos; michael@0: mScreenYPos = aYPos; michael@0: mFlip = FlipType_Default; michael@0: mPopupAnchor = POPUPALIGNMENT_NONE; michael@0: mPopupAlignment = POPUPALIGNMENT_NONE; michael@0: mIsContextMenu = aIsContextMenu; michael@0: mAdjustOffsetForContextMenu = aIsContextMenu; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, michael@0: nsAString& aAnchor, michael@0: nsAString& aAlign, michael@0: int32_t aXPos, int32_t aYPos) michael@0: { michael@0: EnsureWidget(); michael@0: michael@0: mPopupState = ePopupShowing; michael@0: mAdjustOffsetForContextMenu = false; michael@0: mFlip = FlipType_Default; michael@0: michael@0: // this popup opening function is provided for backwards compatibility michael@0: // only. It accepts either coordinates or an anchor and alignment value michael@0: // but doesn't use both together. michael@0: if (aXPos == -1 && aYPos == -1) { michael@0: mAnchorContent = aAnchorContent; michael@0: mScreenXPos = -1; michael@0: mScreenYPos = -1; michael@0: mXPos = 0; michael@0: mYPos = 0; michael@0: InitPositionFromAnchorAlign(aAnchor, aAlign); michael@0: } michael@0: else { michael@0: mAnchorContent = nullptr; michael@0: mPopupAnchor = POPUPALIGNMENT_NONE; michael@0: mPopupAlignment = POPUPALIGNMENT_NONE; michael@0: mScreenXPos = aXPos; michael@0: mScreenYPos = aYPos; michael@0: mXPos = aXPos; michael@0: mYPos = aYPos; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::ShowPopup(bool aIsContextMenu, bool aSelectFirstItem) michael@0: { michael@0: mIsContextMenu = aIsContextMenu; michael@0: michael@0: InvalidateFrameSubtree(); michael@0: michael@0: if (mPopupState == ePopupShowing) { michael@0: mPopupState = ePopupOpen; michael@0: mIsOpenChanged = true; michael@0: michael@0: nsMenuFrame* menuFrame = do_QueryFrame(GetParent()); michael@0: if (menuFrame) { michael@0: nsWeakFrame weakFrame(this); michael@0: menuFrame->PopupOpened(); michael@0: if (!weakFrame.IsAlive()) michael@0: return; michael@0: } michael@0: michael@0: // do we need an actual reflow here? michael@0: // is SetPopupPosition all that is needed? michael@0: PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: michael@0: if (mPopupType == ePopupTypeMenu) { michael@0: nsCOMPtr sound(do_CreateInstance("@mozilla.org/sound;1")); michael@0: if (sound) michael@0: sound->PlayEventSound(nsISound::EVENT_MENU_POPUP); michael@0: } michael@0: } michael@0: michael@0: mShouldAutoPosition = true; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState) michael@0: { michael@0: NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible, michael@0: "popup being set to unexpected state"); michael@0: michael@0: // don't hide the popup when it isn't open michael@0: if (mPopupState == ePopupClosed || mPopupState == ePopupShowing) michael@0: return; michael@0: michael@0: // clear the trigger content if the popup is being closed. But don't clear michael@0: // it if the popup is just being made invisible as a popuphiding or command michael@0: // event may want to retrieve it. michael@0: if (aNewState == ePopupClosed) { michael@0: // if the popup had a trigger node set, clear the global window popup node michael@0: // as well michael@0: if (mTriggerContent) { michael@0: nsIDocument* doc = mContent->GetCurrentDoc(); michael@0: if (doc) { michael@0: nsPIDOMWindow* win = doc->GetWindow(); michael@0: if (win) { michael@0: nsCOMPtr root = win->GetTopWindowRoot(); michael@0: if (root) { michael@0: root->SetPopupNode(nullptr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: mTriggerContent = nullptr; michael@0: mAnchorContent = nullptr; michael@0: } michael@0: michael@0: // when invisible and about to be closed, HidePopup has already been called, michael@0: // so just set the new state to closed and return michael@0: if (mPopupState == ePopupInvisible) { michael@0: if (aNewState == ePopupClosed) michael@0: mPopupState = ePopupClosed; michael@0: return; michael@0: } michael@0: michael@0: mPopupState = aNewState; michael@0: michael@0: if (IsMenu()) michael@0: SetCurrentMenuItem(nullptr); michael@0: michael@0: mIncrementalString.Truncate(); michael@0: michael@0: LockMenuUntilClosed(false); michael@0: michael@0: mIsOpenChanged = false; michael@0: mCurrentMenu = nullptr; // make sure no current menu is set michael@0: mHFlip = mVFlip = false; michael@0: michael@0: nsView* view = GetView(); michael@0: nsViewManager* viewManager = view->GetViewManager(); michael@0: viewManager->SetViewVisibility(view, nsViewVisibility_kHide); michael@0: michael@0: FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent); michael@0: michael@0: // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no michael@0: // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually. michael@0: // This code may not the best solution, but we can leave it here until we find the better approach. michael@0: NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?"); michael@0: EventStates state = mContent->AsElement()->State(); michael@0: michael@0: if (state.HasState(NS_EVENT_STATE_HOVER)) { michael@0: EventStateManager* esm = PresContext()->EventStateManager(); michael@0: esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); michael@0: } michael@0: michael@0: nsMenuFrame* menuFrame = do_QueryFrame(GetParent()); michael@0: if (menuFrame) { michael@0: menuFrame->PopupClosed(aDeselectMenu); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::GetLayoutFlags(uint32_t& aFlags) michael@0: { michael@0: aFlags = NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // GetRootViewForPopup michael@0: // Retrieves the view for the popup widget that contains the given frame. michael@0: // If the given frame is not contained by a popup widget, return the michael@0: // root view of the root viewmanager. michael@0: nsView* michael@0: nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame) michael@0: { michael@0: nsView* view = aStartFrame->GetClosestView(); michael@0: NS_ASSERTION(view, "frame must have a closest view!"); michael@0: while (view) { michael@0: // Walk up the view hierarchy looking for a view whose widget has a michael@0: // window type of eWindowType_popup - in other words a popup window michael@0: // widget. If we find one, this is the view we want. michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (widget && widget->WindowType() == eWindowType_popup) { michael@0: return view; michael@0: } michael@0: michael@0: nsView* temp = view->GetParent(); michael@0: if (!temp) { michael@0: // Otherwise, we've walked all the way up to the root view and not michael@0: // found a view for a popup window widget. Just return the root view. michael@0: return view; michael@0: } michael@0: view = temp; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsPoint michael@0: nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect, michael@0: FlipStyle& aHFlip, FlipStyle& aVFlip) michael@0: { michael@0: // flip the anchor and alignment for right-to-left michael@0: int8_t popupAnchor(mPopupAnchor); michael@0: int8_t popupAlign(mPopupAlignment); michael@0: if (IsDirectionRTL()) { michael@0: // no need to flip the centered anchor types vertically michael@0: if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) { michael@0: popupAnchor = -popupAnchor; michael@0: } michael@0: popupAlign = -popupAlign; michael@0: } michael@0: michael@0: // first, determine at which corner of the anchor the popup should appear michael@0: nsPoint pnt; michael@0: switch (popupAnchor) { michael@0: case POPUPALIGNMENT_LEFTCENTER: michael@0: pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2); michael@0: anchorRect.y = pnt.y; michael@0: anchorRect.height = 0; michael@0: break; michael@0: case POPUPALIGNMENT_RIGHTCENTER: michael@0: pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2); michael@0: anchorRect.y = pnt.y; michael@0: anchorRect.height = 0; michael@0: break; michael@0: case POPUPALIGNMENT_TOPCENTER: michael@0: pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y); michael@0: anchorRect.x = pnt.x; michael@0: anchorRect.width = 0; michael@0: break; michael@0: case POPUPALIGNMENT_BOTTOMCENTER: michael@0: pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost()); michael@0: anchorRect.x = pnt.x; michael@0: anchorRect.width = 0; michael@0: break; michael@0: case POPUPALIGNMENT_TOPRIGHT: michael@0: pnt = anchorRect.TopRight(); michael@0: break; michael@0: case POPUPALIGNMENT_BOTTOMLEFT: michael@0: pnt = anchorRect.BottomLeft(); michael@0: break; michael@0: case POPUPALIGNMENT_BOTTOMRIGHT: michael@0: pnt = anchorRect.BottomRight(); michael@0: break; michael@0: case POPUPALIGNMENT_TOPLEFT: michael@0: default: michael@0: pnt = anchorRect.TopLeft(); michael@0: break; michael@0: } michael@0: michael@0: // If the alignment is on the right edge of the popup, move the popup left michael@0: // by the width. Similarly, if the alignment is on the bottom edge of the michael@0: // popup, move the popup up by the height. In addition, account for the michael@0: // margins of the popup on the edge on which it is aligned. michael@0: nsMargin margin(0, 0, 0, 0); michael@0: StyleMargin()->GetMargin(margin); michael@0: switch (popupAlign) { michael@0: case POPUPALIGNMENT_TOPRIGHT: michael@0: pnt.MoveBy(-mRect.width - margin.right, margin.top); michael@0: break; michael@0: case POPUPALIGNMENT_BOTTOMLEFT: michael@0: pnt.MoveBy(margin.left, -mRect.height - margin.bottom); michael@0: break; michael@0: case POPUPALIGNMENT_BOTTOMRIGHT: michael@0: pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom); michael@0: break; michael@0: case POPUPALIGNMENT_TOPLEFT: michael@0: default: michael@0: pnt.MoveBy(margin.left, margin.top); michael@0: break; michael@0: } michael@0: michael@0: // Flipping horizontally is allowed as long as the popup is above or below michael@0: // the anchor. This will happen if both the anchor and alignment are top or michael@0: // both are bottom, but different values. Similarly, flipping vertically is michael@0: // allowed if the popup is to the left or right of the anchor. In this case, michael@0: // the values of the constants are such that both must be positive or both michael@0: // must be negative. A special case, used for overlap, allows flipping michael@0: // vertically as well. michael@0: // If we are flipping in both directions, we want to set a flip style both michael@0: // horizontally and vertically. However, we want to flip on the inside edge michael@0: // of the anchor. Consider the example of a typical dropdown menu. michael@0: // Vertically, we flip the popup on the outside edges of the anchor menu, michael@0: // however horizontally, we want to to use the inside edges so the popup michael@0: // still appears underneath the anchor menu instead of floating off the michael@0: // side of the menu. michael@0: switch (popupAnchor) { michael@0: case POPUPALIGNMENT_LEFTCENTER: michael@0: case POPUPALIGNMENT_RIGHTCENTER: michael@0: aHFlip = FlipStyle_Outside; michael@0: aVFlip = FlipStyle_Inside; michael@0: break; michael@0: case POPUPALIGNMENT_TOPCENTER: michael@0: case POPUPALIGNMENT_BOTTOMCENTER: michael@0: aHFlip = FlipStyle_Inside; michael@0: aVFlip = FlipStyle_Outside; michael@0: break; michael@0: default: michael@0: { michael@0: FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None; michael@0: aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge; michael@0: if (((popupAnchor > 0) == (popupAlign > 0)) || michael@0: (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT)) michael@0: aVFlip = FlipStyle_Outside; michael@0: else michael@0: aVFlip = anchorEdge; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return pnt; michael@0: } michael@0: michael@0: nscoord michael@0: nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize, michael@0: nscoord aScreenBegin, nscoord aScreenEnd, michael@0: nscoord *aOffset) michael@0: { michael@0: // The popup may be positioned such that either the left/top or bottom/right michael@0: // is outside the screen - but never both. michael@0: nscoord newPos = michael@0: std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint)); michael@0: *aOffset = newPos - aScreenPoint; michael@0: aScreenPoint = newPos; michael@0: return std::min(aSize, aScreenEnd - aScreenPoint); michael@0: } michael@0: michael@0: nscoord michael@0: nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, michael@0: nscoord aScreenBegin, nscoord aScreenEnd, michael@0: nscoord aAnchorBegin, nscoord aAnchorEnd, michael@0: nscoord aMarginBegin, nscoord aMarginEnd, michael@0: nscoord aOffsetForContextMenu, FlipStyle aFlip, michael@0: bool* aFlipSide) michael@0: { michael@0: // all of the coordinates used here are in app units relative to the screen michael@0: nscoord popupSize = aSize; michael@0: if (aScreenPoint < aScreenBegin) { michael@0: // at its current position, the popup would extend past the left or top michael@0: // edge of the screen, so it will have to be moved or resized. michael@0: if (aFlip) { michael@0: // for inside flips, we flip on the opposite side of the anchor michael@0: nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd; michael@0: nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin; michael@0: michael@0: // check whether there is more room to the left and right (or top and michael@0: // bottom) of the anchor and put the popup on the side with more room. michael@0: if (startpos - aScreenBegin >= aScreenEnd - endpos) { michael@0: aScreenPoint = aScreenBegin; michael@0: popupSize = startpos - aScreenPoint - aMarginEnd; michael@0: } michael@0: else { michael@0: // If the newly calculated position is different than the existing michael@0: // position, flip such that the popup is to the right or bottom of the michael@0: // anchor point instead . However, when flipping use the same margin michael@0: // size. michael@0: nscoord newScreenPoint = endpos + aMarginEnd; michael@0: if (newScreenPoint != aScreenPoint) { michael@0: *aFlipSide = true; michael@0: aScreenPoint = newScreenPoint; michael@0: // check if the new position is still off the right or bottom edge of michael@0: // the screen. If so, resize the popup. michael@0: if (aScreenPoint + aSize > aScreenEnd) { michael@0: popupSize = aScreenEnd - aScreenPoint; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: aScreenPoint = aScreenBegin; michael@0: } michael@0: } michael@0: else if (aScreenPoint + aSize > aScreenEnd) { michael@0: // at its current position, the popup would extend past the right or michael@0: // bottom edge of the screen, so it will have to be moved or resized. michael@0: if (aFlip) { michael@0: // for inside flips, we flip on the opposite side of the anchor michael@0: nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd; michael@0: nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin; michael@0: michael@0: // check whether there is more room to the left and right (or top and michael@0: // bottom) of the anchor and put the popup on the side with more room. michael@0: if (aScreenEnd - endpos >= startpos - aScreenBegin) { michael@0: if (mIsContextMenu) { michael@0: aScreenPoint = aScreenEnd - aSize; michael@0: } michael@0: else { michael@0: aScreenPoint = endpos + aMarginBegin; michael@0: popupSize = aScreenEnd - aScreenPoint; michael@0: } michael@0: } michael@0: else { michael@0: // if the newly calculated position is different than the existing michael@0: // position, we flip such that the popup is to the left or top of the michael@0: // anchor point instead. michael@0: nscoord newScreenPoint = startpos - aSize - aMarginBegin - aOffsetForContextMenu; michael@0: if (newScreenPoint != aScreenPoint) { michael@0: *aFlipSide = true; michael@0: aScreenPoint = newScreenPoint; michael@0: michael@0: // check if the new position is still off the left or top edge of the michael@0: // screen. If so, resize the popup. michael@0: if (aScreenPoint < aScreenBegin) { michael@0: aScreenPoint = aScreenBegin; michael@0: if (!mIsContextMenu) { michael@0: popupSize = startpos - aScreenPoint - aMarginBegin; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: aScreenPoint = aScreenEnd - aSize; michael@0: } michael@0: } michael@0: michael@0: // Make sure that the point is within the screen boundaries and that the michael@0: // size isn't off the edge of the screen. This can happen when a large michael@0: // positive or negative margin is used. michael@0: if (aScreenPoint < aScreenBegin) { michael@0: aScreenPoint = aScreenBegin; michael@0: } michael@0: if (aScreenPoint > aScreenEnd) { michael@0: aScreenPoint = aScreenEnd - aSize; michael@0: } michael@0: michael@0: // If popupSize ended up being negative, or the original size was actually michael@0: // smaller than the calculated popup size, just use the original size instead. michael@0: if (popupSize <= 0 || aSize < popupSize) { michael@0: popupSize = aSize; michael@0: } michael@0: return std::min(popupSize, aScreenEnd - aScreenPoint); michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup) michael@0: { michael@0: if (!mShouldAutoPosition) michael@0: return NS_OK; michael@0: michael@0: // If this is due to a move, return early if the popup hasn't been laid out michael@0: // yet. On Windows, this can happen when using a drag popup before it opens. michael@0: if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame(); michael@0: NS_ASSERTION(rootFrame->GetView() && GetView() && michael@0: rootFrame->GetView() == GetView()->GetParent(), michael@0: "rootFrame's view is not our view's parent???"); michael@0: michael@0: // if the frame is not specified, use the anchor node passed to OpenPopup. If michael@0: // that wasn't specified either, use the root frame. Note that mAnchorContent michael@0: // might be a different document so its presshell must be used. michael@0: if (!aAnchorFrame) { michael@0: if (mAnchorContent) { michael@0: aAnchorFrame = mAnchorContent->GetPrimaryFrame(); michael@0: } michael@0: michael@0: if (!aAnchorFrame) { michael@0: aAnchorFrame = rootFrame; michael@0: if (!aAnchorFrame) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // the dimensions of the anchor in its app units michael@0: nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits(); michael@0: michael@0: // the anchor may be in a different document with a different scale, michael@0: // so adjust the size so that it is in the app units of the popup instead michael@0: // of the anchor. michael@0: parentRect = parentRect.ConvertAppUnitsRoundOut( michael@0: aAnchorFrame->PresContext()->AppUnitsPerDevPixel(), michael@0: presContext->AppUnitsPerDevPixel()); michael@0: michael@0: // Set the popup's size to the preferred size. Below, this size will be michael@0: // adjusted to fit on the screen or within the content area. If the anchor michael@0: // is sized to the popup, use the anchor's width instead of the preferred michael@0: // width. The preferred size should already be set by the parent frame. michael@0: NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0, michael@0: "preferred size of popup not set"); michael@0: mRect.width = aSizedToPopup ? parentRect.width : mPrefSize.width; michael@0: mRect.height = mPrefSize.height; michael@0: michael@0: // the screen position in app units where the popup should appear michael@0: nsPoint screenPoint; michael@0: michael@0: // For anchored popups, the anchor rectangle. For non-anchored popups, the michael@0: // size will be 0. michael@0: nsRect anchorRect = parentRect; michael@0: michael@0: // indicators of whether the popup should be flipped or resized. michael@0: FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None; michael@0: michael@0: nsMargin margin(0, 0, 0, 0); michael@0: StyleMargin()->GetMargin(margin); michael@0: michael@0: // the screen rectangle of the root frame, in dev pixels. michael@0: nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits(); michael@0: michael@0: nsDeviceContext* devContext = presContext->DeviceContext(); michael@0: nscoord offsetForContextMenu = 0; michael@0: michael@0: bool isNoAutoHide = IsNoAutoHide(); michael@0: nsPopupLevel popupLevel = PopupLevel(isNoAutoHide); michael@0: michael@0: if (IsAnchored()) { michael@0: // if we are anchored, there are certain things we don't want to do when michael@0: // repositioning the popup to fit on the screen, such as end up positioned michael@0: // over the anchor, for instance a popup appearing over the menu label. michael@0: // When doing this reposition, we want to move the popup to the side with michael@0: // the most room. The combination of anchor and alignment dictate if we michael@0: // readjust above/below or to the left/right. michael@0: if (mAnchorContent) { michael@0: // move the popup according to the anchor and alignment. This will also michael@0: // tell us which axis the popup is flush against in case we have to move michael@0: // it around later. The AdjustPositionForAnchorAlign method accounts for michael@0: // the popup's margin. michael@0: screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip); michael@0: } michael@0: else { michael@0: // with no anchor, the popup is positioned relative to the root frame michael@0: anchorRect = rootScreenRect; michael@0: screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top); michael@0: } michael@0: michael@0: // mXPos and mYPos specify an additonal offset passed to OpenPopup that michael@0: // should be added to the position. We also add the offset to the anchor michael@0: // pos so a later flip/resize takes the offset into account. michael@0: nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos); michael@0: if (IsDirectionRTL()) { michael@0: screenPoint.x -= anchorXOffset; michael@0: anchorRect.x -= anchorXOffset; michael@0: } else { michael@0: screenPoint.x += anchorXOffset; michael@0: anchorRect.x += anchorXOffset; michael@0: } michael@0: nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos); michael@0: screenPoint.y += anchorYOffset; michael@0: anchorRect.y += anchorYOffset; michael@0: michael@0: // If this is a noautohide popup, set the screen coordinates of the popup. michael@0: // This way, the popup stays at the location where it was opened even when michael@0: // the window is moved. Popups at the parent level follow the parent michael@0: // window as it is moved and remained anchored, so we want to maintain the michael@0: // anchoring instead. michael@0: if (isNoAutoHide && popupLevel != ePopupLevelParent) { michael@0: // Account for the margin that will end up being added to the screen coordinate michael@0: // the next time SetPopupPosition is called. michael@0: mScreenXPos = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left); michael@0: mScreenYPos = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top); michael@0: } michael@0: } michael@0: else { michael@0: // the popup is positioned at a screen coordinate. michael@0: // first convert the screen position in mScreenXPos and mScreenYPos from michael@0: // CSS pixels into device pixels, ignoring any scaling as mScreenXPos and michael@0: // mScreenYPos are unscaled screen coordinates. michael@0: int32_t factor = devContext->UnscaledAppUnitsPerDevPixel(); michael@0: michael@0: // context menus should be offset by two pixels so that they don't appear michael@0: // directly where the cursor is. Otherwise, it is too easy to have the michael@0: // context menu close up again. michael@0: if (mAdjustOffsetForContextMenu) { michael@0: int32_t offsetForContextMenuDev = michael@0: nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS) / factor; michael@0: offsetForContextMenu = presContext->DevPixelsToAppUnits(offsetForContextMenuDev); michael@0: } michael@0: michael@0: // next, convert into app units accounting for the scaling michael@0: screenPoint.x = presContext->DevPixelsToAppUnits( michael@0: nsPresContext::CSSPixelsToAppUnits(mScreenXPos) / factor); michael@0: screenPoint.y = presContext->DevPixelsToAppUnits( michael@0: nsPresContext::CSSPixelsToAppUnits(mScreenYPos) / factor); michael@0: anchorRect = nsRect(screenPoint, nsSize(0, 0)); michael@0: michael@0: // add the margins on the popup michael@0: screenPoint.MoveBy(margin.left + offsetForContextMenu, michael@0: margin.top + offsetForContextMenu); michael@0: michael@0: // screen positioned popups can be flipped vertically but never horizontally michael@0: vFlip = FlipStyle_Outside; michael@0: } michael@0: michael@0: // If a panel is being moved or has flip="none", don't constrain or flip it. But always do this for michael@0: // content shells, so that the popup doesn't extend outside the containing frame. michael@0: if (mInContentShell || (mFlip != FlipType_None && (!aIsMove || mPopupType != ePopupTypePanel))) { michael@0: nsRect screenRect = GetConstraintRect(anchorRect, rootScreenRect, popupLevel); michael@0: michael@0: // Ensure that anchorRect is on screen. michael@0: anchorRect = anchorRect.Intersect(screenRect); michael@0: michael@0: // shrink the the popup down if it is larger than the screen size michael@0: if (mRect.width > screenRect.width) michael@0: mRect.width = screenRect.width; michael@0: if (mRect.height > screenRect.height) michael@0: mRect.height = screenRect.height; michael@0: michael@0: // at this point the anchor (anchorRect) is within the available screen michael@0: // area (screenRect) and the popup is known to be no larger than the screen. michael@0: michael@0: // We might want to "slide" an arrow if the panel is of the correct type - michael@0: // but we can only slide on one axis - the other axis must be "flipped or michael@0: // resized" as normal. michael@0: bool slideHorizontal = false, slideVertical = false; michael@0: if (mFlip == FlipType_Slide) { michael@0: int8_t position = GetAlignmentPosition(); michael@0: slideHorizontal = position >= POPUPPOSITION_BEFORESTART && michael@0: position <= POPUPPOSITION_AFTEREND; michael@0: slideVertical = position >= POPUPPOSITION_STARTBEFORE && michael@0: position <= POPUPPOSITION_ENDAFTER; michael@0: } michael@0: michael@0: // Next, check if there is enough space to show the popup at full size when michael@0: // positioned at screenPoint. If not, flip the popups to the opposite side michael@0: // of their anchor point, or resize them as necessary. michael@0: if (slideHorizontal) { michael@0: mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x, michael@0: screenRect.XMost(), &mAlignmentOffset); michael@0: } else { michael@0: mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x, michael@0: screenRect.XMost(), anchorRect.x, anchorRect.XMost(), michael@0: margin.left, margin.right, offsetForContextMenu, hFlip, michael@0: &mHFlip); michael@0: } michael@0: if (slideVertical) { michael@0: mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y, michael@0: screenRect.YMost(), &mAlignmentOffset); michael@0: } else { michael@0: mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y, michael@0: screenRect.YMost(), anchorRect.y, anchorRect.YMost(), michael@0: margin.top, margin.bottom, offsetForContextMenu, vFlip, michael@0: &mVFlip); michael@0: } michael@0: michael@0: NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y && michael@0: screenPoint.x + mRect.width <= screenRect.XMost() && michael@0: screenPoint.y + mRect.height <= screenRect.YMost(), michael@0: "Popup is offscreen"); michael@0: } michael@0: michael@0: // snap the popup's position in screen coordinates to device pixels, michael@0: // see bug 622507, bug 961431 michael@0: screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x); michael@0: screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y); michael@0: michael@0: // determine the x and y position of the view by subtracting the desired michael@0: // screen position from the screen position of the root frame. michael@0: nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft(); michael@0: michael@0: nsView* view = GetView(); michael@0: NS_ASSERTION(view, "popup with no view"); michael@0: michael@0: // Offset the position by the width and height of the borders and titlebar. michael@0: // Even though GetClientOffset should return (0, 0) when there is no michael@0: // titlebar or borders, we skip these calculations anyway for non-panels michael@0: // to save time since they will never have a titlebar. michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (mPopupType == ePopupTypePanel && widget) { michael@0: mLastClientOffset = widget->GetClientOffset(); michael@0: viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x); michael@0: viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y); michael@0: } michael@0: michael@0: presContext->GetPresShell()->GetViewManager()-> michael@0: MoveViewTo(view, viewPoint.x, viewPoint.y); michael@0: michael@0: // Now that we've positioned the view, sync up the frame's origin. michael@0: nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame)); michael@0: michael@0: if (aSizedToPopup) { michael@0: nsBoxLayoutState state(PresContext()); michael@0: // XXXndeakin can parentSize.width still extend outside? michael@0: SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ nsMenuFrame* michael@0: nsMenuPopupFrame::GetCurrentMenuItem() michael@0: { michael@0: return mCurrentMenu; michael@0: } michael@0: michael@0: nsRect michael@0: nsMenuPopupFrame::GetConstraintRect(const nsRect& aAnchorRect, michael@0: const nsRect& aRootScreenRect, michael@0: nsPopupLevel aPopupLevel) michael@0: { michael@0: nsIntRect screenRectPixels; michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // determine the available screen space. It will be reduced by the OS chrome michael@0: // such as menubars. It addition, for content shells, it will be the area of michael@0: // the content rather than the screen. michael@0: nsCOMPtr screen; michael@0: nsCOMPtr sm(do_GetService("@mozilla.org/gfx/screenmanager;1")); michael@0: if (sm) { michael@0: // for content shells, get the screen where the root frame is located. michael@0: // This is because we need to constrain the content to this content area, michael@0: // so we should use the same screen. Otherwise, use the screen where the michael@0: // anchor is located. michael@0: nsRect rect = mInContentShell ? aRootScreenRect : aAnchorRect; michael@0: // nsIScreenManager::ScreenForRect wants the coordinates in CSS pixels michael@0: int32_t width = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.width)); michael@0: int32_t height = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.height)); michael@0: sm->ScreenForRect(nsPresContext::AppUnitsToIntCSSPixels(rect.x), michael@0: nsPresContext::AppUnitsToIntCSSPixels(rect.y), michael@0: width, height, getter_AddRefs(screen)); michael@0: if (screen) { michael@0: // Non-top-level popups (which will always be panels) michael@0: // should never overlap the OS bar: michael@0: bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop; michael@0: // get the total screen area if the popup is allowed to overlap it. michael@0: if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell) michael@0: screen->GetRect(&screenRectPixels.x, &screenRectPixels.y, michael@0: &screenRectPixels.width, &screenRectPixels.height); michael@0: else michael@0: screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y, michael@0: &screenRectPixels.width, &screenRectPixels.height); michael@0: } michael@0: } michael@0: michael@0: nsRect screenRect = screenRectPixels.ToAppUnits(presContext->AppUnitsPerDevPixel()); michael@0: if (mInContentShell) { michael@0: // for content shells, clip to the client area rather than the screen area michael@0: screenRect.IntersectRect(screenRect, aRootScreenRect); michael@0: } michael@0: michael@0: return screenRect; michael@0: } michael@0: michael@0: void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide, int8_t aVerticalSide, nsIntPoint& aChange) michael@0: { michael@0: int8_t popupAlign(mPopupAlignment); michael@0: if (IsDirectionRTL()) { michael@0: popupAlign = -popupAlign; michael@0: } michael@0: michael@0: if (aHorizontalSide == (mHFlip ? NS_SIDE_RIGHT : NS_SIDE_LEFT)) { michael@0: if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) { michael@0: aChange.x = 0; michael@0: } michael@0: } michael@0: else if (aHorizontalSide == (mHFlip ? NS_SIDE_LEFT : NS_SIDE_RIGHT)) { michael@0: if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { michael@0: aChange.x = 0; michael@0: } michael@0: } michael@0: michael@0: if (aVerticalSide == (mVFlip ? NS_SIDE_BOTTOM : NS_SIDE_TOP)) { michael@0: if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) { michael@0: aChange.y = 0; michael@0: } michael@0: } michael@0: else if (aVerticalSide == (mVFlip ? NS_SIDE_TOP : NS_SIDE_BOTTOM)) { michael@0: if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { michael@0: aChange.y = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool nsMenuPopupFrame::ConsumeOutsideClicks() michael@0: { michael@0: // If the popup has explicitly set a consume mode, honor that. michael@0: if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT) michael@0: return (mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME); michael@0: michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return true; michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks, michael@0: nsGkAtoms::_false, eCaseMatters)) michael@0: return false; michael@0: michael@0: nsCOMPtr parentContent = mContent->GetParent(); michael@0: if (parentContent) { michael@0: nsINodeInfo *ni = parentContent->NodeInfo(); michael@0: if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) michael@0: return true; // Consume outside clicks for combo boxes on all platforms michael@0: #if defined(XP_WIN) michael@0: // Don't consume outside clicks for menus in Windows michael@0: if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) || michael@0: ni->Equals(nsGkAtoms::splitmenu, kNameSpaceID_XUL) || michael@0: ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) || michael@0: ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) || michael@0: ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) && michael@0: (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::menu, eCaseMatters) || michael@0: parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::menuButton, eCaseMatters)))) { michael@0: return false; michael@0: } michael@0: #endif michael@0: if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) { michael@0: // Don't consume outside clicks for autocomplete widget michael@0: if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::autocomplete, eCaseMatters)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // XXXroc this is megalame. Fossicking around for a frame of the right michael@0: // type is a recipe for disaster in the long term. michael@0: nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart) michael@0: { michael@0: if (!aStart) michael@0: return nullptr; michael@0: michael@0: // try start frame and siblings michael@0: nsIFrame* currFrame = aStart; michael@0: do { michael@0: nsIScrollableFrame* sf = do_QueryFrame(currFrame); michael@0: if (sf) michael@0: return sf; michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } while (currFrame); michael@0: michael@0: // try children michael@0: currFrame = aStart; michael@0: do { michael@0: nsIFrame* childFrame = currFrame->GetFirstPrincipalChild(); michael@0: nsIScrollableFrame* sf = GetScrollFrame(childFrame); michael@0: if (sf) michael@0: return sf; michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } while (currFrame); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) michael@0: { michael@0: if (aMenuItem) { michael@0: aMenuItem->PresContext()->PresShell()->ScrollFrameRectIntoView( michael@0: aMenuItem, michael@0: nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()), michael@0: nsIPresShell::ScrollAxis(), michael@0: nsIPresShell::ScrollAxis(), michael@0: nsIPresShell::SCROLL_OVERFLOW_HIDDEN | michael@0: nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) michael@0: { michael@0: if (mCurrentMenu == aMenuItem) michael@0: return NS_OK; michael@0: michael@0: if (mCurrentMenu) { michael@0: mCurrentMenu->SelectMenu(false); michael@0: } michael@0: michael@0: if (aMenuItem) { michael@0: EnsureMenuItemIsVisible(aMenuItem); michael@0: aMenuItem->SelectMenu(true); michael@0: } michael@0: michael@0: mCurrentMenu = aMenuItem; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::CurrentMenuIsBeingDestroyed() michael@0: { michael@0: mCurrentMenu = nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, michael@0: bool aSelectFirstItem) michael@0: { michael@0: if (mCurrentMenu == aMenuItem) michael@0: return NS_OK; michael@0: michael@0: // When a context menu is open, the current menu is locked, and no change michael@0: // to the menu is allowed. michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (!mIsContextMenu && pm && pm->HasContextMenu(this)) michael@0: return NS_OK; michael@0: michael@0: // Unset the current child. michael@0: if (mCurrentMenu) { michael@0: mCurrentMenu->SelectMenu(false); michael@0: nsMenuPopupFrame* popup = mCurrentMenu->GetPopup(); michael@0: if (popup) { michael@0: if (mCurrentMenu->IsOpen()) { michael@0: if (pm) michael@0: pm->HidePopupAfterDelay(popup); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Set the new child. michael@0: if (aMenuItem) { michael@0: EnsureMenuItemIsVisible(aMenuItem); michael@0: aMenuItem->SelectMenu(true); michael@0: } michael@0: michael@0: mCurrentMenu = aMenuItem; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsMenuPopupFrame::Enter(WidgetGUIEvent* aEvent) michael@0: { michael@0: mIncrementalString.Truncate(); michael@0: michael@0: // Give it to the child. michael@0: if (mCurrentMenu) michael@0: return mCurrentMenu->Enter(aEvent); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction) michael@0: { michael@0: uint32_t charCode, keyCode; michael@0: aKeyEvent->GetCharCode(&charCode); michael@0: aKeyEvent->GetKeyCode(&keyCode); michael@0: michael@0: doAction = false; michael@0: michael@0: // Enumerate over our list of frames. michael@0: nsIFrame* immediateParent = PresContext()->PresShell()-> michael@0: FrameConstructor()->GetInsertionPoint(GetContent(), nullptr); michael@0: if (!immediateParent) michael@0: immediateParent = this; michael@0: michael@0: uint32_t matchCount = 0, matchShortcutCount = 0; michael@0: bool foundActive = false; michael@0: bool isShortcut; michael@0: nsMenuFrame* frameBefore = nullptr; michael@0: nsMenuFrame* frameAfter = nullptr; michael@0: nsMenuFrame* frameShortcut = nullptr; michael@0: michael@0: nsIContent* parentContent = mContent->GetParent(); michael@0: michael@0: bool isMenu = parentContent && michael@0: !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL); michael@0: michael@0: static DOMTimeStamp lastKeyTime = 0; michael@0: DOMTimeStamp keyTime; michael@0: aKeyEvent->GetTimeStamp(&keyTime); michael@0: michael@0: if (charCode == 0) { michael@0: if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { michael@0: if (!isMenu && !mIncrementalString.IsEmpty()) { michael@0: mIncrementalString.SetLength(mIncrementalString.Length() - 1); michael@0: return nullptr; michael@0: } michael@0: else { michael@0: #ifdef XP_WIN michael@0: nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); michael@0: if (soundInterface) michael@0: soundInterface->Beep(); michael@0: #endif // #ifdef XP_WIN michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: else { michael@0: char16_t uniChar = ToLowerCase(static_cast(charCode)); michael@0: if (isMenu || // Menu supports only first-letter navigation michael@0: keyTime - lastKeyTime > INC_TYP_INTERVAL) // Interval too long, treat as new typing michael@0: mIncrementalString = uniChar; michael@0: else { michael@0: mIncrementalString.Append(uniChar); michael@0: } michael@0: } michael@0: michael@0: // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one michael@0: nsAutoString incrementalString(mIncrementalString); michael@0: uint32_t charIndex = 1, stringLength = incrementalString.Length(); michael@0: while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) { michael@0: charIndex++; michael@0: } michael@0: if (charIndex == stringLength) { michael@0: incrementalString.Truncate(1); michael@0: stringLength = 1; michael@0: } michael@0: michael@0: lastKeyTime = keyTime; michael@0: michael@0: nsIFrame* currFrame; michael@0: // NOTE: If you crashed here due to a bogus |immediateParent| it is michael@0: // possible that the menu whose shortcut is being looked up has michael@0: // been destroyed already. One strategy would be to michael@0: // setTimeout(,0) as detailed in: michael@0: // michael@0: currFrame = immediateParent->GetFirstPrincipalChild(); michael@0: michael@0: int32_t menuAccessKey = -1; michael@0: nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); michael@0: michael@0: // We start searching from first child. This process is divided into two parts michael@0: // -- before current and after current -- by the current item michael@0: while (currFrame) { michael@0: nsIContent* current = currFrame->GetContent(); michael@0: michael@0: // See if it's a menu item. michael@0: if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, true)) { michael@0: nsAutoString textKey; michael@0: if (menuAccessKey >= 0) { michael@0: // Get the shortcut attribute. michael@0: current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey); michael@0: } michael@0: if (textKey.IsEmpty()) { // No shortcut, try first letter michael@0: isShortcut = false; michael@0: current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey); michael@0: if (textKey.IsEmpty()) // No label, try another attribute (value) michael@0: current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey); michael@0: } michael@0: else michael@0: isShortcut = true; michael@0: michael@0: if (StringBeginsWith(textKey, incrementalString, michael@0: nsCaseInsensitiveStringComparator())) { michael@0: // mIncrementalString is a prefix of textKey michael@0: nsMenuFrame* menu = do_QueryFrame(currFrame); michael@0: if (menu) { michael@0: // There is one match michael@0: matchCount++; michael@0: if (isShortcut) { michael@0: // There is one shortcut-key match michael@0: matchShortcutCount++; michael@0: // Record the matched item. If there is only one matched shortcut item, do it michael@0: frameShortcut = menu; michael@0: } michael@0: if (!foundActive) { michael@0: // It's a first candidate item located before/on the current item michael@0: if (!frameBefore) michael@0: frameBefore = menu; michael@0: } michael@0: else { michael@0: // It's a first candidate item located after the current item michael@0: if (!frameAfter) michael@0: frameAfter = menu; michael@0: } michael@0: } michael@0: else michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the active status michael@0: if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: foundActive = true; michael@0: if (stringLength > 1) { michael@0: // If there is more than one char typed, the current item has highest priority, michael@0: // otherwise the item next to current has highest priority michael@0: if (currFrame == frameBefore) michael@0: return frameBefore; michael@0: } michael@0: } michael@0: } michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } michael@0: michael@0: doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1)); michael@0: michael@0: if (matchShortcutCount == 1) // We have one matched shortcut item michael@0: return frameShortcut; michael@0: if (frameAfter) // If we have matched item after the current, use it michael@0: return frameAfter; michael@0: else if (frameBefore) // If we haven't, use the item before the current michael@0: return frameBefore; michael@0: michael@0: // If we don't match anything, rollback the last typing michael@0: mIncrementalString.SetLength(mIncrementalString.Length() - 1); michael@0: michael@0: // didn't find a matching menu item michael@0: #ifdef XP_WIN michael@0: // behavior on Windows - this item is in a menu popup off of the michael@0: // menu bar, so beep and do nothing else michael@0: if (isMenu) { michael@0: nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); michael@0: if (soundInterface) michael@0: soundInterface->Beep(); michael@0: } michael@0: #endif // #ifdef XP_WIN michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::LockMenuUntilClosed(bool aLock) michael@0: { michael@0: mIsMenuLocked = aLock; michael@0: michael@0: // Lock / unlock the parent, too. michael@0: nsMenuFrame* menu = do_QueryFrame(GetParent()); michael@0: if (menu) { michael@0: nsMenuParent* parentParent = menu->GetMenuParent(); michael@0: if (parentParent) { michael@0: parentParent->LockMenuUntilClosed(aLock); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsMenuPopupFrame::GetWidget() michael@0: { michael@0: nsView * view = GetRootViewForPopup(this); michael@0: if (!view) michael@0: return nullptr; michael@0: michael@0: return view->GetWidget(); michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::AttachedDismissalListener() michael@0: { michael@0: mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT; michael@0: } michael@0: michael@0: // helpers ///////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: michael@0: { michael@0: nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, michael@0: aModType); michael@0: michael@0: if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top) michael@0: MoveToAttributePosition(); michael@0: michael@0: if (aAttribute == nsGkAtoms::label) { michael@0: // set the label for the titlebar michael@0: nsView* view = GetView(); michael@0: if (view) { michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (widget) { michael@0: nsAutoString title; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title); michael@0: if (!title.IsEmpty()) { michael@0: widget->SetTitle(title); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::MoveToAttributePosition() michael@0: { michael@0: // Move the widget around when the user sets the |left| and |top| attributes. michael@0: // Note that this is not the best way to move the widget, as it results in lots michael@0: // of FE notifications and is likely to be slow as molasses. Use |moveTo| on michael@0: // nsIPopupBoxObject if possible. michael@0: nsAutoString left, top; michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); michael@0: nsresult err1, err2; michael@0: int32_t xpos = left.ToInteger(&err1); michael@0: int32_t ypos = top.ToInteger(&err2); michael@0: michael@0: if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) michael@0: MoveTo(xpos, ypos, false); michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: nsMenuFrame* menu = do_QueryFrame(GetParent()); michael@0: if (menu) { michael@0: // clear the open attribute on the parent menu michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open)); michael@0: } michael@0: michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) michael@0: pm->PopupDestroyed(this); michael@0: michael@0: nsIRootBox* rootBox = michael@0: nsIRootBox::GetRootBox(PresContext()->GetPresShell()); michael@0: if (rootBox && rootBox->GetDefaultTooltip() == mContent) { michael@0: rootBox->SetDefaultTooltip(nullptr); michael@0: } michael@0: michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsMenuPopupFrame::MoveTo(int32_t aLeft, int32_t aTop, bool aUpdateAttrs) michael@0: { michael@0: nsIWidget* widget = GetWidget(); michael@0: if ((mScreenXPos == aLeft && mScreenYPos == aTop) && michael@0: (!widget || widget->GetClientOffset() == mLastClientOffset)) { michael@0: return; michael@0: } michael@0: michael@0: // reposition the popup at the specified coordinates. Don't clear the anchor michael@0: // and position, because the popup can be reset to its anchor position by michael@0: // using (-1, -1) as coordinates. Subtract off the margin as it will be michael@0: // added to the position when SetPopupPosition is called. michael@0: nsMargin margin(0, 0, 0, 0); michael@0: StyleMargin()->GetMargin(margin); michael@0: michael@0: // Workaround for bug 788189. See also bug 708278 comment #25 and following. michael@0: if (mAdjustOffsetForContextMenu) { michael@0: nscoord offsetForContextMenu = michael@0: nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS); michael@0: margin.left += offsetForContextMenu; michael@0: margin.top += offsetForContextMenu; michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left); michael@0: mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top); michael@0: michael@0: SetPopupPosition(nullptr, true, false); michael@0: michael@0: nsCOMPtr popup = mContent; michael@0: if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) || michael@0: popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top))) michael@0: { michael@0: nsAutoString left, top; michael@0: left.AppendInt(aLeft); michael@0: top.AppendInt(aTop); michael@0: popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false); michael@0: popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent, michael@0: const nsAString& aPosition, michael@0: int32_t aXPos, int32_t aYPos, michael@0: bool aAttributesOverride) michael@0: { michael@0: NS_ASSERTION(mPopupState == ePopupOpenAndVisible, "popup must be open to move it"); michael@0: michael@0: InitializePopup(aAnchorContent, mTriggerContent, aPosition, michael@0: aXPos, aYPos, aAttributesOverride); michael@0: // InitializePopup changed the state so reset it. michael@0: mPopupState = ePopupOpenAndVisible; michael@0: michael@0: // Pass false here so that flipping and adjusting to fit on the screen happen. michael@0: SetPopupPosition(nullptr, false, false); michael@0: } michael@0: michael@0: bool michael@0: nsMenuPopupFrame::GetAutoPosition() michael@0: { michael@0: return mShouldAutoPosition; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition) michael@0: { michael@0: mShouldAutoPosition = aShouldAutoPosition; michael@0: } michael@0: michael@0: void michael@0: nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode) michael@0: { michael@0: mConsumeRollupEvent = aConsumeMode; michael@0: } michael@0: michael@0: int8_t michael@0: nsMenuPopupFrame::GetAlignmentPosition() const michael@0: { michael@0: // The code below handles most cases of alignment, anchor and position values. Those that are michael@0: // not handled just return POPUPPOSITION_UNKNOWN. michael@0: michael@0: if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER) michael@0: return mPosition; michael@0: michael@0: int8_t position = mPosition; michael@0: michael@0: if (position == POPUPPOSITION_UNKNOWN) { michael@0: switch (mPopupAnchor) { michael@0: case POPUPALIGNMENT_BOTTOMCENTER: michael@0: position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ? michael@0: POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART; michael@0: break; michael@0: case POPUPALIGNMENT_TOPCENTER: michael@0: position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ? michael@0: POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART; michael@0: break; michael@0: case POPUPALIGNMENT_LEFTCENTER: michael@0: position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ? michael@0: POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE; michael@0: break; michael@0: case POPUPALIGNMENT_RIGHTCENTER: michael@0: position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ? michael@0: POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (mHFlip) { michael@0: position = POPUPPOSITION_HFLIP(position); michael@0: } michael@0: michael@0: if (mVFlip) { michael@0: position = POPUPPOSITION_VFLIP(position); michael@0: } michael@0: michael@0: return position; michael@0: } michael@0: michael@0: /** michael@0: * KEEP THIS IN SYNC WITH nsContainerFrame::CreateViewForFrame michael@0: * as much as possible. Until we get rid of views finally... michael@0: */ michael@0: void michael@0: nsMenuPopupFrame::CreatePopupView() michael@0: { michael@0: if (HasView()) { michael@0: return; michael@0: } michael@0: michael@0: nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager(); michael@0: NS_ASSERTION(nullptr != viewManager, "null view manager"); michael@0: michael@0: // Create a view michael@0: nsView* parentView = viewManager->GetRootView(); michael@0: nsViewVisibility visibility = nsViewVisibility_kHide; michael@0: int32_t zIndex = INT32_MAX; michael@0: bool autoZIndex = false; michael@0: michael@0: NS_ASSERTION(parentView, "no parent view"); michael@0: michael@0: // Create a view michael@0: nsView *view = viewManager->CreateView(GetRect(), parentView, visibility); michael@0: viewManager->SetViewZIndex(view, autoZIndex, zIndex); michael@0: // XXX put view last in document order until we can do better michael@0: viewManager->InsertChild(parentView, view, nullptr, true); michael@0: michael@0: // Remember our view michael@0: SetView(view); michael@0: michael@0: NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, michael@0: ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view)); michael@0: }