diff options
Diffstat (limited to 'layout/xul/nsXULPopupManager.h')
-rw-r--r-- | layout/xul/nsXULPopupManager.h | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/layout/xul/nsXULPopupManager.h b/layout/xul/nsXULPopupManager.h new file mode 100644 index 0000000000..9100ad7738 --- /dev/null +++ b/layout/xul/nsXULPopupManager.h @@ -0,0 +1,881 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsHashtablesFwd.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 "nsPresContext.h" +#include "nsStyleConsts.h" +#include "nsWidgetInitData.h" +#include "mozilla/Attributes.h" +#include "mozilla/widget/NativeMenu.h" +#include "Units.h" + +// XXX Avoid including this here by moving function bodies to the cpp file. +#include "mozilla/dom/Element.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. + * 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 nsMenuPopupFrame; +class nsMenuBarFrame; +class nsIDocShellTreeItem; +class nsPIDOMWindowOuter; +class nsRefreshDriver; + +namespace mozilla { +class PresShell; +namespace dom { +class Event; +class KeyboardEvent; +class UIEvent; +class XULButtonElement; +} // namespace dom +} // namespace mozilla + +// XUL popups can be in several different states. When opening a popup, the +// state changes as follows: +// ePopupClosed - initial state +// ePopupShowing - during the period when the popupshowing event fires +// ePopupOpening - between the popupshowing event and being visible. Creation +// of the child frames, layout and reflow occurs in this +// state. The popup is stored in the popup manager's list of +// open popups during this state. +// ePopupVisible - layout is done and the popup's view and widget are made +// visible. The popup is visible on screen but may be +// transitioning. The popupshown event has not yet fired. +// ePopupShown - the popup has been shown and is fully ready. This state is +// assigned just before the popupshown event fires. +// When closing a popup: +// ePopupHidden - during the period when the popuphiding event fires and +// the popup is removed. +// ePopupClosed - the popup's widget is made invisible. +enum nsPopupState { + // state when a popup is not open + ePopupClosed, + // state from when a popup is requested to be shown to after the + // popupshowing event has been fired. + ePopupShowing, + // state while a popup is waiting to be laid out and positioned + ePopupPositioning, + // state while a popup is open but the widget is not yet visible + ePopupOpening, + // state while a popup is visible and waiting for the popupshown event + ePopupVisible, + // state while a popup is open and visible on screen + ePopupShown, + // state from when a popup is requested to be hidden to when it is closed. + ePopupHiding, + // state which indicates that the popup was hidden without firing the + // popuphiding or popuphidden events. It is used when executing a menu + // command because the menu needs to be hidden before the command event + // fires, yet the popuphiding and popuphidden events are fired after. This + // state can also occur when the popup is removed because the document is + // unloaded. + ePopupInvisible +}; + +// 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(static_cast<uint8_t>(mozilla::StyleDirection::Ltr) == 0 && + static_cast<uint8_t>(mozilla::StyleDirection::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[static_cast<uint8_t>( \ + (frame)->StyleVisibility()->mDirection)][( \ + keycode)-mozilla::dom::KeyboardEvent_Binding::DOM_VK_END]) + +// Used to hold information about a popup that is about to be opened. +struct PendingPopup { + PendingPopup(nsIContent* aPopup, mozilla::dom::Event* aEvent); + + const RefPtr<nsIContent> mPopup; + const RefPtr<mozilla::dom::Event> mEvent; + + // Device pixels relative to the showing popup's presshell's + // root prescontext's root frame. + mozilla::LayoutDeviceIntPoint mMousePoint; + + // Cached modifiers used to trigger the popup. + mozilla::Modifiers mModifiers; + + already_AddRefed<nsIContent> GetTriggerContent() const; + + void InitMousePoint(); + + void SetMousePoint(mozilla::LayoutDeviceIntPoint aMousePoint) { + mMousePoint = aMousePoint; + } + + uint16_t MouseInputSource() const; +}; + +// 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 mNoAutoHide; // true for noautohide panels + 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 + + // True if the popup should maintain its position relative to the anchor when + // the anchor moves. + bool mFollowAnchor; + + // The last seen position of the anchor, relative to the screen. + nsRect mCurrentRect; + + mozilla::UniquePtr<nsMenuChainItem> mParent; + // Back pointer, safe because mChild keeps us alive. + nsMenuChainItem* mChild = nullptr; + + public: + nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aNoAutoHide, bool aIsContext, + nsPopupType aPopupType) + : mFrame(aFrame), + mPopupType(aPopupType), + mNoAutoHide(aNoAutoHide), + mIsContext(aIsContext), + mOnMenuBar(false), + mIgnoreKeys(eIgnoreKeys_False), + mFollowAnchor(false) { + NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor"); + MOZ_COUNT_CTOR(nsMenuChainItem); + } + + MOZ_COUNTED_DTOR(nsMenuChainItem) + + nsIContent* Content(); + nsMenuPopupFrame* Frame() { return mFrame; } + nsPopupType PopupType() { return mPopupType; } + bool IsNoAutoHide() { return mNoAutoHide; } + void SetNoAutoHide(bool aNoAutoHide) { mNoAutoHide = aNoAutoHide; } + 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.get(); } + nsMenuChainItem* GetChild() { return mChild; } + bool FollowsAnchor() { return mFollowAnchor; } + void UpdateFollowAnchor(); + void CheckForAnchorChange(); + + // set the parent of this item to aParent, also changing the parent + // to have this as a child. + void SetParent(mozilla::UniquePtr<nsMenuChainItem> aParent); + // Removes the parent pointer and returns it. + mozilla::UniquePtr<nsMenuChainItem> Detach(); +}; + +// 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) + : mozilla::Runnable("nsXULPopupHidingEvent"), + 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) + : mozilla::Runnable("nsXULPopupPositionedEvent"), mPopup(aPopup) { + MOZ_ASSERT(aPopup); + } + + 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); + + private: + const nsCOMPtr<nsIContent> mPopup; +}; + +// this class is used for dispatching menu command events asynchronously. +class nsXULMenuCommandEvent : public mozilla::Runnable { + public: + nsXULMenuCommandEvent(mozilla::dom::Element* aMenu, bool aIsTrusted, + mozilla::Modifiers aModifiers, bool aUserInput, + bool aFlipChecked, int16_t aButton) + : mozilla::Runnable("nsXULMenuCommandEvent"), + mMenu(aMenu), + mModifiers(aModifiers), + mButton(aButton), + mIsTrusted(aIsTrusted), + mUserInput(aUserInput), + mFlipChecked(aFlipChecked), + mCloseMenuMode(CloseMenuMode_Auto) { + NS_ASSERTION(aMenu, + "null menu supplied to nsXULMenuCommandEvent constructor"); + } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override; + + void SetCloseMenuMode(CloseMenuMode aCloseMenuMode) { + mCloseMenuMode = aCloseMenuMode; + } + + private: + RefPtr<mozilla::dom::Element> mMenu; + + mozilla::Modifiers mModifiers; + int16_t mButton; + bool mIsTrusted; + bool mUserInput; + bool mFlipChecked; + CloseMenuMode mCloseMenuMode; +}; + +class nsXULPopupManager final : public nsIDOMEventListener, + public nsIRollupListener, + public nsIObserver, + public mozilla::widget::NativeMenu::Observer { + public: + friend class nsXULPopupHidingEvent; + friend class nsXULPopupPositionedEvent; + friend class nsXULMenuCommandEvent; + friend class TransitionEnder; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER + + // nsIRollupListener + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool Rollup(uint32_t aCount, bool aFlush, + const mozilla::LayoutDeviceIntPoint* 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 nsIWidget* GetRollupWidget() override; + virtual bool RollupNativeMenu() override; + + // NativeMenu::Observer + void OnNativeMenuOpened() override; + void OnNativeMenuClosed() override; + void OnNativeSubMenuWillOpen(mozilla::dom::Element* aPopupElement) override; + void OnNativeSubMenuDidOpen(mozilla::dom::Element* aPopupElement) override; + void OnNativeSubMenuClosed(mozilla::dom::Element* aPopupElement) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY void OnNativeMenuWillActivateItem( + mozilla::dom::Element* aMenuItemElement) 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(mozilla::PresShell* aPresShell); + + // 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); + + struct MayShowMenuResult { + const bool mIsNative = false; + mozilla::dom::XULButtonElement* const mMenuButton = nullptr; + nsMenuPopupFrame* const mMenuPopupFrame = nullptr; + + explicit operator bool() const { + MOZ_ASSERT(!!mMenuButton == !!mMenuPopupFrame); + return mIsNative || mMenuButton; + } + }; + + MayShowMenuResult MayShowMenu(nsIContent* aMenu); + + /** + * Open a <menu> given its content node. If aSelectFirstItem is + * set to true, the first item on the menu will automatically be + * selected. + */ + void ShowMenu(nsIContent* aMenu, bool aSelectFirstItem); + + /** + * 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 XULPopupElement::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, mozilla::dom::Event* 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, + mozilla::dom::Event* 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, + mozilla::dom::Event* aTriggerEvent); + + /** + * Open a popup as a native menu, at a specific screen position specified by + * aXPos and aYPos, measured in CSS pixels. + * + * This fires the popupshowing event synchronously. + * + * Returns whether native menus are supported for aPopup on this platform. + * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY bool ShowPopupAsNativeMenu( + nsIContent* aPopup, int32_t aXPos, int32_t aYPos, bool aIsContextMenu, + mozilla::dom::Event* aTriggerEvent); + + /** + * Open a tooltip at a specific screen position specified by aXPos and aYPos, + * measured in device pixels. This fires the popupshowing event synchronously. + */ + void ShowTooltipAtScreen(nsIContent* aPopup, nsIContent* aTriggerContent, + const mozilla::LayoutDeviceIntPoint&); + + /* + * 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 of a <menu>. + */ + void HideMenu(nsIContent* aMenu); + + /** + * 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, int32_t aDelay); + + /** + * Hide all of the popups from a given docshell. This should be called when + * the document is hidden. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide); + + /** + * Check if any popups need to be repositioned or hidden after a style or + * layout change. This will update, for example, any arrow type panels when + * the anchor that is is pointing to has moved, resized or gone away. + * Only those popups that pertain to the supplied aRefreshDriver are updated. + */ + void UpdatePopupPositions(nsRefreshDriver* aRefreshDriver); + + /** + * Enable or disable anchor following on the popup if needed. + */ + void UpdateFollowAnchor(nsMenuPopupFrame* aPopup); + + /** + * 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 + */ + MOZ_CAN_RUN_SCRIPT void ExecuteMenu(nsIContent* aMenu, + nsXULMenuCommandEvent* aEvent); + + /** + * If a native menu is open, and aItem is an item in the menu's subtree, + * execute the item with the help of the native menu and close the menu. + * Returns true if a native menu was open. + */ + bool ActivateNativeMenuItem(nsIContent* aItem, mozilla::Modifiers aModifiers, + int16_t aButton, mozilla::ErrorResult& aRv); + + /** + * Return true if the popup for the supplied content node is open. + */ + bool IsPopupOpen(nsIContent* aPopup); + + /** + * 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. + */ + nsIFrame* GetTopPopup(nsPopupType aType); + + /** + * Returns the topmost active menuitem that's currently visible, if any. + */ + nsIContent* GetTopActiveMenuItemContent(); + + /** + * 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<nsINode> GetLastTriggerPopupNode( + mozilla::dom::Document* aDocument) { + return GetLastTriggerNode(aDocument, false); + } + + already_AddRefed<nsINode> GetLastTriggerTooltipNode( + mozilla::dom::Document* 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 coordinates. + */ + void PopupMoved(nsIFrame* aFrame, nsIntPoint aPoint, bool aByMoveToRect); + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT 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(nsMenuPopupFrame*); + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT bool HandleShortcutNavigation( + mozilla::dom::KeyboardEvent& aKeyEvent, nsMenuPopupFrame* aFrame); + + /** + * Handles cursor navigation within a menu. Returns true if the key has + * been handled. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT bool HandleKeyboardEventWithKeyCode( + mozilla::dom::KeyboardEvent* aKeyEvent, + nsMenuChainItem* aTopVisibleMenuItem); + + // Sets mIgnoreKeys of the Top Visible Menu Item + nsresult UpdateIgnoreKeys(bool aIgnoreKeys); + + nsPopupState GetPopupState(mozilla::dom::Element* aPopupElement); + + mozilla::dom::Event* GetOpeningPopupEvent() const { + return mPendingPopup->mEvent.get(); + } + + MOZ_CAN_RUN_SCRIPT nsresult KeyUp(mozilla::dom::KeyboardEvent* aKeyEvent); + MOZ_CAN_RUN_SCRIPT nsresult KeyDown(mozilla::dom::KeyboardEvent* aKeyEvent); + MOZ_CAN_RUN_SCRIPT nsresult KeyPress(mozilla::dom::KeyboardEvent* aKeyEvent); + + protected: + nsXULPopupManager(); + ~nsXULPopupManager(); + + // get the nsMenuPopupFrame, if any, for the given content node + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsMenuPopupFrame* GetPopupFrameForContent(nsIContent* aContent, + bool aShouldFlush); + + // return the topmost menu, skipping over invisible popups + nsMenuChainItem* GetTopVisibleMenu(); + + // Removes the chain item from the chain and deletes it. + void RemoveMenuChainItem(nsMenuChainItem*); + + // Hide all of the visible popups from the given list. This function can + // cause style changes and frame destruction. + MOZ_CAN_RUN_SCRIPT void HidePopupsInList( + const nsTArray<nsMenuPopupFrame*>& aFrames); + + // Hide, but don't close, visible menus. Called before executing a menu item. + // The caller promises to close the menus properly (with a call to HidePopup) + // once the item has been executed. + MOZ_CAN_RUN_SCRIPT void HideOpenMenusBeforeExecutingMenu(CloseMenuMode aMode); + + // callbacks for ShowPopup and HidePopup as events may be done asynchronously + MOZ_CAN_RUN_SCRIPT void ShowPopupCallback(nsIContent* aPopup, + nsMenuPopupFrame* aPopupFrame, + bool aIsContextMenu, + bool aSelectFirstItem); + MOZ_CAN_RUN_SCRIPT void HidePopupCallback( + nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup, + nsIContent* aLastPopup, nsPopupType aPopupType, bool aDeselectMenu); + + /** + * Trigger frame construction and reflow in the popup, fire a popupshowing + * event on the popup and then open the popup. + * + * aPendingPopup - information about the popup to open + * aIsContextMenu - true for context menus + * aSelectFirstItem - true to select the first item in the menu + * TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY void BeginShowingPopup( + const PendingPopup& aPendingPopup, 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. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + 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. + */ + MOZ_CAN_RUN_SCRIPT + 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. + */ + MOZ_CAN_RUN_SCRIPT bool HandleKeyboardNavigationInPopup( + nsMenuChainItem* aItem, nsMenuPopupFrame* aFrame, + nsNavigationDirection aDir); + + protected: + already_AddRefed<nsINode> GetLastTriggerNode( + mozilla::dom::Document* aDocument, bool aIsTooltip); + + /** + * Fire a popupshowing event for aPopup. + */ + MOZ_CAN_RUN_SCRIPT nsEventStatus FirePopupShowingEvent( + const PendingPopup& aPendingPopup, nsPresContext* aPresContext); + + /** + * 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. + */ + // TODO: Convert UpdateKeyboardListeners() to MOZ_CAN_RUN_SCRIPT and get rid + // of the kungFuDeathGrip in it. + MOZ_CAN_RUN_SCRIPT_BOUNDARY void UpdateKeyboardListeners(); + + /* + * Returns true if the docshell for aDoc is aExpected or a child of aExpected. + */ + bool IsChildOfDocShell(mozilla::dom::Document* aDoc, + nsIDocShellTreeItem* aExpected); + + // Finds a chain item in mPopups. + nsMenuChainItem* FindPopup(nsIContent* aPopup) const; + + // 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; + + // set to the currently active menu bar, if any + nsMenuBarFrame* mActiveMenuBar; + + // linked list of normal menus and panels. mPopups points to the innermost + // popup, which keeps alive all their parents. + mozilla::UniquePtr<nsMenuChainItem> mPopups; + + // timer used for HidePopupAfterDelay + nsCOMPtr<nsITimer> mCloseTimer; + nsMenuPopupFrame* mTimerMenu = nullptr; + + // Information about the popup that is currently firing a popupshowing event. + const PendingPopup* mPendingPopup; + + // If a popup is displayed as a native menu, this is non-null while the + // native menu is open. + // mNativeMenu has a strong reference to the menupopup nsIContent. + RefPtr<mozilla::widget::NativeMenu> mNativeMenu; + + // If the currently open native menu activated an item, this is the item's + // close menu mode. Nothing() if mNativeMenu is null or if no item was + // activated. + mozilla::Maybe<CloseMenuMode> mNativeMenuActivatedItemCloseMenuMode; + + // If a popup is displayed as a native menu, this map contains the popup state + // for any of its non-closed submenus. This state cannot be stored on the + // submenus' nsMenuPopupFrames, because we usually don't generate frames for + // the contents of native menus. + // If a submenu is not present in this map, it means it's closed. + // This map is empty if mNativeMenu is null. + nsTHashMap<RefPtr<mozilla::dom::Element>, nsPopupState> + mNativeMenuSubmenuStates; +}; + +#endif |