From d6cdd07569941ce6bd9c51e9c95372d49e10b676 Mon Sep 17 00:00:00 2001 From: "enndeakin%sympatico.ca" Date: Fri, 29 Jun 2007 22:16:06 +0000 Subject: [PATCH] Bug 279703, backing out the popup changes due to performance regressions. sigh. --- accessible/src/base/nsRootAccessible.cpp | 24 +- browser/base/content/browser.xul | 2 +- content/base/src/nsDocument.cpp | 1 - .../xul/content/public/Makefile.in | 0 .../xul/content/public/nsIXULPopupListener.h | 0 content/xul/content/src/nsXULElement.cpp | 54 +- .../xul/content/src/nsXULPopupListener.cpp | 362 ++-- content/xul/content/src/nsXULPopupListener.h | 123 -- content/xul/document/src/nsXULDocument.cpp | 74 +- dom/public/idl/xul/nsIDOMXULDocument.idl | 5 + dom/src/base/nsGlobalWindow.cpp | 17 +- layout/base/nsCSSFrameConstructor.cpp | 90 +- layout/base/nsCSSFrameConstructor.h | 35 - layout/base/nsDocumentViewer.cpp | 8 +- layout/base/nsIPresShell.h | 8 +- layout/base/nsPresShell.cpp | 44 +- layout/build/nsLayoutModule.cpp | 7 + layout/build/nsLayoutStatics.cpp | 8 - layout/generic/nsContainerFrame.cpp | 7 +- layout/xul/base/public/Makefile.in | 2 +- layout/xul/base/public/nsIMenuFrame.h | 52 +- layout/xul/base/public/nsIPopupBoxObject.idl | 75 +- layout/xul/base/public/nsIPopupSetFrame.h | 0 layout/xul/base/public/nsXULPopupManager.h | 636 ------- layout/xul/base/src/Makefile.in | 3 +- layout/xul/base/src/nsMenuBarFrame.h | 88 +- layout/xul/base/src/nsMenuBarListener.cpp | 51 +- layout/xul/base/src/nsMenuBarListener.h | 4 - layout/xul/base/src/nsMenuBoxObject.cpp | 70 +- .../xul/base/src/nsMenuDismissalListener.cpp | 0 layout/xul/base/src/nsMenuDismissalListener.h | 0 layout/xul/base/src/nsMenuFrame.cpp | 1427 +++++++++++--- layout/xul/base/src/nsMenuFrame.h | 134 +- layout/xul/base/src/nsMenuListener.cpp | 0 layout/xul/base/src/nsMenuListener.h | 0 layout/xul/base/src/nsMenuPopupFrame.cpp | 1663 +++++++++++------ layout/xul/base/src/nsMenuPopupFrame.h | 246 +-- layout/xul/base/src/nsPopupBoxObject.cpp | 127 +- layout/xul/base/src/nsPopupSetFrame.cpp | 627 ++++++- layout/xul/base/src/nsPopupSetFrame.h | 62 +- layout/xul/base/src/nsRootBoxFrame.cpp | 10 +- themes/classic/global/mac/popup.css | 3 +- themes/classic/global/win/popup.css | 3 +- .../public/nsIAutoCompleteController.idl | 8 +- .../public/nsIAutoCompletePopup.idl | 4 +- .../src/nsAutoCompleteController.cpp | 21 + .../satchel/src/nsFormFillController.cpp | 4 +- toolkit/content/widgets/autocomplete.xml | 17 +- toolkit/content/widgets/popup.xml | 52 +- toolkit/content/xul.css | 18 +- toolkit/themes/gnomestripe/global/popup.css | 4 +- .../themes/pinstripe/global/autocomplete.css | 2 +- toolkit/themes/pinstripe/global/popup.css | 3 +- .../themes/winstripe/global/autocomplete.css | 2 +- toolkit/themes/winstripe/global/popup.css | 3 +- widget/src/gtk2/nsNativeThemeGTK.cpp | 9 +- widget/src/windows/nsNativeThemeWin.cpp | 21 +- widget/src/xpwidgets/nsWidgetAtomList.h | 1 - xpfe/appshell/src/nsWebShellWindow.cpp | 2 + .../resources/content/autocomplete.xml | 2 +- .../resources/content/bindings/popup.xml | 36 +- xpfe/global/resources/content/xul.css | 13 +- 62 files changed, 3808 insertions(+), 2566 deletions(-) rename layout/xul/base/src/nsXULPopupManager.cpp => content/xul/content/public/Makefile.in (100%) create mode 100644 content/xul/content/public/nsIXULPopupListener.h delete mode 100644 content/xul/content/src/nsXULPopupListener.h create mode 100644 layout/xul/base/public/nsIPopupSetFrame.h delete mode 100644 layout/xul/base/public/nsXULPopupManager.h create mode 100644 layout/xul/base/src/nsMenuDismissalListener.cpp create mode 100644 layout/xul/base/src/nsMenuDismissalListener.h create mode 100644 layout/xul/base/src/nsMenuListener.cpp create mode 100644 layout/xul/base/src/nsMenuListener.h diff --git a/accessible/src/base/nsRootAccessible.cpp b/accessible/src/base/nsRootAccessible.cpp index c8692a3f835..34f5a44968d 100644 --- a/accessible/src/base/nsRootAccessible.cpp +++ b/accessible/src/base/nsRootAccessible.cpp @@ -62,7 +62,6 @@ #include "nsIEventListenerManager.h" #include "nsIFocusController.h" #include "nsIFrame.h" -#include "nsIMenuFrame.h" #include "nsIHTMLDocument.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIMenuParent.h" @@ -802,23 +801,22 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent, } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { if (!treeItemAccessible) { - nsCOMPtr menuAccessNode = do_QueryInterface(accessible); - NS_ENSURE_TRUE(menuAccessNode, NS_ERROR_FAILURE); - nsIFrame* menuFrame = menuAccessNode->GetFrame(); - NS_ENSURE_TRUE(menuFrame, NS_ERROR_FAILURE); - nsIMenuFrame* imenuFrame; - CallQueryInterface(menuFrame, &imenuFrame); - NS_ENSURE_TRUE(imenuFrame, NS_ERROR_FAILURE); - if (imenuFrame->IsOnMenuBar()) { - if (!imenuFrame->IsOnActiveMenuBar()) { + nsCOMPtr containerAccessible; + accessible->GetParent(getter_AddRefs(containerAccessible)); + NS_ENSURE_TRUE(containerAccessible, NS_OK); + if (Role(containerAccessible) == nsIAccessibleRole::ROLE_MENUBAR) { + nsCOMPtr menuBarAccessNode(do_QueryInterface(containerAccessible)); + NS_ENSURE_TRUE(menuBarAccessNode, NS_ERROR_FAILURE); + nsCOMPtr menuParent = do_QueryInterface(menuBarAccessNode->GetFrame()); + NS_ENSURE_TRUE(menuParent, NS_ERROR_FAILURE); + PRBool isActive; + menuParent->GetIsActive(isActive); + if (!isActive) { // It is a top level menuitem. Only fire a focus event when the menu bar // is active. return NS_OK; } } else { - nsCOMPtr containerAccessible; - accessible->GetParent(getter_AddRefs(containerAccessible)); - NS_ENSURE_TRUE(containerAccessible, NS_ERROR_FAILURE); // It is not top level menuitem // Only fire focus event if it is not inside collapsed popup if (State(containerAccessible) & nsIAccessibleStates::STATE_COLLAPSED) diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index ab66a74dcfa..bc91b23d286 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -107,7 +107,7 @@ - + diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 06d04c23de1..61e40db7edc 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -3682,7 +3682,6 @@ nsDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult) contractID += "-menu"; else if (tag == nsGkAtoms::popup || tag == nsGkAtoms::menupopup || - tag == nsGkAtoms::panel || tag == nsGkAtoms::tooltip) contractID += "-popup"; else if (tag == nsGkAtoms::tree) diff --git a/layout/xul/base/src/nsXULPopupManager.cpp b/content/xul/content/public/Makefile.in similarity index 100% rename from layout/xul/base/src/nsXULPopupManager.cpp rename to content/xul/content/public/Makefile.in diff --git a/content/xul/content/public/nsIXULPopupListener.h b/content/xul/content/public/nsIXULPopupListener.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index 60e8af81e8e..b3748bab53f 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -107,6 +107,7 @@ #include "nsIViewManager.h" #include "nsIWidget.h" #include "nsIXULDocument.h" +#include "nsIXULPopupListener.h" #include "nsIXULTemplateBuilder.h" #include "nsIXBLService.h" #include "nsLayoutCID.h" @@ -118,7 +119,6 @@ #include "nsIBoxObject.h" #include "nsPIBoxObject.h" #include "nsXULDocument.h" -#include "nsXULPopupListener.h" #include "nsRuleWalker.h" #include "nsIDOMViewCSS.h" #include "nsIDOMCSSStyleDeclaration.h" @@ -2047,17 +2047,18 @@ static void PopupListenerPropertyDtor(void* aObject, nsIAtom* aPropertyName, void* aPropertyValue, void* aData) { - nsIDOMEventListener* listener = - NS_STATIC_CAST(nsIDOMEventListener*, aPropertyValue); + nsIXULPopupListener* listener = + NS_STATIC_CAST(nsIXULPopupListener*, aPropertyValue); if (!listener) { return; } + nsCOMPtr eventListener = do_QueryInterface(listener); nsCOMPtr target = do_QueryInterface(NS_STATIC_CAST(nsINode*, aObject)); if (target) { - target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), listener, + target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), eventListener, PR_FALSE); - target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), listener, + target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), eventListener, PR_FALSE); } NS_RELEASE(listener); @@ -2066,38 +2067,43 @@ PopupListenerPropertyDtor(void* aObject, nsIAtom* aPropertyName, nsresult nsXULElement::AddPopupListener(nsIAtom* aName) { - // Add a popup listener to the element - PRBool isContext = (aName == nsGkAtoms::context || - aName == nsGkAtoms::contextmenu); - nsIAtom* listenerAtom = isContext ? - nsGkAtoms::contextmenulistener : - nsGkAtoms::popuplistener; + XULPopupType popupType; + nsCOMPtr listenerAtom; + if (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu) { + popupType = eXULPopupType_context; + listenerAtom = nsGkAtoms::contextmenulistener; + } else { + popupType = eXULPopupType_popup; + listenerAtom = nsGkAtoms::popuplistener; + } - nsCOMPtr popupListener = - NS_STATIC_CAST(nsIDOMEventListener*, GetProperty(listenerAtom)); + nsCOMPtr popupListener = + NS_STATIC_CAST(nsIXULPopupListener*, GetProperty(listenerAtom)); if (popupListener) { // Popup listener is already installed. return NS_OK; } + // Add a popup listener to the element + nsresult rv; - nsresult rv = NS_NewXULPopupListener(this, isContext, - getter_AddRefs(popupListener)); - if (NS_FAILED(rv)) - return rv; + popupListener = do_CreateInstance(kXULPopupListenerCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to create an instance of the popup listener object."); + if (NS_FAILED(rv)) return rv; + + // Add a weak reference to the node. + popupListener->Init(this, popupType); // Add the popup as a listener on this element. + nsCOMPtr eventListener = do_QueryInterface(popupListener); nsCOMPtr target(do_QueryInterface(NS_STATIC_CAST(nsIContent *, this))); NS_ENSURE_TRUE(target, NS_ERROR_FAILURE); rv = SetProperty(listenerAtom, popupListener, PopupListenerPropertyDtor, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); - // Want the property to have a reference to the listener. - nsIDOMEventListener* listener = nsnull; - popupListener.swap(listener); - if (isContext) - target->AddEventListener(NS_LITERAL_STRING("contextmenu"), listener, PR_FALSE); - else - target->AddEventListener(NS_LITERAL_STRING("mousedown"), listener, PR_FALSE); + nsIXULPopupListener* listener = popupListener; + NS_ADDREF(listener); + target->AddEventListener(NS_LITERAL_STRING("mousedown"), eventListener, PR_FALSE); + target->AddEventListener(NS_LITERAL_STRING("contextmenu"), eventListener, PR_FALSE); return NS_OK; } diff --git a/content/xul/content/src/nsXULPopupListener.cpp b/content/xul/content/src/nsXULPopupListener.cpp index 1d5b7d5672a..aa16a138a9b 100644 --- a/content/xul/content/src/nsXULPopupListener.cpp +++ b/content/xul/content/src/nsXULPopupListener.cpp @@ -44,7 +44,6 @@ tracks xul popups and context menus */ -#include "nsXULPopupListener.h" #include "nsCOMPtr.h" #include "nsGkAtoms.h" #include "nsIDOMElement.h" @@ -52,23 +51,27 @@ #include "nsIDOMNodeList.h" #include "nsIDOMDocument.h" #include "nsIDOMDocumentXBL.h" +#include "nsIXULPopupListener.h" +#include "nsIDOMMouseListener.h" +#include "nsIDOMContextMenuListener.h" #include "nsContentCID.h" #include "nsContentUtils.h" -#include "nsXULPopupManager.h" #include "nsIScriptContext.h" #include "nsIDOMWindowInternal.h" #include "nsIDOMXULDocument.h" #include "nsIDocument.h" +#include "nsIContent.h" +#include "nsIDOMMouseEvent.h" #include "nsIDOMNSUIEvent.h" #include "nsIDOMEventTarget.h" #include "nsIDOMNSEvent.h" #include "nsServiceManagerUtils.h" #include "nsIPrincipal.h" #include "nsIScriptSecurityManager.h" -#include "nsLayoutUtils.h" -#include "nsFrameManager.h" -#include "nsHTMLReflowState.h" + +#include "nsIBoxObject.h" +#include "nsIPopupBoxObject.h" // for event firing in context menus #include "nsPresContext.h" @@ -76,8 +79,9 @@ #include "nsIEventStateManager.h" #include "nsIFocusController.h" #include "nsPIDOMWindow.h" -#include "nsIViewManager.h" #include "nsDOMError.h" + +#include "nsIFrame.h" #include "nsIMenuFrame.h" // on win32 and os/2, context menus come up on mouse up. On other platforms, @@ -86,49 +90,124 @@ #define NS_CONTEXT_MENU_IS_MOUSEUP 1 #endif -nsXULPopupListener::nsXULPopupListener(nsIDOMElement *aElement, PRBool aIsContext) - : mElement(aElement), mPopupContent(nsnull), mIsContext(aIsContext) + +//////////////////////////////////////////////////////////////////////// +// PopupListenerImpl +// +// This is the popup listener implementation for popup menus and context menus. +// +class XULPopupListenerImpl : public nsIXULPopupListener, + public nsIDOMMouseListener, + public nsIDOMContextMenuListener +{ +public: + XULPopupListenerImpl(void); + virtual ~XULPopupListenerImpl(void); + +public: + // nsISupports + NS_DECL_ISUPPORTS + + // nsIXULPopupListener + NS_IMETHOD Init(nsIDOMElement* aElement, const XULPopupType& popupType); + + // nsIDOMMouseListener + NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent); + NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; } + NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent) { return NS_OK; } + NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent) { return NS_OK; } + NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent) { return NS_OK; } + NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent) { return NS_OK; } + + // nsIDOMContextMenuListener + NS_IMETHOD ContextMenu(nsIDOMEvent* aContextMenuEvent); + + // nsIDOMEventListener + NS_IMETHOD HandleEvent(nsIDOMEvent* anEvent) { return NS_OK; } + +protected: + + virtual nsresult LaunchPopup(nsIDOMEvent* anEvent); + virtual nsresult LaunchPopup(PRInt32 aClientX, PRInt32 aClientY) ; + +private: + + nsresult PreLaunchPopup(nsIDOMEvent* aMouseEvent); + nsresult FireFocusOnTargetContent(nsIDOMNode* aTargetNode); + + // |mElement| is the node to which this listener is attached. + nsIDOMElement* mElement; // Weak ref. The element will go away first. + + // The popup that is getting shown on top of mElement. + nsCOMPtr mPopup; + + // The type of the popup + XULPopupType popupType; + +}; + +//////////////////////////////////////////////////////////////////////// + +XULPopupListenerImpl::XULPopupListenerImpl(void) + : mElement(nsnull) { } -nsXULPopupListener::~nsXULPopupListener(void) +XULPopupListenerImpl::~XULPopupListenerImpl(void) { - ClosePopup(); + if (mPopup) { + mPopup->HidePopup(); + } + +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: XULPopupListenerImpl\n", gInstanceCount); +#endif } -NS_IMPL_ADDREF(nsXULPopupListener) -NS_IMPL_RELEASE(nsXULPopupListener) +NS_IMPL_ADDREF(XULPopupListenerImpl) +NS_IMPL_RELEASE(XULPopupListenerImpl) -NS_INTERFACE_MAP_BEGIN(nsXULPopupListener) +NS_INTERFACE_MAP_BEGIN(XULPopupListenerImpl) + NS_INTERFACE_MAP_ENTRY(nsIXULPopupListener) NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener) NS_INTERFACE_MAP_ENTRY(nsIDOMContextMenuListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMMouseListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULPopupListener) NS_INTERFACE_MAP_END +NS_IMETHODIMP +XULPopupListenerImpl::Init(nsIDOMElement* aElement, const XULPopupType& popup) +{ + mElement = aElement; // Weak reference. Don't addref it. + popupType = popup; + return NS_OK; +} + //////////////////////////////////////////////////////////////// // nsIDOMMouseListener nsresult -nsXULPopupListener::MouseDown(nsIDOMEvent* aMouseEvent) +XULPopupListenerImpl::MouseDown(nsIDOMEvent* aMouseEvent) { - if(!mIsContext) + if(popupType != eXULPopupType_context) return PreLaunchPopup(aMouseEvent); else return NS_OK; } nsresult -nsXULPopupListener::ContextMenu(nsIDOMEvent* aMouseEvent) +XULPopupListenerImpl::ContextMenu(nsIDOMEvent* aMouseEvent) { - if(mIsContext) + if(popupType == eXULPopupType_context) return PreLaunchPopup(aMouseEvent); else return NS_OK; } nsresult -nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent) +XULPopupListenerImpl::PreLaunchPopup(nsIDOMEvent* aMouseEvent) { PRUint16 button; @@ -151,7 +230,7 @@ nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent) mouseEvent->GetTarget(getter_AddRefs(target)); nsCOMPtr targetNode = do_QueryInterface(target); - if (!targetNode && mIsContext) { + if (!targetNode && popupType == eXULPopupType_context) { // Not a DOM node, see if it's the DOM window (bug 380818). nsCOMPtr domWin = do_QueryInterface(target); if (!domWin) { @@ -171,7 +250,7 @@ nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent) PRBool preventDefault; nsUIEvent->GetPreventDefault(&preventDefault); - if (preventDefault && targetNode && mIsContext) { + if (preventDefault && targetNode && popupType == eXULPopupType_context) { // Someone called preventDefault on a context menu. // Let's make sure they are allowed to do so. PRBool eventEnabled = @@ -199,13 +278,13 @@ nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent) return NS_OK; } - // prevent popups on menu and menuitems as they handle their own popups - // This was added for bug 96920. + // This is a gross hack to deal with a recursive popup situation happening in AIM code. + // See http://bugzilla.mozilla.org/show_bug.cgi?id=96920. // If a menu item child was clicked on that leads to a popup needing // to show, we know (guaranteed) that we're dealing with a menu or // submenu of an already-showing popup. We don't need to do anything at all. - nsCOMPtr targetContent = do_QueryInterface(target); - if (!mIsContext) { + if (popupType == eXULPopupType_popup) { + nsCOMPtr targetContent = do_QueryInterface(target); nsIAtom *tag = targetContent ? targetContent->Tag() : nsnull; if (tag == nsGkAtoms::menu || tag == nsGkAtoms::menuitem) return NS_OK; @@ -216,38 +295,48 @@ nsXULPopupListener::PreLaunchPopup(nsIDOMEvent* aMouseEvent) // Turn the document into a XUL document so we can use SetPopupNode. nsCOMPtr xulDocument = do_QueryInterface(content->GetDocument()); - if (!xulDocument) + if (!xulDocument) { + NS_ERROR("Popup attached to an element that isn't in XUL!"); return NS_ERROR_FAILURE; + } // Store clicked-on node in xul document for context menus and menu popups. - xulDocument->SetPopupNode(targetNode); + // CLEAR THE POPUP EVENT BEFORE THIS FUNCTION EXITS + xulDocument->SetPopupNode( targetNode ); + xulDocument->SetTrustedPopupEvent( aMouseEvent ); nsCOMPtr nsevent(do_QueryInterface(aMouseEvent)); - if (mIsContext) { + switch (popupType) { + case eXULPopupType_popup: + // Check for left mouse button down + mouseEvent->GetButton(&button); + if (button == 0) { + // Time to launch a popup menu. + LaunchPopup(aMouseEvent); + aMouseEvent->StopPropagation(); + aMouseEvent->PreventDefault(); + } + break; + case eXULPopupType_context: + + // Time to launch a context menu #ifndef NS_CONTEXT_MENU_IS_MOUSEUP - // If the context menu launches on mousedown, - // we have to fire focus on the content we clicked on - FireFocusOnTargetContent(targetNode); + // If the context menu launches on mousedown, + // we have to fire focus on the content we clicked on + FireFocusOnTargetContent(targetNode); #endif + LaunchPopup(aMouseEvent); + aMouseEvent->StopPropagation(); + aMouseEvent->PreventDefault(); + break; } - else { - // Only open popups when the left mouse button is down. - mouseEvent->GetButton(&button); - if (button != 0) - return NS_OK; - } - - // Open the popup and cancel the default handling of the event. - LaunchPopup(aMouseEvent, targetContent); - aMouseEvent->StopPropagation(); - aMouseEvent->PreventDefault(); - + xulDocument->SetTrustedPopupEvent(nsnull); return NS_OK; } nsresult -nsXULPopupListener::FireFocusOnTargetContent(nsIDOMNode* aTargetNode) +XULPopupListenerImpl::FireFocusOnTargetContent(nsIDOMNode* aTargetNode) { nsresult rv; nsCOMPtr domDoc; @@ -319,25 +408,26 @@ nsXULPopupListener::FireFocusOnTargetContent(nsIDOMNode* aTargetNode) return rv; } -// ClosePopup // -// Do everything needed to shut down the popup. +// LaunchPopup // -// NOTE: This routine is safe to call even if the popup is already closed. -// -void -nsXULPopupListener::ClosePopup() +nsresult +XULPopupListenerImpl::LaunchPopup ( nsIDOMEvent* anEvent ) { - if (mPopupContent) { - // this is called when the listener is going away, so make sure that the - // popup is hidden. Use asynchronous hiding just to be safe so we don't - // fire events during destruction. - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->HidePopup(mPopupContent, PR_FALSE, PR_TRUE, PR_TRUE); - mPopupContent = nsnull; // release the popup + // Retrieve our x and y position. + nsCOMPtr mouseEvent ( do_QueryInterface(anEvent) ); + if (!mouseEvent) { + //non-ui event passed in. bad things. + return NS_OK; } -} // ClosePopup + + PRInt32 xPos, yPos; + mouseEvent->GetClientX(&xPos); + mouseEvent->GetClientY(&yPos); + + return LaunchPopup(xPos, yPos); +} + static void GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult) @@ -356,6 +446,53 @@ GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult) return; } +static void ConvertPosition(nsIDOMElement* aPopupElt, nsString& aAnchor, nsString& aAlign, PRInt32& aY) +{ + nsAutoString position; + aPopupElt->GetAttribute(NS_LITERAL_STRING("position"), position); + if (position.IsEmpty()) + return; + + if (position.EqualsLiteral("before_start")) { + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("bottomleft"); + } + else if (position.EqualsLiteral("before_end")) { + aAnchor.AssignLiteral("topright"); + aAlign.AssignLiteral("bottomright"); + } + else if (position.EqualsLiteral("after_start")) { + aAnchor.AssignLiteral("bottomleft"); + aAlign.AssignLiteral("topleft"); + } + else if (position.EqualsLiteral("after_end")) { + aAnchor.AssignLiteral("bottomright"); + aAlign.AssignLiteral("topright"); + } + else if (position.EqualsLiteral("start_before")) { + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("topright"); + } + else if (position.EqualsLiteral("start_after")) { + aAnchor.AssignLiteral("bottomleft"); + aAlign.AssignLiteral("bottomright"); + } + else if (position.EqualsLiteral("end_before")) { + aAnchor.AssignLiteral("topright"); + aAlign.AssignLiteral("topleft"); + } + else if (position.EqualsLiteral("end_after")) { + aAnchor.AssignLiteral("bottomright"); + aAlign.AssignLiteral("bottomleft"); + } + else if (position.EqualsLiteral("overlap")) { + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("topleft"); + } + else if (position.EqualsLiteral("after_pointer")) + aY += 21; +} + // // LaunchPopup // @@ -363,22 +500,25 @@ GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult) // Client and widget coordinates, popup a new window showing the appropriate // content. // -// aTargetContent is the target of the mouse event aEvent that triggered the -// popup. mElement is the element that the popup menu is attached to. The -// former may be equal to mElement or it may be a descendant. -// -// This looks for an attribute on |mElement| of the appropriate popup type +// This looks for an attribute on |aElement| of the appropriate popup type // (popup, context) and uses that attribute's value as an ID for // the popup content in the document. // nsresult -nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent) +XULPopupListenerImpl::LaunchPopup(PRInt32 aClientX, PRInt32 aClientY) { nsresult rv = NS_OK; nsAutoString type(NS_LITERAL_STRING("popup")); - if (mIsContext) + if ( popupType == eXULPopupType_context ) { type.AssignLiteral("context"); + + // position the menu two pixels down and to the right from the current + // mouse position. This makes it easier to dismiss the menu by just + // clicking. + aClientX += 2; + aClientY += 2; + } nsAutoString identifier; mElement->GetAttribute(type, identifier); @@ -404,14 +544,14 @@ nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent) } // Handle the _child case for popups and context menus - nsCOMPtr popupElement; + nsCOMPtr popupContent; if (identifier.EqualsLiteral("_child")) { nsCOMPtr popup; GetImmediateChild(content, nsGkAtoms::menupopup, getter_AddRefs(popup)); if (popup) - popupElement = do_QueryInterface(popup); + popupContent = do_QueryInterface(popup); else { nsCOMPtr nsDoc(do_QueryInterface(domDocument)); nsCOMPtr list; @@ -426,7 +566,7 @@ nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent) if (childContent->NodeInfo()->Equals(nsGkAtoms::menupopup, kNameSpaceID_XUL)) { - popupElement = do_QueryInterface(childContent); + popupContent = do_QueryInterface(childContent); break; } } @@ -434,60 +574,57 @@ nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent) } } else if (NS_FAILED(rv = domDocument->GetElementById(identifier, - getter_AddRefs(popupElement)))) { + getter_AddRefs(popupContent)))) { // Use getElementById to obtain the popup content and gracefully fail if // we didn't find any popup content in the document. NS_ERROR("GetElementById had some kind of spasm."); return rv; } - - // return if no popup was found or the popup is the element itself. - if ( !popupElement || popupElement == mElement) + if ( !popupContent ) return NS_OK; // Submenus can't be used as context menus or popups, bug 288763. // Similar code also in nsXULTooltipListener::GetTooltipFor. - nsCOMPtr popup = do_QueryInterface(popupElement); + nsCOMPtr popup = do_QueryInterface(popupContent); nsIContent* parent = popup->GetParent(); if (parent) { nsIDocument* doc = parent->GetCurrentDoc(); nsIPresShell* presShell = doc ? doc->GetPrimaryShell() : nsnull; nsIFrame* frame = presShell ? presShell->GetPrimaryFrameFor(parent) : nsnull; - if (frame && frame->GetType() == nsGkAtoms::menuFrame) - return NS_OK; - } - - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (!pm) - return NS_OK; - - // XXXndeakin this is temporary. It is needed to grab the mouse location details - // used by the spellchecking popup. See bug 383930. - pm->SetMouseLocation(aEvent); - - // if the popup has an anchoring attribute, anchor it to the element, - // otherwise just open it at the screen position where the mouse was clicked. - mPopupContent = popup; - if (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) || - mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) || - mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)) { - pm->ShowPopup(mPopupContent, content, EmptyString(), 0, 0, - mIsContext, PR_TRUE, PR_FALSE); - } - else { - PRInt32 xPos = 0, yPos = 0; - nsCOMPtr mouseEvent = do_QueryInterface(aEvent); - mouseEvent->GetScreenX(&xPos); - mouseEvent->GetScreenY(&yPos); - - if (mIsContext) { - // position the menu two pixels down and to the right from the current - // mouse position. This makes it easier to dismiss the menu by just clicking - xPos += 2; - yPos += 2; + if (frame) { + nsIMenuFrame* menu = nsnull; + CallQueryInterface(frame, &menu); + NS_ENSURE_FALSE(menu, NS_OK); } + } - pm->ShowPopupAtScreen(mPopupContent, xPos, yPos, mIsContext); + // We have some popup content. Obtain our window. + nsPIDOMWindow *domWindow = document->GetWindow(); + + if (domWindow) { + // Find out if we're anchored. + nsAutoString anchorAlignment; + popupContent->GetAttribute(NS_LITERAL_STRING("popupanchor"), anchorAlignment); + + nsAutoString popupAlignment; + popupContent->GetAttribute(NS_LITERAL_STRING("popupalign"), popupAlignment); + + PRInt32 xPos = aClientX, yPos = aClientY; + + ConvertPosition(popupContent, anchorAlignment, popupAlignment, yPos); + if (!anchorAlignment.IsEmpty() && !popupAlignment.IsEmpty()) + xPos = yPos = -1; + + nsCOMPtr popupBox; + nsCOMPtr xulPopupElt(do_QueryInterface(popupContent)); + xulPopupElt->GetBoxObject(getter_AddRefs(popupBox)); + nsCOMPtr popupBoxObject(do_QueryInterface(popupBox)); + if (popupBoxObject) { + mPopup = popupBoxObject; + popupBoxObject->ShowPopup(mElement, popupContent, xPos, yPos, + type.get(), anchorAlignment.get(), + popupAlignment.get()); + } } return NS_OK; @@ -495,14 +632,13 @@ nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent) //////////////////////////////////////////////////////////////// nsresult -NS_NewXULPopupListener(nsIDOMElement* aElement, PRBool aIsContext, - nsIDOMEventListener** aListener) +NS_NewXULPopupListener(nsIXULPopupListener** pop) { - nsXULPopupListener* pl = new nsXULPopupListener(aElement, aIsContext); - if (!pl) + XULPopupListenerImpl* popup = new XULPopupListenerImpl(); + if (!popup) return NS_ERROR_OUT_OF_MEMORY; - - *aListener = NS_STATIC_CAST(nsIDOMMouseListener *, pl); - NS_ADDREF(*aListener); + + NS_ADDREF(popup); + *pop = popup; return NS_OK; } diff --git a/content/xul/content/src/nsXULPopupListener.h b/content/xul/content/src/nsXULPopupListener.h deleted file mode 100644 index 07e24b81c3a..00000000000 --- a/content/xul/content/src/nsXULPopupListener.h +++ /dev/null @@ -1,123 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Original Author: David W. Hyatt (hyatt@netscape.com) - * Dean Tessman - * Pierre Phaneuf - * Robert O'Callahan - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/** - * This is the popup listener implementation for popup menus and context menus. - */ - -#ifndef nsXULPopupListener_h___ -#define nsXULPopupListener_h___ - -#include "nsCOMPtr.h" - -#include "nsIContent.h" -#include "nsIDOMElement.h" -#include "nsIDOMMouseEvent.h" -#include "nsIFrame.h" -#include "nsIDOMMouseListener.h" -#include "nsIDOMContextMenuListener.h" - -class nsXULPopupListener : public nsIDOMMouseListener, - public nsIDOMContextMenuListener -{ -public: - // aElement is the element that the popup is attached to. If aIsContext is - // false, the popup opens on left click on aElement or a descendant. If - // aIsContext is true, the popup is a context menu which opens on a - // context menu event. - nsXULPopupListener(nsIDOMElement *aElement, PRBool aIsContext); - virtual ~nsXULPopupListener(void); - -public: - // nsISupports - NS_DECL_ISUPPORTS - - // nsIDOMMouseListener - NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent); - NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; } - NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent) { return NS_OK; } - NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent) { return NS_OK; } - NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent) { return NS_OK; } - NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent) { return NS_OK; } - - // nsIDOMContextMenuListener - NS_IMETHOD ContextMenu(nsIDOMEvent* aContextMenuEvent); - - // nsIDOMEventListener - NS_IMETHOD HandleEvent(nsIDOMEvent* anEvent) { return NS_OK; } - -protected: - - // open the popup. aEvent is the event that triggered the popup such as - // a mouse click and aTargetContent is the target of this event. - virtual nsresult LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent); - - // close the popup when the listener goes away - virtual void ClosePopup(); - -private: - - // PreLaunchPopup is called before LaunchPopup to ensure that the event is - // suitable and to initialize the XUL document's popupNode to the event - // target. - nsresult PreLaunchPopup(nsIDOMEvent* aMouseEvent); - - // When a context menu is opened, focus the target of the contextmenu event. - nsresult FireFocusOnTargetContent(nsIDOMNode* aTargetNode); - - // |mElement| is the node to which this listener is attached. - nsIDOMElement* mElement; // Weak ref. The element will go away first. - - // The popup that is getting shown on top of mElement. - nsCOMPtr mPopupContent; - - // true if a context popup - PRBool mIsContext; -}; - -// Construct a new nsXULPopupListener and return in aListener. See the -// nsXULPopupListener constructor for details about the aElement and -// aIsContext arguments. -nsresult -NS_NewXULPopupListener(nsIDOMElement* aElement, PRBool aIsContext, - nsIDOMEventListener** aListener); - -#endif // nsXULPopupListener_h___ diff --git a/content/xul/document/src/nsXULDocument.cpp b/content/xul/document/src/nsXULDocument.cpp index 596d1d01efc..4240f766fd0 100644 --- a/content/xul/document/src/nsXULDocument.cpp +++ b/content/xul/document/src/nsXULDocument.cpp @@ -128,7 +128,6 @@ #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeOwner.h" #include "nsIXULWindow.h" -#include "nsXULPopupManager.h" //---------------------------------------------------------------------- // @@ -1457,49 +1456,84 @@ nsXULDocument::SetPopupNode(nsIDOMNode* aNode) return rv; } -// Returns the rangeOffset element from the XUL Popup Manager. This is for -// chrome callers only. +NS_IMETHODIMP +nsXULDocument::GetTrustedPopupEvent(nsIDOMEvent** aEvent) +{ + nsresult rv; + + nsCOMPtr focusController; + GetFocusController(getter_AddRefs(focusController)); + NS_ENSURE_TRUE(focusController, NS_ERROR_FAILURE); + + rv = focusController->GetPopupEvent(aEvent); + + return rv; +} + +NS_IMETHODIMP +nsXULDocument::SetTrustedPopupEvent(nsIDOMEvent* aEvent) +{ + nsresult rv; + + nsCOMPtr focusController; + GetFocusController(getter_AddRefs(focusController)); + NS_ENSURE_TRUE(focusController, NS_ERROR_FAILURE); + + rv = focusController->SetPopupEvent(aEvent); + + return rv; +} + +// Returns the rangeOffset element from the popupEvent. This is for chrome +// callers only. NS_IMETHODIMP nsXULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent) { NS_ENSURE_ARG_POINTER(aRangeParent); *aRangeParent = nsnull; - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (!pm) - return NS_ERROR_FAILURE; + nsCOMPtr event; + nsresult rv = GetTrustedPopupEvent(getter_AddRefs(event)); + NS_ENSURE_SUCCESS(rv, rv); + if (! event) + return NS_ERROR_UNEXPECTED; // no event active - PRInt32 offset; - pm->GetMouseLocation(aRangeParent, &offset); + nsCOMPtr uiEvent = do_QueryInterface(event, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = uiEvent->GetRangeParent(aRangeParent); // addrefs - if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) { + if (NS_SUCCEEDED(rv) && *aRangeParent && + !nsContentUtils::CanCallerAccess(*aRangeParent)) { NS_RELEASE(*aRangeParent); return NS_ERROR_DOM_SECURITY_ERR; } - - return NS_OK; + return rv; } -// Returns the rangeOffset element from the XUL Popup Manager. We check the -// rangeParent to determine if the caller has rights to access to the data. +// Returns the rangeOffset element from the popupEvent. We check the rangeParent +// to determine if the caller has rights to access to the data. NS_IMETHODIMP nsXULDocument::GetPopupRangeOffset(PRInt32* aRangeOffset) { NS_ENSURE_ARG_POINTER(aRangeOffset); - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (!pm) - return NS_ERROR_FAILURE; + nsCOMPtr event; + nsresult rv = GetTrustedPopupEvent(getter_AddRefs(event)); + NS_ENSURE_SUCCESS(rv, rv); + if (! event) + return NS_ERROR_UNEXPECTED; // no event active + + nsCOMPtr uiEvent = do_QueryInterface(event, &rv); + NS_ENSURE_SUCCESS(rv, rv); - PRInt32 offset; nsCOMPtr parent; - pm->GetMouseLocation(getter_AddRefs(parent), &offset); + rv = uiEvent->GetRangeParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); if (parent && !nsContentUtils::CanCallerAccess(parent)) return NS_ERROR_DOM_SECURITY_ERR; - *aRangeOffset = offset; - return NS_OK; + return uiEvent->GetRangeOffset(aRangeOffset); } NS_IMETHODIMP diff --git a/dom/public/idl/xul/nsIDOMXULDocument.idl b/dom/public/idl/xul/nsIDOMXULDocument.idl index ed8be7f8d02..46da2faa03a 100644 --- a/dom/public/idl/xul/nsIDOMXULDocument.idl +++ b/dom/public/idl/xul/nsIDOMXULDocument.idl @@ -110,4 +110,9 @@ interface nsIDOMXULDocument : nsISupports * Like trustedGetPopupNode, but gets the tooltip node instead. */ [noscript] nsIDOMNode trustedGetTooltipNode(); + + /** + * Like trustedGetPopupNode, but gets the + */ + [noscript] attribute nsIDOMEvent trustedPopupEvent; }; diff --git a/dom/src/base/nsGlobalWindow.cpp b/dom/src/base/nsGlobalWindow.cpp index 17dd34d9be6..6e314913733 100644 --- a/dom/src/base/nsGlobalWindow.cpp +++ b/dom/src/base/nsGlobalWindow.cpp @@ -158,7 +158,6 @@ #include "nsEventDispatcher.h" #include "nsIObserverService.h" #include "nsNetUtil.h" -#include "nsXULPopupManager.h" #include "plbase64.h" @@ -3036,10 +3035,10 @@ nsGlobalWindow::CheckSecurityWidthAndHeight(PRInt32* aWidth, PRInt32* aHeight) { if (!nsContentUtils::IsCallerTrustedForWrite()) { // if attempting to resize the window, hide any open popups - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - nsCOMPtr doc(do_QueryInterface(mDocument)); - if (pm && doc) - pm->HidePopupsInDocument(doc); + nsCOMPtr presShell; + mDocShell->GetPresShell(getter_AddRefs(presShell)); + if (presShell) + presShell->HidePopups(); } // This one is easy. Just ensure the variable is greater than 100; @@ -3069,10 +3068,10 @@ nsGlobalWindow::CheckSecurityLeftAndTop(PRInt32* aLeft, PRInt32* aTop) if (!nsContentUtils::IsCallerTrustedForWrite()) { // if attempting to move the window, hide any open popups - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - nsCOMPtr doc(do_QueryInterface(mDocument)); - if (pm && doc) - pm->HidePopupsInDocument(doc); + nsCOMPtr presShell; + mDocShell->GetPresShell(getter_AddRefs(presShell)); + if (presShell) + presShell->HidePopups(); PRInt32 screenLeft, screenTop, screenWidth, screenHeight; PRInt32 winLeft, winTop, winWidth, winHeight; diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 502a433a8cb..9193a9dccd5 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -119,6 +119,7 @@ #include "nsContentErrors.h" #include "nsIDOMWindowInternal.h" +#include "nsIMenuFrame.h" #include "nsBox.h" @@ -1705,8 +1706,11 @@ GetChildListNameFor(nsIFrame* aChildFrame) // Out-of-flows that are DISPLAY_POPUP must be kids of the root popup set #ifdef DEBUG nsIFrame* parent = aChildFrame->GetParent(); - NS_ASSERTION(parent && parent->GetType() == nsGkAtoms::popupSetFrame, - "Unexpected parent"); + if (parent) { + nsIPopupSetFrame* popupSet; + CallQueryInterface(parent, &popupSet); + NS_ASSERTION(popupSet, "Unexpected parent"); + } #endif // DEBUG // XXX FIXME: Bug 350740 @@ -5954,7 +5958,9 @@ nsCSSFrameConstructor::ConstructXULFrame(nsFrameConstructorState& aState, // If, however, the parent is *not* a menu frame, then we need to create // a placeholder frame for the popup, and then we add the popup frame to the // root popup set (that manages all such "detached" popups). - if (aParentFrame->GetType() != nsGkAtoms::menuFrame) { + nsIMenuFrame* menuFrame; + CallQueryInterface(aParentFrame, &menuFrame); + if (!menuFrame) { if (!aState.mPopupItems.containingBlock) { // Just don't create a frame for this popup; we can't do // anything with it, since there is no root popup set. @@ -5964,9 +5970,9 @@ nsCSSFrameConstructor::ConstructXULFrame(nsFrameConstructorState& aState, } #ifdef NS_DEBUG - NS_ASSERTION(aState.mPopupItems.containingBlock->GetType() == - nsGkAtoms::popupSetFrame, - "Popup containing block isn't a nsIPopupSetFrame"); + nsIPopupSetFrame* popupSet; + CallQueryInterface(aState.mPopupItems.containingBlock, &popupSet); + NS_ASSERTION(popupSet, "Popup containing block isn't a nsIPopupSetFrame"); #endif isPopup = PR_TRUE; } @@ -6141,16 +6147,6 @@ nsCSSFrameConstructor::ConstructXULFrame(nsFrameConstructorState& aState, return rv; } -nsresult -nsCSSFrameConstructor::AddLazyChildren(nsIContent* aContent, - nsLazyFrameConstructionCallback* aCallback, - void* aArg) -{ - nsCOMPtr event = - new LazyGenerateChildrenEvent(aContent, mPresShell, aCallback, aArg); - return NS_DispatchToCurrentThread(event); -} - already_AddRefed nsCSSFrameConstructor::BeginBuildingScrollFrame(nsFrameConstructorState& aState, nsIContent* aContent, @@ -10140,6 +10136,29 @@ nsCSSFrameConstructor::AttributeChanged(nsIContent* aContent, aAttribute, aModType); + // Menus and such can't deal with asynchronous changes of display + // when the menugenerated or menuactive attribute changes, so make + // sure to process that immediately + if (aNameSpaceID == kNameSpaceID_None && + ((aAttribute == nsGkAtoms::menugenerated && + aModType != nsIDOMMutationEvent::REMOVAL) || + aAttribute == nsGkAtoms::menuactive)) { + PRInt32 namespaceID; + nsIAtom* tag = + mDocument->BindingManager()->ResolveTag(aContent, &namespaceID); + + if (namespaceID == kNameSpaceID_XUL && + (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup || + tag == nsGkAtoms::tooltip || tag == nsGkAtoms::menu)) { + nsIViewManager* viewManager = mPresShell->GetViewManager(); + viewManager->BeginUpdateViewBatch(); + ProcessOneRestyle(aContent, rshint, hint); + viewManager->EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); + + return result; + } + } + PostRestyleEvent(aContent, rshint, hint); return result; @@ -13009,42 +13028,3 @@ NS_IMETHODIMP nsCSSFrameConstructor::RestyleEvent::Run() { return NS_OK; } -NS_IMETHODIMP -nsCSSFrameConstructor::LazyGenerateChildrenEvent::Run() -{ - mPresShell->GetDocument()->FlushPendingNotifications(Flush_Layout); - - // this is hard-coded to handle only menu popup frames - nsIFrame* frame = mPresShell->GetPrimaryFrameFor(mContent); - if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) { - // it is possible that the frame is different than the one that requested - // the lazy generation, but as long as it's a popup frame that hasn't - // generated its children yet, that's OK. - nsMenuPopupFrame* menuPopupFrame = NS_STATIC_CAST(nsMenuPopupFrame *, frame); - if (menuPopupFrame->HasGeneratedChildren()) - return NS_OK; - - // indicate that the children have been generated - menuPopupFrame->SetGeneratedChildren(); - - nsFrameItems childItems; - nsFrameConstructorState state(mPresShell, nsnull, nsnull, nsnull); - nsCSSFrameConstructor* fc = mPresShell->FrameConstructor(); - nsresult rv = fc->ProcessChildren(state, mContent, frame, PR_FALSE, - childItems, PR_FALSE); - if (NS_FAILED(rv)) - return rv; - - fc->CreateAnonymousFrames(mContent->Tag(), state, mContent, frame, - PR_FALSE, childItems); - frame->SetInitialChildList(nsnull, childItems.childList); - - if (mCallback) - mCallback(mContent, frame, mArg); - - // call XBL constructors after the frames are created - mPresShell->GetDocument()->BindingManager()->ProcessAttachedQueue(); - } - - return NS_OK; -} diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index e479992c900..7f67d74b216 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -72,9 +72,6 @@ struct nsFindFrameHint nsFindFrameHint() : mPrimaryFrameForPrevSibling(nsnull) { } }; -typedef void (PR_CALLBACK nsLazyFrameConstructionCallback) - (nsIContent* aContent, nsIFrame* aFrame, void* aArg); - class nsFrameConstructorState; class nsFrameConstructorSaveState; @@ -125,17 +122,6 @@ public: nsIContent* aContent2, PRInt32 aStateMask); - // Process the children of aContent and indicate that frames should be - // created for them. This is used for lazily built content such as that - // inside popups so that it is only created when the popup is opened. - // This method constructs the frames asynchronously. - // aCallback will be called with three arguments, the first is the value - // of aContent, the second is aContent's primary frame, and the third is - // the value of aArg. - nsresult AddLazyChildren(nsIContent* aContent, - nsLazyFrameConstructionCallback* aCallback, - void* aArg); - // Should be called when a frame is going to be destroyed and // WillDestroyFrameTree hasn't been called yet. void NotifyDestroyingFrame(nsIFrame* aFrame); @@ -1017,27 +1003,6 @@ public: friend class nsFrameConstructorState; private: - - class LazyGenerateChildrenEvent; - friend class LazyGenerateChildrenEvent; - - class LazyGenerateChildrenEvent : public nsRunnable { - public: - NS_DECL_NSIRUNNABLE - LazyGenerateChildrenEvent(nsIContent *aContent, - nsIPresShell *aPresShell, - nsLazyFrameConstructionCallback* aCallback, - void* aArg) - : mContent(aContent), mPresShell(aPresShell), mCallback(aCallback), mArg(aArg) - {} - - private: - nsCOMPtr mContent; - nsCOMPtr mPresShell; - nsLazyFrameConstructionCallback* mCallback; - void* mArg; - }; - nsIDocument* mDocument; // Weak ref nsIPresShell* mPresShell; // Weak ref diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 8f8f8866da7..272f036534a 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -111,7 +111,6 @@ #ifdef MOZ_XUL #include "nsIXULDocument.h" #endif -#include "nsXULPopupManager.h" #include "nsPrintfCString.h" #include "nsIClipboardHelper.h" @@ -1154,9 +1153,10 @@ DocumentViewerImpl::PageHide(PRBool aIsUnload) // look for open menupopups and close them after the unload event, in case // the unload event listeners open any new popups - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && mDocument) - pm->HidePopupsInDocument(mDocument); + if (mPresShell) { + nsCOMPtr kungFuDeathGrip = mPresShell; + mPresShell->HidePopups(); + } return NS_OK; } diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index 049e055d989..6b955deba1b 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -102,10 +102,10 @@ class gfxContext; typedef short SelectionType; typedef PRUint32 nsFrameState; -// D93B931B-D5EF-4D3C-AB99-444176963464 +// 9562bb2b-990c-4875-aafd-bd46fc9a4fc1 #define NS_IPRESSHELL_IID \ -{ 0xd93b931b, 0xd5ef, 0x4d3c, \ - { 0xab, 0x99, 0x44, 0x41, 0x76, 0x96, 0x34, 0x64 } } +{ 0x9562bb2b, 0x990c, 0x4875, \ + { 0xaa, 0xfd, 0xbd, 0x46, 0xfc, 0x9a, 0x4f, 0xc1 } } // Constants for ScrollContentIntoView() function #define NS_PRESSHELL_SCROLL_TOP 0 @@ -725,6 +725,8 @@ public: nsPoint& aPoint, nsRect* aScreenRect) = 0; + virtual void HidePopups() = 0; + void AddWeakFrame(nsWeakFrame* aWeakFrame); void RemoveWeakFrame(nsWeakFrame* aWeakFrame); diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 432cc96575b..3f78ea32783 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -189,7 +189,7 @@ #include "nsStyleChangeList.h" #include "nsCSSFrameConstructor.h" #ifdef MOZ_XUL -#include "nsMenuFrame.h" +#include "nsIMenuFrame.h" #include "nsITreeBoxObject.h" #endif #include "nsIMenuParent.h" @@ -887,6 +887,8 @@ public: nsPoint& aPoint, nsRect* aScreenRect); + virtual void HidePopups(); + //nsIViewObserver interface NS_IMETHOD Paint(nsIView *aView, @@ -5837,6 +5839,18 @@ PresShell::Thaw() UnsuppressPainting(); } +void +PresShell::HidePopups() +{ + nsIViewManager *vm = GetViewManager(); + if (vm) { + nsIView *rootView = nsnull; + vm->GetRootView(rootView); + if (rootView) + HideViewIfPopup(rootView); + } +} + //-------------------------------------------------------- // Start of protected and private methods on the PresShell //-------------------------------------------------------- @@ -6171,8 +6185,11 @@ ReResolveMenusAndTrees(nsIFrame *aFrame, void *aClosure) // sub-content, since doing so slows menus to a crawl. That means we // have to special-case them on a skin switch, and ensure that the // popup frames just get destroyed completely. - if (aFrame && aFrame->GetType() == nsGkAtoms::menuFrame) - (NS_STATIC_CAST(nsMenuFrame *, aFrame))->CloseMenu(PR_TRUE); + nsCOMPtr menuFrame(do_QueryInterface(aFrame)); + if (menuFrame) { + menuFrame->UngenerateMenu(); + menuFrame->OpenMenu(PR_FALSE); + } return PR_TRUE; } @@ -6310,6 +6327,27 @@ PresShell::EnumeratePlugins(nsIDOMDocument *aDocument, } } +void +PresShell::HideViewIfPopup(nsIView* aView) +{ + nsIFrame* frame = NS_STATIC_CAST(nsIFrame*, aView->GetClientData()); + if (frame) { + nsIMenuParent* parent; + CallQueryInterface(frame, &parent); + if (parent) { + parent->HideChain(); + // really make sure the view is hidden + mViewManager->SetViewVisibility(aView, nsViewVisibility_kHide); + } + } + + nsIView* child = aView->GetFirstChild(); + while (child) { + HideViewIfPopup(child); + child = child->GetNextSibling(); + } +} + //------------------------------------------------------ // End of protected and private methods on the PresShell //------------------------------------------------------ diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 5101a946cd9..2d6302bc6e3 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -236,6 +236,7 @@ static NS_DEFINE_CID(kWindowCommandTableCID, NS_WINDOWCOMMANDTABLE_CID); #ifdef MOZ_XUL #include "nsIBoxObject.h" #include "nsIXULDocument.h" +#include "nsIXULPopupListener.h" #include "nsIXULPrototypeCache.h" #include "nsIXULSortService.h" @@ -508,6 +509,7 @@ MAKE_CTOR(CreateXULSortService, nsIXULSortService, NS_NewXUL // NS_NewXULContentBuilder // NS_NewXULTreeBuilder MAKE_CTOR(CreateXULDocument, nsIXULDocument, NS_NewXULDocument) +MAKE_CTOR(CreateXULPopupListener, nsIXULPopupListener, NS_NewXULPopupListener) // NS_NewXULControllers // NS_NewXULPrototypeCache #endif @@ -1192,6 +1194,11 @@ static const nsModuleComponentInfo gComponents[] = { "@mozilla.org/xul/xul-document;1", CreateXULDocument }, + { "XUL PopupListener", + NS_XULPOPUPLISTENER_CID, + "@mozilla.org/xul/xul-popup-listener;1", + CreateXULPopupListener }, + { "XUL Prototype Cache", NS_XULPROTOTYPECACHE_CID, "@mozilla.org/xul/xul-prototype-cache;1", diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index a252980de39..883474b40b0 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -80,7 +80,6 @@ #include "nsCellMap.h" #include "nsTextFrameTextRunCache.h" #include "nsCCUncollectableMarker.h" -#include "nsXULPopupManager.h" #ifdef MOZ_XUL #include "nsXULContentUtils.h" @@ -224,19 +223,12 @@ nsLayoutStatics::Initialize() return rv; } - rv = nsXULPopupManager::Init(); - if (NS_FAILED(rv)) { - NS_ERROR("Could not initialize nsXULPopupManager"); - return rv; - } - return NS_OK; } void nsLayoutStatics::Shutdown() { - nsXULPopupManager::Shutdown(); nsDOMStorageManager::Shutdown(); txMozillaXSLTProcessor::Shutdown(); nsDOMAttribute::Shutdown(); diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index 838a7aa08a8..2b88074af4c 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -717,12 +717,7 @@ nsContainerFrame::PositionChildViews(nsIFrame* aFrame) childFrame = childFrame->GetNextSibling(); } - // also process the additional child lists, but skip the popup list as the - // view for popups is managed by the parent. Currently only nsMenuFrame - // has a popupList and during layout will call nsMenuPopupFrame::AdjustView. - do { - childListName = aFrame->GetAdditionalChildListName(childListIndex++); - } while (childListName == nsGkAtoms::popupList); + childListName = aFrame->GetAdditionalChildListName(childListIndex++); } while (childListName); } diff --git a/layout/xul/base/public/Makefile.in b/layout/xul/base/public/Makefile.in index 56793820011..401c97ba859 100644 --- a/layout/xul/base/public/Makefile.in +++ b/layout/xul/base/public/Makefile.in @@ -49,8 +49,8 @@ GRE_MODULE = 1 EXPORTS = \ nsPIBoxObject.h \ nsIMenuFrame.h \ + nsIPopupSetFrame.h \ nsIScrollbarMediator.h \ - nsXULPopupManager.h \ $(NULL) XPIDLSRCS= nsIBoxObject.idl \ diff --git a/layout/xul/base/public/nsIMenuFrame.h b/layout/xul/base/public/nsIMenuFrame.h index c17ff03e780..5c8d6d24b83 100644 --- a/layout/xul/base/public/nsIMenuFrame.h +++ b/layout/xul/base/public/nsIMenuFrame.h @@ -38,25 +38,53 @@ #ifndef nsIMenuFrame_h___ #define nsIMenuFrame_h___ -#include "nsISupports.h" - -// this interface exists solely because native themes need to call into it. -// Only menu frames should implement it - -// {212521C8-1509-4F41-ADDB-6A0B9356770F} +// {2281EFC8-A8BA-4a73-8CF7-DB4EECA5EAEC} #define NS_IMENUFRAME_IID \ -{ 0x212521C8, 0x1509, 0x4F41, { 0xAD, 0xDB, 0x6A, 0x0B, 0x93, 0x56, 0x77, 0x0F } } +{ 0x2281efc8, 0xa8ba, 0x4a73, { 0x8c, 0xf7, 0xdb, 0x4e, 0xec, 0xa5, 0xea, 0xec } } + +class nsIMenuParent; +class nsIDOMElement; +class nsIDOMKeyEvent; + +enum nsMenuType { + eMenuType_Normal = 0, + eMenuType_Checkbox = 1, + eMenuType_Radio = 2 +}; class nsIMenuFrame : public nsISupports { public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMENUFRAME_IID) - virtual PRBool IsOpen() = 0; - virtual PRBool IsMenu() = 0; - virtual PRBool IsOnMenuBar() = 0; - virtual PRBool IsOnActiveMenuBar() = 0; + NS_IMETHOD ActivateMenu(PRBool aActivate) = 0; + NS_IMETHOD SelectMenu(PRBool aFlag) = 0; + NS_IMETHOD OpenMenu(PRBool aFlag) = 0; + + NS_IMETHOD MenuIsOpen(PRBool& aResult) = 0; + NS_IMETHOD MenuIsContainer(PRBool& aResult) = 0; + NS_IMETHOD MenuIsChecked(PRBool& aResult) = 0; + NS_IMETHOD MenuIsDisabled(PRBool& aResult) = 0; + + NS_IMETHOD SelectFirstItem() = 0; + + NS_IMETHOD Escape(PRBool& aHandledFlag) = 0; + NS_IMETHOD Enter() = 0; + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) = 0; + NS_IMETHOD KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag) = 0; + + virtual nsIMenuParent *GetMenuParent() = 0; + virtual nsIFrame *GetMenuChild() = 0; + + NS_IMETHOD GetRadioGroupName(nsString &aName) = 0; + NS_IMETHOD GetMenuType(nsMenuType &aType) = 0; + + NS_IMETHOD MarkAsGenerated() = 0; + + NS_IMETHOD UngenerateMenu() = 0; + + NS_IMETHOD GetActiveChild(nsIDOMElement** aResult)=0; + NS_IMETHOD SetActiveChild(nsIDOMElement* aChild)=0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIMenuFrame, NS_IMENUFRAME_IID) diff --git a/layout/xul/base/public/nsIPopupBoxObject.idl b/layout/xul/base/public/nsIPopupBoxObject.idl index 8bf70f961d1..22133f6aa74 100644 --- a/layout/xul/base/public/nsIPopupBoxObject.idl +++ b/layout/xul/base/public/nsIPopupBoxObject.idl @@ -41,37 +41,24 @@ interface nsIDOMElement; -[scriptable, uuid(8714441F-0E24-4EB5-BE58-905F2854B4EB)] + +[scriptable, uuid(116ffbea-336d-4ff1-a978-7335f54d11da)] interface nsIPopupBoxObject : nsISupports { - /** - * This method is deprecated. Use openPopup or openPopupAtScreen instead. - */ void showPopup(in nsIDOMElement srcContent, in nsIDOMElement popupContent, in long xpos, in long ypos, in wstring popupType, in wstring anchorAlignment, in wstring popupAlignment); - - /** - * Hide the popup if it is open. - */ void hidePopup(); + /** * Allow the popup to automatically position itself. */ attribute boolean autoPosition; /** - * If keyboard navigation is enabled, the keyboard may be used to navigate - * the menuitems on the popup. Enabling keyboard navigation is the default - * behaviour and will install capturing key event listeners on the popup - * that do not propagate key events to the contents. If you wish to place - * elements in a popup which accept key events, such as textboxes, keyboard - * navigation should be disabled. - * - * Setting ignorekeys="true" on the popup element also disables keyboard - * navigation, and is recommended over calling this method. + * Allow the popup to eat all key events */ void enableKeyboardNavigator(in boolean enableKeyboardNavigator); @@ -98,62 +85,10 @@ interface nsIPopupBoxObject : nsISupports void sizeTo(in long width, in long height); /** - * Move the popup to a point on screen in CSS pixels. + * Move the popup to a point on screen */ void moveTo(in long left, in long top); - /** - * Open the popup relative to a specified node at a specific location. - * - * The popup may be either anchored to another node or opened freely. - * To anchor a popup to a node, supply an anchor node and set the position - * to a string indicating the manner in which the popup should be anchored. - * Possible values for position are: - * before_start, before_end, after_start, after_end, - * start_before, start_after, end_before, end_after, - * overlap, after_pointer - * - * The anchor node does not need to be in the same document as the popup. - * - * If the attributesOverride argument is true, the popupanchor, popupalign - * and position attributes on the popup node override the position value - * argument. If attributesOverride is false, the attributes are only used - * if position is empty. - * - * For an anchored popup, the x and y arguments may be used to offset the - * popup from its anchored position by some number, measured in CSS pixels. - * - * Unanchored popups may be created by supplying null as the anchor node. - * An unanchored popup appears at the position specified by x and y, - * relative to the viewport of the document containing the popup node. In - * this case, position and attributesOverride are ignored. - * - * @param anchorElement the node to anchor the popup to, may be null - * @param position manner is which to anchor the popup to node - * @param x horizontal offset - * @param y vertical offset - * @param isContextMenu true for context menus, false for other popups - * @param attributesOverride true if popup node attributes override position - */ - void openPopup(in nsIDOMElement anchorElement, - in AString position, - in long x, in long y, - in boolean isContextMenu, - in boolean attributesOverride); - - /** - * Open the popup at a specific screen position specified by x and y. This - * position may be adjusted if it would cause the popup to be off of the - * screen. The x and y coordinates are measured in CSS pixels. The monitor - * selected is determined within the platform specific widget code, but - * in general, the coordinates are relative to the screen the window - * containing the popup is on. - * - * @param isContextMenu true for context menus, false for other popups - * @param x horizontal screen position - * @param y vertical screen position - */ - void openPopupAtScreen(in long x, in long y, in boolean isContextMenu); }; %{C++ diff --git a/layout/xul/base/public/nsIPopupSetFrame.h b/layout/xul/base/public/nsIPopupSetFrame.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/layout/xul/base/public/nsXULPopupManager.h b/layout/xul/base/public/nsXULPopupManager.h deleted file mode 100644 index 921e0aebbd9..00000000000 --- a/layout/xul/base/public/nsXULPopupManager.h +++ /dev/null @@ -1,636 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is Neil Deakin - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/** - * The XUL Popup Manager keeps track of all open popups. - */ - -#ifndef nsXULPopupManager_h__ -#define nsXULPopupManager_h__ - -#include "nsIContent.h" -#include "nsIWidget.h" -#include "nsIRollupListener.h" -#include "nsIMenuRollup.h" -#include "nsIDOMKeyListener.h" -#include "nsCOMPtr.h" -#include "nsITimer.h" -#include "nsThreadUtils.h" - -/** - * There are two types that are used: - * - dismissable popups such as menus, which should close up when there is a - * click outside the popup. In this situation, the entire chain of menus - * above should also be closed. - * - panels, which stay open until a request is made to close them. This - * type is used by tooltips. - * XXXndeakin note that panels don't work too well currently due to widget - * changes needed to handle activation events properly. - * - * When a new popup is opened, it is appended to the popup chain, stored in a - * linked list in mCurrentMenu for dismissable menus or mPanels for panels. - * Popups are stored in this list linked from newest to oldest. When a click - * occurs outside one of the open dismissable popups, the chain is closed by - * calling Rollup. - */ - -class nsIPresShell; -class nsMenuFrame; -class nsMenuPopupFrame; -class nsMenuBarFrame; -class nsIMenuParent; -class nsIDOMKeyEvent; - -/** - * nsNavigationDirection: an enum expressing navigation through the menus in - * terms which are independent of the directionality of the chrome. The - * terminology, derived from XSL-FO and CSS3 (e.g. - * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start, - * End), with the addition of First and Last (mapped to Home and End - * respectively). - * - * In languages such as English where the inline progression is left-to-right - * and the block progression is top-to-bottom (lr-tb), these terms will map out - * as in the following diagram - * - * --- inline progression ---> - * - * First | - * ... | - * Before | - * +--------+ block - * Start | | End progression - * +--------+ | - * After | - * ... | - * Last V - * - */ - -enum nsNavigationDirection { - eNavigationDirection_Last, - eNavigationDirection_First, - eNavigationDirection_Start, - eNavigationDirection_Before, - eNavigationDirection_End, - eNavigationDirection_After -}; - -#define NS_DIRECTION_IS_INLINE(dir) (dir == eNavigationDirection_Start || \ - dir == eNavigationDirection_End) -#define NS_DIRECTION_IS_BLOCK(dir) (dir == eNavigationDirection_Before || \ - dir == eNavigationDirection_After) -#define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) (dir == eNavigationDirection_First || \ - dir == eNavigationDirection_Last) - -/** - * DirectionFromKeyCode_lr_tb: an array that maps keycodes to values of - * nsNavigationDirection for left-to-right and top-to-bottom flow orientation - */ -static nsNavigationDirection DirectionFromKeyCode_lr_tb [6] = { - eNavigationDirection_Last, // NS_VK_END - eNavigationDirection_First, // NS_VK_HOME - eNavigationDirection_Start, // NS_VK_LEFT - eNavigationDirection_Before, // NS_VK_UP - eNavigationDirection_End, // NS_VK_RIGHT - eNavigationDirection_After // NS_VK_DOWN -}; - -/** - * DirectionFromKeyCode_rl_tb: an array that maps keycodes to values of - * nsNavigationDirection for right-to-left and top-to-bottom flow orientation - */ -static nsNavigationDirection DirectionFromKeyCode_rl_tb [6] = { - eNavigationDirection_Last, // NS_VK_END - eNavigationDirection_First, // NS_VK_HOME - eNavigationDirection_End, // NS_VK_LEFT - eNavigationDirection_Before, // NS_VK_UP - eNavigationDirection_Start, // NS_VK_RIGHT - eNavigationDirection_After // NS_VK_DOWN -}; - -#define NS_DIRECTION_FROM_KEY_CODE(frame, direction, keycode) \ - NS_ASSERTION(NS_VK_HOME == NS_VK_END + 1, "Broken ordering"); \ - NS_ASSERTION(NS_VK_LEFT == NS_VK_END + 2, "Broken ordering"); \ - NS_ASSERTION(NS_VK_UP == NS_VK_END + 3, "Broken ordering"); \ - NS_ASSERTION(NS_VK_RIGHT == NS_VK_END + 4, "Broken ordering"); \ - NS_ASSERTION(NS_VK_DOWN == NS_VK_END + 5, "Broken ordering"); \ - NS_ASSERTION(keycode >= NS_VK_END && keycode <= NS_VK_DOWN, \ - "Illegal key code"); \ - const nsStyleVisibility* vis = frame->GetStyleVisibility(); \ - if (vis->mDirection == NS_STYLE_DIRECTION_RTL) \ - direction = DirectionFromKeyCode_rl_tb[keycode - NS_VK_END]; \ - else \ - direction = DirectionFromKeyCode_lr_tb[keycode - NS_VK_END]; - -// nsMenuChainItem holds info about an open popup. Items are stored in a -// doubly linked list. Note that the linked list is stored beginning from -// the lowest child in a chain of menus, as this is the active submenu. -class nsMenuChainItem -{ -private: - nsMenuPopupFrame* mFrame; // the popup frame - PRPackedBool mIsMenu; // true if the popup is a menu, false for a panel - PRPackedBool mIsContext; // true for context menus - PRPackedBool mOnMenuBar; // true if the menu is on a menu bar - PRPackedBool mIgnoreKeys; // true if keyboard listeners should not be used - - nsMenuChainItem* mParent; - nsMenuChainItem* mChild; - -public: - nsMenuChainItem(nsMenuPopupFrame* aFrame, PRBool aIsContext, PRBool aIsMenu) - : mFrame(aFrame), - mIsMenu(aIsMenu), - mIsContext(aIsContext), - mOnMenuBar(PR_FALSE), - mIgnoreKeys(!aIsMenu), // always ignore keys on non-menus - mParent(nsnull), - mChild(nsnull) - { - NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); - MOZ_COUNT_CTOR(nsMenuChainItem); - } - - ~nsMenuChainItem() - { - MOZ_COUNT_DTOR(nsMenuChainItem); - } - - nsIContent* Content(); - nsMenuPopupFrame* Frame() { return mFrame; } - PRBool IsMenu() { return mIsMenu; } - PRBool IsContextMenu() { return mIsContext; } - PRBool IgnoreKeys() { return mIgnoreKeys; } - PRBool IsOnMenuBar() { return mOnMenuBar; } - void SetIgnoreKeys(PRBool aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } - void SetOnMenuBar(PRBool aOnMenuBar) { mOnMenuBar = aOnMenuBar; } - nsMenuChainItem* GetParent() { return mParent; } - nsMenuChainItem* GetChild() { return mChild; } - - // set the parent of this item to aParent, also changing the parent - // to have this as a child. - void SetParent(nsMenuChainItem* aParent); - - // removes an item from the chain. The root pointer must be supplied in case - // the item is the first item in the chain in which case the pointer will be - // set to the next item, or null if there isn't another item. After detaching, - // this item will not have a parent or a child. - void Detach(nsMenuChainItem** aRoot); -}; - -// this class is used for dispatching popupshowing events asynchronously. -class nsXULPopupShowingEvent : public nsRunnable -{ -public: - nsXULPopupShowingEvent(nsIContent *aPopup, - nsIContent *aMenu, - PRBool aIsContextMenu, - PRBool aSelectFirstItem) - : mPopup(aPopup), - mMenu(aMenu), - mIsContextMenu(aIsContextMenu), - mSelectFirstItem(aSelectFirstItem) - { - NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); - NS_ASSERTION(aMenu, "null menu supplied to nsXULPopupShowingEvent constructor"); - } - - NS_IMETHOD Run(); - -private: - nsCOMPtr mPopup; - nsCOMPtr mMenu; - PRBool mIsContextMenu; - PRBool mSelectFirstItem; -}; - -// this class is used for dispatching popuphiding events asynchronously. -class nsXULPopupHidingEvent : public nsRunnable -{ -public: - nsXULPopupHidingEvent(nsIContent *aPopup, - nsIContent* aNextPopup, - nsIContent* aLastPopup, - PRBool aIsMenu, - PRBool aDeselectMenu) - : mPopup(aPopup), - mNextPopup(aNextPopup), - mLastPopup(aLastPopup), - mIsMenu(aIsMenu), - mDeselectMenu(aDeselectMenu) - { - NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor"); - // aNextPopup and aLastPopup may be null - } - - NS_IMETHOD Run(); - -private: - nsCOMPtr mPopup; - nsCOMPtr mNextPopup; - nsCOMPtr mLastPopup; - PRBool mIsMenu; - PRBool mDeselectMenu; -}; - -// this class is used for dispatching menu command events asynchronously. -class nsXULMenuCommandEvent : public nsRunnable -{ -public: - nsXULMenuCommandEvent(nsIContent *aMenu, - PRBool aIsTrusted, - PRBool aShift, - PRBool aControl, - PRBool aAlt, - PRBool aMeta) - : mMenu(aMenu), - mIsTrusted(aIsTrusted), - mShift(aShift), - mControl(aControl), - mAlt(aAlt), - mMeta(aMeta) - { - NS_ASSERTION(aMenu, "null menu supplied to nsXULMenuCommandEvent constructor"); - } - - NS_IMETHOD Run(); - -private: - nsCOMPtr mMenu; - PRBool mIsTrusted; - PRBool mShift; - PRBool mControl; - PRBool mAlt; - PRBool mMeta; -}; - -class nsXULPopupManager : public nsIDOMKeyListener, - public nsIMenuRollup, - public nsIRollupListener, - public nsITimerCallback -{ - -public: - friend class nsXULPopupShowingEvent; - friend class nsXULPopupHidingEvent; - friend class nsXULMenuCommandEvent; - - NS_DECL_ISUPPORTS - NS_DECL_NSIROLLUPLISTENER - NS_DECL_NSIMENUROLLUP - NS_DECL_NSITIMERCALLBACK - - static nsXULPopupManager* sInstance; - - // initialize and shutdown methods called by nsLayoutStatics - static nsresult Init(); - static void Shutdown(); - - // returns a weak reference to the popup manager instance, could return null - // if a popup manager could not be allocated - static nsXULPopupManager* GetInstance(); - - // given a menu frame, find the prevous or next menu frame. If aPopup is - // true then navigate a menupopup, from one item on the menu to the previous - // or next one. This is used for cursor navigation between items in a popup - // menu. If aIsPopup is false, the navigation is on a menubar, so navigate - // between menus on the menubar. This is used for left/right cursor navigation. - // - // Items that not valid, such as non-menu or menuitem elements are skipped, - // and the next or previous item after that is checked. - // - // If aStart is null, the first valid item is retrieved for GetNextMenuItem - // or the last valid item for GetPreviousMenuItem is used. - // - // aParent - the parent menubar or menupopup - // aStart - the menu/menuitem to start navigation from. GetPreviousMenuItem - // returns the item before it, while GetNextMenuItem returns the - // next item. - // aIsPopup - true for menupopups, false for menubars - static nsMenuFrame* GetPreviousMenuItem(nsIFrame* aParent, - nsMenuFrame* aStart, - PRBool aIsPopup); - static nsMenuFrame* GetNextMenuItem(nsIFrame* aParent, - nsMenuFrame* aStart, - PRBool aIsPopup); - - // returns true if the menu item aContent is a valid menuitem which may - // be navigated to. aIsPopup should be true for items on a popup, or false - // for items on a menubar. - static PRBool IsValidMenuItem(nsPresContext* aPresContext, - nsIContent* aContent, - PRBool aOnPopup); - - // inform the popup manager that a menu bar has been activated or deactivated, - // either because one of its menus has opened or closed, or that the menubar - // has been focused such that its menus may be navigated with the keyboard. - // aActivate should be true when the menubar should be focused, and false - // when the active menu bar should be defocused. In the latter case, if - // aMenuBar isn't currently active, yet another menu bar is, that menu bar - // will remain active. - void SetActiveMenuBar(nsMenuBarFrame* aMenuBar, PRBool aActivate); - - // retrieve the node and offset of the last mouse event used to open a - // context menu. This information is determined from the rangeParent and - // the rangeOffset of the event supplied from the last call to SetMouseLocation. - // This is used by the implementation of nsIDOMXULDocument::GetPopupRangeParent - // and nsIDOMXULDocument::GetPopupRangeOffset. - void GetMouseLocation(nsIDOMNode** aNode, PRInt32* aOffset); - // set the mouse event that was used to activate the next popup to be opened. - void SetMouseLocation(nsIDOMEvent* aEvent); - - /** - * Open a given its content node. If aSelectFirstItem is - * set to true, the first item on the menu will automatically be - * selected. If aAsynchronous is true, the event will be dispatched - * asynchronously. This should be true when called from frame code. - */ - void ShowMenu(nsIContent *aMenu, PRBool aSelectFirstItem, PRBool aAsynchronous); - - /** - * Open a popup, either anchored or unanchored. If aSelectFirstItem is - * true, then the first item in the menu is selected. The arguments are - * similar to those for nsIPopupBoxObject::OpenPopup. - * - * This fires the popupshowing event synchronously. - */ - void ShowPopup(nsIContent* aPopup, - nsIContent* aAnchorContent, - const nsAString& aPosition, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aIsContextMenu, - PRBool aAttributesOverride, - PRBool aSelectFirstItem); - - /** - * Open a popup at a specific screen position specified by aXPos and aYPos, - * measured in CSS pixels. - * - * This fires the popupshowing event synchronously. - */ - void ShowPopupAtScreen(nsIContent* aPopup, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aIsContextMenu); - - /** - * This method is provided only for compatibility with an older popup API. - * New code should not call this function and should call ShowPopup instead. - * - * This fires the popupshowing event synchronously. - */ - void ShowPopupWithAnchorAlign(nsIContent* aPopup, - nsIContent* aAnchorContent, - nsAString& aAnchor, - nsAString& aAlign, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aIsContextMenu); - - /* - * Hide a popup aPopup. If the popup is in a , then also inform the - * menu that the popup is being hidden. - * - * aHideChain - true if the entire chain of menus should be closed. If false, - * only this popup is closed. - * aDeselectMenu - true if the parent of the popup should be deselected. - * This will be false when the menu is closed by pressing the - * Escape key. - * aAsynchronous - true if the first popuphiding event should be sent - * asynchrously. This should be true if HidePopup is called - * from a frame. - */ - void HidePopup(nsIContent* aPopup, - PRBool aHideChain, - PRBool aDeselectMenu, - PRBool aAsynchronous); - - /** - * Hide a popup after a short delay. This is used when rolling over menu items. - * This timer is stored in mCloseTimer. The timer may be cancelled and the popup - * closed by calling KillMenuTimer. - */ - void HidePopupAfterDelay(nsMenuPopupFrame* aPopup); - - /** - * Hide all of the popups from a given document. This should be called when the - * document is hidden. - */ - void HidePopupsInDocument(nsIDocument* aDocument); - - /** - * Execute a menu command from the triggering event aEvent. - * - * aMenu - a menuitem to execute - * aEvent - the mouse event which triggered the menu to be executed, - * may be null - */ - void ExecuteMenu(nsIContent* aMenu, nsEvent* aEvent); - - /** - * Return true if the popup for the supplied menu parent is open. - */ - PRBool IsPopupOpenForMenuParent(nsIMenuParent* aMenuParent); - - /** - * Return false if a popup may not be opened. This will return false if the - * popup is already open, if the popup is in a content shell that is not - * focused, or if it is a submenu of another menu that isn't open. - */ - PRBool MayShowPopup(nsMenuPopupFrame* aFrame); - - /** - * Called when a popup frame is destroyed. In this case, just remove the - * item and later popups from the list. No point going through HidePopup as - * the frames have gone away. - */ - void PopupDestroyed(nsMenuPopupFrame* aFrame); - - /** - * Returns true if there is a context menu open. If aPopup is specified, - * then the context menu must be later in the chain than aPopup. If aPopup - * is null, returns true if any context menu at all is open. - */ - PRBool HasContextMenu(nsMenuPopupFrame* aPopup); - - /** - * Update the commands for the menus within the menu popup for a given - * content node. aPopup should be a XUL menupopup element. This method - * changes attributes on the children of aPopup, and deals only with the - * content of the popup, not the frames. - */ - void UpdateMenuItems(nsIContent* aPopup); - - /** - * Stop the timer which hides a popup after a delay, started by a previous - * call to HidePopupAfterDelay. In addition, the popup awaiting to be hidden - * is closed asynchronously. - */ - void KillMenuTimer(); - - /** - * Handles navigation for menu accelkeys. Returns true if the key has - * been handled. - */ - PRBool HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent); - - /** - * Handles cursor navigation within a menu. Returns true if the key has - * been handled. - */ - PRBool HandleKeyboardNavigation(PRUint32 aKeyCode); - - NS_IMETHODIMP HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; } - - NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent); - NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent); - NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent); - -protected: - nsXULPopupManager(); - ~nsXULPopupManager(); - - // get the frame for a content node aContent if the frame's type - // matches aFrameType. Otherwise, return null. - nsIFrame* GetFrameOfTypeForContent(nsIContent* aContent, nsIAtom* aFrameType); - - // get the nsMenuFrame, if any, for the given content node - nsMenuFrame* GetMenuFrameForContent(nsIContent* aContent); - - // get the nsMenuPopupFrame, if any, for the given content node - nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent); - - // callbacks for ShowPopup and HidePopup as events may be done asynchronously - void ShowPopupCallback(nsIContent* aPopup, - nsMenuPopupFrame* aPopupFrame, - PRBool aIsContextMenu, - PRBool aSelectFirstItem); - void HidePopupCallback(nsIContent* aPopup, - nsMenuPopupFrame* aPopupFrame, - nsIContent* aNextPopup, - nsIContent* aLastPopup, - PRBool aIsMenu, - PRBool aDeselectMenu); - - /** - * Fire a popupshowing event on the popup aPopup and then open the popup. - * - * aPopup - the popup node to open - * aMenu - should be set to the parent menu if this is a popup associated - * with a menu. Otherwise, should be null. - * aPresContext - the prescontext - * aIsContextMenu - true for context menus - * aSelectFirstItem - true to select the first item in the menu - */ - void FirePopupShowingEvent(nsIContent* aPopup, - nsIContent* aMenu, - nsPresContext* aPresContext, - PRBool aIsContextMenu, - PRBool aSelectFirstItem); - - /** - * Fire a popuphiding event and then hide the popup. This will be called - * recursively if aNextPopup and aLastPopup are set in order to hide a chain - * of open menus. If these are not set, only one popup is closed. However, - * if aIsMenu is true, yet the next popup is not a menu, then this ends the - * closing of popups. This allows a menulist inside a non-menu to close up - * the menu but not close up the panel it is contained within. - * - * aPopup - the popup to hide - * aNextPopup - the next popup to hide - * aLastPopup - the last popup in the chain to hide - * aPresContext - nsPresContext for the popup's frame - * aIsMenu - true if aPopup is a menu. - * aDeselectMenu - true to unhighlight the menu when hiding it - */ - void FirePopupHidingEvent(nsIContent* aPopup, - nsIContent* aNextPopup, - nsIContent* aLastPopup, - nsPresContext *aPresContext, - PRBool aIsMenu, - PRBool aDeselectMenu); - - // handle keyboard navigation within a menu popup. Returns true if the - // key was handled and that other default handling should not occur. - PRBool HandleKeyboardNavigationInPopup(nsMenuChainItem* item, - nsNavigationDirection aDir); - - /** - * Set mouse capturing for the current popup. This traps mouse clicks that - * occur outside the popup so that it can be closed up. aOldPopup should be - * set to the popup that was previously the current popup. - */ - void SetCaptureState(nsIContent *aOldPopup); - - /** - * Key event listeners are attached to the document containing the current - * menu for menu and shortcut navigation. Only one listener is needed at a - * time, stored in mKeyListener, so switch it only if the document changes. - * Having menus in different documents is very rare, so the listeners will - * usually only be attached when the first menu opens and removed when all - * menus have closed. - * - * This is also used when only a menubar is active without any open menus, - * so that keyboard navigation between menus on the menubar may be done. - */ - void UpdateKeyboardListeners(); - - // the document the key event listener is attached to - nsCOMPtr mKeyListener; - - // widget that is currently listening to rollup events - nsCOMPtr mWidget; - - // range parent and offset set in SetMouseLocation - nsCOMPtr mRangeParent; - PRInt32 mRangeOffset; - - // set to the currently active menu bar, if any - nsMenuBarFrame* mActiveMenuBar; - - // linked list of dismissable menus. - nsMenuChainItem* mCurrentMenu; - - // linked list of panels - nsMenuChainItem* mPanels; - - // timer used for HidePopupAfterDelay - nsCOMPtr mCloseTimer; - - // a popup that is waiting on the timer - nsMenuPopupFrame* mTimerMenu; -}; - -#endif diff --git a/layout/xul/base/src/Makefile.in b/layout/xul/base/src/Makefile.in index 1303b60ddde..1882b27fddf 100644 --- a/layout/xul/base/src/Makefile.in +++ b/layout/xul/base/src/Makefile.in @@ -119,13 +119,14 @@ CPPSRCS += \ nsMenuFrame.cpp \ nsMenuBarFrame.cpp \ nsMenuBarListener.cpp \ + nsMenuListener.cpp \ + nsMenuDismissalListener.cpp \ nsPopupSetFrame.cpp \ nsTitleBarFrame.cpp \ nsResizerFrame.cpp \ nsListBoxBodyFrame.cpp \ nsListItemFrame.cpp \ nsListBoxLayout.cpp \ - nsXULPopupManager.cpp \ $(NULL) endif diff --git a/layout/xul/base/src/nsMenuBarFrame.h b/layout/xul/base/src/nsMenuBarFrame.h index a70ab6c50db..2089d1e2922 100644 --- a/layout/xul/base/src/nsMenuBarFrame.h +++ b/layout/xul/base/src/nsMenuBarFrame.h @@ -49,12 +49,13 @@ #include "nsIAtom.h" #include "nsCOMPtr.h" #include "nsBoxFrame.h" -#include "nsMenuFrame.h" #include "nsMenuBarListener.h" +#include "nsMenuListener.h" #include "nsIMenuParent.h" #include "nsIWidget.h" class nsIContent; +class nsIMenuFrame; nsIFrame* NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); @@ -62,25 +63,47 @@ class nsMenuBarFrame : public nsBoxFrame, public nsIMenuParent { public: nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext); + virtual ~nsMenuBarFrame(); + + NS_DECL_ISUPPORTS // nsIMenuParentInterface - virtual nsMenuFrame* GetCurrentMenuItem(); - NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem); - virtual void CurrentMenuIsBeingDestroyed(); - NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, PRBool aSelectFirstItem); - + virtual nsIMenuFrame* GetCurrentMenuItem(); + NS_IMETHOD SetCurrentMenuItem(nsIMenuFrame* aMenuItem); + virtual nsIMenuFrame* GetNextMenuItem(nsIMenuFrame* aStart); + virtual nsIMenuFrame* GetPreviousMenuItem(nsIMenuFrame* aStart); NS_IMETHOD SetActive(PRBool aActiveFlag); + NS_IMETHOD GetIsActive(PRBool& isActive) { isActive = IsActive(); return NS_OK; } + NS_IMETHOD IsMenuBar(PRBool& isMenuBar) { isMenuBar = PR_TRUE; return NS_OK; } + NS_IMETHOD ConsumeOutsideClicks(PRBool& aConsumeOutsideClicks) \ + {aConsumeOutsideClicks = PR_FALSE; return NS_OK;} + NS_IMETHOD ClearRecentlyRolledUp(); + NS_IMETHOD RecentlyRolledUp(nsIMenuFrame *aMenuFrame, PRBool *aJustRolledUp); - virtual PRBool IsMenuBar() { return PR_TRUE; } - virtual PRBool IsContextMenu() { return PR_FALSE; } - virtual PRBool IsActive() { return mIsActive; } - virtual PRBool IsMenu() { return PR_FALSE; } - virtual PRBool IsOpen() { return PR_TRUE; } // menubars are considered always open + NS_IMETHOD SetIsContextMenu(PRBool aIsContextMenu) { return NS_OK; } + NS_IMETHOD GetIsContextMenu(PRBool& aIsContextMenu) { aIsContextMenu = PR_FALSE; return NS_OK; } - PRBool IsMenuOpen() { return mCurrentMenu && mCurrentMenu->IsOpen(); } + NS_IMETHOD GetParentPopup(nsIMenuParent** aResult) { *aResult = nsnull; + return NS_OK;} - void InstallKeyboardNavigator(); - void RemoveKeyboardNavigator(); + NS_IMETHOD IsActive() { return mIsActive; } + + NS_IMETHOD IsOpen(); + NS_IMETHOD KillPendingTimers(); + NS_IMETHOD CancelPendingTimers() { return NS_OK; } + + // Closes up the chain of open cascaded menus. + NS_IMETHOD DismissChain(); + + // Hides the chain of cascaded menus without closing them up. + NS_IMETHOD HideChain(); + + NS_IMETHOD InstallKeyboardNavigator(); + NS_IMETHOD RemoveKeyboardNavigator(); + + NS_IMETHOD GetWidget(nsIWidget **aWidget); + // The dismissal listener gets created and attached to the window. + NS_IMETHOD AttachedDismissalListener() { return NS_OK; } NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, @@ -88,24 +111,24 @@ public: virtual void Destroy(); - virtual nsIAtom* GetType() const { return nsGkAtoms::menuBarFrame; } - // Non-interface helpers - // Called when a menu on the menu bar is clicked on. Returns a menu if one - // needs to be closed. - nsMenuFrame* ToggleMenuActiveState(); - - // indicate that a menu on the menubar was closed. Returns true if the caller - // may deselect the menuitem. - virtual PRBool MenuClosed(); - - // Called when Enter is pressed while the menubar is focused. If the current - // menu is open, let the child handle the key. - nsMenuFrame* Enter(); + // Called when a menu on the menu bar is clicked on. + void ToggleMenuActiveState(); + + // Used to move up, down, left, and right in menus. + NS_IMETHOD KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag); + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); + // Called when the ESC key is held down to close levels of menus. + NS_IMETHOD Escape(PRBool& aHandledFlag); + // Called to execute a menu item. + NS_IMETHOD Enter(); // Used to handle ALT+key combos - nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent); + nsIMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent); + + PRBool IsValidItem(nsIContent* aContent); + PRBool IsDisabled(nsIContent* aContent); virtual PRBool IsFrameOfType(PRUint32 aFlags) const { @@ -124,11 +147,14 @@ public: protected: nsMenuBarListener* mMenuBarListener; // The listener that tells us about key and mouse events. + nsMenuListener* mKeyboardNavigator; PRBool mIsActive; // Whether or not the menu bar is active (a menu item is highlighted or shown). - // The current menu that is active (highlighted), which may not be open. This will - // be null if no menu is active. - nsMenuFrame* mCurrentMenu; + nsIMenuFrame* mCurrentMenu; // The current menu that is active. + + // Can contain a menu that was rolled up via nsIMenuDismissalListener::Rollup() + // if nothing has happened since the last click. Otherwise, contains nsnull. + nsIMenuFrame* mRecentRollupMenu; nsIDOMEventTarget* mTarget; diff --git a/layout/xul/base/src/nsMenuBarListener.cpp b/layout/xul/base/src/nsMenuBarListener.cpp index 65bcec109ee..7e042f3a7b8 100644 --- a/layout/xul/base/src/nsMenuBarListener.cpp +++ b/layout/xul/base/src/nsMenuBarListener.cpp @@ -40,7 +40,6 @@ #include "nsMenuBarListener.h" #include "nsMenuBarFrame.h" -#include "nsMenuPopupFrame.h" #include "nsIDOMKeyListener.h" #include "nsIDOMEventTarget.h" #include "nsIDOMEventListener.h" @@ -132,18 +131,6 @@ void nsMenuBarListener::InitAccessKey() nsContentUtils::GetBoolPref("ui.key.menuAccessKeyFocuses"); } -void -nsMenuBarListener::ToggleMenuActiveState() -{ - nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState(); - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && closemenu) { - nsMenuPopupFrame* popupFrame = closemenu->GetPopup(); - if (popupFrame) - pm->HidePopup(popupFrame->GetContent(), PR_FALSE, PR_FALSE, PR_TRUE); - } -} - //////////////////////////////////////////////////////////////////////// nsresult nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) @@ -174,7 +161,7 @@ nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) { // The access key was down and is now up, and no other // keys were pressed in between. - ToggleMenuActiveState(); + mMenuBarFrame->ToggleMenuActiveState(); } mAccessKeyDown = PR_FALSE; @@ -182,7 +169,7 @@ nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) if (active) { aKeyEvent->StopPropagation(); aKeyEvent->PreventDefault(); - return NS_OK; // I am consuming event + return NS_ERROR_BASE; // I am consuming event } } @@ -193,6 +180,8 @@ nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) nsresult nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) { + mMenuBarFrame->ClearRecentlyRolledUp(); + // if event has already been handled, bail nsCOMPtr uiEvent ( do_QueryInterface(aKeyEvent) ); if ( uiEvent ) { @@ -240,13 +229,14 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) // Do shortcut navigation. // A letter was pressed. We want to see if a shortcut gets matched. If // so, we'll know the menu got activated. - nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); - if (result) { - mMenuBarFrame->SetActive(PR_TRUE); - result->OpenMenu(PR_TRUE); + PRBool active = PR_FALSE; + mMenuBarFrame->ShortcutNavigation(keyEvent, active); + + if (active) { aKeyEvent->StopPropagation(); aKeyEvent->PreventDefault(); - retVal = NS_OK; // I am consuming event + + retVal = NS_ERROR_BASE; // I am consuming event } } #if !defined(XP_MAC) && !defined(XP_MACOSX) @@ -255,17 +245,16 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) if ((GetModifiers(keyEvent) & ~MODIFIER_CONTROL) == 0) { // The F10 key just went down by itself or with ctrl pressed. // In Windows, both of these activate the menu bar. - ToggleMenuActiveState(); + mMenuBarFrame->ToggleMenuActiveState(); aKeyEvent->StopPropagation(); aKeyEvent->PreventDefault(); - return NS_OK; // consume the event + return NS_ERROR_BASE; // consume the event } } #endif // !XP_MAC && !XP_MACOSX } } - return retVal; } @@ -360,8 +349,10 @@ nsMenuBarListener::Focus(nsIDOMEvent* aEvent) nsresult nsMenuBarListener::Blur(nsIDOMEvent* aEvent) { - if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) { - ToggleMenuActiveState(); + if (!mMenuBarFrame->IsOpen() && mMenuBarFrame->IsActive()) { + mMenuBarFrame->ToggleMenuActiveState(); + PRBool handled; + mMenuBarFrame->Escape(handled); mAccessKeyDown = PR_FALSE; } return NS_OK; // means I am NOT consuming event @@ -371,8 +362,12 @@ nsMenuBarListener::Blur(nsIDOMEvent* aEvent) nsresult nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) { - if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) - ToggleMenuActiveState(); + if (!mMenuBarFrame->IsOpen() && mMenuBarFrame->IsActive()) { + mMenuBarFrame->ToggleMenuActiveState(); + PRBool handled; + mMenuBarFrame->Escape(handled); + } + mAccessKeyDown = PR_FALSE; return NS_OK; // means I am NOT consuming event @@ -382,6 +377,8 @@ nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) nsresult nsMenuBarListener::MouseUp(nsIDOMEvent* aMouseEvent) { + mMenuBarFrame->ClearRecentlyRolledUp(); + return NS_OK; // means I am NOT consuming event } diff --git a/layout/xul/base/src/nsMenuBarListener.h b/layout/xul/base/src/nsMenuBarListener.h index cf2b5a1bb6d..bce41f8c7b7 100644 --- a/layout/xul/base/src/nsMenuBarListener.h +++ b/layout/xul/base/src/nsMenuBarListener.h @@ -87,10 +87,6 @@ protected: static PRUint32 GetModifiers(nsIDOMKeyEvent* event); - // This should only be called by the nsMenuBarListener during event dispatch, - // thus ensuring that this doesn't get destroyed during the process. - void ToggleMenuActiveState(); - nsMenuBarFrame* mMenuBarFrame; // The menu bar object. PRBool mAccessKeyDown; // Whether or not the ALT key is currently down. static PRBool mAccessKeyFocuses; // Does the access key by itself focus the menubar? diff --git a/layout/xul/base/src/nsMenuBoxObject.cpp b/layout/xul/base/src/nsMenuBoxObject.cpp index eea6173894d..a3700907857 100644 --- a/layout/xul/base/src/nsMenuBoxObject.cpp +++ b/layout/xul/base/src/nsMenuBoxObject.cpp @@ -39,12 +39,11 @@ #include "nsIMenuBoxObject.h" #include "nsBoxObject.h" #include "nsIPresShell.h" +#include "nsIMenuFrame.h" #include "nsIFrame.h" #include "nsGUIEvent.h" #include "nsIDOMNSUIEvent.h" #include "nsMenuBarListener.h" -#include "nsMenuFrame.h" -#include "nsMenuPopupFrame.h" #include "nsPopupSetFrame.h" class nsMenuBoxObject : public nsIMenuBoxObject, public nsBoxObject @@ -90,41 +89,47 @@ nsMenuBoxObject::~nsMenuBoxObject() /* void openMenu (in boolean openFlag); */ NS_IMETHODIMP nsMenuBoxObject::OpenMenu(PRBool aOpenFlag) { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - nsIFrame* frame = GetFrame(PR_FALSE); - if (frame) { - if (aOpenFlag) { - nsCOMPtr content = mContent; - pm->ShowMenu(content, PR_FALSE, PR_FALSE); - } - else { - if (frame->GetType() == nsGkAtoms::menuFrame) { - nsMenuPopupFrame* popupFrame = (NS_STATIC_CAST(nsMenuFrame *, frame))->GetPopup(); - if (popupFrame) - pm->HidePopup(popupFrame->GetContent(), PR_FALSE, PR_TRUE, PR_FALSE); - } - } - } - } + nsIFrame* frame = GetFrame(PR_FALSE); + if (!frame) + return NS_OK; - return NS_OK; + if (!nsPopupSetFrame::MayOpenPopup(frame)) + return NS_OK; + + nsIMenuFrame* menuFrame; + CallQueryInterface(frame, &menuFrame); + if (!menuFrame) + return NS_OK; + + return menuFrame->OpenMenu(aOpenFlag); } NS_IMETHODIMP nsMenuBoxObject::GetActiveChild(nsIDOMElement** aResult) { *aResult = nsnull; nsIFrame* frame = GetFrame(PR_FALSE); - if (frame && frame->GetType() == nsGkAtoms::menuFrame) - return NS_STATIC_CAST(nsMenuFrame *, frame)->GetActiveChild(aResult); + if (!frame) + return NS_OK; + + nsIMenuFrame* menuFrame; + CallQueryInterface(frame, &menuFrame); + if (menuFrame) + menuFrame->GetActiveChild(aResult); return NS_OK; } NS_IMETHODIMP nsMenuBoxObject::SetActiveChild(nsIDOMElement* aResult) { nsIFrame* frame = GetFrame(PR_FALSE); - if (frame && frame->GetType() == nsGkAtoms::menuFrame) - return NS_STATIC_CAST(nsMenuFrame *, frame)->SetActiveChild(aResult); + if (!frame) + return NS_OK; + + nsIMenuFrame* menuFrame; + CallQueryInterface(frame, &menuFrame); + if (menuFrame) { + menuFrame->MarkAsGenerated(); + menuFrame->SetActiveChild(aResult); + } return NS_OK; } @@ -134,10 +139,6 @@ NS_IMETHODIMP nsMenuBoxObject::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent, PRBool* *aHandledFlag = PR_FALSE; NS_ENSURE_ARG(aKeyEvent); - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (!pm) - return NS_OK; - // if event has already been handled, bail nsCOMPtr uiEvent(do_QueryInterface(aKeyEvent)); if (!uiEvent) @@ -152,11 +153,12 @@ NS_IMETHODIMP nsMenuBoxObject::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent, PRBool* return NS_OK; nsIFrame* frame = GetFrame(PR_FALSE); - if (!frame || frame->GetType() != nsGkAtoms::menuFrame) + if (!frame) return NS_OK; - nsMenuPopupFrame* popupFrame = NS_STATIC_CAST(nsMenuFrame *, frame)->GetPopup(); - if (!popupFrame) + nsIMenuFrame* menuFrame; + CallQueryInterface(frame, &menuFrame); + if (!menuFrame) return NS_OK; PRUint32 keyCode; @@ -166,11 +168,9 @@ NS_IMETHODIMP nsMenuBoxObject::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent, PRBool* case NS_VK_DOWN: case NS_VK_HOME: case NS_VK_END: - *aHandledFlag = pm->HandleKeyboardNavigation(keyCode); - return NS_OK; + return menuFrame->KeyboardNavigation(keyCode, *aHandledFlag); default: - *aHandledFlag = pm->HandleShortcutNavigation(aKeyEvent); - return NS_OK; + return menuFrame->ShortcutNavigation(aKeyEvent, *aHandledFlag); } } diff --git a/layout/xul/base/src/nsMenuDismissalListener.cpp b/layout/xul/base/src/nsMenuDismissalListener.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/layout/xul/base/src/nsMenuDismissalListener.h b/layout/xul/base/src/nsMenuDismissalListener.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/layout/xul/base/src/nsMenuFrame.cpp b/layout/xul/base/src/nsMenuFrame.cpp index 3f1f4d30efd..274ffd91d58 100644 --- a/layout/xul/base/src/nsMenuFrame.cpp +++ b/layout/xul/base/src/nsMenuFrame.cpp @@ -71,19 +71,18 @@ #include "nsIServiceManager.h" #include "nsCSSFrameConstructor.h" #include "nsIDOMKeyEvent.h" -#include "nsEventDispatcher.h" -#include "nsIPrivateDOMEvent.h" #include "nsIScrollableView.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsIStringBundle.h" #include "nsGUIEvent.h" +#include "nsIEventStateManager.h" #include "nsContentUtils.h" #include "nsDisplayList.h" #include "nsIReflowCallback.h" -#define NS_MENU_POPUP_LIST_INDEX 0 +#define NS_MENU_POPUP_LIST_INDEX 0 #if defined(XP_WIN) || defined(XP_OS2) #define NSCONTEXTMENUISMOUSEUP 1 @@ -142,10 +141,11 @@ NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame) nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): nsBoxFrame(aShell, aContext), mIsMenu(PR_FALSE), + mMenuOpen(PR_FALSE), + mCreateHandlerSucceeded(PR_FALSE), mChecked(PR_FALSE), mType(eMenuType_Normal), mMenuParent(nsnull), - mPopupFrame(nsnull), mLastPref(-1,-1) { @@ -155,25 +155,15 @@ NS_IMETHODIMP nsMenuFrame::SetParent(const nsIFrame* aParent) { nsBoxFrame::SetParent(aParent); - InitMenuParent(NS_CONST_CAST(nsIFrame *, aParent)); - return NS_OK; -} + const nsIFrame* currFrame = aParent; + while (!mMenuParent && currFrame) { + // Set our menu parent. + CallQueryInterface(NS_CONST_CAST(nsIFrame*, currFrame), &mMenuParent); -void -nsMenuFrame::InitMenuParent(nsIFrame* aParent) -{ - while (aParent) { - nsIAtom* type = aParent->GetType(); - if (type == nsGkAtoms::menuPopupFrame) { - mMenuParent = NS_STATIC_CAST(nsMenuPopupFrame *, aParent); - break; - } - else if (type == nsGkAtoms::menuBarFrame) { - mMenuParent = NS_STATIC_CAST(nsMenuBarFrame *, aParent); - break; - } - aParent = aParent->GetParent(); + currFrame = currFrame->GetParent(); } + + return NS_OK; } class nsASyncMenuInitialization : public nsIReflowCallback @@ -187,8 +177,10 @@ public: virtual PRBool ReflowFinished() { PRBool shouldFlush = PR_FALSE; if (mWeakFrame.IsAlive()) { - if (mWeakFrame.GetFrame()->GetType() == nsGkAtoms::menuFrame) { - nsMenuFrame* menu = NS_STATIC_CAST(nsMenuFrame*, mWeakFrame.GetFrame()); + nsIMenuFrame* imenu = nsnull; + CallQueryInterface(mWeakFrame.GetFrame(), &imenu); + if (imenu) { + nsMenuFrame* menu = NS_STATIC_CAST(nsMenuFrame*, imenu); menu->UpdateMenuType(menu->PresContext()); shouldFlush = PR_TRUE; } @@ -212,7 +204,13 @@ nsMenuFrame::Init(nsIContent* aContent, if (NS_UNLIKELY(!mTimerMediator)) return NS_ERROR_OUT_OF_MEMORY; - InitMenuParent(aParent); + nsIFrame* currFrame = aParent; + while (!mMenuParent && currFrame) { + // Set our menu parent. + CallQueryInterface(currFrame, &mMenuParent); + + currFrame = currFrame->GetParent(); + } //load the display strings for the keyboard accelerators, but only once if (gRefCnt++ == 0) { @@ -278,7 +276,7 @@ nsIFrame* nsMenuFrame::GetFirstChild(nsIAtom* aListName) const { if (nsGkAtoms::popupList == aListName) { - return mPopupFrame; + return mPopupFrames.FirstChild(); } return nsBoxFrame::GetFirstChild(aListName); } @@ -287,22 +285,38 @@ NS_IMETHODIMP nsMenuFrame::SetInitialChildList(nsIAtom* aListName, nsIFrame* aChildList) { - // Check for a menupopup and move it to mPopupFrame - nsFrameList frames(aChildList); - nsIFrame* frame = frames.FirstChild(); - while (frame) { - if (frame->GetType() == nsGkAtoms::menuPopupFrame) { - // Remove this frame from the list and set it as mPopupFrame - frames.RemoveFrame(frame); - mPopupFrame = (nsMenuPopupFrame *)frame; - aChildList = frames.FirstChild(); - break; - } - frame = frame->GetNextSibling(); - } + nsresult rv = NS_OK; + if (nsGkAtoms::popupList == aListName) { + mPopupFrames.SetFrames(aChildList); + } else { - // Didn't find it. - return nsBoxFrame::SetInitialChildList(aListName, aChildList); + nsFrameList frames(aChildList); + + // We may have a menupopup in here. Get it out, and move it into + // the popup frame list. + nsIFrame* frame = frames.FirstChild(); + while (frame) { + nsIMenuParent *menuPar; + CallQueryInterface(frame, &menuPar); + if (menuPar) { + PRBool isMenuBar; + menuPar->IsMenuBar(isMenuBar); + if (!isMenuBar) { + // Remove this frame from the list and place it in the other list. + frames.RemoveFrame(frame); + mPopupFrames.AppendFrame(this, frame); + nsIFrame* first = frames.FirstChild(); + rv = nsBoxFrame::SetInitialChildList(aListName, first); + return rv; + } + } + frame = frame->GetNextSibling(); + } + + // Didn't find it. + rv = nsBoxFrame::SetInitialChildList(aListName, aChildList); + } + return rv; } nsIAtom* @@ -311,9 +325,27 @@ nsMenuFrame::GetAdditionalChildListName(PRInt32 aIndex) const if (NS_MENU_POPUP_LIST_INDEX == aIndex) { return nsGkAtoms::popupList; } + return nsnull; } +nsresult +nsMenuFrame::DestroyPopupFrames(nsPresContext* aPresContext) +{ + // Remove our frame mappings + nsCSSFrameConstructor* frameConstructor = + aPresContext->PresShell()->FrameConstructor(); + nsIFrame* curFrame = mPopupFrames.FirstChild(); + while (curFrame) { + frameConstructor->RemoveMappingsForFrameSubtree(curFrame); + curFrame = curFrame->GetNextSibling(); + } + + // Cleanup frames in popup child list + mPopupFrames.DestroyFrames(); + return NS_OK; +} + void nsMenuFrame::Destroy() { @@ -328,15 +360,20 @@ nsMenuFrame::Destroy() // doesn't try to interact with a deallocated frame. mTimerMediator->ClearFrame(); + nsWeakFrame weakFrame(this); // are we our menu parent's current menu item? - if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) { - // yes; tell it that we're going away - mMenuParent->CurrentMenuIsBeingDestroyed(); + if (mMenuParent) { + nsIMenuFrame *curItem = mMenuParent->GetCurrentMenuItem(); + if (curItem == this) { + // yes; tell it that we're going away + mMenuParent->SetCurrentMenuItem(nsnull); + ENSURE_TRUE(weakFrame.IsAlive()); + } } - if (mPopupFrame) - mPopupFrame->Destroy(); - + UngenerateMenu(); + ENSURE_TRUE(weakFrame.IsAlive()); + DestroyPopupFrames(PresContext()); nsBoxFrame::Destroy(); } @@ -355,18 +392,16 @@ nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, return WrapListsInRedirector(aBuilder, set, aLists); } -NS_IMETHODIMP +NS_IMETHODIMP nsMenuFrame::HandleEvent(nsPresContext* aPresContext, - nsGUIEvent* aEvent, - nsEventStatus* aEventStatus) + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) { NS_ENSURE_ARG_POINTER(aEventStatus); nsWeakFrame weakFrame(this); if (*aEventStatus == nsEventStatus_eIgnore) *aEventStatus = nsEventStatus_eConsumeDoDefault; - - PRBool onmenu = IsOnMenu(); - + if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) { nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; PRUint32 keyCode = keyEvent->keyCode; @@ -374,27 +409,46 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->isMeta) || (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) - OpenMenu(PR_FALSE); + OpenMenu(PR_TRUE); #else // On other platforms, toggle menulist on unmodified F4 or Alt arrow if ((keyCode == NS_VK_F4 && !keyEvent->isAlt) || ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->isAlt)) - ToggleMenuState(); + OpenMenu(!IsOpen()); #endif } else if (aEvent->eventStructType == NS_MOUSE_EVENT && aEvent->message == NS_MOUSE_BUTTON_DOWN && NS_STATIC_CAST(nsMouseEvent*, aEvent)->button == nsMouseEvent::eLeftButton && !IsDisabled() && IsMenu()) { + PRBool isMenuBar = PR_FALSE; + if (mMenuParent) + mMenuParent->IsMenuBar(isMenuBar); + // The menu item was selected. Bring up the menu. // We have children. - if (!mMenuParent || mMenuParent->IsMenuBar()) { + if ( isMenuBar || !mMenuParent ) { ToggleMenuState(); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + + if (!IsOpen() && mMenuParent) { + // We closed up. The menu bar should always be + // deactivated when this happens. + mMenuParent->SetActive(PR_FALSE); + } } - else { - if (!IsOpen()) - OpenMenu(PR_FALSE); - } + else + if ( !IsOpen() ) { + // one of our siblings is probably open and even possibly waiting + // for its close timer to fire. Tell our parent to close it down. Not + // doing this before its timer fires will cause the rollup state to + // get very confused. + if ( mMenuParent ) + mMenuParent->KillPendingTimers(); + + // safe to open up + OpenMenu(PR_TRUE); + } } else if ( #ifndef NSCONTEXTMENUISMOUSEUP @@ -405,7 +459,7 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, #else aEvent->message == NS_CONTEXTMENU && #endif - onmenu && !IsMenu() && !IsDisabled()) { + mMenuParent && !IsMenu() && !IsDisabled()) { // if this menu is a context menu it accepts right-clicks...fire away! // Make sure we cancel default processing of the context menu event so // that it doesn't bubble and get seen again by the popuplistener and show @@ -416,7 +470,9 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, // on others we get it on a mouse down. For the ones where we get it on a // mouse down, we must continue listening for the right button up event to // dismiss the menu. - if (mMenuParent->IsContextMenu()) { + PRBool isContextMenu = PR_FALSE; + mMenuParent->GetIsContextMenu(isContextMenu); + if ( isContextMenu ) { *aEventStatus = nsEventStatus_eConsumeNoDefault; Execute(aEvent); } @@ -424,7 +480,7 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, else if (aEvent->eventStructType == NS_MOUSE_EVENT && aEvent->message == NS_MOUSE_BUTTON_UP && NS_STATIC_CAST(nsMouseEvent*, aEvent)->button == nsMouseEvent::eLeftButton && - !IsMenu() && !IsDisabled()) { + !IsMenu() && mMenuParent && !IsDisabled()) { // Execute the execute event handler. Execute(aEvent); } @@ -436,42 +492,52 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, } // Deactivate the menu. + PRBool isActive = PR_FALSE; + PRBool isMenuBar = PR_FALSE; if (mMenuParent) { - PRBool onmenubar = mMenuParent->IsMenuBar(); - if (!(onmenubar && mMenuParent->IsActive())) { - if (IsMenu() && !onmenubar && IsOpen()) { + mMenuParent->IsMenuBar(isMenuBar); + PRBool cancel = PR_TRUE; + if (isMenuBar) { + mMenuParent->GetIsActive(isActive); + if (isActive) cancel = PR_FALSE; + } + + if (cancel) { + if (IsMenu() && !isMenuBar && mMenuOpen) { // Submenus don't get closed up immediately. } - else - mMenuParent->ChangeMenuItem(nsnull, PR_FALSE); + else mMenuParent->SetCurrentMenuItem(nsnull); } } } - else if (aEvent->message == NS_MOUSE_MOVE && - (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) { + else if (aEvent->message == NS_MOUSE_MOVE && mMenuParent) { if (gEatMouseMove) { gEatMouseMove = PR_FALSE; return NS_OK; } + // we checked for mMenuParent right above + + PRBool isMenuBar = PR_FALSE; + mMenuParent->IsMenuBar(isMenuBar); + // Let the menu parent know we're the new item. - mMenuParent->ChangeMenuItem(this, PR_FALSE); + mMenuParent->SetCurrentMenuItem(this); NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); NS_ENSURE_TRUE(mMenuParent, NS_OK); - + // we need to check if we really became the current menu // item or not - nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem(); + nsIMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem(); if (realCurrentItem != this) { // we didn't (presumably because a context menu was active) return NS_OK; } - // Hovering over a menu in a popup should open it without a need for a click. - // A timer is used so that it doesn't open if the user moves the mouse quickly - // past the menu. This conditional check ensures that only menus have this - // behaviour - if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) { + // If we're a menu (and not a menu item), + // kick off the timer. + if (!IsDisabled() && !isMenuBar && IsMenu() && !mMenuOpen && !mOpenTimer) { + PRInt32 menuDelay = 300; // ms nsCOMPtr lookAndFeel(do_GetService(kLookAndFeelCID)); @@ -481,116 +547,203 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext, // We're a menu, we're built, we're closed, and no timer has been kicked off. mOpenTimer = do_CreateInstance("@mozilla.org/timer;1"); mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); + } } return NS_OK; } -void +NS_IMETHODIMP nsMenuFrame::ToggleMenuState() -{ - if (IsOpen()) - CloseMenu(PR_FALSE); - else - OpenMenu(PR_FALSE); -} - -void -nsMenuFrame::PopupOpened() { nsWeakFrame weakFrame(this); - mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, - NS_LITERAL_STRING("true"), PR_TRUE); - if (!weakFrame.IsAlive()) - return; + if (mMenuOpen) { + OpenMenu(PR_FALSE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + } + else { + PRBool justRolledUp = PR_FALSE; + if (mMenuParent) { + mMenuParent->RecentlyRolledUp(this, &justRolledUp); + } + if (justRolledUp) { + // Don't let a click reopen a menu that was just rolled up + // from the same click. Otherwise, the user can't click on + // a menubar item to toggle its submenu closed. + OpenMenu(PR_FALSE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + SelectMenu(PR_TRUE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + NS_ENSURE_TRUE(mMenuParent, NS_OK); + mMenuParent->SetActive(PR_FALSE); + } + else { + if (mMenuParent) { + mMenuParent->SetActive(PR_TRUE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + } + OpenMenu(PR_TRUE); + } + } + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); if (mMenuParent) { - mMenuParent->SetActive(PR_TRUE); // Make sure the current menu which is being toggled on // the menubar is highlighted mMenuParent->SetCurrentMenuItem(this); - } -} - -void -nsMenuFrame::PopupClosed(PRBool aDeselectMenu) -{ - nsWeakFrame weakFrame(this); - mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, PR_TRUE); - if (!weakFrame.IsAlive()) - return; - - // if the popup is for a menu on a menubar, inform menubar to deactivate - if (mMenuParent && mMenuParent->MenuClosed()) { - if (aDeselectMenu) - SelectMenu(PR_FALSE); - } -} - -// this class is used for dispatching menu activation events asynchronously. -class nsMenuActivateEvent : public nsRunnable -{ -public: - nsMenuActivateEvent(nsIContent *aMenu, - nsPresContext* aPresContext, - PRBool aIsActivate) - : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate) - { - } - - NS_IMETHOD Run() - { - nsAutoString domEventToFire; - - if (mIsActivate) { - // Highlight the menu. - mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, - NS_LITERAL_STRING("true"), PR_TRUE); - // The menuactivated event is used by accessibility to track the user's - // movements through menus - domEventToFire.AssignLiteral("DOMMenuItemActive"); - } - else { - // Unhighlight the menu. - mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, PR_TRUE); - domEventToFire.AssignLiteral("DOMMenuItemInactive"); - } - - nsCOMPtr event; - if (NS_SUCCEEDED(nsEventDispatcher::CreateEvent(mPresContext, nsnull, - NS_LITERAL_STRING("Events"), - getter_AddRefs(event)))) { - event->InitEvent(domEventToFire, PR_TRUE, PR_TRUE); - - nsCOMPtr privateEvent(do_QueryInterface(event)); - privateEvent->SetTrusted(PR_TRUE); - - nsEventDispatcher::DispatchDOMEvent(mMenu, nsnull, event, - mPresContext, nsnull); - } - - return NS_OK; - } - -private: - nsCOMPtr mMenu; - nsCOMPtr mPresContext; - PRBool mIsActivate; -}; - -NS_IMETHODIMP -nsMenuFrame::SelectMenu(PRBool aActivateFlag) -{ - if (mContent) { - nsCOMPtr event = - new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag); - NS_DispatchToCurrentThread(event); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + NS_ENSURE_TRUE(mMenuParent, NS_OK); + // We've successfully prevented the same click from both + // dismissing and reopening this menu. + // Clear the recent rollup state so we don't prevent + // this menu from being opened by the next click. + mMenuParent->ClearRecentlyRolledUp(); } return NS_OK; } +NS_IMETHODIMP +nsMenuFrame::SelectMenu(PRBool aActivateFlag) +{ + if (!mContent) { + return NS_OK; + } + + nsAutoString domEventToFire; + + nsWeakFrame weakFrame(this); + if (aActivateFlag) { + if (mMenuParent) { + nsIMenuParent* ancestor = nsnull; + nsresult rv = mMenuParent->GetParentPopup(&ancestor); + while (NS_SUCCEEDED(rv) && ancestor) { + ancestor->CancelPendingTimers(); + rv = ancestor->GetParentPopup(&ancestor); + } + } + // Highlight the menu. + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, NS_LITERAL_STRING("true"), PR_TRUE); + // The menuactivated event is used by accessibility to track the user's movements through menus + domEventToFire.AssignLiteral("DOMMenuItemActive"); + } + else { + // Unhighlight the menu. + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, PR_TRUE); + domEventToFire.AssignLiteral("DOMMenuItemInactive"); + } + + if (weakFrame.IsAlive()) { + FireDOMEventSynch(domEventToFire); + } + return NS_OK; +} + +PRBool nsMenuFrame::IsGenerated() +{ + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + // Generate the menu if it hasn't been generated already. This + // takes it from display: none to display: block and gives us + // a menu forevermore. + if (child && + !nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None, + nsGkAtoms::menugenerated)) { + return PR_FALSE; + } + + return PR_TRUE; +} + +NS_IMETHODIMP +nsMenuFrame::MarkAsGenerated() +{ + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + // Generate the menu if it hasn't been generated already. This + // takes it from display: none to display: block and gives us + // a menu forevermore. + if (child && + !nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None, + nsGkAtoms::menugenerated)) { + child->SetAttr(kNameSpaceID_None, nsGkAtoms::menugenerated, + NS_LITERAL_STRING("true"), PR_TRUE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::UngenerateMenu() +{ + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + if (child && + nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None, + nsGkAtoms::menugenerated)) { + child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menugenerated, PR_TRUE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::ActivateMenu(PRBool aActivateFlag) +{ + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + + if (!menuPopup) + return NS_OK; + + if (aActivateFlag) { + nsRect rect = menuPopup->GetRect(); + nsIView* view = menuPopup->GetView(); + nsIViewManager* viewManager = view->GetViewManager(); + rect.x = rect.y = 0; + viewManager->ResizeView(view, rect); + + // make sure the scrolled window is at 0,0 + if (mLastPref.height <= rect.height) { + nsIBox* child = menuPopup->GetChildBox(); + + nsCOMPtr scrollframe(do_QueryInterface(child)); + if (scrollframe) { + scrollframe->ScrollTo(nsPoint(0,0)); + } + } + + viewManager->UpdateView(view, rect, NS_VMREFRESH_IMMEDIATE); + viewManager->SetViewVisibility(view, nsViewVisibility_kShow); + PresContext()->RootPresContext()->NotifyAddedActivePopupToTop(menuPopup); + } else { + if (mMenuOpen) { + nsWeakFrame weakFrame(this); + nsWeakFrame weakPopup(menuPopup); + FireDOMEventSynch(NS_LITERAL_STRING("DOMMenuInactive"), menuPopup->GetContent()); + NS_ENSURE_TRUE(weakFrame.IsAlive() && weakPopup.IsAlive(), NS_OK); + } + nsIView* view = menuPopup->GetView(); + NS_ASSERTION(view, "View is gone, looks like someone forgot to rollup the popup!"); + if (view) { + nsIViewManager* viewManager = view->GetViewManager(); + if (viewManager) { // the view manager can be null during widget teardown + viewManager->SetViewVisibility(view, nsViewVisibility_kHide); + viewManager->ResizeView(view, nsRect(0, 0, 0, 0)); + } + } + // set here so hide chain can close the menu as well. + mMenuOpen = PR_FALSE; + PresContext()->RootPresContext()->NotifyRemovedActivePopup(menuPopup); + } + + return NS_OK; +} + NS_IMETHODIMP nsMenuFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, @@ -607,37 +760,247 @@ nsMenuFrame::AttributeChanged(PRInt32 aNameSpaceID, BuildAcceleratorText(); } else if (aAttribute == nsGkAtoms::key) { BuildAcceleratorText(); - } else if (aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::name) + } else if ( aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::name ) UpdateMenuType(PresContext()); return NS_OK; } -void -nsMenuFrame::OpenMenu(PRBool aSelectFirstItem) +NS_IMETHODIMP +nsMenuFrame::OpenMenu(PRBool aActivateFlag) { if (!mContent) - return; + return NS_OK; + nsWeakFrame weakFrame(this); + if (aActivateFlag) { + // Now that the menu is opened, we should have a menupopup child built. + // Mark it as generated, which ensures a frame gets built. + MarkAsGenerated(); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), PR_TRUE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + FireDOMEventSynch(NS_LITERAL_STRING("DOMMenuItemActive")); + } + else { + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, PR_TRUE); + } + + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + OpenMenuInternal(aActivateFlag); + + return NS_OK; +} + +void +nsMenuFrame::OpenMenuInternal(PRBool aActivateFlag) +{ gEatMouseMove = PR_TRUE; - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - pm->KillMenuTimer(); - // This opens the menu asynchronously - pm->ShowMenu(mContent, aSelectFirstItem, PR_TRUE); + if (!mIsMenu) + return; + + nsPresContext* presContext = PresContext(); + nsWeakFrame weakFrame(this); + + if (aActivateFlag) { + // Execute the oncreate handler + if (!OnCreate() || !weakFrame.IsAlive()) + return; + + mCreateHandlerSucceeded = PR_TRUE; + + // Set the focus back to our view's widget. + if (nsMenuDismissalListener::sInstance) + nsMenuDismissalListener::sInstance->EnableListener(PR_FALSE); + + // XXX Only have this here because of RDF-generated content. + MarkAsGenerated(); + ENSURE_TRUE(weakFrame.IsAlive()); + + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + + PRBool wasOpen = mMenuOpen; + mMenuOpen = PR_TRUE; + + if (menuPopup) { + nsWeakFrame weakMenuPopup(frame); + // inherit whether or not we're a context menu from the parent + if ( mMenuParent ) { + PRBool parentIsContextMenu = PR_FALSE; + mMenuParent->GetIsContextMenu(parentIsContextMenu); + menuPopup->SetIsContextMenu(parentIsContextMenu); + ENSURE_TRUE(weakFrame.IsAlive()); + } + + // Install a keyboard navigation listener if we're the root of the menu chain. + PRBool onMenuBar = PR_TRUE; + if (mMenuParent) + mMenuParent->IsMenuBar(onMenuBar); + + if (mMenuParent && onMenuBar) + mMenuParent->InstallKeyboardNavigator(); + else if (!mMenuParent) { + ENSURE_TRUE(weakMenuPopup.IsAlive()); + menuPopup->InstallKeyboardNavigator(); + } + + // Tell the menu bar we're active. + if (mMenuParent) { + mMenuParent->SetActive(PR_TRUE); + ENSURE_TRUE(weakFrame.IsAlive()); + } + + nsIContent* menuPopupContent = menuPopup->GetContent(); + + // Sync up the view. + nsAutoString popupAnchor, popupAlign; + + menuPopupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, popupAnchor); + menuPopupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, popupAlign); + + ConvertPosition(menuPopupContent, popupAnchor, popupAlign); + + if (onMenuBar) { + if (popupAnchor.IsEmpty()) + popupAnchor.AssignLiteral("bottomleft"); + if (popupAlign.IsEmpty()) + popupAlign.AssignLiteral("topleft"); + } + else { + if (popupAnchor.IsEmpty()) + popupAnchor.AssignLiteral("topright"); + if (popupAlign.IsEmpty()) + popupAlign.AssignLiteral("topleft"); + } + + // If the menu popup was not open, do a reflow. This is either the + // initial reflow for a brand-new popup, or a subsequent reflow for + // a menu that was deactivated and needs to be brought back to its + // active dimensions. + if (!wasOpen) + { + presContext->PresShell()-> + FrameNeedsReflow(menuPopup, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + presContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow); + } + + nsRect curRect(menuPopup->GetRect()); + nsBoxLayoutState state(presContext); + menuPopup->SetBounds(state, nsRect(0,0,mLastPref.width, mLastPref.height)); + + nsIView* view = menuPopup->GetView(); + nsIViewManager* vm = view->GetViewManager(); + if (vm) { + vm->SetViewVisibility(view, nsViewVisibility_kHide); + } + menuPopup->SyncViewWithFrame(presContext, popupAnchor, popupAlign, this, -1, -1); + nscoord newHeight = menuPopup->GetRect().height; + + // if the height is different then reflow. It might need scrollbars force a reflow + if (curRect.height != newHeight || mLastPref.height != newHeight) + { + presContext->PresShell()-> + FrameNeedsReflow(menuPopup, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + presContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow); + } + + ActivateMenu(PR_TRUE); + ENSURE_TRUE(weakFrame.IsAlive()); + + nsIMenuParent *childPopup = nsnull; + CallQueryInterface(frame, &childPopup); + + nsMenuDismissalListener* listener = nsMenuDismissalListener::GetInstance(); + if (listener) + listener->SetCurrentMenuParent(childPopup); + + OnCreated(); + ENSURE_TRUE(weakFrame.IsAlive()); + } + + // Set the focus back to our view's widget. + if (nsMenuDismissalListener::sInstance) + nsMenuDismissalListener::sInstance->EnableListener(PR_TRUE); + } + else { + + // Close the menu. + // Execute the ondestroy handler, but only if we're actually open + if ( !mCreateHandlerSucceeded || !OnDestroy() || !weakFrame.IsAlive()) + return; + + // Set the focus back to our view's widget. + if (nsMenuDismissalListener::sInstance) { + nsMenuDismissalListener::sInstance->EnableListener(PR_FALSE); + nsMenuDismissalListener::sInstance->SetCurrentMenuParent(mMenuParent); + } + + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + + // Make sure we clear out our own items. + if (menuPopup) { + menuPopup->SetCurrentMenuItem(nsnull); + ENSURE_TRUE(weakFrame.IsAlive()); + menuPopup->KillCloseTimer(); + + PRBool onMenuBar = PR_TRUE; + if (mMenuParent) + mMenuParent->IsMenuBar(onMenuBar); + + if (mMenuParent && onMenuBar) + mMenuParent->RemoveKeyboardNavigator(); + else if (!mMenuParent) + menuPopup->RemoveKeyboardNavigator(); + + // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no + // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually. + // This code may not the best solution, but we can leave it here until we find the better approach. + + nsIEventStateManager *esm = presContext->EventStateManager(); + + PRInt32 state; + esm->GetContentState(menuPopup->GetContent(), state); + + if (state & NS_EVENT_STATE_HOVER) + esm->SetContentState(nsnull, NS_EVENT_STATE_HOVER); + } + + ActivateMenu(PR_FALSE); + ENSURE_TRUE(weakFrame.IsAlive()); + // XXX hack: ensure that mMenuOpen is set to false, in case where + // there is actually no popup. because ActivateMenu() will return + // early without setting it. It could be that mMenuOpen is true + // in that case, because OpenMenuInternal(true) gets called if + // the attribute open="true", whether there is a popup or not. + // We should not allow mMenuOpen unless there is a popup in the first place, + // in which case this line would not be necessary. + mMenuOpen = PR_FALSE; + + OnDestroyed(); + ENSURE_TRUE(weakFrame.IsAlive()); + + if (nsMenuDismissalListener::sInstance) + nsMenuDismissalListener::sInstance->EnableListener(PR_TRUE); + + mCreateHandlerSucceeded = PR_FALSE; + } + } void -nsMenuFrame::CloseMenu(PRBool aDeselectMenu) +nsMenuFrame::GetMenuChildrenElement(nsIContent** aResult) { - gEatMouseMove = PR_TRUE; - - // Close the menu asynchronously - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && mPopupFrame) - pm->HidePopup(mPopupFrame->GetContent(), PR_FALSE, aDeselectMenu, PR_TRUE); + *aResult = nsContentUtils::FindFirstChildWithResolvedTag(mContent, + kNameSpaceID_XUL, + nsGkAtoms::menupopup); + NS_IF_ADDREF(*aResult); } PRBool @@ -675,12 +1038,17 @@ nsMenuFrame::DoLayout(nsBoxLayoutState& aState) nsresult rv = nsBoxFrame::DoLayout(aState); // layout the popup. First we need to get it. - if (mPopupFrame) { + nsIFrame* popupChild = mPopupFrames.FirstChild(); + + if (popupChild) { PRBool sizeToPopup = IsSizedToPopup(mContent, PR_FALSE); + + NS_ASSERTION(popupChild->IsBoxFrame(), "popupChild is not box!!"); + // then get its preferred size - nsSize prefSize = mPopupFrame->GetPrefSize(aState); - nsSize minSize = mPopupFrame->GetMinSize(aState); - nsSize maxSize = mPopupFrame->GetMaxSize(aState); + nsSize prefSize = popupChild->GetPrefSize(aState); + nsSize minSize = popupChild->GetMinSize(aState); + nsSize maxSize = popupChild->GetMaxSize(aState); BoundsCheck(minSize, prefSize, maxSize); @@ -688,44 +1056,49 @@ nsMenuFrame::DoLayout(nsBoxLayoutState& aState) prefSize.width = mRect.width; // if the pref size changed then set bounds to be the pref size - PRBool sizeChanged = (mLastPref != prefSize); - if (sizeChanged) { - mPopupFrame->SetBounds(aState, nsRect(0,0,prefSize.width, prefSize.height)); + // and sync the view. And set new pref size. + if (mLastPref != prefSize) { + popupChild->SetBounds(aState, nsRect(0,0,prefSize.width, prefSize.height)); + RePositionPopup(aState); mLastPref = prefSize; } - // if the menu has just been opened, or its size changed, position - // the popup. The flag that the popup checks in the HasOpenChanged - // method will get cleared in AdjustView which is called below. - if (IsOpen() && (sizeChanged || mPopupFrame->HasOpenChanged())) - mPopupFrame->SetPopupPosition(this); - // is the new size too small? Make sure we handle scrollbars correctly - nsIBox* child = mPopupFrame->GetChildBox(); + nsIBox* child = popupChild->GetChildBox(); - nsRect bounds(mPopupFrame->GetRect()); + nsRect bounds(popupChild->GetRect()); nsCOMPtr scrollframe(do_QueryInterface(child)); if (scrollframe && scrollframe->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) { if (bounds.height < prefSize.height) { // layout the child - mPopupFrame->Layout(aState); + popupChild->Layout(aState); nsMargin scrollbars = scrollframe->GetActualScrollbarSizes(); if (bounds.width < prefSize.width + scrollbars.left + scrollbars.right) { bounds.width += scrollbars.left + scrollbars.right; - mPopupFrame->SetBounds(aState, bounds); + //printf("Width=%d\n",width); + popupChild->SetBounds(aState, bounds); } } } - + // layout the child - mPopupFrame->Layout(aState); - mPopupFrame->AdjustView(); + popupChild->Layout(aState); + + // Only size the popups view if open. + if (mMenuOpen) { + nsIView* view = popupChild->GetView(); + nsRect r(0, 0, bounds.width, bounds.height); + view->GetViewManager()->ResizeView(view, r); + } + } + SyncLayout(aState); + return rv; } @@ -741,8 +1114,7 @@ nsMenuFrame::SetDebug(nsBoxLayoutState& aState, PRBool aDebug) if (debugChanged) { nsBoxFrame::SetDebug(aState, aDebug); - if (mPopupFrame) - SetDebug(aState, mPopupFrame, aDebug); + SetDebug(aState, mPopupFrames.FirstChild(), aDebug); } return NS_OK; @@ -765,44 +1137,189 @@ nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, PRBool aDebug) } #endif +void +nsMenuFrame::ConvertPosition(nsIContent* aPopupElt, nsString& aAnchor, nsString& aAlign) +{ + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_empty, &nsGkAtoms::before_start, &nsGkAtoms::before_end, + &nsGkAtoms::after_start, &nsGkAtoms::after_end, &nsGkAtoms::start_before, + &nsGkAtoms::start_after, &nsGkAtoms::end_before, &nsGkAtoms::end_after, + &nsGkAtoms::overlap, nsnull}; + + switch (aPopupElt->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::position, + strings, eCaseMatters)) { + case nsIContent::ATTR_MISSING: + case 0: + return; + case 1: + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("bottomleft"); + break; + case 2: + aAnchor.AssignLiteral("topright"); + aAlign.AssignLiteral("bottomright"); + break; + case 3: + aAnchor.AssignLiteral("bottomleft"); + aAlign.AssignLiteral("topleft"); + break; + case 4: + aAnchor.AssignLiteral("bottomright"); + aAlign.AssignLiteral("topright"); + break; + case 5: + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("topright"); + break; + case 6: + aAnchor.AssignLiteral("bottomleft"); + aAlign.AssignLiteral("bottomright"); + break; + case 7: + aAnchor.AssignLiteral("topright"); + aAlign.AssignLiteral("topleft"); + break; + case 8: + aAnchor.AssignLiteral("bottomright"); + aAlign.AssignLiteral("bottomleft"); + break; + case 9: + aAnchor.AssignLiteral("topleft"); + aAlign.AssignLiteral("topleft"); + break; + } +} + +void +nsMenuFrame::RePositionPopup(nsBoxLayoutState& aState) +{ + nsPresContext* presContext = aState.PresContext(); + + // Sync up the view. + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + if (mMenuOpen && menuPopup) { + nsIContent* menuPopupContent = menuPopup->GetContent(); + nsAutoString popupAnchor, popupAlign; + + menuPopupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, popupAnchor); + menuPopupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, popupAlign); + + ConvertPosition(menuPopupContent, popupAnchor, popupAlign); + + PRBool onMenuBar = PR_TRUE; + if (mMenuParent) + mMenuParent->IsMenuBar(onMenuBar); + + if (onMenuBar) { + if (popupAnchor.IsEmpty()) + popupAnchor.AssignLiteral("bottomleft"); + if (popupAlign.IsEmpty()) + popupAlign.AssignLiteral("topleft"); + } + else { + if (popupAnchor.IsEmpty()) + popupAnchor.AssignLiteral("topright"); + if (popupAlign.IsEmpty()) + popupAlign.AssignLiteral("topleft"); + } + + menuPopup->SyncViewWithFrame(presContext, popupAnchor, popupAlign, this, -1, -1); + } +} + +NS_IMETHODIMP +nsMenuFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) +{ + nsIFrame* frame = mPopupFrames.FirstChild(); + if (frame) { + nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; + popup->ShortcutNavigation(aKeyEvent, aHandledFlag); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag) +{ + nsIFrame* frame = mPopupFrames.FirstChild(); + if (frame) { + nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; + popup->KeyboardNavigation(aKeyCode, aHandledFlag); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuFrame::Escape(PRBool& aHandledFlag) +{ + if (mMenuParent) { + mMenuParent->ClearRecentlyRolledUp(); + } + nsIFrame* frame = mPopupFrames.FirstChild(); + if (frame) { + nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; + popup->Escape(aHandledFlag); + } + + return NS_OK; +} + + // // Enter // // Called when the user hits the / keys or presses the // shortcut key. If this is a leaf item, the item's action will be executed. +// If it is a submenu parent, open the submenu and select the first time. // In either case, do nothing if the item is disabled. // -nsMenuFrame* +NS_IMETHODIMP nsMenuFrame::Enter() { if (IsDisabled()) { #ifdef XP_WIN // behavior on Windows - close the popup chain - if (mMenuParent) { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->Rollup(); - } + if (mMenuParent) + mMenuParent->DismissChain(); #endif // #ifdef XP_WIN // this menu item was disabled - exit - return nsnull; + return NS_OK; } - - if (!IsOpen()) { + + if (!mMenuOpen) { // The enter key press applies to us. if (!IsMenu() && mMenuParent) Execute(0); // Execute our event handler - else - return this; + else { + OpenMenu(PR_TRUE); + SelectFirstItem(); + } + + return NS_OK; } - return nsnull; + nsIFrame* frame = mPopupFrames.FirstChild(); + if (frame) { + nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; + popup->Enter(); + } + + return NS_OK; } -PRBool -nsMenuFrame::IsOpen() +NS_IMETHODIMP +nsMenuFrame::SelectFirstItem() { - return mPopupFrame && mPopupFrame->IsOpen(); + nsIFrame* frame = mPopupFrames.FirstChild(); + if (frame) { + nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; + popup->SetCurrentMenuItem(popup->GetNextMenuItem(nsnull)); + } + + return NS_OK; } PRBool @@ -816,22 +1333,32 @@ nsMenuFrame::Notify(nsITimer* aTimer) { // Our timer has fired. if (aTimer == mOpenTimer.get()) { - mOpenTimer = nsnull; - - if (!IsOpen() && mMenuParent) { + if (!mMenuOpen && mMenuParent) { // make sure we didn't open a context menu in the meantime // (i.e. the user right-clicked while hovering over a submenu). - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - if ((!pm->HasContextMenu(nsnull) || mMenuParent->IsContextMenu()) && - mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, + // However, also make sure that we're not the context menu itself, + // to allow context submenus to open. + nsIMenuParent *ctxMenu = nsMenuFrame::GetContextMenu(); + PRBool parentIsContextMenu = PR_FALSE; + + if (ctxMenu) + mMenuParent->GetIsContextMenu(parentIsContextMenu); + + if (ctxMenu == nsnull || parentIsContextMenu) { + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, nsGkAtoms::_true, eCaseMatters)) { - OpenMenu(PR_FALSE); + // We're still the active menu. Make sure all submenus/timers are closed + // before opening this one + mMenuParent->KillPendingTimers(); + OpenMenu(PR_TRUE); } } } + mOpenTimer->Cancel(); + mOpenTimer = nsnull; } - + + mOpenTimer = nsnull; return NS_OK; } @@ -870,11 +1397,11 @@ nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext) /* update checked-ness for type="checkbox" and type="radio" */ void -nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) -{ +nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) { PRBool newChecked = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true, eCaseMatters); + if (newChecked == mChecked) { /* checked state didn't change */ @@ -911,27 +1438,43 @@ nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) */ /* walk siblings, looking for the other checked item with the same name */ + nsIMenuFrame *sibMenu; + nsMenuType sibType; + nsAutoString sibGroup; + PRBool sibChecked; + // get the first sibling in this menu popup. This frame may be it, and if we're // being called at creation time, this frame isn't yet in the parent's child list. // All I'm saying is that this may fail, but it's most likely alright. nsIFrame* sib = GetParent()->GetFirstChild(nsnull); + if ( !sib ) + return; - while (sib) { - if (sib != this && sib->GetType() == nsGkAtoms::menuFrame) { - nsMenuFrame* menu = NS_STATIC_CAST(nsMenuFrame*, sib); - if (menu->GetMenuType() == eMenuType_Radio && - menu->IsChecked() && - (menu->GetRadioGroupName() == mGroupName)) { - /* uncheck the old item */ - sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, - PR_TRUE); - /* XXX in DEBUG, check to make sure that there aren't two checked items */ - return; - } + // XXX - egcs 1.1.2 & gcc 2.95.x -Oy builds, where y > 1, + // are known to break if we declare nsCOMPtrs inside this loop. + // Moving the declaration out of the loop works around this problem. + // http://bugzilla.mozilla.org/show_bug.cgi?id=80988 + + do { + if (NS_FAILED(sib->QueryInterface(NS_GET_IID(nsIMenuFrame), + (void **)&sibMenu))) + continue; + + if (sibMenu != (nsIMenuFrame *)this && // correct way to check? + (sibMenu->GetMenuType(sibType), sibType == eMenuType_Radio) && + (sibMenu->MenuIsChecked(sibChecked), sibChecked) && + (sibMenu->GetRadioGroupName(sibGroup), sibGroup == mGroupName)) { + + /* uncheck the old item */ + sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, + PR_TRUE); + + /* XXX in DEBUG, check to make sure that there aren't two checked items */ + return; } - sib = sib->GetNextSibling(); - } + } while ((sib = sib->GetNextSibling()) != nsnull); + } void @@ -1101,25 +1644,237 @@ nsMenuFrame::Execute(nsGUIEvent *aEvent) mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("true"), PR_TRUE); ENSURE_TRUE(weakFrame.IsAlive()); + } + /* the AttributeChanged code will update all the internal state */ + } + } + + // Temporarily disable rollup events on this menu. This is + // to suppress this menu getting removed in the case where + // the oncommand handler opens a dialog, etc. + if ( nsMenuDismissalListener::sInstance ) { + nsMenuDismissalListener::sInstance->EnableListener(PR_FALSE); + } + + // Get our own content node and hold on to it to keep it from going away. + nsCOMPtr content = mContent; + + // Deselect ourselves. + SelectMenu(PR_FALSE); + ENSURE_TRUE(weakFrame.IsAlive()); + + // Now hide all of the open menus. + if (mMenuParent) { + mMenuParent->HideChain(); + + // Since menu was not dismissed via click outside menu + // we don't want to keep track of this rollup. + // Otherwise, we keep track so that the same click + // won't both dismiss and then reopen a menu. + mMenuParent->ClearRecentlyRolledUp(); + } + + + nsEventStatus status = nsEventStatus_eIgnore; + // Create a trusted event if the triggering event was trusted, or if + // we're called from chrome code (since at least one of our caller + // passes in a null event). + nsXULCommandEvent event(aEvent ? NS_IS_TRUSTED_EVENT(aEvent) : + nsContentUtils::IsCallerChrome(), + NS_XUL_COMMAND, nsnull); + if (aEvent && (aEvent->eventStructType == NS_MOUSE_EVENT || + aEvent->eventStructType == NS_KEY_EVENT || + aEvent->eventStructType == NS_ACCESSIBLE_EVENT)) { + + event.isShift = NS_STATIC_CAST(nsInputEvent *, aEvent)->isShift; + event.isControl = NS_STATIC_CAST(nsInputEvent *, aEvent)->isControl; + event.isAlt = NS_STATIC_CAST(nsInputEvent *, aEvent)->isAlt; + event.isMeta = NS_STATIC_CAST(nsInputEvent *, aEvent)->isMeta; + } + + // The order of the nsIViewManager and nsIPresShell COM pointers is + // important below. We want the pres shell to get released before the + // associated view manager on exit from this function. + // See bug 54233. + nsPresContext* presContext = PresContext(); + nsCOMPtr kungFuDeathGrip = presContext->GetViewManager(); + nsCOMPtr shell = presContext->GetPresShell(); + if (shell) { + shell->HandleDOMEventWithTarget(mContent, &event, &status); + ENSURE_TRUE(weakFrame.IsAlive()); + } + + if (mMenuParent) { + mMenuParent->DismissChain(); + } + + // Re-enable rollup events on this menu. + if ( nsMenuDismissalListener::sInstance ) { + nsMenuDismissalListener::sInstance->EnableListener(PR_TRUE); + } +} + +PRBool +nsMenuFrame::OnCreate() +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull, + nsMouseEvent::eReal); + + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + nsresult rv = NS_OK; + + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + if (child) { + rv = shell->HandleDOMEventWithTarget(child, &event, &status); + } + else { + rv = shell->HandleDOMEventWithTarget(mContent, &event, &status); + } + } + + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + + // The menu is going to show, and the create handler has executed. + // We should now walk all of our menu item children, checking to see if any + // of them has a command attribute. If so, then several attributes must + // potentially be updated. + if (child) { + nsCOMPtr domDoc(do_QueryInterface(child->GetDocument())); + + PRUint32 count = child->GetChildCount(); + for (PRUint32 i = 0; i < count; i++) { + nsCOMPtr grandChild = child->GetChildAt(i); + + if (grandChild->Tag() == nsGkAtoms::menuitem) { + // See if we have a command attribute. + nsAutoString command; + grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + if (!command.IsEmpty()) { + // We do! Look it up in our document + nsCOMPtr commandElt; + domDoc->GetElementById(command, getter_AddRefs(commandElt)); + nsCOMPtr commandContent(do_QueryInterface(commandElt)); + + if ( commandContent ) { + nsAutoString commandAttr; + // The menu's disabled state needs to be updated to match the command. + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandAttr)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandAttr, PR_TRUE); + else + grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE); + + // The menu's label, accesskey and checked states need to be updated + // to match the command. Note that unlike the disabled state if the + // command has *no* value, we assume the menu is supplying its own. + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandAttr)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandAttr, PR_TRUE); + + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandAttr)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandAttr, PR_TRUE); + + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandAttr)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandAttr, PR_TRUE); + } + } } } } - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && mMenuParent) - pm->ExecuteMenu(mContent, aEvent); + return PR_TRUE; +} + +PRBool +nsMenuFrame::OnCreated() +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull, + nsMouseEvent::eReal); + + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + nsresult rv = NS_OK; + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + if (child) { + rv = shell->HandleDOMEventWithTarget(child, &event, &status); + } + else { + rv = shell->HandleDOMEventWithTarget(mContent, &event, &status); + } + } + + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + return PR_TRUE; +} + +PRBool +nsMenuFrame::OnDestroy() +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull, + nsMouseEvent::eReal); + + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + nsresult rv = NS_OK; + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + if (child) { + rv = shell->HandleDOMEventWithTarget(child, &event, &status); + } + else { + rv = shell->HandleDOMEventWithTarget(mContent, &event, &status); + } + } + + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + return PR_TRUE; +} + +PRBool +nsMenuFrame::OnDestroyed() +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDDEN, nsnull, + nsMouseEvent::eReal); + + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + + nsresult rv = NS_OK; + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + if (child) { + rv = shell->HandleDOMEventWithTarget(child, &event, &status); + } + else { + rv = shell->HandleDOMEventWithTarget(mContent, &event, &status); + } + } + + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + return PR_TRUE; } NS_IMETHODIMP nsMenuFrame::RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame) { - nsresult rv = NS_OK; + nsresult rv; - if (mPopupFrame == aOldFrame) { + if (mPopupFrames.ContainsFrame(aOldFrame)) { // Go ahead and remove this frame. - mPopupFrame->Destroy(); - mPopupFrame = nsnull; + mPopupFrames.DestroyFrame(aOldFrame); PresContext()->PresShell()-> FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_HAS_DIRTY_CHILDREN); @@ -1138,11 +1893,13 @@ nsMenuFrame::InsertFrames(nsIAtom* aListName, { nsresult rv; - if (!mPopupFrame && aFrameList->GetType() == nsGkAtoms::menuPopupFrame) { - mPopupFrame = NS_STATIC_CAST(nsMenuPopupFrame *, aFrameList); + nsIMenuParent *menuPar; + if (aFrameList && NS_SUCCEEDED(CallQueryInterface(aFrameList, &menuPar))) { + NS_ASSERTION(aFrameList->IsBoxFrame(),"Popup is not a box!!!"); + mPopupFrames.InsertFrames(nsnull, nsnull, aFrameList); #ifdef DEBUG_LAYOUT - nsBoxLayoutState state(PresContext()); + nsBoxLayoutState state(GetPresContext()); SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); #endif PresContext()->PresShell()-> @@ -1165,11 +1922,13 @@ nsMenuFrame::AppendFrames(nsIAtom* aListName, nsresult rv; - if (!mPopupFrame && aFrameList->GetType() == nsGkAtoms::menuPopupFrame) { - mPopupFrame = NS_STATIC_CAST(nsMenuPopupFrame *, aFrameList); + nsIMenuParent *menuPar; + if (aFrameList && NS_SUCCEEDED(CallQueryInterface(aFrameList, &menuPar))) { + NS_ASSERTION(aFrameList->IsBoxFrame(),"Popup is not a box!!!"); + mPopupFrames.AppendFrames(nsnull, aFrameList); #ifdef DEBUG_LAYOUT - nsBoxLayoutState state(PresContext()); + nsBoxLayoutState state(GetPresContext()); SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); #endif PresContext()->PresShell()-> @@ -1183,6 +1942,44 @@ nsMenuFrame::AppendFrames(nsIAtom* aListName, return rv; } +class nsASyncMenuGeneration : public nsIReflowCallback +{ +public: + nsASyncMenuGeneration(nsIFrame* aFrame) + : mWeakFrame(aFrame) + { + nsIContent* content = aFrame ? aFrame->GetContent() : nsnull; + mDocument = content ? content->GetCurrentDoc() : nsnull; + if (mDocument) { + mDocument->BlockOnload(); + } + } + + virtual PRBool ReflowFinished() { + PRBool shouldFlush = PR_FALSE; + nsIFrame* frame = mWeakFrame.GetFrame(); + if (frame) { + nsBoxLayoutState state(frame->PresContext()); + if (!frame->IsCollapsed(state)) { + nsIMenuFrame* imenu = nsnull; + CallQueryInterface(frame, &imenu); + if (imenu) { + imenu->MarkAsGenerated(); + shouldFlush = PR_TRUE; + } + } + } + if (mDocument) { + mDocument->UnblockOnload(PR_FALSE); + } + delete this; + return shouldFlush; + } + + nsWeakFrame mWeakFrame; + nsCOMPtr mDocument; +}; + PRBool nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) { @@ -1190,9 +1987,24 @@ nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) nsSize tmpSize(-1, 0); nsIBox::AddCSSPrefSize(aState, this, tmpSize); if (tmpSize.width == -1 && GetFlex(aState) == 0) { - if (!mPopupFrame) + nsIFrame* frame = mPopupFrames.FirstChild(); + if (!frame) { + nsCOMPtr child; + GetMenuChildrenElement(getter_AddRefs(child)); + if (child && + !nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None, + nsGkAtoms::menugenerated)) { + nsIReflowCallback* cb = new nsASyncMenuGeneration(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + } return PR_FALSE; - tmpSize = mPopupFrame->GetPrefSize(aState); + } + + NS_ASSERTION(frame->IsBoxFrame(), "popupChild is not box!!"); + + tmpSize = frame->GetPrefSize(aState); aSize.width = tmpSize.width; return PR_TRUE; } @@ -1224,15 +2036,20 @@ nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState) NS_IMETHODIMP nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) { - if (!mPopupFrame) + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + if (!frame) return NS_ERROR_FAILURE; - nsMenuFrame* menuFrame = mPopupFrame->GetCurrentMenuItem(); + nsIMenuFrame* menuFrame = menuPopup->GetCurrentMenuItem(); + if (!menuFrame) { *aResult = nsnull; } else { - nsCOMPtr elt(do_QueryInterface(menuFrame->GetContent())); + nsIFrame* f; + menuFrame->QueryInterface(NS_GET_IID(nsIFrame), (void**)&f); + nsCOMPtr elt(do_QueryInterface(f->GetContent())); *aResult = elt; NS_IF_ADDREF(*aResult); } @@ -1243,31 +2060,95 @@ nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) NS_IMETHODIMP nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) { - if (!mPopupFrame) + nsIFrame* frame = mPopupFrames.FirstChild(); + nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame; + if (!frame) return NS_ERROR_FAILURE; if (!aChild) { // Remove the current selection - mPopupFrame->ChangeMenuItem(nsnull, PR_FALSE); + menuPopup->SetCurrentMenuItem(nsnull); return NS_OK; } nsCOMPtr child(do_QueryInterface(aChild)); - + nsIFrame* kid = PresContext()->PresShell()->GetPrimaryFrameFor(child); - if (kid && kid->GetType() == nsGkAtoms::menuFrame) - mPopupFrame->ChangeMenuItem(NS_STATIC_CAST(nsMenuFrame *, kid), PR_FALSE); + if (!kid) + return NS_ERROR_FAILURE; + nsIMenuFrame *menuFrame; + nsresult rv = CallQueryInterface(kid, &menuFrame); + if (NS_FAILED(rv)) + return rv; + menuPopup->SetCurrentMenuItem(menuFrame); return NS_OK; } nsIScrollableView* nsMenuFrame::GetScrollableView() { - if (!mPopupFrame) + if (!mPopupFrames.FirstChild()) return nsnull; - nsIFrame* childFrame = mPopupFrame->GetFirstChild(nsnull); - if (childFrame) - return mPopupFrame->GetScrollableView(childFrame); + nsMenuPopupFrame* popup = (nsMenuPopupFrame*) mPopupFrames.FirstChild(); + nsIFrame* childFrame = popup->GetFirstChild(nsnull); + if (childFrame) { + return popup->GetScrollableView(childFrame); + } + return nsnull; +} + +/* Need to figure out what this does. +NS_IMETHODIMP +nsMenuFrame::GetBoxInfo(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsBoxInfo& aSize) +{ + nsresult rv = nsBoxFrame::GetBoxInfo(aPresContext, aReflowState, aSize); + nsCOMPtr menulist(do_QueryInterface(mContent)); + if (menulist) { + nsCalculatedBoxInfo boxInfo(this); + boxInfo.prefSize.width = NS_UNCONSTRAINEDSIZE; + boxInfo.prefSize.height = NS_UNCONSTRAINEDSIZE; + boxInfo.flex = 0; + GetRedefinedMinPrefMax(aPresContext, this, boxInfo); + if (boxInfo.prefSize.width == NS_UNCONSTRAINEDSIZE && + boxInfo.prefSize.height == NS_UNCONSTRAINEDSIZE && + boxInfo.flex == 0) { + nsIFrame* frame = mPopupFrames.FirstChild(); + if (!frame) { + MarkAsGenerated(); + frame = mPopupFrames.FirstChild(); + } + + nsCalculatedBoxInfo childInfo(frame); + frame->GetBoxInfo(aPresContext, aReflowState, childInfo); + GetRedefinedMinPrefMax(aPresContext, this, childInfo); + aSize.prefSize.width = childInfo.prefSize.width; + } + + // This retrieval guarantess that the selectedItem will + // be set before we lay out. + nsCOMPtr element; + menulist->GetSelectedItem(getter_AddRefs(element)); + } + return rv; +} +*/ + +nsIMenuParent* +nsMenuFrame::GetContextMenu() +{ + if (!nsMenuDismissalListener::sInstance) + return nsnull; + + nsIMenuParent *menuParent = + nsMenuDismissalListener::sInstance->GetCurrentMenuParent(); + if (!menuParent) + return nsnull; + + PRBool isContextMenu; + menuParent->GetIsContextMenu(isContextMenu); + if (isContextMenu) + return menuParent; + return nsnull; } diff --git a/layout/xul/base/src/nsMenuFrame.h b/layout/xul/base/src/nsMenuFrame.h index 08b0fed02f6..0bee50a141f 100644 --- a/layout/xul/base/src/nsMenuFrame.h +++ b/layout/xul/base/src/nsMenuFrame.h @@ -49,10 +49,9 @@ #include "nsBoxFrame.h" #include "nsFrameList.h" -#include "nsGkAtoms.h" #include "nsIMenuParent.h" #include "nsIMenuFrame.h" -#include "nsXULPopupManager.h" +#include "nsMenuDismissalListener.h" #include "nsITimer.h" #include "nsISupportsArray.h" #include "nsIDOMText.h" @@ -62,21 +61,11 @@ nsIFrame* NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags); class nsMenuBarFrame; +class nsMenuPopupFrame; class nsIScrollableView; #define NS_STATE_ACCELTEXT_IS_DERIVED NS_STATE_BOX_CHILD_RESERVED -// the type of menuitem -enum nsMenuType { - // a normal menuitem where a command is carried out when activated - eMenuType_Normal = 0, - // a menuitem with a checkmark that toggles when activated - eMenuType_Checkbox = 1, - // a radio menuitem where only one of it and its siblings with the same - // name attribute can be checked at a time - eMenuType_Radio = 2 -}; - class nsMenuFrame; /** @@ -104,7 +93,7 @@ private: }; /** - * @note *** Methods marked with '@see comment above ***' may cause the frame to be + * @note *** Methods marked with '@see comment ***' may cause the frame to be * deleted during the method call. Be careful whenever using those * methods. */ @@ -133,14 +122,14 @@ public: NS_IMETHOD IsActive(PRBool& aResult) { aResult = PR_TRUE; return NS_OK; } - // The following methods are all overridden so that the menupopup - // can be stored in a separate list, so that it doesn't impact reflow of the - // actual menu item at all. + // The following four methods are all overridden so that the menu children + // can be stored in a separate list (so that they don't impact reflow of the + // actual menu item at all). virtual nsIFrame* GetFirstChild(nsIAtom* aListName) const; NS_IMETHOD SetInitialChildList(nsIAtom* aListName, nsIFrame* aChildList); virtual nsIAtom* GetAdditionalChildListName(PRInt32 aIndex) const; - virtual void Destroy(); // @see comment above *** + virtual void Destroy(); // @see comment *** // Overridden to prevent events from going to children of the menu. NS_IMETHOD BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, @@ -149,7 +138,7 @@ public: NS_IMETHOD HandleEvent(nsPresContext* aPresContext, nsGUIEvent* aEvent, - nsEventStatus* aEventStatus); // @see comment above *** + nsEventStatus* aEventStatus); // @see comment *** NS_IMETHOD AppendFrames(nsIAtom* aListName, nsIFrame* aFrameList); @@ -161,35 +150,36 @@ public: NS_IMETHOD RemoveFrame(nsIAtom* aListName, nsIFrame* aOldFrame); - virtual nsIAtom* GetType() const { return nsGkAtoms::menuFrame; } + // nsIMenuFrame Interface - NS_IMETHOD SelectMenu(PRBool aActivateFlag); // @see comment above *** - - /** - * NOTE: OpenMenu will open the menu synchronously. Don't call this if a frame - * is manipulated afterwards without checking to make sure it is still alive. - * All current calls to OpenMenu do not adjust the frame. - */ - void OpenMenu(PRBool aSelectFirstItem); - // CloseMenu closes the menu asynchronously - void CloseMenu(PRBool aDeselectMenu); - - PRBool IsChecked() { return mChecked; } + NS_IMETHOD ActivateMenu(PRBool aActivateFlag); // @see comment *** + NS_IMETHOD SelectMenu(PRBool aActivateFlag); // @see comment *** + NS_IMETHOD OpenMenu(PRBool aActivateFlag); // @see comment *** + NS_IMETHOD MenuIsOpen(PRBool& aResult) { aResult = IsOpen(); return NS_OK; } + NS_IMETHOD MenuIsContainer(PRBool& aResult) { aResult = IsMenu(); return NS_OK; } + NS_IMETHOD MenuIsChecked(PRBool& aResult) { aResult = mChecked; return NS_OK; } + NS_IMETHOD MenuIsDisabled(PRBool& aResult) { aResult = IsDisabled(); return NS_OK; } + NS_IMETHOD GetActiveChild(nsIDOMElement** aResult); - NS_IMETHOD SetActiveChild(nsIDOMElement* aChild); // @see comment above *** + NS_IMETHOD SetActiveChild(nsIDOMElement* aChild); // @see comment *** - // called when the Enter key is pressed while the menuitem is the current - // one in its parent popup. This will carry out the command attached to - // the menuitem. - nsMenuFrame* Enter(); + NS_IMETHOD UngenerateMenu(); // @see comment *** + + NS_IMETHOD SelectFirstItem(); // @see comment *** + + NS_IMETHOD Escape(PRBool& aHandledFlag); // @see comment *** + NS_IMETHOD Enter(); // @see comment *** + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); // @see comment *** + NS_IMETHOD KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag); // @see comment *** NS_IMETHOD SetParent(const nsIFrame* aParent); virtual nsIMenuParent *GetMenuParent() { return mMenuParent; } - const nsAString& GetRadioGroupName() { return mGroupName; } - nsMenuType GetMenuType() { return mType; } - nsMenuPopupFrame* GetPopup() { return mPopupFrame; } + virtual nsIFrame *GetMenuChild() { return mPopupFrames.FirstChild(); } + NS_IMETHOD GetRadioGroupName(nsString &aName) { aName = mGroupName; return NS_OK; } + NS_IMETHOD GetMenuType(nsMenuType &aType) { aType = mType; return NS_OK; } + NS_IMETHOD MarkAsGenerated(); // nsIScrollableViewProvider methods @@ -199,26 +189,12 @@ public: nsresult DestroyPopupFrames(nsPresContext* aPresContext); - virtual PRBool IsOnMenuBar() { return mMenuParent && mMenuParent->IsMenuBar(); } - virtual PRBool IsOnActiveMenuBar() { return IsOnMenuBar() && mMenuParent->IsActive(); } - virtual PRBool IsOpen(); - virtual PRBool IsMenu(); + PRBool IsOpen() { return mMenuOpen; } + PRBool IsMenu(); PRBool IsDisabled(); PRBool IsGenerated(); - void ToggleMenuState(); + NS_IMETHOD ToggleMenuState(); // @see comment *** - // indiciate that the menu's popup has just been opened, so that the menu - // can update its open state. This method modifies the open attribute on - // the menu, so the frames could be gone after this call - void PopupOpened(); - // indiciate that the menu's popup has just been closed, so that the menu - // can update its open state. The menu should be unhighlighted if - // aDeselectedMenu is true. - void PopupClosed(PRBool aDeselectMenu); - - // returns true if this is a menu on another menu popup. A menu is a submenu - // if it has a parent popup or menupopup. - PRBool IsOnMenu() { return mMenuParent && mMenuParent->IsMenu(); } void SetIsMenu(PRBool aIsMenu) { mIsMenu = aIsMenu; } #ifdef DEBUG @@ -230,27 +206,44 @@ public: static PRBool IsSizedToPopup(nsIContent* aContent, PRBool aRequireAlways); + static nsIMenuParent *GetContextMenu(); + protected: friend class nsMenuTimerMediator; + + virtual void RePositionPopup(nsBoxLayoutState& aState); + + void + ConvertPosition(nsIContent* aPopupElt, nsString& aAnchor, nsString& aAlign); + friend class nsASyncMenuInitialization; + void UpdateMenuType(nsPresContext* aPresContext); // @see comment *** + void UpdateMenuSpecialState(nsPresContext* aPresContext); // @see comment *** - // set mMenuParent to the nearest enclosing menu bar or menupopup frame of - // aParent (or aParent itself). This is called when initializing the frame, - // so aParent should be the expected parent of this frame. - void InitMenuParent(nsIFrame* aParent); - - void UpdateMenuType(nsPresContext* aPresContext); // @see comment above *** - void UpdateMenuSpecialState(nsPresContext* aPresContext); // @see comment above *** + void OpenMenuInternal(PRBool aActivateFlag); // @see comment *** + void GetMenuChildrenElement(nsIContent** aResult); // Examines the key node and builds the accelerator. void BuildAcceleratorText(); // Called to execute our command handler. - void Execute(nsGUIEvent *aEvent); // @see comment above *** + void Execute(nsGUIEvent *aEvent); // @see comment *** + + // Called as a hook just before the menu gets opened. + PRBool OnCreate(); // @see comment *** + + // Called as a hook just after the menu gets opened. + PRBool OnCreated(); // @see comment *** + + // Called as a hook just before the menu goes away. + PRBool OnDestroy(); // @see comment *** + + // Called as a hook just after the menu goes away. + PRBool OnDestroyed(); // @see comment *** NS_IMETHOD AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, - PRInt32 aModType); // @see comment above *** + PRInt32 aModType); // @see comment *** virtual ~nsMenuFrame(); PRBool SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize); @@ -261,23 +254,22 @@ protected: #endif NS_HIDDEN_(nsresult) Notify(nsITimer* aTimer); + nsFrameList mPopupFrames; PRPackedBool mIsMenu; // Whether or not we can even have children or not. + PRPackedBool mMenuOpen; + PRPackedBool mCreateHandlerSucceeded; // Did the create handler succeed? PRPackedBool mChecked; // are we checked? nsMenuType mType; nsIMenuParent* mMenuParent; // Our parent menu. - // the popup for this menu, owned - nsMenuPopupFrame* mPopupFrame; - - nsSize mLastPref; - // Reference to the mediator which wraps this frame. nsRefPtr mTimerMediator; nsCOMPtr mOpenTimer; nsString mGroupName; + nsSize mLastPref; //we load some display strings from platformKeys.properties only once static nsrefcnt gRefCnt; diff --git a/layout/xul/base/src/nsMenuListener.cpp b/layout/xul/base/src/nsMenuListener.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/layout/xul/base/src/nsMenuListener.h b/layout/xul/base/src/nsMenuListener.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/layout/xul/base/src/nsMenuPopupFrame.cpp b/layout/xul/base/src/nsMenuPopupFrame.cpp index f2d76edd06d..e7d3ed16dfa 100644 --- a/layout/xul/base/src/nsMenuPopupFrame.cpp +++ b/layout/xul/base/src/nsMenuPopupFrame.cpp @@ -44,6 +44,7 @@ #include "nsMenuPopupFrame.h" #include "nsGkAtoms.h" #include "nsIContent.h" +#include "nsContentUtils.h" #include "prtypes.h" #include "nsIAtom.h" #include "nsPresContext.h" @@ -53,9 +54,7 @@ #include "nsIViewManager.h" #include "nsWidgetsCID.h" #include "nsMenuFrame.h" -#include "nsMenuBarFrame.h" -#include "nsPopupSetFrame.h" -#include "nsEventDispatcher.h" +#include "nsIPopupSetFrame.h" #include "nsPIDOMWindow.h" #include "nsIDOMScreen.h" #include "nsIPresShell.h" @@ -63,6 +62,7 @@ #include "nsIDocument.h" #include "nsIDeviceContext.h" #include "nsRect.h" +#include "nsIDOMXULDocument.h" #include "nsILookAndFeel.h" #include "nsIComponentManager.h" #include "nsBoxLayoutState.h" @@ -73,9 +73,7 @@ #include "nsIDocShellTreeItem.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" -#include "nsLayoutUtils.h" #include "nsCSSFrameConstructor.h" -#include "nsIEventStateManager.h" #include "nsIBoxLayout.h" #include "nsIPopupBoxObject.h" #include "nsIReflowCallback.h" @@ -85,16 +83,24 @@ const PRInt32 kMaxZ = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32 -static nsPopupSetFrame* + +static nsIPopupSetFrame* GetPopupSetFrame(nsPresContext* aPresContext) { nsIRootBox* rootBox = nsIRootBox::GetRootBox(aPresContext->PresShell()); if (!rootBox) return nsnull; - return rootBox->GetPopupSetFrame(); + nsIFrame* popupSetFrame = rootBox->GetPopupSetFrame(); + if (!popupSetFrame) + return nsnull; + + nsIPopupSetFrame* popupSet = nsnull; + CallQueryInterface(popupSetFrame, &popupSet); + return popupSet; } + // NS_NewMenuPopupFrame // // Wrapper for creating a new menu popup container @@ -105,25 +111,42 @@ NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) return new (aPresShell) nsMenuPopupFrame (aPresShell, aContext); } +NS_IMETHODIMP_(nsrefcnt) +nsMenuPopupFrame::AddRef(void) +{ + return NS_OK; +} + +NS_IMETHODIMP_(nsrefcnt) +nsMenuPopupFrame::Release(void) +{ + return NS_OK; +} + + +// +// QueryInterface +// +NS_INTERFACE_MAP_BEGIN(nsMenuPopupFrame) + NS_INTERFACE_MAP_ENTRY(nsIMenuParent) +NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame) + + // // nsMenuPopupFrame ctor // nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext) :nsBoxFrame(aShell, aContext), mCurrentMenu(nsnull), - mPopupAlignment(POPUPALIGNMENT_NONE), - mPopupAnchor(POPUPALIGNMENT_NONE), - mPopupType(ePopupTypePanel), - mIsOpen(PR_FALSE), - mIsOpenChanged(PR_FALSE), - mIsOpenPending(PR_FALSE), - mIsContextMenu(PR_FALSE), - mGeneratedChildren(PR_FALSE), + mTimerMenu(nsnull), + mCloseTimer(nsnull), mMenuCanOverlapOSBar(PR_FALSE), mShouldAutoPosition(PR_TRUE), + mShouldRollup(PR_TRUE), mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT), mInContentShell(PR_TRUE) { + SetIsContextMenu(PR_FALSE); // we're not a context menu by default } // ctor @@ -135,6 +158,11 @@ nsMenuPopupFrame::Init(nsIContent* aContent, nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow); NS_ENSURE_SUCCESS(rv, rv); + // Set up a mediator which can be used for callbacks on this frame. + mTimerMediator = new nsMenuPopupTimerMediator(this); + if (NS_UNLIKELY(!mTimerMediator)) + return NS_ERROR_OUT_OF_MEMORY; + nsPresContext* presContext = PresContext(); // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the @@ -162,24 +190,11 @@ nsMenuPopupFrame::Init(nsIContent* aContent, viewManager->SetViewZIndex(ourView, PR_FALSE, kMaxZ); viewManager->InsertChild(rootView, ourView, nsnull, PR_TRUE); - // XXX Hack. The popup's view should float above all other views, + // XXX Hack. The menu's view should float above all other views, // so we use the nsIView::SetFloating() to tell the view manager // about that constraint. viewManager->SetViewFloating(ourView, PR_TRUE); - mPopupType = ePopupTypePanel; - nsIDocument* doc = aContent->GetOwnerDoc(); - if (doc) { - PRInt32 namespaceID; - nsCOMPtr tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID); - if (namespaceID == kNameSpaceID_XUL) { - if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup) - mPopupType = ePopupTypeMenu; - else if (tag == nsGkAtoms::tooltip) - mPopupType = ePopupTypeTooltip; - } - } - nsCOMPtr cont = PresContext()->GetContainer(); nsCOMPtr dsti = do_QueryInterface(cont); PRInt32 type = -1; @@ -193,6 +208,8 @@ nsMenuPopupFrame::Init(nsIContent* aContent, CreateWidgetForView(ourView); } + MoveToAttributePosition(); + return rv; } @@ -225,345 +242,6 @@ nsMenuPopupFrame::CreateWidgetForView(nsIView* aView) return NS_OK; } -// this class is used for dispatching popupshowing events asynchronously. -class nsXULPopupShownEvent : public nsRunnable -{ -public: - nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext) - : mPopup(aPopup), mPresContext(aPresContext) - { - } - - NS_IMETHOD Run() - { - nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull, nsMouseEvent::eReal); - return nsEventDispatcher::Dispatch(mPopup, mPresContext, &event); - } - -private: - nsCOMPtr mPopup; - nsRefPtr mPresContext; -}; - -NS_IMETHODIMP -nsMenuPopupFrame::SetInitialChildList(nsIAtom* aListName, - nsIFrame* aChildList) -{ - // unless the list is empty, indicate that children have been generated. - if (aChildList) - mGeneratedChildren = PR_TRUE; - return nsBoxFrame::SetInitialChildList(aListName, aChildList); -} - -void -nsMenuPopupFrame::AdjustView() -{ - if (mIsOpen) { - // if the popup has just opened, make sure the scrolled window is at 0,0 - if (mIsOpenChanged) { - nsIBox* child = GetChildBox(); - nsCOMPtr scrollframe(do_QueryInterface(child)); - if (scrollframe) - scrollframe->ScrollTo(nsPoint(0,0)); - } - - nsIView* view = GetView(); - nsIViewManager* viewManager = view->GetViewManager(); - nsRect rect = GetRect(); - rect.x = rect.y = 0; - viewManager->ResizeView(view, rect); - viewManager->SetViewVisibility(view, nsViewVisibility_kShow); - - nsPresContext* pc = PresContext(); - nsContainerFrame::SyncFrameViewProperties(pc, this, nsnull, view, 0); - - // fire popupshown event when the state has changed - if (mIsOpenChanged) { - mIsOpenChanged = PR_FALSE; - nsCOMPtr event = new nsXULPopupShownEvent(GetContent(), pc); - NS_DispatchToCurrentThread(event); - } - } -} - -void -nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor, - const nsAString& aAlign) -{ - if (aAnchor.EqualsLiteral("topleft")) - mPopupAnchor = POPUPALIGNMENT_TOPLEFT; - else if (aAnchor.EqualsLiteral("topright")) - mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; - else if (aAnchor.EqualsLiteral("bottomleft")) - mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; - else if (aAnchor.EqualsLiteral("bottomright")) - mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; - else - mPopupAnchor = POPUPALIGNMENT_NONE; - - if (aAlign.EqualsLiteral("topleft")) - mPopupAlignment = POPUPALIGNMENT_TOPLEFT; - else if (aAlign.EqualsLiteral("topright")) - mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; - else if (aAlign.EqualsLiteral("bottomleft")) - mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; - else if (aAlign.EqualsLiteral("bottomright")) - mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; - else - mPopupAlignment = POPUPALIGNMENT_NONE; -} - -void -nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent, - const nsAString& aPosition, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aAttributesOverride) -{ - mIsOpenPending = PR_TRUE; - mAnchorContent = aAnchorContent; - mXPos = aXPos; - mYPos = aYPos; - - // if aAttributesOverride is true, then the popupanchor, popupalign and - // position attributes on the override those values passed in. - // If false, those attributes are only used if the values passed in are empty - if (aAnchorContent) { - nsAutoString anchor, align, position; - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor); - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align); - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position); - - if (aAttributesOverride) { - // if the attributes are set, clear the offset position. Otherwise, - // the offset is used to adjust the position from the anchor point - if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty()) - position.Assign(aPosition); - else - mXPos = mYPos = 0; - } - else if (!aPosition.IsEmpty()) { - position.Assign(aPosition); - } - - if (position.EqualsLiteral("before_start")) { - mPopupAnchor = POPUPALIGNMENT_TOPLEFT; - mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; - } - else if (position.EqualsLiteral("before_end")) { - mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; - mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; - } - else if (position.EqualsLiteral("after_start")) { - mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; - mPopupAlignment = POPUPALIGNMENT_TOPLEFT; - } - else if (position.EqualsLiteral("after_end")) { - mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; - mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; - } - else if (position.EqualsLiteral("start_before")) { - mPopupAnchor = POPUPALIGNMENT_TOPLEFT; - mPopupAlignment = POPUPALIGNMENT_TOPRIGHT; - } - else if (position.EqualsLiteral("start_after")) { - mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT; - mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT; - } - else if (position.EqualsLiteral("end_before")) { - mPopupAnchor = POPUPALIGNMENT_TOPRIGHT; - mPopupAlignment = POPUPALIGNMENT_TOPLEFT; - } - else if (position.EqualsLiteral("end_after")) { - mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT; - mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT; - } - else if (position.EqualsLiteral("overlap")) { - mPopupAnchor = POPUPALIGNMENT_TOPLEFT; - mPopupAlignment = POPUPALIGNMENT_TOPLEFT; - } - else if (position.EqualsLiteral("after_pointer")) { - mPopupAnchor = POPUPALIGNMENT_NONE; - mPopupAlignment = POPUPALIGNMENT_NONE; - // XXXndeakin this is supposed to anchor vertically after, but with the - // horizontal position as the mouse pointer. - mYPos += 21; - } - else { - InitPositionFromAnchorAlign(anchor, align); - } - } - - mScreenXPos = -1; - mScreenYPos = -1; - - if (aAttributesOverride) { - // Use |left| and |top| dimension attributes to position the popup if - // present, as they may have been persisted. - nsAutoString left, top; - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); - - PRInt32 err; - if (!left.IsEmpty()) { - PRInt32 x = left.ToInteger(&err); - if (NS_SUCCEEDED(err)) - mScreenXPos = x; - } - if (!top.IsEmpty()) { - PRInt32 y = top.ToInteger(&err); - if (NS_SUCCEEDED(err)) - mScreenYPos = y; - } - } -} - -void -nsMenuPopupFrame::InitializePopupAtScreen(PRInt32 aXPos, PRInt32 aYPos) -{ - mIsOpenPending = PR_TRUE; - mAnchorContent = nsnull; - mScreenXPos = aXPos; - mScreenYPos = aYPos; - mPopupAnchor = POPUPALIGNMENT_NONE; - mPopupAlignment = POPUPALIGNMENT_NONE; -} - -void -nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, - nsAString& aAnchor, - nsAString& aAlign, - PRInt32 aXPos, PRInt32 aYPos) -{ - mIsOpenPending = PR_TRUE; - mXPos = aXPos; - mYPos = aYPos; - - // this popup opening function is provided for backwards compatibility - // only. It accepts either coordinates or an anchor and alignment value - // but doesn't use both together. - if (aXPos == -1 && aYPos == -1) { - mAnchorContent = aAnchorContent; - mScreenXPos = -1; - mScreenYPos = -1; - InitPositionFromAnchorAlign(aAnchor, aAlign); - } - else { - mAnchorContent = nsnull; - mPopupAnchor = POPUPALIGNMENT_NONE; - mPopupAlignment = POPUPALIGNMENT_NONE; - mScreenXPos = aXPos; - mScreenYPos = aYPos; - } -} - -void PR_CALLBACK -LazyGeneratePopupDone(nsIContent* aPopup, nsIFrame* aFrame, void* aArg) -{ - // be safe and check the frame type - if (aFrame->GetType() == nsGkAtoms::menuPopupFrame) { - nsWeakFrame weakFrame(aFrame); - nsMenuPopupFrame* popupFrame = NS_STATIC_CAST(nsMenuPopupFrame*, aFrame); - - popupFrame->SetGeneratedChildren(); - - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && popupFrame->IsMenu()) { - nsCOMPtr popup = aPopup; - PRBool selectFirstItem = (PRBool)aArg; - if (selectFirstItem) { - nsMenuFrame* next = pm->GetNextMenuItem(popupFrame, nsnull, PR_TRUE); - popupFrame->SetCurrentMenuItem(next); - } - - pm->UpdateMenuItems(popup); - } - - if (weakFrame.IsAlive()) { - popupFrame->PresContext()->PresShell()-> - FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange, - NS_FRAME_HAS_DIRTY_CHILDREN); - } - } -} - - -PRBool -nsMenuPopupFrame::ShowPopup(PRBool aIsContextMenu, PRBool aSelectFirstItem) -{ - mIsContextMenu = aIsContextMenu; - - PRBool hasChildren = PR_FALSE; - - if (!mIsOpen) { - mIsOpen = PR_TRUE; - mIsOpenChanged = PR_TRUE; - - nsIFrame* parent = GetParent(); - if (parent && parent->GetType() == nsGkAtoms::menuFrame) { - nsWeakFrame weakFrame(this); - (NS_STATIC_CAST(nsMenuFrame*, parent))->PopupOpened(); - if (!weakFrame.IsAlive()) - return PR_FALSE; - PresContext()->RootPresContext()->NotifyAddedActivePopupToTop(this); - } - - // the frames for the child menus have not been created yet, so tell the - // frame constructor to build them - if (mFrames.IsEmpty() && !mGeneratedChildren) { - PresContext()->PresShell()->FrameConstructor()-> - AddLazyChildren(mContent, LazyGeneratePopupDone, (void *)aSelectFirstItem); - } - else { - hasChildren = PR_TRUE; - PresContext()->PresShell()-> - FrameNeedsReflow(this, nsIPresShell::eTreeChange, - NS_FRAME_HAS_DIRTY_CHILDREN); - } - } - - mShouldAutoPosition = PR_TRUE; - return hasChildren; -} - -void -nsMenuPopupFrame::HidePopup(PRBool aDeselectMenu) -{ - if (mIsOpen) { - if (IsMenu()) - SetCurrentMenuItem(nsnull); - - mIncrementalString.Truncate(); - - mIsOpen = PR_FALSE; - mIsOpenChanged = PR_FALSE; - mCurrentMenu = nsnull; // make sure no current menu is set - - nsIView* view = GetView(); - nsIViewManager* viewManager = view->GetViewManager(); - viewManager->SetViewVisibility(view, nsViewVisibility_kHide); - viewManager->ResizeView(view, nsRect(0, 0, 0, 0)); - - FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent); - } - - // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no - // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually. - // This code may not the best solution, but we can leave it here until we find the better approach. - nsIEventStateManager *esm = PresContext()->EventStateManager(); - - PRInt32 state; - esm->GetContentState(mContent, state); - - if (state & NS_EVENT_STATE_HOVER) - esm->SetContentState(nsnull, NS_EVENT_STATE_HOVER); - - nsIFrame* parent = GetParent(); - if (parent && parent->GetType() == nsGkAtoms::menuFrame) { - (NS_STATIC_CAST(nsMenuFrame*, parent))->PopupClosed(aDeselectMenu); - PresContext()->RootPresContext()->NotifyRemovedActivePopup(this); - } -} - void nsMenuPopupFrame::InvalidateInternal(const nsRect& aDamageRect, nscoord aX, nscoord aY, nsIFrame* aForChild, @@ -606,10 +284,13 @@ nsMenuPopupFrame::GetViewOffset(nsIView* aView, nsPoint& aPoint) // root view. This is the root view of the pres context's // viewmanager if aStopAtViewManagerRoot is true; otherwise it's the // root view of the root viewmanager. -nsIView* +void nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame, - PRBool aStopAtViewManagerRoot) + PRBool aStopAtViewManagerRoot, + nsIView** aResult) { + *aResult = nsnull; + nsIView* view = aStartFrame->GetClosestView(); NS_ASSERTION(view, "frame must have a closest view!"); if (view) { @@ -627,96 +308,210 @@ nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame, nsWindowType wtype; widget->GetWindowType(wtype); if (wtype == eWindowType_popup) { - return view; + *aResult = view; + return; } } if (aStopAtViewManagerRoot && view == rootView) { - return view; + *aResult = view; + return; } nsIView* temp = view->GetParent(); if (!temp) { // Otherwise, we've walked all the way up to the root view and not // found a view for a popup window widget. Just return the root view. - return view; + *aResult = view; } view = temp; } } - - return nsnull; } + +// +// AdjustClientXYForNestedDocuments +// +// almost certainly, the document where the mouse was clicked is not +// the document that contains the popup, especially if we're viewing a page +// with frames. Thus we need to make adjustments to the client coordinates to +// take this into account and get them back into the relative coordinates of +// this document. +// +void +nsMenuPopupFrame::AdjustClientXYForNestedDocuments ( nsIDOMXULDocument* inPopupDoc, nsIPresShell* inPopupShell, + PRInt32 inClientX, PRInt32 inClientY, + PRInt32* outAdjX, PRInt32* outAdjY ) +{ + if ( !inPopupDoc || !outAdjX || !outAdjY ) + return; + + // Find the widget associated with the popup's document + nsIWidget* popupDocumentWidget = nsnull; + nsIViewManager* viewManager = inPopupShell->GetViewManager(); + if ( viewManager ) { + nsIView* rootView; + viewManager->GetRootView(rootView); + if ( rootView ) + popupDocumentWidget = rootView->GetNearestWidget(nsnull); + } + NS_ASSERTION(popupDocumentWidget, "ACK, BAD WIDGET"); + + // Find the widget associated with the target's document. + // For tooltips, we check the document's tooltipNode (which is set by + // nsXULTooltipListener). For regular popups, use popupNode (set by + // nsXULPopupListener). + + nsCOMPtr targetNode; + if (mContent->Tag() == nsGkAtoms::tooltip) + inPopupDoc->TrustedGetTooltipNode(getter_AddRefs(targetNode)); + else + inPopupDoc->TrustedGetPopupNode(getter_AddRefs(targetNode)); + + //NS_ASSERTION(targetNode, "no popup/tooltip node on document!"); + nsCOMPtr targetAsContent ( do_QueryInterface(targetNode) ); + nsIWidget* targetDocumentWidget = nsnull; + if ( targetAsContent ) { + nsCOMPtr targetDocument = targetAsContent->GetDocument(); + if (targetDocument) { + nsIPresShell *shell = targetDocument->GetPrimaryShell(); + if ( shell ) { + // We might be inside a popup widget. If so, we need to use that widget and + // not the root view's widget. + nsIFrame* targetFrame = shell->GetPrimaryFrameFor(targetAsContent); + nsIView* parentView = nsnull; + if (targetFrame) { + GetRootViewForPopup(targetFrame, PR_TRUE, &parentView); + if (parentView) { + targetDocumentWidget = parentView->GetNearestWidget(nsnull); + } + } + if (!targetDocumentWidget) { + // We aren't inside a popup. This means we should use the root view's + // widget. + nsIViewManager* viewManagerTarget = shell->GetViewManager(); + if ( viewManagerTarget ) { + nsIView* rootViewTarget; + viewManagerTarget->GetRootView(rootViewTarget); + if ( rootViewTarget ) { + targetDocumentWidget = rootViewTarget->GetNearestWidget(nsnull); + } + } + } + } + } + } + //NS_ASSERTION(targetDocumentWidget, "ACK, BAD TARGET"); + + // the offset we need is the difference between the upper left corner of the two widgets. Use + // screen coordinates to find the global offset between them. + nsRect popupDocTopLeft; + if ( popupDocumentWidget ) { + nsRect topLeftClient ( 0, 0, 10, 10 ); + popupDocumentWidget->WidgetToScreen ( topLeftClient, popupDocTopLeft ); + } + nsRect targetDocTopLeft; + if ( targetDocumentWidget ) { + nsRect topLeftClient ( 0, 0, 10, 10 ); + targetDocumentWidget->WidgetToScreen ( topLeftClient, targetDocTopLeft ); + } + nsPoint pixelOffset ( targetDocTopLeft.x - popupDocTopLeft.x, targetDocTopLeft.y - popupDocTopLeft.y ); + + nsPresContext* context = PresContext(); + *outAdjX = nsPresContext::CSSPixelsToAppUnits(inClientX) + + context->DevPixelsToAppUnits(pixelOffset.x); + *outAdjY = nsPresContext::CSSPixelsToAppUnits(inClientY) + + context->DevPixelsToAppUnits(pixelOffset.y); + +} // AdjustClientXYForNestedDocuments + + // // AdjustPositionForAnchorAlign // -// Uses the anchor and alignment to move the popup around and anchor it to its -// parent. |outFlushWithTopBottom| will be TRUE if the popup is flush with -// either the top or bottom edge of its parent, and FALSE if it is flush with -// the left or right edge of the parent. +// Uses the |popupanchor| and |popupalign| attributes on the popup to move the popup around and +// anchor it to its parent. |outFlushWithTopBottom| will be TRUE if the popup is flush with either +// the top or bottom edge of its parent, and FALSE if it is flush with the left or right edge of +// the parent. // void -nsMenuPopupFrame::AdjustPositionForAnchorAlign(PRInt32* ioXPos, PRInt32* ioYPos, const nsRect & inParentRect, - PRBool* outFlushWithTopBottom) +nsMenuPopupFrame::AdjustPositionForAnchorAlign ( PRInt32* ioXPos, PRInt32* ioYPos, const nsRect & inParentRect, + const nsString& aPopupAnchor, const nsString& aPopupAlign, + PRBool* outFlushWithTopBottom ) { - PRInt8 popupAnchor(mPopupAnchor); - PRInt8 popupAlign(mPopupAlignment); + nsAutoString popupAnchor(aPopupAnchor); + nsAutoString popupAlign(aPopupAlign); if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { - popupAnchor = -popupAnchor; - popupAlign = -popupAlign; + if (popupAnchor.EqualsLiteral("topright")) + popupAnchor.AssignLiteral("topleft"); + else if (popupAnchor.EqualsLiteral("topleft")) + popupAnchor.AssignLiteral("topright"); + else if (popupAnchor.EqualsLiteral("bottomleft")) + popupAnchor.AssignLiteral("bottomright"); + else if (popupAnchor.EqualsLiteral("bottomright")) + popupAnchor.AssignLiteral("bottomleft"); + + if (popupAlign.EqualsLiteral("topright")) + popupAlign.AssignLiteral("topleft"); + else if (popupAlign.EqualsLiteral("topleft")) + popupAlign.AssignLiteral("topright"); + else if (popupAlign.EqualsLiteral("bottomleft")) + popupAlign.AssignLiteral("bottomright"); + else if (popupAnchor.EqualsLiteral("bottomright")) + popupAlign.AssignLiteral("bottomleft"); } // Adjust position for margins at the aligned corner nsMargin margin; GetStyleMargin()->GetMargin(margin); - if (popupAlign == POPUPALIGNMENT_TOPLEFT) { + if (popupAlign.EqualsLiteral("topleft")) { *ioXPos += margin.left; *ioYPos += margin.top; - } else if (popupAlign == POPUPALIGNMENT_TOPRIGHT) { + } else if (popupAlign.EqualsLiteral("topright")) { *ioXPos += margin.right; *ioYPos += margin.top; - } else if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT) { + } else if (popupAlign.EqualsLiteral("bottomleft")) { *ioXPos += margin.left; *ioYPos += margin.bottom; - } else if (popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { + } else if (popupAlign.EqualsLiteral("bottomright")) { *ioXPos += margin.right; *ioYPos += margin.bottom; } - if (popupAnchor == POPUPALIGNMENT_TOPRIGHT && popupAlign == POPUPALIGNMENT_TOPLEFT) { + if (popupAnchor.EqualsLiteral("topright") && popupAlign.EqualsLiteral("topleft")) { *ioXPos += inParentRect.width; } - else if (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT) { + else if (popupAnchor.EqualsLiteral("topleft") && popupAlign.EqualsLiteral("topleft")) { *outFlushWithTopBottom = PR_TRUE; } - else if (popupAnchor == POPUPALIGNMENT_TOPRIGHT && popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { + else if (popupAnchor.EqualsLiteral("topright") && popupAlign.EqualsLiteral("bottomright")) { *ioXPos -= (mRect.width - inParentRect.width); *ioYPos -= mRect.height; *outFlushWithTopBottom = PR_TRUE; } - else if (popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT && popupAlign == POPUPALIGNMENT_BOTTOMLEFT) { + else if (popupAnchor.EqualsLiteral("bottomright") && popupAlign.EqualsLiteral("bottomleft")) { *ioXPos += inParentRect.width; *ioYPos -= (mRect.height - inParentRect.height); } - else if (popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT && popupAlign == POPUPALIGNMENT_TOPRIGHT) { + else if (popupAnchor.EqualsLiteral("bottomright") && popupAlign.EqualsLiteral("topright")) { *ioXPos -= (mRect.width - inParentRect.width); *ioYPos += inParentRect.height; *outFlushWithTopBottom = PR_TRUE; } - else if (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPRIGHT) { + else if (popupAnchor.EqualsLiteral("topleft") && popupAlign.EqualsLiteral("topright")) { *ioXPos -= mRect.width; } - else if (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_BOTTOMLEFT) { + else if (popupAnchor.EqualsLiteral("topleft") && popupAlign.EqualsLiteral("bottomleft")) { *ioYPos -= mRect.height; *outFlushWithTopBottom = PR_TRUE; } - else if (popupAnchor == POPUPALIGNMENT_BOTTOMLEFT && popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) { + else if (popupAnchor.EqualsLiteral("bottomleft") && popupAlign.EqualsLiteral("bottomright")) { *ioXPos -= mRect.width; *ioYPos -= (mRect.height - inParentRect.height); } - else if (popupAnchor == POPUPALIGNMENT_BOTTOMLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT) { + else if (popupAnchor.EqualsLiteral("bottomleft") && popupAlign.EqualsLiteral("topleft")) { *ioYPos += inParentRect.height; *outFlushWithTopBottom = PR_TRUE; } @@ -822,43 +617,46 @@ nsMenuPopupFrame::MovePopupToOtherSideOfParent ( PRBool inFlushAboveBelow, PRInt } // MovePopupToOtherSideOfParent -// XXXndeakin this function will be reworked in bug 384062 such that positioning -// of the popup is done only when the popup is first opened, so that the popup doesn't -// move around when it is changed in some way. -nsresult -nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) +class nsASyncMenuActivation : public nsIReflowCallback { +public: + nsASyncMenuActivation(nsIContent* aContent) + : mContent(aContent) + { + } + + virtual PRBool ReflowFinished() { + PRBool shouldFlush = PR_FALSE; + if (mContent && + !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, + nsGkAtoms::_true, eCaseMatters) && + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menutobedisplayed, + nsGkAtoms::_true, eCaseMatters)) { + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, + NS_LITERAL_STRING("true"), PR_TRUE); + shouldFlush = PR_TRUE; + } + + delete this; + return shouldFlush; + } + + nsCOMPtr mContent; +}; + +nsresult +nsMenuPopupFrame::SyncViewWithFrame(nsPresContext* aPresContext, + const nsString& aPopupAnchor, + const nsString& aPopupAlign, + nsIFrame* aFrame, + PRInt32 aXPos, PRInt32 aYPos) +{ + NS_ENSURE_ARG(aPresContext); + NS_ENSURE_ARG(aFrame); + if (!mShouldAutoPosition && !mInContentShell) return NS_OK; - PRBool sizedToPopup = PR_FALSE; - - nsPresContext* presContext = PresContext(); - - // if the frame is not specified, use the anchor node passed to ShowPopup. If - // that wasn't specified either, use the root frame. Note that mAnchorContent - // might be a different document so its presshell must be used. - if (!aAnchorFrame) { - if (mAnchorContent) { - nsCOMPtr document = mAnchorContent->GetDocument(); - nsIPresShell *shell = document->GetPrimaryShell(); - if (!shell) - return NS_ERROR_FAILURE; - - aAnchorFrame = shell->GetPrimaryFrameFor(mAnchorContent); - } - else { - aAnchorFrame = presContext->PresShell()->FrameManager()->GetRootFrame(); - } - - if (!aAnchorFrame) - return NS_OK; - } - else { - // the popup should be the same size as the anchor menu, for example, a menulist. - sizedToPopup = nsMenuFrame::IsSizedToPopup(aAnchorFrame->GetContent(), PR_FALSE); - } - // |containingView| // The view that contains the frame that is invoking this popup. This is // the canvas view inside the scrollport view. It can have negative bounds @@ -866,10 +664,15 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) nsIView* containingView = nsnull; nsPoint offset; nsMargin margin; - containingView = aAnchorFrame->GetClosestView(&offset); + containingView = aFrame->GetClosestView(&offset); if (!containingView) return NS_OK; + // |view| + // The root view for the popup window widget associated with this frame, + // or, the view associated with this frame. + nsIView* view = GetView(); + // |parentPos| // The distance between the containingView and the root view. This provides // a hint as to where to position the menu relative to the window. @@ -878,93 +681,58 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) // |parentRect| // The dimensions of the frame invoking the popup. - nsRect parentRect = aAnchorFrame->GetRect(); + nsRect parentRect = aFrame->GetRect(); // get the document and the global script object - nsIPresShell *presShell = presContext->PresShell(); + nsIPresShell *presShell = aPresContext->PresShell(); nsIDocument *document = presShell->GetDocument(); + PRBool sizedToPopup = (mContent->Tag() != nsGkAtoms::tooltip) && + (nsMenuFrame::IsSizedToPopup(aFrame->GetContent(), PR_FALSE)); + // If we stick to our parent's width, set it here before we move the // window around, because moving is done with respect to the width... if (sizedToPopup) { mRect.width = parentRect.width; } - // Use containingView instead of parentView, to account for the scrollarrows - // that a parent menu might have. - nsPoint parentViewWidgetOffset; - nsIWidget* parentViewWidget = containingView->GetNearestWidget(&parentViewWidgetOffset); - nsRect localParentWidgetRect(0,0,0,0), screenParentWidgetRect; - parentViewWidget->WidgetToScreen ( localParentWidgetRect, screenParentWidgetRect ); - // |xpos| and |ypos| hold the x and y positions of where the popup will be moved to, // in _twips_, in the coordinate system of the _parent view_. - PRBool readjustAboveBelow = PR_FALSE; PRInt32 xpos = 0, ypos = 0; - PRInt32 screenViewLocX, screenViewLocY; - if (mScreenXPos == -1 && mScreenYPos == -1) { - // if we are anchored to our parent, there are certain things we don't want to do - // when repositioning the view to fit on the screen, such as end up positioned over - // the parent. When doing this reposition, we want to move the popup to the side with - // the most room. The combination of anchor and alignment dictate if we readjust - // above/below or to the left/right. + // if we are anchored to our parent, there are certain things we don't want to do + // when repositioning the view to fit on the screen, such as end up positioned over + // the parent. When doing this reposition, we want to move the popup to the side with + // the most room. The combination of anchor and alignment dictate if we readjst + // above/below or to the left/right. + PRBool anchoredToParent = PR_FALSE; + PRBool readjustAboveBelow = PR_FALSE; - if (mAnchorContent) { - xpos = parentPos.x + offset.x; - ypos = parentPos.y + offset.y; + if ( aXPos != -1 || aYPos != -1 ) { + + // for this case, we've been handed a specific x/y location (in client coordinates) for + // the popup. However, we may be deeply nested in a frameset, etc and so the client coordinates + // need some adjusting. + nsCOMPtr xulDoc ( do_QueryInterface(document) ); + AdjustClientXYForNestedDocuments ( xulDoc, presShell, aXPos, aYPos, &xpos, &ypos ); - // move the popup according to the anchor and alignment. This will also tell us - // which axis the popup is flush against in case we have to move it around later. - AdjustPositionForAnchorAlign(&xpos, &ypos, parentRect, &readjustAboveBelow); - - // the x and y position may be used to offset the popup after it has been anchored - xpos += presContext->DevPixelsToAppUnits(mXPos); - ypos += presContext->DevPixelsToAppUnits(mYPos); - } - else { - GetStyleMargin()->GetMargin(margin); - xpos = presContext->DevPixelsToAppUnits(mXPos) + margin.left; - ypos = presContext->DevPixelsToAppUnits(mYPos) + margin.top; - } - - // Recall that |xpos| and |ypos| are in the coordinate system of the parent view. In - // order to determine the screen coordinates of where our view will end up, we - // need to find the x/y position of the parent view in screen coords. That is done - // by getting the widget associated with the parent view and determining the offset - // based on converting (0,0) in its coordinate space to screen coords. We then - // offset that point by (|xpos|,|ypos|) to get the true screen coordinates of - // the view. *whew* - - // |parentView| - // The root view for the window that contains the frame, for frames inside - // menupopups this is the first view inside the popup window widget, for - // frames inside a toplevel window, this is the root view of the toplevel - // window. - nsIView* parentView = GetRootViewForPopup(aAnchorFrame, PR_FALSE); - if (!parentView) - return NS_OK; - - screenViewLocX = presContext->DevPixelsToAppUnits(screenParentWidgetRect.x) + - (xpos - parentPos.x) + parentViewWidgetOffset.x; - screenViewLocY = presContext->DevPixelsToAppUnits(screenParentWidgetRect.y) + - (ypos - parentPos.y) + parentViewWidgetOffset.y; - } - else { - // positioned on screen + // Add in the top and left margins GetStyleMargin()->GetMargin(margin); - screenViewLocX = nsPresContext::CSSPixelsToAppUnits(mScreenXPos) + margin.left; - screenViewLocY = nsPresContext::CSSPixelsToAppUnits(mScreenYPos) + margin.top; - xpos = screenViewLocX - presContext->DevPixelsToAppUnits(screenParentWidgetRect.x) - - parentViewWidgetOffset.x - parentPos.x; - ypos = screenViewLocY - presContext->DevPixelsToAppUnits(screenParentWidgetRect.y) - - parentViewWidgetOffset.y - parentPos.y; + xpos += margin.left; + ypos += margin.top; + } + else { + anchoredToParent = PR_TRUE; - // once the popup is positioned on screen, it doesn't need to be positioned again - mShouldAutoPosition = PR_FALSE; + xpos = parentPos.x + offset.x; + ypos = parentPos.y + offset.y; + + // move the popup according to the anchor/alignment attributes. This will also tell us + // which axis the popup is flush against in case we have to move it around later. + AdjustPositionForAnchorAlign ( &xpos, &ypos, parentRect, aPopupAnchor, aPopupAlign, &readjustAboveBelow ); } - + // Compute info about the screen dimensions. Because of multiple monitor systems, // the left or top sides of the screen may be in negative space (main monitor is on the // right, etc). We need to be sure to do the right thing. @@ -988,7 +756,7 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) // for content shells, clip to the client area rather than the screen area if (mInContentShell) { nsRect rootScreenRect = presShell->GetRootFrame()->GetScreenRect(); - rootScreenRect.ScaleRoundIn(presContext->AppUnitsPerDevPixel()); + rootScreenRect.ScaleRoundIn(aPresContext->AppUnitsPerDevPixel()); rect.IntersectRect(rect, rootScreenRect); } @@ -998,8 +766,39 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) PRInt32 screenHeightTwips = rect.height; PRInt32 screenRightTwips = rect.XMost(); PRInt32 screenBottomTwips = rect.YMost(); + + // Recall that |xpos| and |ypos| are in the coordinate system of the parent view. In + // order to determine the screen coordinates of where our view will end up, we + // need to find the x/y position of the parent view in screen coords. That is done + // by getting the widget associated with the parent view and determining the offset + // based on converting (0,0) in its coordinate space to screen coords. We then + // offset that point by (|xpos|,|ypos|) to get the true screen coordinates of + // the view. *whew* - if (mPopupAnchor != POPUPALIGNMENT_NONE) { + // |parentView| + // The root view for the window that contains the frame, for frames inside + // menupopups this is the first view inside the popup window widget, for + // frames inside a toplevel window, this is the root view of the toplevel + // window. + nsIView* parentView = nsnull; + GetRootViewForPopup(aFrame, PR_FALSE, &parentView); + if (!parentView) + return NS_OK; + + // Use containingView instead of parentView, to account for the scrollarrows + // that a parent menu might have. + + nsPoint parentViewWidgetOffset; + nsIWidget* parentViewWidget = containingView->GetNearestWidget(&parentViewWidgetOffset); + nsRect localParentWidgetRect(0,0,0,0), screenParentWidgetRect; + parentViewWidget->WidgetToScreen ( localParentWidgetRect, screenParentWidgetRect ); + PRInt32 screenViewLocX = aPresContext->DevPixelsToAppUnits(screenParentWidgetRect.x) + + (xpos - parentPos.x) + parentViewWidgetOffset.x; + PRInt32 screenViewLocY = aPresContext->DevPixelsToAppUnits(screenParentWidgetRect.y) + + (ypos - parentPos.y) + parentViewWidgetOffset.y; + + if ( anchoredToParent ) { + // // Popup is anchored to the parent, guarantee that it does not cover the parent. We // shouldn't do anything funky if it will already fit on the screen as is. @@ -1026,11 +825,11 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) // compute screen coordinates of parent frame so we can play with it. Make sure we put it // into twips as everything else is as well. - nsRect screenParentFrameRect (presContext->AppUnitsToDevPixels(offset.x), presContext->AppUnitsToDevPixels(offset.y), + nsRect screenParentFrameRect (aPresContext->AppUnitsToDevPixels(offset.x), aPresContext->AppUnitsToDevPixels(offset.y), parentRect.width, parentRect.height ); parentViewWidget->WidgetToScreen ( screenParentFrameRect, screenParentFrameRect ); - screenParentFrameRect.x = presContext->DevPixelsToAppUnits(screenParentFrameRect.x); - screenParentFrameRect.y = presContext->DevPixelsToAppUnits(screenParentFrameRect.y); + screenParentFrameRect.x = aPresContext->DevPixelsToAppUnits(screenParentFrameRect.x); + screenParentFrameRect.y = aPresContext->DevPixelsToAppUnits(screenParentFrameRect.y); // Don't let it spill off the screen to the top if (screenViewLocY < screenTopTwips) { @@ -1144,10 +943,10 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) // y-direction too). // shrink to fit onto the screen, vertically and horizontally - if(mRect.width > screenWidthTwips) - mRect.width = screenWidthTwips; + if(mRect.width > screenWidthTwips) + mRect.width = screenWidthTwips; if(mRect.height > screenHeightTwips) - mRect.height = screenHeightTwips; + mRect.height = screenHeightTwips; // First, adjust the X position. For the X position, we slide the popup // left or right as needed to get it on screen. @@ -1192,7 +991,7 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) } } - presContext->GetViewManager()->MoveViewTo(GetView(), xpos, ypos); + aPresContext->GetViewManager()->MoveViewTo(view, xpos, ypos); // Now that we've positioned the view, sync up the frame's origin. nsPoint frameOrigin = GetPosition(); @@ -1202,45 +1001,189 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame) nsBoxFrame::SetPosition(frameOrigin); if (sizedToPopup) { - nsBoxLayoutState state(PresContext()); - SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height)); + nsBoxLayoutState state(PresContext()); + SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height)); + } + + if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, + nsGkAtoms::_true, eCaseMatters) && + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menutobedisplayed, + nsGkAtoms::_true, eCaseMatters)) { + nsIReflowCallback* cb = new nsASyncMenuActivation(mContent); + NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY); + PresContext()->PresShell()->PostReflowCallback(cb); } return NS_OK; } -/* virtual */ nsMenuFrame* +static void GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, + nsIFrame** aResult) +{ + nsIContent* child = nsnull; + if (aChild) + child = aChild->GetContent(); + aShell->FrameConstructor()->GetInsertionPoint(aFrame, child, aResult); +} + +/* virtual */ nsIMenuFrame* +nsMenuPopupFrame::GetNextMenuItem(nsIMenuFrame* aStart) +{ + nsIFrame* immediateParent = nsnull; + GetInsertionPoint(PresContext()->PresShell(), this, nsnull, + &immediateParent); + if (!immediateParent) + immediateParent = this; + + nsIFrame* currFrame = nsnull; + nsIFrame* startFrame = nsnull; + if (aStart) { + aStart->QueryInterface(NS_GET_IID(nsIFrame), (void**)&currFrame); + if (currFrame) { + startFrame = currFrame; + currFrame = currFrame->GetNextSibling(); + } + } + else + currFrame = immediateParent->GetFirstChild(nsnull); + + while (currFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + currFrame = currFrame->GetNextSibling(); + } + + currFrame = immediateParent->GetFirstChild(nsnull); + + // Still don't have anything. Try cycling from the beginning. + while (currFrame && currFrame != startFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + + currFrame = currFrame->GetNextSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +/* virtual */ nsIMenuFrame* +nsMenuPopupFrame::GetPreviousMenuItem(nsIMenuFrame* aStart) +{ + nsIFrame* immediateParent = nsnull; + GetInsertionPoint(PresContext()->PresShell(), this, nsnull, + &immediateParent); + if (!immediateParent) + immediateParent = this; + + nsFrameList frames(immediateParent->GetFirstChild(nsnull)); + + nsIFrame* currFrame = nsnull; + nsIFrame* startFrame = nsnull; + if (aStart) { + aStart->QueryInterface(NS_GET_IID(nsIFrame), (void**)&currFrame); + if (currFrame) { + startFrame = currFrame; + currFrame = frames.GetPrevSiblingFor(currFrame); + } + } + else currFrame = frames.LastChild(); + + while (currFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + currFrame = frames.GetPrevSiblingFor(currFrame); + } + + currFrame = frames.LastChild(); + + // Still don't have anything. Try cycling from the end. + while (currFrame && currFrame != startFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + + currFrame = frames.GetPrevSiblingFor(currFrame); + } + + // No luck. Just return our start value. + return aStart; +} + +/* virtual */ nsIMenuFrame* nsMenuPopupFrame::GetCurrentMenuItem() { return mCurrentMenu; } -PRBool nsMenuPopupFrame::ConsumeOutsideClicks() +NS_IMETHODIMP nsMenuPopupFrame::ConsumeOutsideClicks(PRBool& aConsumeOutsideClicks) { + /* + * When this popup is open, should clicks outside of it be consumed? + * Return PR_TRUE if the popup hould rollup on an outside click, + * but consume that click so it can't be used for anything else. + * Return PR_FALSE to allow clicks outside the popup to activate content + * even when the popup is open. + * --------------------------------------------------------------------- + * + * Should clicks outside of a popup be eaten? + * + * Menus Autocomplete Comboboxes + * Mac Eat No Eat + * Win No No Eat + * Unix Eat No Eat + * + */ + // If the popup has explicitly set a consume mode, honor that. - if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT) - return (mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME); + if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT) { + aConsumeOutsideClicks = mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME; + return NS_OK; + } + + aConsumeOutsideClicks = PR_TRUE; nsCOMPtr parentContent = mContent->GetParent(); + if (parentContent) { - nsINodeInfo *ni = parentContent->NodeInfo(); - if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) - return PR_TRUE; // Consume outside clicks for combo boxes on all platforms + nsIAtom *parentTag = parentContent->Tag(); + if (parentTag == nsGkAtoms::menulist) + return NS_OK; // Consume outside clicks for combo boxes on all platforms + if (parentTag == nsGkAtoms::menu || parentTag == nsGkAtoms::popupset) { #if defined(XP_WIN) || defined(XP_OS2) - // Don't consume outside clicks for menus in Windows - if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) || - (ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL))) - return PR_FALSE; + // Don't consume outside clicks for menus in Windows + aConsumeOutsideClicks = PR_FALSE; #endif - if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) { + return NS_OK; + } + if (parentTag == nsGkAtoms::textbox) { // Don't consume outside clicks for autocomplete widget if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::autocomplete, eCaseMatters)) - return PR_FALSE; + aConsumeOutsideClicks = PR_FALSE; } } - return PR_TRUE; + return NS_OK; } static nsIScrollableView* GetScrollableViewForFrame(nsIFrame* aFrame) @@ -1285,17 +1228,20 @@ nsIScrollableView* nsMenuPopupFrame::GetScrollableView(nsIFrame* aStart) return nsnull; } -void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) +void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsIMenuFrame* aMenuItem) { - if (aMenuItem) { - nsIFrame* childFrame = GetFirstChild(nsnull); + nsIFrame* frame=nsnull; + aMenuItem->QueryInterface(NS_GET_IID(nsIFrame), (void**)&frame); + if ( frame ) { + nsIFrame* childFrame=nsnull; + childFrame = GetFirstChild(nsnull); nsIScrollableView *scrollableView; - scrollableView = GetScrollableView(childFrame); - if (scrollableView) { + scrollableView=GetScrollableView(childFrame); + if ( scrollableView ) { nscoord scrollX, scrollY; nsRect viewRect = scrollableView->View()->GetBounds(); - nsRect itemRect = aMenuItem->GetRect(); + nsRect itemRect = frame->GetRect(); scrollableView->GetScrollPosition(scrollX, scrollY); // scroll down @@ -1309,53 +1255,36 @@ void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) } } -NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) +NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsIMenuFrame* aMenuItem) { - if (mCurrentMenu == aMenuItem) - return NS_OK; - - if (mCurrentMenu) { - mCurrentMenu->SelectMenu(PR_FALSE); - } - - if (aMenuItem) { - EnsureMenuItemIsVisible(aMenuItem); - aMenuItem->SelectMenu(PR_TRUE); - } - - mCurrentMenu = aMenuItem; - - return NS_OK; -} - -void -nsMenuPopupFrame::CurrentMenuIsBeingDestroyed() -{ - mCurrentMenu = nsnull; -} - -NS_IMETHODIMP -nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, - PRBool aSelectFirstItem) -{ - if (mCurrentMenu == aMenuItem) - return NS_OK; - // When a context menu is open, the current menu is locked, and no change // to the menu is allowed. - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (!mIsContextMenu && pm && pm->HasContextMenu(this)) + nsIMenuParent *contextMenu = GetContextMenu(); + if (contextMenu) return NS_OK; + if (mCurrentMenu == aMenuItem) + return NS_OK; + // Unset the current child. if (mCurrentMenu) { + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsOpen(isOpen); mCurrentMenu->SelectMenu(PR_FALSE); - nsMenuPopupFrame* popup = mCurrentMenu->GetPopup(); - if (popup) { - if (mCurrentMenu->IsOpen()) { - if (pm) - pm->HidePopupAfterDelay(popup); - } + // XXX bug 294183 sometimes mCurrentMenu gets cleared + if (mCurrentMenu && isOpen) { + // Don't close up immediately. + // Kick off a close timer. + KillCloseTimer(); // Ensure we don't have another stray waiting closure. + PRInt32 menuDelay = 300; // ms + + PresContext()->LookAndFeel()-> + GetMetric(nsILookAndFeel::eMetric_SubmenuDelay, menuDelay); + + // Kick off the timer. + mCloseTimer = do_CreateInstance("@mozilla.org/timer;1"); + mCloseTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); + mTimerMenu = mCurrentMenu; } } @@ -1370,19 +1299,74 @@ nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, return NS_OK; } -nsMenuFrame* + +NS_IMETHODIMP +nsMenuPopupFrame::Escape(PRBool& aHandledFlag) +{ + mIncrementalString.Truncate(); + + // See if we have a context menu open. + nsIMenuParent* contextMenu = GetContextMenu(); + if (contextMenu) { + // Get the context menu parent. + nsIFrame* childFrame; + CallQueryInterface(contextMenu, &childFrame); + nsIPopupSetFrame* popupSetFrame = GetPopupSetFrame(PresContext()); + if (popupSetFrame) + // Destroy the popup. + popupSetFrame->DestroyPopup(childFrame, PR_FALSE); + aHandledFlag = PR_TRUE; + return NS_OK; + } + + if (!mCurrentMenu) + return NS_OK; + + // See if our menu is open. + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsOpen(isOpen); + if (isOpen) { + // Let the child menu handle this. + mCurrentMenu->Escape(aHandledFlag); + if (!aHandledFlag) { + // We should close up. + mCurrentMenu->OpenMenu(PR_FALSE); + // SelectMenu() so DOMMenuItemActive is fired for accessibility + mCurrentMenu->SelectMenu(PR_TRUE); + aHandledFlag = PR_TRUE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMenuPopupFrame::Enter() { mIncrementalString.Truncate(); + // See if we have a context menu open. + nsIMenuParent *contextMenu = GetContextMenu(); + if (contextMenu) + return contextMenu->Enter(); + // Give it to the child. if (mCurrentMenu) - return mCurrentMenu->Enter(); + mCurrentMenu->Enter(); - return nsnull; + return NS_OK; } -nsMenuFrame* +nsIMenuParent* +nsMenuPopupFrame::GetContextMenu() +{ + if (mIsContextMenu) + return nsnull; + + return nsMenuFrame::GetContextMenu(); +} + +nsIMenuFrame* nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction) { PRUint32 charCode, keyCode; @@ -1393,22 +1377,22 @@ nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doActi // Enumerate over our list of frames. nsIFrame* immediateParent = nsnull; - PresContext()->PresShell()-> - FrameConstructor()->GetInsertionPoint(this, nsnull, &immediateParent); + GetInsertionPoint(PresContext()->PresShell(), this, nsnull, + &immediateParent); if (!immediateParent) immediateParent = this; PRUint32 matchCount = 0, matchShortcutCount = 0; PRBool foundActive = PR_FALSE; PRBool isShortcut; - nsMenuFrame* frameBefore = nsnull; - nsMenuFrame* frameAfter = nsnull; - nsMenuFrame* frameShortcut = nsnull; + nsIMenuFrame* frameBefore = nsnull; + nsIMenuFrame* frameAfter = nsnull; + nsIMenuFrame* frameShortcut = nsnull; nsIContent* parentContent = mContent->GetParent(); - PRBool isMenu = parentContent && - !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL); + PRBool isMenu = + parentContent && parentContent->Tag() != nsGkAtoms::menulist; static DOMTimeStamp lastKeyTime = 0; DOMTimeStamp keyTime; @@ -1461,21 +1445,16 @@ nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doActi // currFrame = immediateParent->GetFirstChild(nsnull); - PRInt32 menuAccessKey = -1; - nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); - // We start searching from first child. This process is divided into two parts // -- before current and after current -- by the current item while (currFrame) { nsIContent* current = currFrame->GetContent(); // See if it's a menu item. - if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, PR_TRUE)) { + if (IsValidItem(current)) { nsAutoString textKey; - if (menuAccessKey >= 0) { - // Get the shortcut attribute. - current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey); - } + // Get the shortcut attribute. + current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey); if (textKey.IsEmpty()) { // No shortcut, try first letter isShortcut = PR_FALSE; current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey); @@ -1488,24 +1467,25 @@ nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doActi if (StringBeginsWith(textKey, incrementalString, nsCaseInsensitiveStringComparator())) { // mIncrementalString is a prefix of textKey - if (currFrame->GetType() == nsGkAtoms::menuFrame) { + nsIMenuFrame* menuFrame; + if (NS_SUCCEEDED(CallQueryInterface(currFrame, &menuFrame))) { // There is one match matchCount++; if (isShortcut) { // There is one shortcut-key match matchShortcutCount++; // Record the matched item. If there is only one matched shortcut item, do it - frameShortcut = NS_STATIC_CAST(nsMenuFrame *, currFrame); + frameShortcut = menuFrame; } if (!foundActive) { // It's a first candidate item located before/on the current item if (!frameBefore) - frameBefore = NS_STATIC_CAST(nsMenuFrame *, currFrame); + frameBefore = menuFrame; } else { // It's a first candidate item located after the current item if (!frameAfter) - frameAfter = NS_STATIC_CAST(nsMenuFrame *, currFrame); + frameAfter = menuFrame; } } else @@ -1519,8 +1499,11 @@ nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doActi if (stringLength > 1) { // If there is more than one char typed, the current item has highest priority, // otherwise the item next to current has highest priority - if (currFrame == frameBefore) + nsIMenuFrame* menuFrame; + if (NS_SUCCEEDED(CallQueryInterface(currFrame, &menuFrame)) && + menuFrame == frameBefore) { return frameBefore; + } } } } @@ -1553,12 +1536,237 @@ nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doActi return nsnull; } +NS_IMETHODIMP +nsMenuPopupFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) +{ + // See if we have a context menu open. + nsIMenuParent *contextMenu = GetContextMenu(); + if (contextMenu) + return contextMenu->ShortcutNavigation(aKeyEvent, aHandledFlag); + + if (mCurrentMenu) { + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsOpen(isOpen); + if (isOpen) { + // No way this applies to us. Give it to our child. + mCurrentMenu->ShortcutNavigation(aKeyEvent, aHandledFlag); + return NS_OK; + } + } + + // This applies to us. Let's see if one of the shortcuts applies + PRBool action; + nsIMenuFrame* result = FindMenuWithShortcut(aKeyEvent, action); + if (result) { + // We got one! + nsIFrame* frame = nsnull; + CallQueryInterface(result, &frame); + nsWeakFrame weakResult(frame); + aHandledFlag = PR_TRUE; + SetCurrentMenuItem(result); + if (action && weakResult.IsAlive()) { + result->Enter(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag) +{ + // See if we have a context menu open. + nsIMenuParent *contextMenu = GetContextMenu(); + if (contextMenu) + return contextMenu->KeyboardNavigation(aKeyCode, aHandledFlag); + + nsNavigationDirection theDirection; + NS_DIRECTION_FROM_KEY_CODE(theDirection, aKeyCode); + + mIncrementalString.Truncate(); + + // This method only gets called if we're open. + if (!mCurrentMenu && NS_DIRECTION_IS_INLINE(theDirection)) { + // We've been opened, but we haven't had anything selected. + // We can handle End, but our parent handles Start. + if (theDirection == eNavigationDirection_End) { + nsIMenuFrame* nextItem = GetNextMenuItem(nsnull); + if (nextItem) { + aHandledFlag = PR_TRUE; + SetCurrentMenuItem(nextItem); + } + } + return NS_OK; + } + + PRBool isContainer = PR_FALSE; + PRBool isOpen = PR_FALSE; + PRBool isDisabled = PR_FALSE; + nsWeakFrame weakFrame(this); + if (mCurrentMenu) { + mCurrentMenu->MenuIsContainer(isContainer); + mCurrentMenu->MenuIsOpen(isOpen); + mCurrentMenu->MenuIsDisabled(isDisabled); + + if (isOpen) { + // Give our child a shot. + mCurrentMenu->KeyboardNavigation(aKeyCode, aHandledFlag); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + } + else if (theDirection == eNavigationDirection_End && + isContainer && !isDisabled) { + // The menu is not yet open. Open it and select the first item. + aHandledFlag = PR_TRUE; + nsIFrame* frame = nsnull; + CallQueryInterface(mCurrentMenu, &frame); + nsWeakFrame weakCurrentFrame(frame); + mCurrentMenu->OpenMenu(PR_TRUE); + NS_ENSURE_TRUE(weakCurrentFrame.IsAlive(), NS_OK); + mCurrentMenu->SelectFirstItem(); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + } + } + + if (aHandledFlag) + return NS_OK; // The child menu took it for us. + + // For block progression, we can move in either direction + if (NS_DIRECTION_IS_BLOCK(theDirection) || + NS_DIRECTION_IS_BLOCK_TO_EDGE(theDirection)) { + + nsIMenuFrame* nextItem; + + if (theDirection == eNavigationDirection_Before) + nextItem = GetPreviousMenuItem(mCurrentMenu); + else if (theDirection == eNavigationDirection_After) + nextItem = GetNextMenuItem(mCurrentMenu); + else if (theDirection == eNavigationDirection_First) + nextItem = GetNextMenuItem(nsnull); + else + nextItem = GetPreviousMenuItem(nsnull); + + if (nextItem) { + aHandledFlag = PR_TRUE; + SetCurrentMenuItem(nextItem); + } + } + else if (mCurrentMenu && isContainer && isOpen) { + if (theDirection == eNavigationDirection_Start) { + // Close it up. + mCurrentMenu->OpenMenu(PR_FALSE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + // SelectMenu() so DOMMenuItemActive is fired for accessibility + mCurrentMenu->SelectMenu(PR_TRUE); + aHandledFlag = PR_TRUE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::GetParentPopup(nsIMenuParent** aMenuParent) +{ + *aMenuParent = nsnull; + nsIFrame* parent = GetParent(); + while (parent) { + nsCOMPtr menuParent = do_QueryInterface(parent); + if (menuParent) { + *aMenuParent = menuParent.get(); + NS_ADDREF(*aMenuParent); + return NS_OK; + } + parent = parent->GetParent(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::HideChain() +{ + if (!mShouldRollup) + return NS_OK; + + // Stop capturing rollups + // (must do this during Hide, which happens before the menu item is executed, + // since this reinstates normal event handling.) + nsMenuDismissalListener::Shutdown(); + + nsIFrame* frame = GetParent(); + if (frame) { + nsWeakFrame weakMenu(frame); + nsIMenuFrame* menuFrame; + if (NS_FAILED(CallQueryInterface(frame, &menuFrame))) { + nsIPopupSetFrame* popupSetFrame = GetPopupSetFrame(PresContext()); + if (popupSetFrame) + // Hide the popup. + popupSetFrame->HidePopup(this); + return NS_OK; + } + + menuFrame->ActivateMenu(PR_FALSE); + NS_ENSURE_TRUE(weakMenu.IsAlive(), NS_OK); + menuFrame->SelectMenu(PR_FALSE); + NS_ENSURE_TRUE(weakMenu.IsAlive(), NS_OK); + + // Get the parent. + nsIMenuParent *menuParent = menuFrame->GetMenuParent(); + if (menuParent) + menuParent->HideChain(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::DismissChain() +{ + if (!mShouldRollup) + return NS_OK; + + // Stop capturing rollups + nsMenuDismissalListener::Shutdown(); + + // Get our menu parent. + nsIFrame* frame = GetParent(); + if (frame) { + nsIMenuFrame *menuFrame = nsnull; + CallQueryInterface(frame, &menuFrame); + if (!menuFrame) { + nsIPopupSetFrame* popupSetFrame = GetPopupSetFrame(PresContext()); + if (popupSetFrame) { + // make sure the menu is not highlighted + if (mCurrentMenu) { + PRBool wasOpen; + mCurrentMenu->MenuIsOpen(wasOpen); + if (wasOpen) + mCurrentMenu->OpenMenu(PR_FALSE); + mCurrentMenu->SelectMenu(PR_FALSE); + } + // Destroy the popup. + popupSetFrame->DestroyPopup(this, PR_TRUE); + } + return NS_OK; + } + + menuFrame->OpenMenu(PR_FALSE); + + // Get the parent. + nsIMenuParent* menuParent = menuFrame->GetMenuParent(); + if (menuParent) + menuParent->DismissChain(); + } + + return NS_OK; +} + NS_IMETHODIMP nsMenuPopupFrame::GetWidget(nsIWidget **aWidget) { // Get parent view + nsIView * view = nsnull; // XXX should this be passing PR_FALSE or PR_TRUE for aStopAtViewManagerRoot? - nsIView * view = GetRootViewForPopup(this, PR_FALSE); + nsMenuPopupFrame::GetRootViewForPopup(this, PR_FALSE, &view); if (!view) return NS_OK; @@ -1567,14 +1775,79 @@ nsMenuPopupFrame::GetWidget(nsIWidget **aWidget) return NS_OK; } -void +NS_IMETHODIMP nsMenuPopupFrame::AttachedDismissalListener() { mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT; + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::InstallKeyboardNavigator() +{ + if (mKeyboardNavigator) + return NS_OK; + + nsCOMPtr target = do_QueryInterface(mContent->GetDocument()); + + mTarget = target; + mKeyboardNavigator = new nsMenuListener(this); + NS_IF_ADDREF(mKeyboardNavigator); + + target->AddEventListener(NS_LITERAL_STRING("keypress"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + target->AddEventListener(NS_LITERAL_STRING("keydown"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + target->AddEventListener(NS_LITERAL_STRING("keyup"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + + nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_TRUE); + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::RemoveKeyboardNavigator() +{ + if (!mKeyboardNavigator) + return NS_OK; + + mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + + NS_IF_RELEASE(mKeyboardNavigator); + + nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_FALSE); + + return NS_OK; } // helpers ///////////////////////////////////////////////////////////// +PRBool +nsMenuPopupFrame::IsValidItem(nsIContent* aContent) +{ + nsIAtom *tag = aContent->Tag(); + + PRBool skipNavigatingDisabledMenuItem; + PresContext()->LookAndFeel()-> + GetMetric(nsILookAndFeel::eMetric_SkipNavigatingDisabledMenuItem, + skipNavigatingDisabledMenuItem); + + PRBool result = (tag == nsGkAtoms::menu || + tag == nsGkAtoms::menuitem || + tag == nsGkAtoms::option); + if (skipNavigatingDisabledMenuItem) + result = result && !IsDisabled(aContent); + + return result; +} + +PRBool +nsMenuPopupFrame::IsDisabled(nsIContent* aContent) +{ + return aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters); +} + NS_IMETHODIMP nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, @@ -1586,19 +1859,11 @@ nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID, if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top) MoveToAttributePosition(); - - // accessibility needs this to ensure the frames get constructed when the - // menugenerated attribute is set, see bug 279703 comment 42 for discussion - if (aAttribute == nsGkAtoms::menugenerated && - mFrames.IsEmpty() && !mGeneratedChildren) { - PresContext()->PresShell()->FrameConstructor()-> - AddLazyChildren(mContent, LazyGeneratePopupDone, nsnull); - } return rv; } -void +void nsMenuPopupFrame::MoveToAttributePosition() { // Move the widget around when the user sets the |left| and |top| attributes. @@ -1608,29 +1873,145 @@ nsMenuPopupFrame::MoveToAttributePosition() nsAutoString left, top; mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); - PRInt32 err1, err2; - mScreenXPos = left.ToInteger(&err1); - mScreenYPos = top.ToInteger(&err2); + PRInt32 err1, err2, xPos, yPos; + xPos = left.ToInteger(&err1); + yPos = top.ToInteger(&err2); - if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) - MoveToInternal(mScreenXPos, mScreenYPos); + if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) { + MoveToInternal(xPos, yPos); + } +} + + +NS_IMETHODIMP +nsMenuPopupFrame::HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus) +{ + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } void nsMenuPopupFrame::Destroy() { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->PopupDestroyed(this); + // Null out the pointer to this frame in the mediator wrapper so that it + // doesn't try to interact with a deallocated frame. + mTimerMediator->ClearFrame(); + + if (mCloseTimer) + mCloseTimer->Cancel(); nsPresContext* rootPresContext = PresContext()->RootPresContext(); if (rootPresContext->ContainsActivePopup(this)) { rootPresContext->NotifyRemovedActivePopup(this); } + RemoveKeyboardNavigator(); nsBoxFrame::Destroy(); } +// REVIEW: The override here was doing nothing at all since nsBoxFrame is our +// parent class +// +// Notify +// +// The item selection timer has fired, we might have to readjust the +// selected item. There are two cases here that we are trying to deal with: +// (1) diagonal movement from a parent menu to a submenu passing briefly over +// other items, and +// (2) moving out from a submenu to a parent or grandparent menu. +// In both cases, |mTimerMenu| is the menu item that might have an open submenu and +// |mCurrentMenu| is the item the mouse is currently over, which could be none of them. +// +// case (1): +// As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the +// submenu, it probably passes through one or more sibilings (B). As the mouse passes +// through B, it becomes the current menu item and the timer is set and mTimerMenu is +// set to A. Before the timer fires, the mouse leaves the menu containing A and B and +// enters the submenus. Now when the timer fires, |mCurrentMenu| is null (!= |mTimerMenu|) +// so we have to see if anything in A's children is selected (recall that even disabled +// items are selected, the style just doesn't show it). If that is the case, we need to +// set the selected item back to A. +// +// case (2); +// Item A has an open submenu, and in it there is an item (B) which also has an open +// submenu (so there are 3 menus displayed right now). The mouse then leaves B's child +// submenu and selects an item that is a sibling of A, call it C. When the mouse enters C, +// the timer is set and |mTimerMenu| is A and |mCurrentMenu| is C. As the timer fires, +// the mouse is still within C. The correct behavior is to set the current item to C +// and close up the chain parented at A. +// +// This brings up the question of is the logic of case (1) enough? The answer is no, +// and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected +// child, and if it does, set the selected item to A. Because B has a submenu open, it +// is selected and as a result, A is set to be the selected item even though the mouse +// rests in C -- very wrong. +// +// The solution is to use the same idea, but instead of only checking one level, +// drill all the way down to the deepest open submenu and check if it has something +// selected. Since the mouse is in a grandparent, it won't, and we know that we can +// safely close up A and all its children. +// +// The code below melds the two cases together. +// +nsresult +nsMenuPopupFrame::Notify(nsITimer* aTimer) +{ + // Our timer has fired. + if (aTimer == mCloseTimer.get()) { + PRBool menuOpen = PR_FALSE; + mTimerMenu->MenuIsOpen(menuOpen); + if (menuOpen) + mTimerMenu->OpenMenu(PR_FALSE); + + if (mCloseTimer) + mCloseTimer->Cancel(); + } + + mCloseTimer = nsnull; + mTimerMenu = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsMenuPopupFrame::KillCloseTimer() +{ + if (mCloseTimer && mTimerMenu) { + PRBool menuOpen = PR_FALSE; + mTimerMenu->MenuIsOpen(menuOpen); + if (menuOpen) { + mTimerMenu->OpenMenu(PR_FALSE); + } + mCloseTimer->Cancel(); + mCloseTimer = nsnull; + mTimerMenu = nsnull; + } + return NS_OK; +} + + + +NS_IMETHODIMP +nsMenuPopupFrame::KillPendingTimers ( ) +{ + return KillCloseTimer(); + +} // KillPendingTimers + +NS_IMETHODIMP +nsMenuPopupFrame::CancelPendingTimers() +{ + if (mCloseTimer && mTimerMenu) { + if (mTimerMenu != mCurrentMenu) { + SetCurrentMenuItem(mTimerMenu); + } + mCloseTimer->Cancel(); + mCloseTimer = nsnull; + mTimerMenu = nsnull; + } + return NS_OK; +} + void nsMenuPopupFrame::MoveTo(PRInt32 aLeft, PRInt32 aTop) { @@ -1661,14 +2042,10 @@ nsMenuPopupFrame::MoveToInternal(PRInt32 aLeft, PRInt32 aTop) nsIView* view = GetView(); NS_ASSERTION(view->GetParent(), "Must have parent!"); - + // Retrieve screen position of parent view nsIntPoint screenPos = view->GetParent()->GetScreenPosition(); - nsPresContext* context = PresContext(); - aLeft = context->AppUnitsToDevPixels(nsPresContext::CSSPixelsToAppUnits(aLeft)); - aTop = context->AppUnitsToDevPixels(nsPresContext::CSSPixelsToAppUnits(aTop)); - // Move the widget // XXXbz don't we want screenPos to be the parent _widget_'s position, then? view->GetWidget()->Move(aLeft - screenPos.x, aTop - screenPos.y); @@ -1686,8 +2063,60 @@ nsMenuPopupFrame::SetAutoPosition(PRBool aShouldAutoPosition) mShouldAutoPosition = aShouldAutoPosition; } +void +nsMenuPopupFrame::EnableRollup(PRBool aShouldRollup) +{ + if (!nsMenuDismissalListener::sInstance || + nsMenuDismissalListener::sInstance->GetCurrentMenuParent() != this) + return; + + if (aShouldRollup) + nsMenuDismissalListener::sInstance->Register(); + else + nsMenuDismissalListener::sInstance->Unregister(); +} + void nsMenuPopupFrame::SetConsumeRollupEvent(PRUint32 aConsumeMode) { mConsumeRollupEvent = aConsumeMode; } + +// nsMenuPopupTimerMediator implementation. +NS_IMPL_ISUPPORTS1(nsMenuPopupTimerMediator, nsITimerCallback) + +/** + * Constructs a wrapper around an nsMenuFrame. + * @param aFrame nsMenuFrame to create a wrapper around. + */ +nsMenuPopupTimerMediator::nsMenuPopupTimerMediator(nsMenuPopupFrame *aFrame) : + mFrame(aFrame) +{ + NS_ASSERTION(mFrame, "Must have frame"); +} + +nsMenuPopupTimerMediator::~nsMenuPopupTimerMediator() +{ +} + +/** + * Delegates the notification to the contained frame if it has not been destroyed. + * @param aTimer Timer which initiated the callback. + * @return NS_ERROR_FAILURE if the frame has been destroyed. + */ +NS_IMETHODIMP nsMenuPopupTimerMediator::Notify(nsITimer* aTimer) +{ + if (!mFrame) + return NS_ERROR_FAILURE; + + return mFrame->Notify(aTimer); +} + +/** + * Clear the pointer to the contained nsMenuFrame. This should be called + * when the contained nsMenuFrame is destroyed. + */ +void nsMenuPopupTimerMediator::ClearFrame() +{ + mFrame = nsnull; +} diff --git a/layout/xul/base/src/nsMenuPopupFrame.h b/layout/xul/base/src/nsMenuPopupFrame.h index 36be6d67355..717d593040d 100644 --- a/layout/xul/base/src/nsMenuPopupFrame.h +++ b/layout/xul/base/src/nsMenuPopupFrame.h @@ -47,10 +47,9 @@ #include "prtypes.h" #include "nsIAtom.h" -#include "nsGkAtoms.h" #include "nsCOMPtr.h" -#include "nsMenuFrame.h" #include "nsIDOMEventTarget.h" +#include "nsMenuListener.h" #include "nsBoxFrame.h" #include "nsIMenuParent.h" @@ -58,20 +57,6 @@ #include "nsITimer.h" -enum nsPopupType { - ePopupTypePanel, - ePopupTypeMenu, - ePopupTypeTooltip -}; - -// values are selected so that the direction can be flipped just by -// changing the sign -#define POPUPALIGNMENT_NONE 0 -#define POPUPALIGNMENT_TOPLEFT 1 -#define POPUPALIGNMENT_TOPRIGHT -1 -#define POPUPALIGNMENT_BOTTOMLEFT 2 -#define POPUPALIGNMENT_BOTTOMRIGHT -2 - #define INC_TYP_INTERVAL 1000 // 1s. If the interval between two keypresses is shorter than this, // treat as a continue typing // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: @@ -84,54 +69,75 @@ nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContex class nsIViewManager; class nsIView; class nsIMenuParent; +class nsIMenuFrame; +class nsIDOMXULDocument; + class nsMenuPopupFrame; +/** + * nsMenuPopupTimerMediator is a wrapper around an nsMenuPopupFrame which can be safely + * passed to timers. The class is reference counted unlike the underlying + * nsMenuPopupFrame, so that it will exist as long as the timer holds a reference + * to it. The callback is delegated to the contained nsMenuPopupFrame as long as + * the contained nsMenuPopupFrame has not been destroyed. + */ +class nsMenuPopupTimerMediator : public nsITimerCallback +{ +public: + nsMenuPopupTimerMediator(nsMenuPopupFrame* aFrame); + ~nsMenuPopupTimerMediator(); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + void ClearFrame(); + +private: + + // Pointer to the wrapped frame. + nsMenuPopupFrame* mFrame; +}; + class nsMenuPopupFrame : public nsBoxFrame, public nsIMenuParent { public: nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext); + NS_DECL_ISUPPORTS + + // nsIMenuParentInterface - virtual nsMenuFrame* GetCurrentMenuItem(); - NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem); - virtual void CurrentMenuIsBeingDestroyed(); - NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, PRBool aSelectFirstItem); - - // as popups are opened asynchronously, the popup pending state is used to - // prevent multiple requests from attempting to open the same popup twice - PRBool IsOpenPending() { return mIsOpenPending; } - void ClearOpenPending() { mIsOpenPending = PR_FALSE; } - + virtual nsIMenuFrame* GetCurrentMenuItem(); + NS_IMETHOD SetCurrentMenuItem(nsIMenuFrame* aMenuItem); + virtual nsIMenuFrame* GetNextMenuItem(nsIMenuFrame* aStart); + virtual nsIMenuFrame* GetPreviousMenuItem(nsIMenuFrame* aStart); NS_IMETHOD SetActive(PRBool aActiveFlag) { return NS_OK; } // We don't care. - virtual PRBool IsActive() { return PR_FALSE; } - virtual PRBool IsMenuBar() { return PR_FALSE; } + NS_IMETHOD GetIsActive(PRBool& isActive) { isActive = PR_FALSE; return NS_OK; } + NS_IMETHOD IsMenuBar(PRBool& isMenuBar) { isMenuBar = PR_FALSE; return NS_OK; } + NS_IMETHOD ConsumeOutsideClicks(PRBool& aConsumeOutsideClicks); + NS_IMETHOD ClearRecentlyRolledUp() {return NS_OK;} + NS_IMETHOD RecentlyRolledUp(nsIMenuFrame *aMenuFrame, PRBool *aJustRolledUp) {*aJustRolledUp = PR_FALSE; return NS_OK;} + NS_IMETHOD SetIsContextMenu(PRBool aIsContextMenu) { mIsContextMenu = aIsContextMenu; return NS_OK; } + NS_IMETHOD GetIsContextMenu(PRBool& aIsContextMenu) { aIsContextMenu = mIsContextMenu; return NS_OK; } + + NS_IMETHOD GetParentPopup(nsIMenuParent** aResult); - /* - * When this popup is open, should clicks outside of it be consumed? - * Return PR_TRUE if the popup should rollup on an outside click, - * but consume that click so it can't be used for anything else. - * Return PR_FALSE to allow clicks outside the popup to activate content - * even when the popup is open. - * --------------------------------------------------------------------- - * - * Should clicks outside of a popup be eaten? - * - * Menus Autocomplete Comboboxes - * Mac Eat No Eat - * Win No No Eat - * Unix Eat No Eat - * - */ - PRBool ConsumeOutsideClicks(); + // Closes up the chain of open cascaded menus. + NS_IMETHOD DismissChain(); - virtual PRBool IsContextMenu() { return mIsContextMenu; } + // Hides the chain of cascaded menus without closing them up. + NS_IMETHOD HideChain(); - virtual PRBool MenuClosed() { return PR_TRUE; } + NS_IMETHOD KillPendingTimers(); + NS_IMETHOD CancelPendingTimers(); + + NS_IMETHOD InstallKeyboardNavigator(); + NS_IMETHOD RemoveKeyboardNavigator(); NS_IMETHOD GetWidget(nsIWidget **aWidget); // The dismissal listener gets created and attached to the window. - void AttachedDismissalListener(); + NS_IMETHOD AttachedDismissalListener(); // Overridden methods NS_IMETHOD Init(nsIContent* aContent, @@ -142,6 +148,10 @@ public: nsIAtom* aAttribute, PRInt32 aModType); + NS_IMETHOD HandleEvent(nsPresContext* aPresContext, + nsGUIEvent* aEvent, + nsEventStatus* aEventStatus); + virtual void Destroy(); virtual void InvalidateInternal(const nsRect& aDamageRect, @@ -150,82 +160,29 @@ public: virtual nsresult CreateWidgetForView(nsIView* aView); - NS_IMETHOD SetInitialChildList(nsIAtom* aListName, - nsIFrame* aChildList); - - virtual PRBool IsLeaf() const - { - if (!mGeneratedChildren && mPopupType == ePopupTypeMenu) { - // menu popups generate their child frames lazily only when opened, so - // behave like a leaf frame. However, generate child frames normally if - // the parent menu has a sizetopopup attribute. In this case the size of - // the parent menu is dependant on the size of the popup, so the frames - // need to exist in order to calculate this size. - nsIContent* parentContent = mContent->GetParent(); - if (parentContent && - !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup)) - return PR_TRUE; - } - - return PR_FALSE; - } - - // AdjustView should be called by the parent frame after the popup has been - // laid out, so that the view can be shown. - void AdjustView(); - void GetViewOffset(nsIView* aView, nsPoint& aPoint); - nsIView* GetRootViewForPopup(nsIFrame* aStartFrame, - PRBool aStopAtViewManagerRoot); + static void GetRootViewForPopup(nsIFrame* aStartFrame, + PRBool aStopAtViewManagerRoot, + nsIView** aResult); - // set the position of the popup either relative to the anchor aAnchorFrame - // (or the frame for mAnchorContent if aAnchorFrame is null) or at a specific - // point if a screen position (mScreenXPos and mScreenYPos) are set. The popup - // will be adjusted so that it is on screen. - nsresult SetPopupPosition(nsIFrame* aAnchorFrame); + nsresult SyncViewWithFrame(nsPresContext* aPresContext, const nsString& aPopupAnchor, + const nsString& aPopupAlign, + nsIFrame* aFrame, PRInt32 aXPos, PRInt32 aYPos); - PRBool HasGeneratedChildren() { return mGeneratedChildren; } - void SetGeneratedChildren() { mGeneratedChildren = PR_TRUE; } + NS_IMETHOD KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag); + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); + + NS_IMETHOD Escape(PRBool& aHandledFlag); + NS_IMETHOD Enter(); - // called when the Enter key is pressed while the popup is open. This will - // just pass the call down to the current menu, if any. Also, calling Enter - // will reset the current incremental search string, calculated in - // FindMenuWithShortcut - nsMenuFrame* Enter(); + nsIMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction); - PRInt32 PopupType() const { return mPopupType; } - PRBool IsMenu() { return mPopupType == ePopupTypeMenu; } - PRBool IsOpen() { return mIsOpen; } - PRBool HasOpenChanged() { return mIsOpenChanged; } + PRBool IsValidItem(nsIContent* aContent); + PRBool IsDisabled(nsIContent* aContent); - // the Initialize methods are used to set the anchor position for - // each way of opening a popup. - void InitializePopup(nsIContent* aAnchorContent, - const nsAString& aPosition, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aAttributesOverride); + nsIMenuParent* GetContextMenu(); - void InitializePopupAtScreen(PRInt32 aXPos, PRInt32 aYPos); - - void InitializePopupWithAnchorAlign(nsIContent* aAnchorContent, - nsAString& aAnchor, - nsAString& aAlign, - PRInt32 aXPos, PRInt32 aYPos); - - // indicate that the popup should be opened - PRBool ShowPopup(PRBool aIsContextMenu, PRBool aSelectFirstItem); - // indicate that the popup should be hidden - void HidePopup(PRBool aDeselectMenu); - - // locate and return the menu frame that should be activated for the - // supplied key event. If doAction is set to true by this method, - // then the menu's action should be carried out, as if the user had pressed - // the Enter key. If doAction is false, the menu should just be highlighted. - // This method also handles incremental searching in menus so the user can - // type the first few letters of an item/s name to select it. - nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction); - - void ClearIncrementalString() { mIncrementalString.Truncate(); } + NS_IMETHOD KillCloseTimer(); virtual nsIAtom* GetType() const { return nsGkAtoms::menuPopupFrame; } @@ -236,7 +193,7 @@ public: } #endif - void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame); + void EnsureMenuItemIsVisible(nsIMenuFrame* aMenuFrame); // This sets 'left' and 'top' attributes. // May kill the frame. @@ -244,22 +201,30 @@ public: void GetAutoPosition(PRBool* aShouldAutoPosition); void SetAutoPosition(PRBool aShouldAutoPosition); + void EnableRollup(PRBool aShouldRollup); void SetConsumeRollupEvent(PRUint32 aConsumeMode); nsIScrollableView* GetScrollableView(nsIFrame* aStart); protected: - // Move without updating attributes. - void MoveToInternal(PRInt32 aLeft, PRInt32 aTop); + friend class nsMenuPopupTimerMediator; + NS_HIDDEN_(nsresult) Notify(nsITimer* aTimer); - // redefine to tell the box system not to move the views. + // Move without updating attributes. + void MoveToInternal(PRInt32 aLeft, PRInt32 aTop); + + // redefine to tell the box system not to move the + // views. virtual void GetLayoutFlags(PRUint32& aFlags); - void InitPositionFromAnchorAlign(const nsAString& aAnchor, - const nsAString& aAlign); + // given x,y in client coordinates, compensate for nested documents like framesets. + void AdjustClientXYForNestedDocuments ( nsIDOMXULDocument* inPopupDoc, nsIPresShell* inPopupShell, + PRInt32 inClientX, PRInt32 inClientY, + PRInt32* outAdjX, PRInt32* outAdjY ) ; void AdjustPositionForAnchorAlign ( PRInt32* ioXPos, PRInt32* ioYPos, const nsRect & inParentRect, - PRBool* outFlushWithTopBottom ) ; + const nsString& aPopupAnchor, const nsString& aPopupAlign, + PRBool* outFlushWithTopBottom ) ; PRBool IsMoreRoomOnOtherSideOfParent ( PRBool inFlushAboveBelow, PRInt32 inScreenViewLocX, PRInt32 inScreenViewLocY, const nsRect & inScreenParentFrameRect, PRInt32 inScreenTopTwips, PRInt32 inScreenLeftTwips, @@ -273,33 +238,24 @@ protected: // Move the popup to the position specified in its |left| and |top| attributes. void MoveToAttributePosition(); - // the content that the popup is anchored to, if any, which may be in a - // different document than the popup. - nsCOMPtr mAnchorContent; - nsMenuFrame* mCurrentMenu; // The current menu that is active. + nsIMenuFrame* mCurrentMenu; // The current menu that is active. - // popup alignment relative to the anchor node - PRInt8 mPopupAlignment; - PRInt8 mPopupAnchor; + nsMenuListener* mKeyboardNavigator; // The listener that tells us about key events. + nsIDOMEventTarget* mTarget; - // the position of the popup. The screen coordinates, if set to values other - // than -1, override mXPos and mYPos. - PRInt32 mXPos; - PRInt32 mYPos; - PRInt32 mScreenXPos; - PRInt32 mScreenYPos; + nsIMenuFrame* mTimerMenu; // A menu awaiting closure. + nsCOMPtr mCloseTimer; // Close timer. - nsPopupType mPopupType; // type of popup - - PRPackedBool mIsOpen; // true if the popup is open - PRPackedBool mIsOpenChanged; // true if the open state changed since the last layout - PRPackedBool mIsOpenPending; // true if an open is pending - PRPackedBool mIsContextMenu; // true for context menus - PRPackedBool mGeneratedChildren; // true if the contents have been created + // Reference to the mediator which wraps this frame. + nsRefPtr mTimerMediator; + PRPackedBool mIsContextMenu; // is this a context menu? + PRPackedBool mMenuCanOverlapOSBar; // can we appear over the taskbar/menubar? - PRPackedBool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup? + + PRPackedBool mShouldAutoPosition; // Should SyncViewWithFrame be allowed to auto position popup? + PRPackedBool mShouldRollup; // Should this menupopup be allowed to dismiss automatically? PRPackedBool mConsumeRollupEvent; // Should the rollup event be consumed? PRPackedBool mInContentShell; // True if the popup is in a content shell diff --git a/layout/xul/base/src/nsPopupBoxObject.cpp b/layout/xul/base/src/nsPopupBoxObject.cpp index 07f1e2fdbf9..bd496793bd0 100644 --- a/layout/xul/base/src/nsPopupBoxObject.cpp +++ b/layout/xul/base/src/nsPopupBoxObject.cpp @@ -38,6 +38,7 @@ * ***** END LICENSE BLOCK ***** */ #include "nsCOMPtr.h" #include "nsIPopupBoxObject.h" +#include "nsIPopupSetFrame.h" #include "nsIRootBox.h" #include "nsBoxObject.h" #include "nsIPresShell.h" @@ -63,86 +64,87 @@ public: protected: virtual ~nsPopupBoxObject() {} - nsPopupSetFrame* GetPopupSetFrame(); + nsIPopupSetFrame* GetPopupSetFrame(); nsMenuPopupFrame* GetMenuPopupFrame() - { - nsIFrame* frame = GetFrame(PR_FALSE); - if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) - return NS_STATIC_CAST(nsMenuPopupFrame*, frame); - return nsnull; - } + { return NS_STATIC_CAST(nsMenuPopupFrame*, GetFrame(PR_FALSE)); } }; NS_IMPL_ISUPPORTS_INHERITED1(nsPopupBoxObject, nsBoxObject, nsIPopupBoxObject) -nsPopupSetFrame* +nsIPopupSetFrame* nsPopupBoxObject::GetPopupSetFrame() { nsIRootBox* rootBox = nsIRootBox::GetRootBox(GetPresShell(PR_FALSE)); if (!rootBox) return nsnull; - return rootBox->GetPopupSetFrame(); + nsIFrame* popupSetFrame = rootBox->GetPopupSetFrame(); + if (!popupSetFrame) + return nsnull; + + nsIPopupSetFrame *popupSet = nsnull; + CallQueryInterface(popupSetFrame, &popupSet); + return popupSet; } NS_IMETHODIMP nsPopupBoxObject::HidePopup() { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->HidePopup(mContent, PR_FALSE, PR_TRUE, PR_FALSE); + nsIPopupSetFrame *popupSet = GetPopupSetFrame(); + nsIFrame *ourFrame = GetFrame(PR_FALSE); + if (ourFrame && popupSet) { + nsWeakFrame weakFrame(ourFrame); + popupSet->HidePopup(ourFrame); + if (weakFrame.IsAlive()) { + popupSet->DestroyPopup(ourFrame, PR_TRUE); + } + } return NS_OK; } NS_IMETHODIMP -nsPopupBoxObject::ShowPopup(nsIDOMElement* aAnchorElement, - nsIDOMElement* aPopupElement, - PRInt32 aXPos, PRInt32 aYPos, +nsPopupBoxObject::ShowPopup(nsIDOMElement* aSrcContent, + nsIDOMElement* aPopupContent, + PRInt32 aXPos, PRInt32 aYPos, const PRUnichar *aPopupType, - const PRUnichar *aAnchorAlignment, + const PRUnichar *anAnchorAlignment, const PRUnichar *aPopupAlignment) { - NS_ENSURE_TRUE(aPopupElement, NS_ERROR_INVALID_ARG); + nsIPopupSetFrame *popupSet = GetPopupSetFrame(); + if (!popupSet) { + return NS_OK; + } + + nsCOMPtr srcContent(do_QueryInterface(aSrcContent)); + nsCOMPtr popupContent(do_QueryInterface(aPopupContent)); + NS_ENSURE_TRUE(popupContent, NS_ERROR_INVALID_ARG); // srcContent can be null. - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - nsCOMPtr anchorContent(do_QueryInterface(aAnchorElement)); - nsAutoString popupType(aPopupType); - nsAutoString anchor(aAnchorAlignment); - nsAutoString align(aPopupAlignment); - pm->ShowPopupWithAnchorAlign(mContent, anchorContent, anchor, align, - aXPos, aYPos, popupType.EqualsLiteral("context")); + nsAutoString popupType(aPopupType); + nsAutoString anchorAlign(anAnchorAlignment); + nsAutoString popupAlign(aPopupAlignment); + + // Use |left| and |top| dimension attributes to position the popup if + // present, as they may have been persisted. + nsAutoString left, top; + popupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left); + popupContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top); + + PRInt32 err; + if (!left.IsEmpty()) { + aXPos = left.ToInteger(&err); + if (NS_FAILED(err)) + return err; + } + if (!top.IsEmpty()) { + aYPos = top.ToInteger(&err); + if (NS_FAILED(err)) + return err; } - return NS_OK; -} - -NS_IMETHODIMP -nsPopupBoxObject::OpenPopup(nsIDOMElement* aAnchorElement, - const nsAString& aPosition, - PRInt32 aXPos, PRInt32 aYPos, - PRBool aIsContextMenu, - PRBool aAttributesOverride) -{ - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - nsCOMPtr anchorContent(do_QueryInterface(aAnchorElement)); - pm->ShowPopup(mContent, anchorContent, aPosition, aXPos, aYPos, - aIsContextMenu, aAttributesOverride, PR_FALSE); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsPopupBoxObject::OpenPopupAtScreen(PRInt32 aXPos, PRInt32 aYPos, PRBool aIsContextMenu) -{ - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->ShowPopupAtScreen(mContent, aXPos, aYPos, aIsContextMenu); - return NS_OK; + return popupSet->ShowPopup(srcContent, popupContent, aXPos, aYPos, + popupType, anchorAlign, popupAlign); } NS_IMETHODIMP @@ -195,7 +197,11 @@ nsPopupBoxObject::SetAutoPosition(PRBool aShouldAutoPosition) NS_IMETHODIMP nsPopupBoxObject::EnableRollup(PRBool aShouldRollup) { - // this does nothing nows + nsMenuPopupFrame *menuPopupFrame = GetMenuPopupFrame(); + if (menuPopupFrame) { + menuPopupFrame->EnableRollup(aShouldRollup); + } + return NS_OK; } @@ -213,12 +219,14 @@ nsPopupBoxObject::SetConsumeRollupEvent(PRUint32 aConsume) NS_IMETHODIMP nsPopupBoxObject::EnableKeyboardNavigator(PRBool aEnableKeyboardNavigator) { - // Use ignorekeys="true" on the popup instead of using this function. - if (aEnableKeyboardNavigator) - mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, - NS_LITERAL_STRING("true"), PR_TRUE); - else - mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, PR_TRUE); + nsMenuPopupFrame *menuPopupFrame = GetMenuPopupFrame(); + if (menuPopupFrame) { + if (aEnableKeyboardNavigator) { + menuPopupFrame->InstallKeyboardNavigator(); + } else { + menuPopupFrame->RemoveKeyboardNavigator(); + } + } return NS_OK; } @@ -234,3 +242,4 @@ NS_NewPopupBoxObject(nsIBoxObject** aResult) NS_ADDREF(*aResult); return NS_OK; } + diff --git a/layout/xul/base/src/nsPopupSetFrame.cpp b/layout/xul/base/src/nsPopupSetFrame.cpp index 8d90a719cab..cb4ff02092e 100644 --- a/layout/xul/base/src/nsPopupSetFrame.cpp +++ b/layout/xul/base/src/nsPopupSetFrame.cpp @@ -66,16 +66,47 @@ #include "nsCSSFrameConstructor.h" #include "nsGUIEvent.h" #include "nsIRootBox.h" +#include "nsIFocusController.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIBaseWindow.h" +#include "nsIViewManager.h" #define NS_MENU_POPUP_LIST_INDEX 0 nsPopupFrameList::nsPopupFrameList(nsIContent* aPopupContent, nsPopupFrameList* aNext) :mNextPopup(aNext), mPopupFrame(nsnull), - mPopupContent(aPopupContent) + mPopupContent(aPopupContent), + mElementContent(nsnull), + mCreateHandlerSucceeded(PR_FALSE), + mIsOpen(PR_FALSE), + mLastPref(-1,-1) { } +nsPopupFrameList* nsPopupFrameList::GetEntry(nsIContent* aPopupContent) { + if (aPopupContent == mPopupContent) + return this; + + if (mNextPopup) + return mNextPopup->GetEntry(aPopupContent); + + return nsnull; +} + +nsPopupFrameList* nsPopupFrameList::GetEntryByFrame(nsIFrame* aPopupFrame) { + if (aPopupFrame == mPopupFrame) + return this; + + if (mNextPopup) + return mNextPopup->GetEntryByFrame(aPopupFrame); + + return nsnull; +} + // // NS_NewPopupSetFrame // @@ -87,6 +118,25 @@ NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) return new (aPresShell) nsPopupSetFrame (aPresShell, aContext); } +NS_IMETHODIMP_(nsrefcnt) +nsPopupSetFrame::AddRef(void) +{ + return NS_OK; +} + +NS_IMETHODIMP_(nsrefcnt) +nsPopupSetFrame::Release(void) +{ + return NS_OK; +} + +// +// QueryInterface +// +NS_INTERFACE_MAP_BEGIN(nsPopupSetFrame) + NS_INTERFACE_MAP_ENTRY(nsIPopupSetFrame) +NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame) + NS_IMETHODIMP nsPopupSetFrame::Init(nsIContent* aContent, nsIFrame* aParent, @@ -148,14 +198,31 @@ nsPopupSetFrame::SetInitialChildList(nsIAtom* aListName, void nsPopupSetFrame::Destroy() { - // remove each popup from the list as we go. - while (mPopupList) { - if (mPopupList->mPopupFrame) - mPopupList->mPopupFrame->Destroy(); + // Remove our frame list. + if (mPopupList) { + // Try to hide any active popups + if (nsMenuDismissalListener::sInstance) { + nsIMenuParent *menuParent = + nsMenuDismissalListener::sInstance->GetCurrentMenuParent(); + nsIFrame* frame; + CallQueryInterface(menuParent, &frame); + // Rollup popups, but only if they're ours + if (frame && mPopupList->GetEntryByFrame(frame)) { + nsMenuDismissalListener::sInstance->Rollup(); + } + } - nsPopupFrameList* temp = mPopupList; - mPopupList = mPopupList->mNextPopup; - delete temp; + // Actually remove each popup from the list as we go. This + // keeps things consistent so reentering won't crash us + while (mPopupList) { + if (mPopupList->mPopupFrame) { + mPopupList->mPopupFrame->Destroy(); + } + + nsPopupFrameList* temp = mPopupList; + mPopupList = mPopupList->mNextPopup; + delete temp; + } } nsIRootBox *rootBox; @@ -177,8 +244,10 @@ nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState) // lay out all of our currently open popups. nsPopupFrameList* currEntry = mPopupList; while (currEntry) { - nsMenuPopupFrame* popupChild = currEntry->mPopupFrame; - if (popupChild && popupChild->IsOpen()) { + nsIFrame* popupChild = currEntry->mPopupFrame; + if (popupChild) { + NS_ASSERTION(popupChild->IsBoxFrame(), "popupChild is not box!!"); + // then get its preferred size nsSize prefSize = popupChild->GetPrefSize(aState); nsSize minSize = popupChild->GetMinSize(aState); @@ -186,8 +255,13 @@ nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState) BoundsCheck(minSize, prefSize, maxSize); - popupChild->SetBounds(aState, nsRect(0,0,prefSize.width, prefSize.height)); - popupChild->SetPopupPosition(nsnull); + // if the pref size changed then set bounds to be the pref size + // and sync the view. Also set new pref size. + // if (currEntry->mLastPref != prefSize) { + popupChild->SetBounds(aState, nsRect(0,0,prefSize.width, prefSize.height)); + RepositionPopup(currEntry, aState); + currEntry->mLastPref = prefSize; + // } // is the new size too small? Make sure we handle scrollbars correctly nsIBox* child = popupChild->GetChildBox(); @@ -206,22 +280,491 @@ nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState) if (bounds.width < prefSize.width + scrollbars.left + scrollbars.right) { bounds.width += scrollbars.left + scrollbars.right; + //printf("Width=%d\n",width); popupChild->SetBounds(aState, bounds); } } } - + // layout the child popupChild->Layout(aState); - popupChild->AdjustView(); + + // only size popup if open + if (currEntry->mCreateHandlerSucceeded) { + nsIView* view = popupChild->GetView(); + nsIViewManager* viewManager = view->GetViewManager(); + nsRect r(0, 0, bounds.width, bounds.height); + viewManager->ResizeView(view, r); + viewManager->SetViewVisibility(view, nsViewVisibility_kShow); + } } currEntry = currEntry->mNextPopup; } + SyncLayout(aState); + return rv; } + +#ifdef DEBUG_LAYOUT +NS_IMETHODIMP +nsPopupSetFrame::SetDebug(nsBoxLayoutState& aState, PRBool aDebug) +{ + // see if our state matches the given debug state + PRBool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG; + PRBool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet); + + // if it doesn't then tell each child below us the new debug state + if (debugChanged) + { + // XXXdwh fix later. nobody uses this anymore anyway. + } + + return NS_OK; +} + +nsresult +nsPopupSetFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, PRBool aDebug) +{ + if (!aList) + return NS_OK; + + while (aList) { + if (aList->IsBoxFrame()) + aList->SetDebug(aState, aDebug); + + aList = aList->GetNextSibling(); + } + + return NS_OK; +} +#endif + + +void +nsPopupSetFrame::RepositionPopup(nsPopupFrameList* aEntry, nsBoxLayoutState& aState) +{ + // Sync up the view. + if (aEntry && aEntry->mElementContent) { + nsPresContext* presContext = aState.PresContext(); + nsIFrame* frameToSyncTo = presContext->PresShell()-> + GetPrimaryFrameFor(aEntry->mElementContent); + ((nsMenuPopupFrame*)(aEntry->mPopupFrame))->SyncViewWithFrame(presContext, + aEntry->mPopupAnchor, aEntry->mPopupAlign, frameToSyncTo, aEntry->mXPos, aEntry->mYPos); + } +} + +NS_IMETHODIMP +nsPopupSetFrame::ShowPopup(nsIContent* aElementContent, nsIContent* aPopupContent, + PRInt32 aXPos, PRInt32 aYPos, + const nsString& aPopupType, const nsString& anAnchorAlignment, + const nsString& aPopupAlignment) +{ + if (!MayOpenPopup(this)) + return NS_OK; + + nsWeakFrame weakFrame(this); + // First fire the popupshowing event. + if (!OnCreate(aXPos, aYPos, aPopupContent) || !weakFrame.IsAlive()) + return NS_OK; + + // See if we already have an entry in our list. We must create a new one on a miss. + nsPopupFrameList* entry = nsnull; + if (mPopupList) + entry = mPopupList->GetEntry(aPopupContent); + if (!entry) { + entry = new nsPopupFrameList(aPopupContent, mPopupList); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; + mPopupList = entry; + } + + // Cache the element content we're supposed to sync to + entry->mPopupType = aPopupType; + entry->mElementContent = aElementContent; + entry->mPopupAlign = aPopupAlignment; + entry->mPopupAnchor = anAnchorAlignment; + entry->mXPos = aXPos; + entry->mYPos = aYPos; + + // If a frame exists already, go ahead and use it. + entry->mPopupFrame = PresContext()->PresShell() + ->GetPrimaryFrameFor(aPopupContent); + +#ifdef DEBUG_PINK + printf("X Pos: %d\n", mXPos); + printf("Y Pos: %d\n", mYPos); +#endif + + // Generate the popup. + entry->mCreateHandlerSucceeded = PR_TRUE; + entry->mIsOpen = PR_TRUE; + // This may destroy or change entry->mPopupFrame or remove the entry from + // mPopupList. |this| may also get deleted. + MarkAsGenerated(aPopupContent); + + if (!weakFrame.IsAlive()) { + return NS_OK; + } + + nsPopupFrameList* newEntry = + mPopupList ? mPopupList->GetEntry(aPopupContent) : nsnull; + if (!newEntry || newEntry != entry) { + NS_WARNING("The popup entry for aPopupContent has changed!"); + return NS_OK; + } + + // determine if this menu is a context menu and flag it + nsIMenuParent* childPopup = nsnull; + if (entry->mPopupFrame) + CallQueryInterface(entry->mPopupFrame, &childPopup); + if ( childPopup && aPopupType.EqualsLiteral("context") ) + childPopup->SetIsContextMenu(PR_TRUE); + + // Now open the popup. + OpenPopup(entry, PR_TRUE); + + if (!weakFrame.IsAlive()) { + return NS_OK; + } + + // Now fire the popupshown event. + OnCreated(aXPos, aYPos, aPopupContent); + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupSetFrame::HidePopup(nsIFrame* aPopup) +{ + if (!mPopupList) + return NS_OK; // No active popups + + nsPopupFrameList* entry = mPopupList->GetEntryByFrame(aPopup); + if (!entry) + return NS_OK; + + if (entry->mCreateHandlerSucceeded) + ActivatePopup(entry, PR_FALSE); + + if (entry->mElementContent && entry->mPopupType.EqualsLiteral("context")) { + // If we are a context menu, and if we are attached to a + // menupopup, then hiding us should also hide the parent menu + // popup. + if (entry->mElementContent->Tag() == nsGkAtoms::menupopup) { + nsIFrame* popupFrame = PresContext()->PresShell() + ->GetPrimaryFrameFor(entry->mElementContent); + if (popupFrame) { + nsIMenuParent *menuParent; + if (NS_SUCCEEDED(CallQueryInterface(popupFrame, &menuParent))) { + menuParent->HideChain(); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPopupSetFrame::DestroyPopup(nsIFrame* aPopup, PRBool aDestroyEntireChain) +{ + if (!mPopupList) + return NS_OK; // No active popups + + nsPopupFrameList* entry = mPopupList->GetEntryByFrame(aPopup); + + if (entry && entry->mCreateHandlerSucceeded) { // ensure the popup was created before we try to destroy it + nsWeakFrame weakFrame(this); + OpenPopup(entry, PR_FALSE); + nsCOMPtr popupContent = entry->mPopupContent; + if (weakFrame.IsAlive()) { + if (aDestroyEntireChain && entry->mElementContent && entry->mPopupType.EqualsLiteral("context")) { + // If we are a context menu, and if we are attached to a + // menupopup, then destroying us should also dismiss the parent + // menu popup. + if (entry->mElementContent->Tag() == nsGkAtoms::menupopup) { + nsIFrame* popupFrame = PresContext()->PresShell() + ->GetPrimaryFrameFor(entry->mElementContent); + if (popupFrame) { + nsIMenuParent *menuParent; + if (NS_SUCCEEDED(CallQueryInterface(popupFrame, &menuParent))) { + menuParent->DismissChain(); + } + } + } + } + + // clear things out for next time + entry->mPopupType.Truncate(); + entry->mCreateHandlerSucceeded = PR_FALSE; + entry->mElementContent = nsnull; + entry->mXPos = entry->mYPos = 0; + entry->mLastPref.width = -1; + entry->mLastPref.height = -1; + } + // ungenerate the popup. + popupContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menugenerated, PR_TRUE); + } + + return NS_OK; +} + +void +nsPopupSetFrame::MarkAsGenerated(nsIContent* aPopupContent) +{ + // Set our attribute, but only if we aren't already generated. + // Retrieve the menugenerated attribute. + if (!aPopupContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menugenerated, + nsGkAtoms::_true, eCaseMatters)) { + // Generate this element. + aPopupContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menugenerated, NS_LITERAL_STRING("true"), + PR_TRUE); + } +} + +void +nsPopupSetFrame::OpenPopup(nsPopupFrameList* aEntry, PRBool aActivateFlag) +{ + nsWeakFrame weakFrame(this); + nsIFrame* activeChild = aEntry->mPopupFrame; + nsWeakFrame weakPopupFrame(activeChild); + nsRefPtr presContext = PresContext(); + nsCOMPtr popupContent = aEntry->mPopupContent; + PRBool createHandlerSucceeded = aEntry->mCreateHandlerSucceeded; + nsAutoString popupType = aEntry->mPopupType; + if (aActivateFlag) { + ActivatePopup(aEntry, PR_TRUE); + + // register the rollup listeners, etc, but not if we're a tooltip + if (!popupType.EqualsLiteral("tooltip")) { + nsIMenuParent* childPopup = nsnull; + if (weakPopupFrame.IsAlive()) + CallQueryInterface(activeChild, &childPopup); + + // Tooltips don't get keyboard navigation + if (childPopup && !nsMenuDismissalListener::sInstance) { + // First check and make sure this popup wants keyboard navigation + if (!popupContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys, + nsGkAtoms::_true, eCaseMatters)) + childPopup->InstallKeyboardNavigator(); + } + + nsMenuDismissalListener* listener = nsMenuDismissalListener::GetInstance(); + if (listener) + listener->SetCurrentMenuParent(childPopup); + } + } + else { + if (createHandlerSucceeded && !OnDestroy(popupContent)) + return; + + // Unregister, but not if we're a tooltip + if (!popupType.EqualsLiteral("tooltip") ) { + nsMenuDismissalListener::Shutdown(); + } + + // Remove any keyboard navigators + nsIMenuParent* childPopup = nsnull; + if (weakPopupFrame.IsAlive()) + CallQueryInterface(activeChild, &childPopup); + if (childPopup) + childPopup->RemoveKeyboardNavigator(); + + if (weakPopupFrame.IsAlive()) + ActivatePopup(aEntry, PR_FALSE); + + OnDestroyed(presContext, popupContent); + } + + if (weakFrame.IsAlive()) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } +} + +void +nsPopupSetFrame::ActivatePopup(nsPopupFrameList* aEntry, PRBool aActivateFlag) +{ + if (aEntry->mPopupContent) { + // When we sync the popup view with the frame, we'll show the popup if |menutobedisplayed| + // is set by setting the |menuactive| attribute. This used to trip css into showing the menu + // but now we do it ourselves. + if (aActivateFlag) + // XXXben hook in |width| and |height| usage here? + aEntry->mPopupContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menutobedisplayed, NS_LITERAL_STRING("true"), PR_TRUE); + else { + nsWeakFrame weakFrame(this); + nsWeakFrame weakActiveChild(aEntry->mPopupFrame); + nsCOMPtr content = aEntry->mPopupContent; + content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, PR_TRUE); + content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menutobedisplayed, PR_TRUE); + + // get rid of the reflows we just created. If we leave them hanging around, we + // can get into trouble if a dialog with a modal event loop comes along and + // processes the reflows before we get to call DestroyChain(). Processing the + // reflow will cause the popup to show itself again. (bug 71219) + nsIDocument* doc = content->GetDocument(); + if (doc) + doc->FlushPendingNotifications(Flush_OnlyReflow); + + // make sure we hide the popup. We can't assume that we'll have a view + // since we could be cleaning up after someone that didn't correctly + // destroy the popup. + if (weakFrame.IsAlive() && weakActiveChild.IsAlive()) { + nsIView* view = weakActiveChild.GetFrame()->GetView(); + NS_ASSERTION(view, "View is gone, looks like someone forgot to roll up the popup!"); + if (view) { + nsIViewManager* viewManager = view->GetViewManager(); + viewManager->SetViewVisibility(view, nsViewVisibility_kHide); + nsRect r(0, 0, 0, 0); + viewManager->ResizeView(view, r); + if (aEntry->mIsOpen) { + aEntry->mIsOpen = PR_FALSE; + FireDOMEventSynch(NS_LITERAL_STRING("DOMMenuInactive"), content); + } + } + } + } + } +} + +PRBool +nsPopupSetFrame::OnCreate(PRInt32 aX, PRInt32 aY, nsIContent* aPopupContent) +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull, + nsMouseEvent::eReal); + // XXX This is messed up: it needs to account for widgets. + nsPoint dummy; + event.widget = GetClosestView()->GetNearestWidget(&dummy); + event.refPoint.x = aX; + event.refPoint.y = aY; + + if (aPopupContent) { + nsCOMPtr kungFuDeathGrip(aPopupContent); + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + nsresult rv = shell->HandleDOMEventWithTarget(aPopupContent, &event, + &status); + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + } + + nsCOMPtr domDoc(do_QueryInterface(aPopupContent->GetDocument())); + if (!domDoc) return PR_FALSE; + + // The menu is going to show, and the create handler has executed. + // We should now walk all of our menu item children, checking to see if any + // of them has a command attribute. If so, then several attributes must + // potentially be updated. + + PRUint32 count = aPopupContent->GetChildCount(); + for (PRUint32 i = 0; i < count; i++) { + nsCOMPtr grandChild = aPopupContent->GetChildAt(i); + + if (grandChild->Tag() == nsGkAtoms::menuitem) { + // See if we have a command attribute. + nsAutoString command; + grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + if (!command.IsEmpty()) { + // We do! Look it up in our document + nsCOMPtr commandElt; + domDoc->GetElementById(command, getter_AddRefs(commandElt)); + nsCOMPtr commandContent(do_QueryInterface(commandElt)); + if ( commandContent ) { + nsAutoString commandValue; + // The menu's disabled state needs to be updated to match the command. + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, PR_TRUE); + else + grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE); + + // The menu's label, accesskey and checked states need to be updated + // to match the command. Note that unlike the disabled state if the + // command has *no* value, we assume the menu is supplying its own. + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, PR_TRUE); + + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, PR_TRUE); + + if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue)) + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, PR_TRUE); + } + } + } + } + } + + return PR_TRUE; +} + +PRBool +nsPopupSetFrame::OnCreated(PRInt32 aX, PRInt32 aY, nsIContent* aPopupContent) +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull, + nsMouseEvent::eReal); + // XXX See OnCreate above + //event.point.x = aX; + //event.point.y = aY; + + if (aPopupContent) { + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + nsresult rv = shell->HandleDOMEventWithTarget(aPopupContent, &event, + &status); + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + } + } + + return PR_TRUE; +} + +PRBool +nsPopupSetFrame::OnDestroy(nsIContent* aPopupContent) +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull, + nsMouseEvent::eReal); + + if (aPopupContent) { + nsCOMPtr shell = PresContext()->GetPresShell(); + if (shell) { + nsresult rv = shell->HandleDOMEventWithTarget(aPopupContent, &event, + &status); + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + } + } + return PR_TRUE; +} + +PRBool +nsPopupSetFrame::OnDestroyed(nsPresContext* aPresContext, + nsIContent* aPopupContent) +{ + nsEventStatus status = nsEventStatus_eIgnore; + nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDDEN, nsnull, + nsMouseEvent::eReal); + + if (aPopupContent && aPresContext) { + nsCOMPtr shell = aPresContext->GetPresShell(); + if (shell) { + nsresult rv = shell->HandleDOMEventWithTarget(aPopupContent, &event, + &status); + if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault ) + return PR_FALSE; + } + } + return PR_TRUE; +} + nsresult nsPopupSetFrame::RemovePopupFrame(nsIFrame* aPopup) { @@ -268,18 +811,14 @@ nsPopupSetFrame::AddPopupFrameList(nsIFrame* aPopupFrameList) nsresult nsPopupSetFrame::AddPopupFrame(nsIFrame* aPopup) { - NS_ASSERTION(aPopup->GetType() == nsGkAtoms::menuPopupFrame, - "expected a menupopup frame to be added to a popupset"); - if (aPopup->GetType() != nsGkAtoms::menuPopupFrame) - return NS_ERROR_UNEXPECTED; - // The entry should already exist, but might not (if someone decided to make their // popup visible straightaway, e.g., the autocomplete widget). + // First look for an entry by content. nsIContent* content = aPopup->GetContent(); - nsPopupFrameList* entry = mPopupList; - while (entry && entry->mPopupContent != content) - entry = entry->mNextPopup; + nsPopupFrameList* entry = nsnull; + if (mPopupList) + entry = mPopupList->GetEntry(content); if (!entry) { entry = new nsPopupFrameList(content, mPopupList); if (!entry) @@ -291,7 +830,49 @@ nsPopupSetFrame::AddPopupFrame(nsIFrame* aPopup) } // Set the frame connection. - entry->mPopupFrame = NS_STATIC_CAST(nsMenuPopupFrame *, aPopup); + entry->mPopupFrame = aPopup; + // Now return. The remaining entry values will be filled in if/when showPopup is + // called for this popup. return NS_OK; } + +//static +PRBool +nsPopupSetFrame::MayOpenPopup(nsIFrame* aFrame) +{ + nsCOMPtr cont = aFrame->PresContext()->GetContainer(); + nsCOMPtr dsti = do_QueryInterface(cont); + if (!dsti) + return PR_FALSE; + + // chrome shells can always open popups + PRInt32 type = -1; + if (NS_SUCCEEDED(dsti->GetItemType(&type)) && type == nsIDocShellTreeItem::typeChrome) + return PR_TRUE; + + nsCOMPtr shell = do_QueryInterface(dsti); + if (!shell) + return PR_FALSE; + + nsCOMPtr win = do_GetInterface(shell); + if (!win) + return PR_FALSE; + + // only allow popups in active windows + PRBool active; + nsIFocusController* focusController = win->GetRootFocusController(); + focusController->GetActive(&active); + if (!active) + return PR_FALSE; + + nsCOMPtr baseWin = do_QueryInterface(shell); + if (!baseWin) + return PR_FALSE; + + // only allow popups in visible frames + PRBool visible; + baseWin->GetVisibility(&visible); + return visible; +} + diff --git a/layout/xul/base/src/nsPopupSetFrame.h b/layout/xul/base/src/nsPopupSetFrame.h index 858833f0879..ce76e6b07e2 100644 --- a/layout/xul/base/src/nsPopupSetFrame.h +++ b/layout/xul/base/src/nsPopupSetFrame.h @@ -46,11 +46,10 @@ #include "prtypes.h" #include "nsIAtom.h" #include "nsCOMPtr.h" -#include "nsGkAtoms.h" +#include "nsIPopupSetFrame.h" #include "nsBoxFrame.h" #include "nsFrameList.h" -#include "nsMenuPopupFrame.h" #include "nsIMenuParent.h" #include "nsITimer.h" #include "nsISupportsArray.h" @@ -59,20 +58,35 @@ nsIFrame* NS_NewPopupSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext struct nsPopupFrameList { nsPopupFrameList* mNextPopup; // The next popup in the list. - nsMenuPopupFrame* mPopupFrame; // Our popup. + nsIFrame* mPopupFrame; // Our popup. nsIContent* mPopupContent; // The content element for the itself. + + nsIContent* mElementContent; // The content that is having something popped up over it + + PRInt32 mXPos; // This child's x position + PRInt32 mYPos; // This child's y position + + nsAutoString mPopupAnchor; // This child's anchor. + nsAutoString mPopupAlign; // This child's align. + + nsAutoString mPopupType; + PRPackedBool mCreateHandlerSucceeded; // Did the create handler succeed? + PRPackedBool mIsOpen; + nsSize mLastPref; public: nsPopupFrameList(nsIContent* aPopupContent, nsPopupFrameList* aNext); + nsPopupFrameList* GetEntry(nsIContent* aPopupContent); + nsPopupFrameList* GetEntryByFrame(nsIFrame* aPopupFrame); }; -class nsPopupSetFrame : public nsBoxFrame +class nsPopupSetFrame : public nsBoxFrame, public nsIPopupSetFrame { public: nsPopupSetFrame(nsIPresShell* aShell, nsStyleContext* aContext): nsBoxFrame(aShell, aContext) {} - ~nsPopupSetFrame() {} + NS_DECL_ISUPPORTS NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, @@ -86,14 +100,41 @@ public: nsIFrame* aFrameList); NS_IMETHOD SetInitialChildList(nsIAtom* aListName, nsIFrame* aChildList); - + // nsIBox NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState); +#ifdef DEBUG_LAYOUT + NS_IMETHOD SetDebug(nsBoxLayoutState& aState, PRBool aDebug); +#endif // Used to destroy our popup frames. virtual void Destroy(); - virtual nsIAtom* GetType() const { return nsGkAtoms::popupSetFrame; } + // Reflow methods + virtual void RepositionPopup(nsPopupFrameList* aEntry, nsBoxLayoutState& aState); + + NS_IMETHOD ShowPopup(nsIContent* aElementContent, nsIContent* aPopupContent, + PRInt32 aXPos, PRInt32 aYPos, + const nsString& aPopupType, const nsString& anAnchorAlignment, + const nsString& aPopupAlignment); + NS_IMETHOD HidePopup(nsIFrame* aPopup); + NS_IMETHOD DestroyPopup(nsIFrame* aPopup, PRBool aDestroyEntireChain); + + PRBool OnCreate(PRInt32 aX, PRInt32 aY, nsIContent* aPopupContent); + PRBool OnDestroy(nsIContent* aPopupContent); + PRBool OnCreated(PRInt32 aX, PRInt32 aY, nsIContent* aPopupContent); + static PRBool OnDestroyed(nsPresContext* aPresContext, + nsIContent* aPopupContent); + + void ActivatePopup(nsPopupFrameList* aEntry, PRBool aActivateFlag); + void OpenPopup(nsPopupFrameList* aEntry, PRBool aOpenFlag); + + /** + * Return true if the docshell containing aFrame may open a popup. aFrame + * doesn't need to be any particular type of frame, just a frame in the + * same document. + */ + static PRBool MayOpenPopup(nsIFrame* aFrame); #ifdef DEBUG NS_IMETHOD GetFrameName(nsAString& aResult) const @@ -108,6 +149,13 @@ protected: nsresult AddPopupFrame(nsIFrame* aPopup); nsresult RemovePopupFrame(nsIFrame* aPopup); + void MarkAsGenerated(nsIContent* aPopupContent); + +protected: +#ifdef DEBUG_LAYOUT + nsresult SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, PRBool aDebug); +#endif + nsPopupFrameList* mPopupList; }; // class nsPopupSetFrame diff --git a/layout/xul/base/src/nsRootBoxFrame.cpp b/layout/xul/base/src/nsRootBoxFrame.cpp index 47c013331b0..302c8158e86 100644 --- a/layout/xul/base/src/nsRootBoxFrame.cpp +++ b/layout/xul/base/src/nsRootBoxFrame.cpp @@ -85,8 +85,8 @@ public: NS_DECL_ISUPPORTS_INHERITED - virtual nsPopupSetFrame* GetPopupSetFrame(); - virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet); + virtual nsIFrame* GetPopupSetFrame(); + virtual void SetPopupSetFrame(nsIFrame* aPopupSet); virtual nsIContent* GetDefaultTooltip(); virtual void SetDefaultTooltip(nsIContent* aTooltip); virtual nsresult AddTooltipSupport(nsIContent* aNode); @@ -131,7 +131,7 @@ public: NS_IMETHOD GetFrameName(nsAString& aResult) const; #endif - nsPopupSetFrame* mPopupSetFrame; + nsIFrame* mPopupSetFrame; protected: nsIContent* mDefaultTooltip; @@ -275,14 +275,14 @@ nsRootBoxFrame::GetType() const return nsGkAtoms::rootFrame; } -nsPopupSetFrame* +nsIFrame* nsRootBoxFrame::GetPopupSetFrame() { return mPopupSetFrame; } void -nsRootBoxFrame::SetPopupSetFrame(nsPopupSetFrame* aPopupSet) +nsRootBoxFrame::SetPopupSetFrame(nsIFrame* aPopupSet) { // Under normal conditions this should only be called once. However, // if something triggers ReconstructDocElementHierarchy, we will diff --git a/themes/classic/global/mac/popup.css b/themes/classic/global/mac/popup.css index 7898e47986f..4b1cbb3e048 100644 --- a/themes/classic/global/mac/popup.css +++ b/themes/classic/global/mac/popup.css @@ -45,8 +45,7 @@ /* ::::: menupopup ::::: */ menupopup, -popup, -panel { +popup { -moz-appearance: menupopup; } diff --git a/themes/classic/global/win/popup.css b/themes/classic/global/win/popup.css index 4c7502f33f8..640bc73a0cf 100644 --- a/themes/classic/global/win/popup.css +++ b/themes/classic/global/win/popup.css @@ -45,8 +45,7 @@ /* ::::: menupopup ::::: */ menupopup, -popup, -panel { +popup { border: 2px solid; -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight; -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow; diff --git a/toolkit/components/autocomplete/public/nsIAutoCompleteController.idl b/toolkit/components/autocomplete/public/nsIAutoCompleteController.idl index c3656b089e2..58e2e4cdea9 100644 --- a/toolkit/components/autocomplete/public/nsIAutoCompleteController.idl +++ b/toolkit/components/autocomplete/public/nsIAutoCompleteController.idl @@ -41,7 +41,7 @@ interface nsIAutoCompleteInput; -[scriptable, uuid(476E1472-4357-4CD0-AFE3-FEA3112617B2)] +[scriptable, uuid(bafcfe4f-0850-4106-a176-be5aef2e1e52)] interface nsIAutoCompleteController : nsISupports { /* @@ -141,4 +141,10 @@ interface nsIAutoCompleteController : nsISupports * Set the current search string, but don't start searching */ void setSearchString(in AString aSearchString); + + /* + * Attach or Detach rollup-listener + */ + void attachRollupListener(); + void detachRollupListener(); }; diff --git a/toolkit/components/autocomplete/public/nsIAutoCompletePopup.idl b/toolkit/components/autocomplete/public/nsIAutoCompletePopup.idl index 38c7350809d..d8e1754922a 100644 --- a/toolkit/components/autocomplete/public/nsIAutoCompletePopup.idl +++ b/toolkit/components/autocomplete/public/nsIAutoCompletePopup.idl @@ -39,7 +39,7 @@ interface nsIAutoCompleteInput; -[scriptable, uuid(816668CC-6AC0-47C3-944E-9C2CF37F224A)] +[scriptable, uuid(65F6CD46-22EC-4329-BB3B-BCD1103F2204)] interface nsIAutoCompletePopup : nsISupports { /* @@ -71,7 +71,7 @@ interface nsIAutoCompletePopup : nsISupports * @param y - The y coordinate to display the popup at * @param width - The width that the popup should size itself to */ - void openAutocompletePopup(in nsIAutoCompleteInput input, in long x, in long y, in long width); + void openPopup(in nsIAutoCompleteInput input, in long x, in long y, in long width); /* * Close the popup and detach from the bound input diff --git a/toolkit/components/autocomplete/src/nsAutoCompleteController.cpp b/toolkit/components/autocomplete/src/nsAutoCompleteController.cpp index 01f26d12444..1b214f0063c 100644 --- a/toolkit/components/autocomplete/src/nsAutoCompleteController.cpp +++ b/toolkit/components/autocomplete/src/nsAutoCompleteController.cpp @@ -611,6 +611,27 @@ nsAutoCompleteController::SetSearchString(const nsAString &aSearchString) return NS_OK; } +NS_IMETHODIMP +nsAutoCompleteController::AttachRollupListener() +{ + nsIWidget* widget = GetPopupWidget(); + NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE); + NS_ASSERTION(mInput, "mInput must not be null."); + PRBool consumeRollupEvent = PR_FALSE; + mInput->GetConsumeRollupEvent(&consumeRollupEvent); + return widget->CaptureRollupEvents((nsIRollupListener*)this, + PR_TRUE, consumeRollupEvent); +} + +NS_IMETHODIMP +nsAutoCompleteController::DetachRollupListener() +{ + nsIWidget* widget = GetPopupWidget(); + NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE); + return widget->CaptureRollupEvents((nsIRollupListener*)this, + PR_FALSE, PR_FALSE); +} + //////////////////////////////////////////////////////////////////////// //// nsIAutoCompleteObserver diff --git a/toolkit/components/satchel/src/nsFormFillController.cpp b/toolkit/components/satchel/src/nsFormFillController.cpp index 02dee2b6ae3..c16d885ddc2 100644 --- a/toolkit/components/satchel/src/nsFormFillController.cpp +++ b/toolkit/components/satchel/src/nsFormFillController.cpp @@ -246,9 +246,7 @@ nsFormFillController::SetPopupOpen(PRBool aPopupOpen) NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE); nsRect popupRect = GetScreenOrigin(mFocusedInput); - mFocusedPopup->OpenAutocompletePopup(this, popupRect.x, - popupRect.y+popupRect.height, - popupRect.width); + mFocusedPopup->OpenPopup(this, popupRect.x, popupRect.y+popupRect.height, popupRect.width); } else mFocusedPopup->ClosePopup(); } diff --git a/toolkit/content/widgets/autocomplete.xml b/toolkit/content/widgets/autocomplete.xml index d2cff5ff1e4..f77b5d2f3e8 100644 --- a/toolkit/content/widgets/autocomplete.xml +++ b/toolkit/content/widgets/autocomplete.xml @@ -110,8 +110,9 @@ if (popupId) popup = document.getElementById(popupId); if (!popup) { - popup = document.createElement("panel"); + popup = document.createElement("popup"); popup.setAttribute("type", "autocomplete"); + popup.setAttribute("hidden", "true"); var popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset"); popupset.appendChild(popup); @@ -319,7 +320,7 @@ @@ -564,7 +565,7 @@ - + @@ -577,6 +578,7 @@ this.showCommentColumn = this.mInput.showCommentColumn; + this.removeAttribute("hidden"); this.setAttribute("width", aWidth < 100 ? 100 : aWidth); document.popupNode = null; @@ -586,7 +588,8 @@ else { this.showPopup(document.documentElement, aX, aY, "popup", null, null); } - this.popupBoxObject.setConsumeRollupEvent(this.mInput.consumeRollupEvent); + this.enableRollup(false); + this.mInput.controller.attachRollupListener(); } ]]> @@ -596,8 +599,10 @@ if (this.mPopupOpen) { this.hidePopup(); document.popupNode = null; -// this.setAttribute("hidden", "true"); + + this.setAttribute("hidden", "true"); this.removeAttribute("width"); + this.mInput.controller.detachRollupListener(); } ]]> @@ -711,8 +716,6 @@ // detect the desired height of the tree var bx = this.tree.treeBoxObject; var view = this.tree.view; - if (!view) - return; var rows = this.maxRows; if (!view.rowCount || (rows && view.rowCount < rows)) rows = view.rowCount; diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml index e9c3279c7e6..743211d3606 100644 --- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -11,8 +11,12 @@ - + + + + + + @@ -30,40 +34,6 @@ return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject); - - - - - - - - - - - - - - - - - - - - - @@ -161,16 +131,6 @@ - - - - - - - - - diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css index ceede9a00df..8b206a1eaab 100644 --- a/toolkit/content/xul.css +++ b/toolkit/content/xul.css @@ -320,23 +320,24 @@ popup, menupopup { -moz-binding: url("chrome://global/content/bindings/popup.xml#popup"); -moz-box-orient: vertical; -} - -panel { - -moz-binding: url("chrome://global/content/bindings/popup.xml#panel"); - -moz-box-orient: vertical; + display: none; } popup, menupopup, -panel, tooltip { - display: -moz-popup; z-index: 2147483647; } +menupopup[menugenerated="true"], +popup[menugenerated="true"], +tooltip[menugenerated="true"] { + display: -moz-popup; +} + tooltip { -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip"); + display: -moz-popup; margin-top: 21px; } @@ -781,8 +782,9 @@ textbox[type="autocomplete"] { -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete"); } -panel[type="autocomplete"] { +popup[type="autocomplete"] { -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup"); + display: -moz-popup !important; } .autocomplete-tree { diff --git a/toolkit/themes/gnomestripe/global/popup.css b/toolkit/themes/gnomestripe/global/popup.css index 46d78eeb546..a6f5187c301 100644 --- a/toolkit/themes/gnomestripe/global/popup.css +++ b/toolkit/themes/gnomestripe/global/popup.css @@ -44,9 +44,7 @@ /* ::::: menupopup ::::: */ -menupopup, -popup, -panel { +menupopup, popup { -moz-appearance: menupopup; min-width: 1px; } diff --git a/toolkit/themes/pinstripe/global/autocomplete.css b/toolkit/themes/pinstripe/global/autocomplete.css index 3265ef7841e..6dd9776cf3b 100644 --- a/toolkit/themes/pinstripe/global/autocomplete.css +++ b/toolkit/themes/pinstripe/global/autocomplete.css @@ -74,7 +74,7 @@ textbox.padded { /* ::::: autocomplete popups ::::: */ -panel[type="autocomplete"], +popup[type="autocomplete"], .autocomplete-history-popup { padding: 0px !important; background-color: -moz-Field !important; diff --git a/toolkit/themes/pinstripe/global/popup.css b/toolkit/themes/pinstripe/global/popup.css index af8f7d9413c..06a43f073fc 100644 --- a/toolkit/themes/pinstripe/global/popup.css +++ b/toolkit/themes/pinstripe/global/popup.css @@ -46,8 +46,7 @@ /* ::::: menupopup ::::: */ menupopup, -popup, -panel { +popup { -moz-appearance: menupopup; } diff --git a/toolkit/themes/winstripe/global/autocomplete.css b/toolkit/themes/winstripe/global/autocomplete.css index 851bc0ab89b..4ed4a32f019 100644 --- a/toolkit/themes/winstripe/global/autocomplete.css +++ b/toolkit/themes/winstripe/global/autocomplete.css @@ -167,7 +167,7 @@ textbox[chromedir="rtl"] .autocomplete-history-dropmarker { /* ::::: autocomplete popups ::::: */ -panel[type="autocomplete"], +popup[type="autocomplete"], .autocomplete-history-popup { border-width: 1px; -moz-border-top-colors: ThreeDDarkShadow; diff --git a/toolkit/themes/winstripe/global/popup.css b/toolkit/themes/winstripe/global/popup.css index 4fa663a9a81..2a3dea7e8cb 100644 --- a/toolkit/themes/winstripe/global/popup.css +++ b/toolkit/themes/winstripe/global/popup.css @@ -45,8 +45,7 @@ /* ::::: menupopup ::::: */ menupopup, -popup, -panel { +popup { border: 2px solid transparent; -moz-border-top-colors : ThreeDLightShadow ThreeDHighlight; -moz-border-left-colors : ThreeDLightShadow ThreeDHighlight; diff --git a/widget/src/gtk2/nsNativeThemeGTK.cpp b/widget/src/gtk2/nsNativeThemeGTK.cpp index 5893aadc050..864c4e12531 100644 --- a/widget/src/gtk2/nsNativeThemeGTK.cpp +++ b/widget/src/gtk2/nsNativeThemeGTK.cpp @@ -55,6 +55,7 @@ #include "nsGfxCIID.h" #include "nsTransform2D.h" #include "nsIMenuFrame.h" +#include "nsIMenuParent.h" #include "prlink.h" #include "nsIDOMHTMLInputElement.h" #include "nsWidgetAtoms.h" @@ -277,11 +278,15 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame, CallQueryInterface(aFrame, &menuFrame); if (menuFrame) { - isTopLevel = menuFrame->IsOnMenuBar(); + nsIMenuParent *menuParent = menuFrame->GetMenuParent(); + if (menuParent) + menuParent->IsMenuBar(isTopLevel); } if (isTopLevel) { - aState->inHover = menuFrame->IsOpen(); + PRBool isOpen; + menuFrame->MenuIsOpen(isOpen); + aState->inHover = isOpen; } else { aState->inHover = CheckBooleanAttr(aFrame, nsWidgetAtoms::mozmenuactive); } diff --git a/widget/src/windows/nsNativeThemeWin.cpp b/widget/src/windows/nsNativeThemeWin.cpp index bdeb0aea8cd..1881070ac56 100644 --- a/widget/src/windows/nsNativeThemeWin.cpp +++ b/widget/src/windows/nsNativeThemeWin.cpp @@ -55,6 +55,7 @@ #include "nsILookAndFeel.h" #include "nsIDOMHTMLInputElement.h" #include "nsIMenuFrame.h" +#include "nsIMenuParent.h" #include "nsWidgetAtoms.h" #include @@ -786,8 +787,8 @@ nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, PRUint8 aWidgetType, nsIContent* content = aFrame->GetContent(); nsIFrame* parentFrame = aFrame->GetParent(); - if (parentFrame->GetType() == nsWidgetAtoms::menuFrame || - (content && content->IsNodeOfType(nsINode::eHTML))) + nsCOMPtr menuFrame(do_QueryInterface(parentFrame)); + if (menuFrame || (content && content->IsNodeOfType(nsINode::eHTML)) ) // XUL menu lists and HTML selects get state from parent aFrame = parentFrame; @@ -1410,7 +1411,9 @@ nsNativeThemeWin::ClassicGetWidgetBorder(nsIDeviceContext* aContext, if (menuFrame) { // If this is a real menu item, we should check if it is part of the // main menu bar or not, as this affects rendering. - isTopLevel = menuFrame->IsOnMenuBar(); + nsIMenuParent *menuParent = menuFrame->GetMenuParent(); + if (menuParent) + menuParent->IsMenuBar(isTopLevel); } // These values are obtained from visual inspection of equivelant @@ -1630,9 +1633,11 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, PRUint8 // If this is a real menu item, we should check if it is part of the // main menu bar or not, and if it is a container, as these affect // rendering. - isTopLevel = menuFrame->IsOnMenuBar(); - isOpen = menuFrame->IsOpen(); - isContainer = menuFrame->IsMenu(); + nsIMenuParent *menuParent = menuFrame->GetMenuParent(); + if (menuParent) + menuParent->IsMenuBar(isTopLevel); + menuFrame->MenuIsOpen(isOpen); + menuFrame->MenuIsContainer(isContainer); } if (IsDisabled(aFrame)) @@ -1699,8 +1704,8 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, PRUint8 nsIContent* content = aFrame->GetContent(); nsIFrame* parentFrame = aFrame->GetParent(); - if (parentFrame->GetType() == nsWidgetAtoms::menuFrame || - (content && content->IsNodeOfType(nsINode::eHTML))) + nsCOMPtr menuFrame(do_QueryInterface(parentFrame)); + if (menuFrame || (content && content->IsNodeOfType(nsINode::eHTML)) ) // XUL menu lists and HTML selects get state from parent aFrame = parentFrame; // XXX the button really shouldn't depress when clicking the diff --git a/widget/src/xpwidgets/nsWidgetAtomList.h b/widget/src/xpwidgets/nsWidgetAtomList.h index db88bfb65d2..16784e2d4d9 100644 --- a/widget/src/xpwidgets/nsWidgetAtomList.h +++ b/widget/src/xpwidgets/nsWidgetAtomList.h @@ -88,7 +88,6 @@ WIDGET_ATOM(menu, "menu") // Represents an XP menu WIDGET_ATOM(menuitem, "menuitem") // Represents an XP menu item WIDGET_ATOM(menupopup, "menupopup") // The XP menu's children. WIDGET_ATOM(menuseparator, "menuseparator") // Divider between menu items -WIDGET_ATOM(menuFrame, "menuFrame") WIDGET_ATOM(minpos, "minpos") WIDGET_ATOM(mode, "mode") WIDGET_ATOM(modifiers, "modifiers") // The modifiers attribute diff --git a/xpfe/appshell/src/nsWebShellWindow.cpp b/xpfe/appshell/src/nsWebShellWindow.cpp index 7e25b7169cf..d102b43ebc5 100644 --- a/xpfe/appshell/src/nsWebShellWindow.cpp +++ b/xpfe/appshell/src/nsWebShellWindow.cpp @@ -114,6 +114,8 @@ #define USE_NATIVE_MENUS #endif +#include "nsIPopupSetFrame.h" + /* Define Class IDs */ static NS_DEFINE_CID(kWindowCID, NS_WINDOW_CID); diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.xml b/xpfe/components/autocomplete/resources/content/autocomplete.xml index c5cfd0ffcd1..7490fbdff04 100644 --- a/xpfe/components/autocomplete/resources/content/autocomplete.xml +++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml @@ -30,7 +30,7 @@ xbl:inherits="open,hidden=disablehistory" anonid="historydropmarker"/> - diff --git a/xpfe/global/resources/content/bindings/popup.xml b/xpfe/global/resources/content/bindings/popup.xml index 3257f6dac46..743211d3606 100644 --- a/xpfe/global/resources/content/bindings/popup.xml +++ b/xpfe/global/resources/content/bindings/popup.xml @@ -34,41 +34,7 @@ return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject); - - - - - - - - - - - - - - - - - - - - - - + diff --git a/xpfe/global/resources/content/xul.css b/xpfe/global/resources/content/xul.css index d804943e5fc..572c3f0c155 100644 --- a/xpfe/global/resources/content/xul.css +++ b/xpfe/global/resources/content/xul.css @@ -298,22 +298,27 @@ menuseparator { /* is deprecated. Only and are still valid. */ popup, -menupopup, -panel { +menupopup { -moz-binding: url("chrome://global/content/bindings/popup.xml#popup"); -moz-box-orient: vertical; + display: none; } popup, menupopup, -panel, tooltip { - display: -moz-popup; z-index: 2147483647; } +menupopup[menugenerated="true"], +popup[menugenerated="true"], +tooltip[menugenerated="true"] { + display: -moz-popup; +} + tooltip { -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip"); + display: -moz-popup; margin-top: 21px; }