diff options
Diffstat (limited to 'layout/xul/nsXULPopupManager.h')
-rw-r--r-- | layout/xul/nsXULPopupManager.h | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/layout/xul/nsXULPopupManager.h b/layout/xul/nsXULPopupManager.h new file mode 100644 index 000000000..41644d7e9 --- /dev/null +++ b/layout/xul/nsXULPopupManager.h @@ -0,0 +1,823 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The XUL Popup Manager keeps track of all open popups. + */ + +#ifndef nsXULPopupManager_h__ +#define nsXULPopupManager_h__ + +#include "mozilla/Logging.h" +#include "nsIContent.h" +#include "nsIRollupListener.h" +#include "nsIDOMEventListener.h" +#include "nsPoint.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIReflowCallback.h" +#include "nsThreadUtils.h" +#include "nsStyleConsts.h" +#include "nsWidgetInitData.h" +#include "mozilla/Attributes.h" +#include "Units.h" + +// X.h defines KeyPress +#ifdef KeyPress +#undef KeyPress +#endif + +/** + * 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. + * + * When a new popup is opened, it is appended to the popup chain, stored in a + * linked list in mPopups for dismissable menus and panels or mNoHidePanels + * for tooltips and panels with noautohide="true". + * 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 nsContainerFrame; +class nsMenuFrame; +class nsMenuPopupFrame; +class nsMenuBarFrame; +class nsMenuParent; +class nsIDOMKeyEvent; +class nsIDocShellTreeItem; +class nsPIDOMWindowOuter; + +// when a menu command is executed, the closemenu attribute may be used +// to define how the menu should be closed up +enum CloseMenuMode { + CloseMenuMode_Auto, // close up the chain of menus, default value + CloseMenuMode_None, // don't close up any menus + CloseMenuMode_Single // close up only the menu the command is inside +}; + +/** + * 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 +}; + +enum nsIgnoreKeys { + eIgnoreKeys_False, + eIgnoreKeys_True, + eIgnoreKeys_Shortcuts, +}; + +#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) + +static_assert(NS_STYLE_DIRECTION_LTR == 0 && NS_STYLE_DIRECTION_RTL == 1, + "Left to Right should be 0 and Right to Left should be 1"); + +/** + * DirectionFromKeyCodeTable: two arrays, the first for left-to-right and the + * other for right-to-left, that map keycodes to values of + * nsNavigationDirection. + */ +extern const nsNavigationDirection DirectionFromKeyCodeTable[2][6]; + +#define NS_DIRECTION_FROM_KEY_CODE(frame, keycode) \ + (DirectionFromKeyCodeTable[frame->StyleVisibility()->mDirection] \ + [keycode - nsIDOMKeyEvent::DOM_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 + nsPopupType mPopupType; // the popup type of the frame + bool mIsContext; // true for context menus + bool mOnMenuBar; // true if the menu is on a menu bar + nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used + + nsMenuChainItem* mParent; + nsMenuChainItem* mChild; + +public: + nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType) + : mFrame(aFrame), + mPopupType(aPopupType), + mIsContext(aIsContext), + mOnMenuBar(false), + mIgnoreKeys(eIgnoreKeys_False), + mParent(nullptr), + mChild(nullptr) + { + NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); + MOZ_COUNT_CTOR(nsMenuChainItem); + } + + ~nsMenuChainItem() + { + MOZ_COUNT_DTOR(nsMenuChainItem); + } + + nsIContent* Content(); + nsMenuPopupFrame* Frame() { return mFrame; } + nsPopupType PopupType() { return mPopupType; } + bool IsMenu() { return mPopupType == ePopupTypeMenu; } + bool IsContextMenu() { return mIsContext; } + nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; } + void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; } + bool IsOnMenuBar() { return mOnMenuBar; } + void SetOnMenuBar(bool 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 mozilla::Runnable +{ +public: + nsXULPopupShowingEvent(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) + : mPopup(aPopup), + mIsContextMenu(aIsContextMenu), + mSelectFirstItem(aSelectFirstItem) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); + } + + NS_IMETHOD Run() override; + +private: + nsCOMPtr<nsIContent> mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + +// this class is used for dispatching popuphiding events asynchronously. +class nsXULPopupHidingEvent : public mozilla::Runnable +{ +public: + nsXULPopupHidingEvent(nsIContent *aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu, + bool aIsCancel) + : mPopup(aPopup), + mNextPopup(aNextPopup), + mLastPopup(aLastPopup), + mPopupType(aPopupType), + mDeselectMenu(aDeselectMenu), + mIsRollup(aIsCancel) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor"); + // aNextPopup and aLastPopup may be null + } + + NS_IMETHOD Run() override; + +private: + nsCOMPtr<nsIContent> mPopup; + nsCOMPtr<nsIContent> mNextPopup; + nsCOMPtr<nsIContent> mLastPopup; + nsPopupType mPopupType; + bool mDeselectMenu; + bool mIsRollup; +}; + +// this class is used for dispatching popuppositioned events asynchronously. +class nsXULPopupPositionedEvent : public mozilla::Runnable +{ +public: + explicit nsXULPopupPositionedEvent(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) + : mPopup(aPopup) + , mIsContextMenu(aIsContextMenu) + , mSelectFirstItem(aSelectFirstItem) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); + } + + NS_IMETHOD Run() override; + + // Asynchronously dispatch a popuppositioned event at aPopup if this is a + // panel that should receieve such events. Return true if the event was sent. + static bool DispatchIfNeeded(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem); + +private: + nsCOMPtr<nsIContent> mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + +// this class is used for dispatching menu command events asynchronously. +class nsXULMenuCommandEvent : public mozilla::Runnable +{ +public: + nsXULMenuCommandEvent(nsIContent *aMenu, + bool aIsTrusted, + bool aShift, + bool aControl, + bool aAlt, + bool aMeta, + bool aUserInput, + bool aFlipChecked) + : mMenu(aMenu), + mIsTrusted(aIsTrusted), + mShift(aShift), + mControl(aControl), + mAlt(aAlt), + mMeta(aMeta), + mUserInput(aUserInput), + mFlipChecked(aFlipChecked), + mCloseMenuMode(CloseMenuMode_Auto) + { + NS_ASSERTION(aMenu, "null menu supplied to nsXULMenuCommandEvent constructor"); + } + + NS_IMETHOD Run() override; + + void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { mCloseMenuMode = aCloseMenuMode; } + +private: + nsCOMPtr<nsIContent> mMenu; + bool mIsTrusted; + bool mShift; + bool mControl; + bool mAlt; + bool mMeta; + bool mUserInput; + bool mFlipChecked; + CloseMenuMode mCloseMenuMode; +}; + +class nsXULPopupManager final : public nsIDOMEventListener, + public nsIRollupListener, + public nsITimerCallback, + public nsIObserver +{ + +public: + friend class nsXULPopupShowingEvent; + friend class nsXULPopupHidingEvent; + friend class nsXULPopupPositionedEvent; + friend class nsXULMenuCommandEvent; + friend class TransitionEnder; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIDOMEVENTLISTENER + + // nsIRollupListener + virtual bool Rollup(uint32_t aCount, bool aFlush, + const nsIntPoint* pos, nsIContent** aLastRolledUp) override; + virtual bool ShouldRollupOnMouseWheelEvent() override; + virtual bool ShouldConsumeOnMouseWheelEvent() override; + virtual bool ShouldRollupOnMouseActivate() override; + virtual uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) override; + virtual void NotifyGeometryChange() override {} + virtual nsIWidget* GetRollupWidget() override; + + 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(); + + // This should be called when a window is moved or resized to adjust the + // popups accordingly. + void AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow); + void AdjustPopupsOnWindowChange(nsIPresShell* aPresShell); + + // 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 are not valid, such as non-menu or non-menuitem elements are + // skipped, and the next or previous item after that is checked. + // + // If aStart is null, the first valid item is retrieved by GetNextMenuItem + // and the last valid item is retrieved by GetPreviousMenuItem. + // + // Both methods will loop around the beginning or end if needed. + // + // aParent - the parent menubar or menupopup + // aStart - the menu/menuitem to start navigation from. GetPreviousMenuItem + // returns the item before it, while GetNextMenuItem returns the + // item after it. + // aIsPopup - true for menupopups, false for menubars + static nsMenuFrame* GetPreviousMenuItem(nsContainerFrame* aParent, + nsMenuFrame* aStart, + bool aIsPopup); + static nsMenuFrame* GetNextMenuItem(nsContainerFrame* aParent, + nsMenuFrame* aStart, + bool 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 bool IsValidMenuItem(nsIContent* aContent, bool 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, bool 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 to ShowPopup or ShowPopupAtScreen. + // This is used by the implementation of nsIDOMXULDocument::GetPopupRangeParent + // and nsIDOMXULDocument::GetPopupRangeOffset. + void GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset); + + /** + * Open a <menu> 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, bool aSelectFirstItem, bool 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. + * + * aTriggerEvent should be the event that triggered the event. This is used + * to determine the coordinates and trigger node for the popup. This may be + * null if the popup was not triggered by an event. + * + * This fires the popupshowing event synchronously. + */ + void ShowPopup(nsIContent* aPopup, + nsIContent* aAnchorContent, + const nsAString& aPosition, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + bool aAttributesOverride, + bool aSelectFirstItem, + nsIDOMEvent* aTriggerEvent); + + /** + * Open a popup at a specific screen position specified by aXPos and aYPos, + * measured in CSS pixels. + * + * This fires the popupshowing event synchronously. + * + * If aIsContextMenu is true, the popup is positioned at a slight + * offset from aXPos/aYPos to ensure that it is not under the mouse + * cursor. + */ + void ShowPopupAtScreen(nsIContent* aPopup, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu, + nsIDOMEvent* aTriggerEvent); + + /* Open a popup anchored at a screen rectangle specified by aRect. + * The remaining arguments are similar to ShowPopup. + */ + void ShowPopupAtScreenRect(nsIContent* aPopup, + const nsAString& aPosition, + const nsIntRect& aRect, + bool aIsContextMenu, + bool aAttributesOverride, + nsIDOMEvent* aTriggerEvent); + + /** + * Open a tooltip at a specific screen position specified by aXPos and aYPos, + * measured in CSS pixels. + * + * This fires the popupshowing event synchronously. + */ + void ShowTooltipAtScreen(nsIContent* aPopup, + nsIContent* aTriggerContent, + int32_t aXPos, int32_t aYPos); + + /** + * 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, + int32_t aXPos, int32_t aYPos, + bool aIsContextMenu); + + /* + * Hide a popup aPopup. If the popup is in a <menu>, 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 <menu> 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. + * aIsCancel - true if this popup is hiding due to being cancelled. + * aLastPopup - optional popup to close last when hiding a chain of menus. + * If null, then all popups will be closed. + */ + void HidePopup(nsIContent* aPopup, + bool aHideChain, + bool aDeselectMenu, + bool aAsynchronous, + bool aIsCancel, + nsIContent* aLastPopup = nullptr); + + /** + * Hide the popup aFrame. This method is called by the view manager when the + * close button is pressed. + */ + void HidePopup(nsIFrame* aFrame); + + /** + * 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 docshell. This should be called when the + * document is hidden. + */ + void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide); + + /** + * Enable or disable the dynamic noautohide state of a panel. + * + * aPanel - the panel whose state is to change + * aShouldRollup - whether the panel is no longer noautohide + */ + void EnableRollup(nsIContent* aPopup, bool aShouldRollup); + + /** + * Execute a menu command from the triggering event aEvent. + * + * aMenu - a menuitem to execute + * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse + * event which triggered the menu to be executed, may not be null + */ + void ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent); + + /** + * Return true if the popup for the supplied content node is open. + */ + bool IsPopupOpen(nsIContent* aPopup); + + /** + * Return true if the popup for the supplied menu parent is open. + */ + bool IsPopupOpenForMenuParent(nsMenuParent* aMenuParent); + + /** + * Return the frame for the topmost open popup of a given type, or null if + * no popup of that type is open. If aType is ePopupTypeAny, a menu of any + * type is returned, except for popups in the mNoHidePanels list. + */ + nsIFrame* GetTopPopup(nsPopupType aType); + + /** + * Return an array of all the open and visible popup frames for + * menus, in order from top to bottom. + */ + void GetVisiblePopups(nsTArray<nsIFrame *>& aPopups); + + /** + * Get the node that last triggered a popup or tooltip in the document + * aDocument. aDocument must be non-null and be a document contained within + * the same window hierarchy as the popup to retrieve. + */ + already_AddRefed<nsIDOMNode> GetLastTriggerPopupNode(nsIDocument* aDocument) + { + return GetLastTriggerNode(aDocument, false); + } + + already_AddRefed<nsIDOMNode> GetLastTriggerTooltipNode(nsIDocument* aDocument) + { + return GetLastTriggerNode(aDocument, true); + } + + /** + * 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. + */ + bool MayShowPopup(nsMenuPopupFrame* aFrame); + + /** + * Indicate that the popup associated with aView has been moved to the + * specified screen coordiates. + */ + void PopupMoved(nsIFrame* aFrame, nsIntPoint aPoint); + + /** + * Indicate that the popup associated with aView has been resized to the + * given device pixel size aSize. + */ + void PopupResized(nsIFrame* aFrame, mozilla::LayoutDeviceIntSize aSize); + + /** + * 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. + */ + bool 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(); + + /** + * Cancel the timer which closes menus after delay, but only if the menu to + * close is aMenuParent. When a submenu is opened, the user might move the + * mouse over a sibling menuitem which would normally close the menu. This + * menu is closed via a timer. However, if the user moves the mouse over the + * submenu before the timer fires, we should instead cancel the timer. This + * ensures that the user can move the mouse diagonally over a menu. + */ + void CancelMenuTimer(nsMenuParent* aMenuParent); + + /** + * Handles navigation for menu accelkeys. If aFrame is specified, then the + * key is handled by that popup, otherwise if aFrame is null, the key is + * handled by the active popup or menubar. + */ + bool HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, + nsMenuPopupFrame* aFrame); + + /** + * Handles cursor navigation within a menu. Returns true if the key has + * been handled. + */ + bool HandleKeyboardNavigation(uint32_t aKeyCode); + + /** + * Handle keyboard navigation within a menu popup specified by aFrame. + * Returns true if the key was handled and other default handling + * should not occur. + */ + bool HandleKeyboardNavigationInPopup(nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir) + { + return HandleKeyboardNavigationInPopup(nullptr, aFrame, aDir); + } + + /** + * Handles the keyboard event with keyCode value. Returns true if the event + * has been handled. + */ + bool HandleKeyboardEventWithKeyCode(nsIDOMKeyEvent* aKeyEvent, + nsMenuChainItem* aTopVisibleMenuItem); + + nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent); + nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent); + nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent); + +protected: + nsXULPopupManager(); + ~nsXULPopupManager(); + + // get the nsMenuPopupFrame, if any, for the given content node + nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush); + + // return the topmost menu, skipping over invisible popups + nsMenuChainItem* GetTopVisibleMenu(); + + // Hide all of the visible popups from the given list. This function can + // cause style changes and frame destruction. + void HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames); + + // set the event that was used to trigger the popup, or null to clear the + // event details. aTriggerContent will be set to the target of the event. + void InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, nsIContent** aTriggerContent); + + // callbacks for ShowPopup and HidePopup as events may be done asynchronously + void ShowPopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + bool aIsContextMenu, + bool aSelectFirstItem); + void HidePopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPopupType aPopupType, + bool aDeselectMenu); + + /** + * Fire a popupshowing event on the popup and then open the popup. + * + * aPopup - the popup to open + * aIsContextMenu - true for context menus + * aSelectFirstItem - true to select the first item in the menu + */ + void FirePopupShowingEvent(nsIContent* aPopup, + bool aIsContextMenu, + bool 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 the popup type indicates a menu, 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. + * + * The caller must keep a strong reference to aPopup, aNextPopup and aLastPopup. + * + * 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 + * aPopupType - the PopupType of the frame. + * aDeselectMenu - true to unhighlight the menu when hiding it + * aIsCancel - true if this popup is hiding due to being cancelled. + */ + void FirePopupHidingEvent(nsIContent* aPopup, + nsIContent* aNextPopup, + nsIContent* aLastPopup, + nsPresContext *aPresContext, + nsPopupType aPopupType, + bool aDeselectMenu, + bool aIsCancel); + + /** + * Handle keyboard navigation within a menu popup specified by aItem. + */ + bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, + nsNavigationDirection aDir) + { + return HandleKeyboardNavigationInPopup(aItem, aItem->Frame(), aDir); + } + +private: + /** + * Handle keyboard navigation within a menu popup aFrame. If aItem is + * supplied, then it is expected to have a frame equal to aFrame. + * If aItem is non-null, then the navigation may be redirected to + * an open submenu if one exists. Returns true if the key was + * handled and other default handling should not occur. + */ + bool HandleKeyboardNavigationInPopup(nsMenuChainItem* aItem, + nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir); + +protected: + + already_AddRefed<nsIDOMNode> GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip); + + /** + * 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(); + + /* + * Returns true if the docshell for aDoc is aExpected or a child of aExpected. + */ + bool IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected); + + // the document the key event listener is attached to + nsCOMPtr<mozilla::dom::EventTarget> mKeyListener; + + // widget that is currently listening to rollup events + nsCOMPtr<nsIWidget> mWidget; + + // range parent and offset set in SetTriggerEvent + nsCOMPtr<nsIDOMNode> mRangeParent; + int32_t mRangeOffset; + // Device pixels relative to the showing popup's presshell's + // root prescontext's root frame. + mozilla::LayoutDeviceIntPoint mCachedMousePoint; + + // cached modifiers + mozilla::Modifiers mCachedModifiers; + + // set to the currently active menu bar, if any + nsMenuBarFrame* mActiveMenuBar; + + // linked list of normal menus and panels. + nsMenuChainItem* mPopups; + + // linked list of noautohide panels and tooltips. + nsMenuChainItem* mNoHidePanels; + + // timer used for HidePopupAfterDelay + nsCOMPtr<nsITimer> mCloseTimer; + + // a popup that is waiting on the timer + nsMenuPopupFrame* mTimerMenu; + + // the popup that is currently being opened, stored only during the + // popupshowing event + nsCOMPtr<nsIContent> mOpeningPopup; + + // If true, all popups won't hide automatically on blur + static bool sDevtoolsDisableAutoHide; +}; + +#endif |