michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef nsFocusManager_h___ michael@0: #define nsFocusManager_h___ michael@0: michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFocusManager.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsWeakReference.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #define FOCUSMETHOD_MASK 0xF000 michael@0: #define FOCUSMETHODANDRING_MASK 0xF0F000 michael@0: michael@0: #define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1" michael@0: michael@0: class nsIContent; michael@0: class nsIDocShellTreeItem; michael@0: class nsPIDOMWindow; michael@0: michael@0: struct nsDelayedBlurOrFocusEvent; michael@0: michael@0: /** michael@0: * The focus manager keeps track of where the focus is, that is, the node michael@0: * which receives key events. michael@0: */ michael@0: michael@0: class nsFocusManager MOZ_FINAL : public nsIFocusManager, michael@0: public nsIObserver, michael@0: public nsSupportsWeakReference michael@0: { michael@0: typedef mozilla::widget::InputContextAction InputContextAction; michael@0: michael@0: public: michael@0: michael@0: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFocusManager, nsIFocusManager) michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: NS_DECL_NSIFOCUSMANAGER michael@0: michael@0: // called to initialize and stop the focus manager at startup and shutdown michael@0: static nsresult Init(); michael@0: static void Shutdown(); michael@0: michael@0: /** michael@0: * Retrieve the single focus manager. michael@0: */ michael@0: static nsFocusManager* GetFocusManager() { return sInstance; } michael@0: michael@0: /** michael@0: * A faster version of nsIFocusManager::GetFocusedElement, returning a michael@0: * raw nsIContent pointer (instead of having AddRef-ed nsIDOMElement michael@0: * pointer filled in to an out-parameter). michael@0: */ michael@0: nsIContent* GetFocusedContent() { return mFocusedContent; } michael@0: michael@0: /** michael@0: * Return a focused window. Version of nsIFocusManager::GetFocusedWindow. michael@0: */ michael@0: nsPIDOMWindow* GetFocusedWindow() const { return mFocusedWindow; } michael@0: michael@0: /** michael@0: * Return an active window. Version of nsIFocusManager::GetActiveWindow. michael@0: */ michael@0: nsPIDOMWindow* GetActiveWindow() const { return mActiveWindow; } michael@0: michael@0: /** michael@0: * Called when content has been removed. michael@0: */ michael@0: nsresult ContentRemoved(nsIDocument* aDocument, nsIContent* aContent); michael@0: michael@0: /** michael@0: * Called when mouse button down event handling is started and finished. michael@0: */ michael@0: void SetMouseButtonDownHandlingDocument(nsIDocument* aDocument) michael@0: { michael@0: NS_ASSERTION(!aDocument || !mMouseDownEventHandlingDocument, michael@0: "Some mouse button down events are nested?"); michael@0: mMouseDownEventHandlingDocument = aDocument; michael@0: } michael@0: michael@0: /** michael@0: * Update the caret with current mode (whether in caret browsing mode or not). michael@0: */ michael@0: void UpdateCaretForCaretBrowsingMode(); michael@0: michael@0: /** michael@0: * Returns the content node that would be focused if aWindow was in an michael@0: * active window. This will traverse down the frame hierarchy, starting at michael@0: * the given window aWindow. Sets aFocusedWindow to the window with the michael@0: * document containing aFocusedContent. If no element is focused, michael@0: * aFocusedWindow may be still be set -- this means that the document is michael@0: * focused but no element within it is focused. michael@0: * michael@0: * aWindow and aFocusedWindow must both be non-null. michael@0: */ michael@0: static nsIContent* GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep, michael@0: nsPIDOMWindow** aFocusedWindow); michael@0: michael@0: /** michael@0: * Returns the content node that focus will be redirected to if aContent was michael@0: * focused. This is used for the special case of certain XUL elements such michael@0: * as textboxes which redirect focus to an anonymous child. michael@0: * michael@0: * aContent must be non-null. michael@0: * michael@0: * XXXndeakin this should be removed eventually but I want to do that as michael@0: * followup work. michael@0: */ michael@0: static nsIContent* GetRedirectedFocus(nsIContent* aContent); michael@0: michael@0: /** michael@0: * Returns an InputContextAction cause for aFlags. michael@0: */ michael@0: static InputContextAction::Cause GetFocusMoveActionCause(uint32_t aFlags); michael@0: michael@0: static bool sMouseFocusesFormControl; michael@0: michael@0: static void MarkUncollectableForCCGeneration(uint32_t aGeneration); michael@0: protected: michael@0: michael@0: nsFocusManager(); michael@0: ~nsFocusManager(); michael@0: michael@0: /** michael@0: * Ensure that the widget associated with the currently focused window is michael@0: * focused at the widget level. michael@0: */ michael@0: void EnsureCurrentWidgetFocused(); michael@0: michael@0: /** michael@0: * Blur whatever is currently focused and focus aNewContent. aFlags is a michael@0: * bitmask of the flags defined in nsIFocusManager. If aFocusChanged is michael@0: * true, then the focus has actually shifted and the caret position will be michael@0: * updated to the new focus, aNewContent will be scrolled into view (unless michael@0: * a flag disables this) and the focus method for the window will be updated. michael@0: * If aAdjustWidget is false, don't change the widget focus state. michael@0: * michael@0: * All actual focus changes must use this method to do so. (as opposed michael@0: * to those that update the focus in an inactive window for instance). michael@0: */ michael@0: void SetFocusInner(nsIContent* aNewContent, int32_t aFlags, michael@0: bool aFocusChanged, bool aAdjustWidget); michael@0: michael@0: /** michael@0: * Returns true if aPossibleAncestor is the same as aWindow or an michael@0: * ancestor of aWindow. michael@0: */ michael@0: bool IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor, michael@0: nsPIDOMWindow* aWindow); michael@0: michael@0: /** michael@0: * Returns the window that is the lowest common ancestor of both aWindow1 michael@0: * and aWindow2, or null if they share no common ancestor. michael@0: */ michael@0: already_AddRefed GetCommonAncestor(nsPIDOMWindow* aWindow1, michael@0: nsPIDOMWindow* aWindow2); michael@0: michael@0: /** michael@0: * When aNewWindow is focused, adjust the ancestors of aNewWindow so that they michael@0: * also have their corresponding frames focused. Thus, one can start at michael@0: * the active top-level window and navigate down the currently focused michael@0: * elements for each frame in the tree to get to aNewWindow. michael@0: */ michael@0: void AdjustWindowFocus(nsPIDOMWindow* aNewWindow, bool aCheckPermission); michael@0: michael@0: /** michael@0: * Returns true if aWindow is visible. michael@0: */ michael@0: bool IsWindowVisible(nsPIDOMWindow* aWindow); michael@0: michael@0: /** michael@0: * Returns true if aContent is a root element and not focusable. michael@0: * I.e., even if aContent is editable root element, this returns true when michael@0: * the document is in designMode. michael@0: * michael@0: * @param aContent must not be null and must be in a document. michael@0: */ michael@0: bool IsNonFocusableRoot(nsIContent* aContent); michael@0: michael@0: /** michael@0: * Checks and returns aContent if it may be focused, another content node if michael@0: * the focus should be retargeted at another node, or null if the node michael@0: * cannot be focused. aFlags are the flags passed to SetFocus and similar michael@0: * methods. michael@0: * michael@0: * An element is focusable if it is in a document, the document isn't in michael@0: * print preview mode and the element has an nsIFrame where the michael@0: * CheckIfFocusable method returns true. For elements, there is no michael@0: * frame, so only the IsFocusable method on the content node must be michael@0: * true. michael@0: */ michael@0: nsIContent* CheckIfFocusable(nsIContent* aContent, uint32_t aFlags); michael@0: michael@0: /** michael@0: * Blurs the currently focused element. Returns false if another element was michael@0: * focused as a result. This would mean that the caller should not proceed michael@0: * with a pending call to Focus. Normally, true would be returned. michael@0: * michael@0: * The currently focused element within aWindowToClear will be cleared. michael@0: * aWindowToClear may be null, which means that no window is cleared. This michael@0: * will be the case, for example, when lowering a window, as we want to fire michael@0: * a blur, but not actually change what element would be focused, so that michael@0: * the same element will be focused again when the window is raised. michael@0: * michael@0: * aAncestorWindowToFocus should be set to the common ancestor of the window michael@0: * that is being blurred and the window that is going to focused, when michael@0: * switching focus to a sibling window. michael@0: * michael@0: * aIsLeavingDocument should be set to true if the document/window is being michael@0: * blurred as well. Document/window blur events will be fired. It should be michael@0: * false if an element is the same document is about to be focused. michael@0: * michael@0: * If aAdjustWidget is false, don't change the widget focus state. michael@0: */ michael@0: bool Blur(nsPIDOMWindow* aWindowToClear, michael@0: nsPIDOMWindow* aAncestorWindowToFocus, michael@0: bool aIsLeavingDocument, michael@0: bool aAdjustWidget); michael@0: michael@0: /** michael@0: * Focus an element in the active window and child frame. michael@0: * michael@0: * aWindow is the window containing the element aContent to focus. michael@0: * michael@0: * aFlags is the flags passed to the various focus methods in michael@0: * nsIFocusManager. michael@0: * michael@0: * aIsNewDocument should be true if a new document is being focused. michael@0: * Document/window focus events will be fired. michael@0: * michael@0: * aFocusChanged should be true if a new content node is being focused, so michael@0: * the focused content will be scrolled into view and the caret position michael@0: * will be updated. If false is passed, then a window is simply being michael@0: * refocused, for instance, due to a window being raised, or a tab is being michael@0: * switched to. michael@0: * michael@0: * If aFocusChanged is true, then the focus has moved to a new location. michael@0: * Otherwise, the focus is just being updated because the window was michael@0: * raised. michael@0: * michael@0: * aWindowRaised should be true if the window is being raised. In this case, michael@0: * command updaters will not be called. michael@0: * michael@0: * If aAdjustWidget is false, don't change the widget focus state. michael@0: */ michael@0: void Focus(nsPIDOMWindow* aWindow, michael@0: nsIContent* aContent, michael@0: uint32_t aFlags, michael@0: bool aIsNewDocument, michael@0: bool aFocusChanged, michael@0: bool aWindowRaised, michael@0: bool aAdjustWidget); michael@0: michael@0: /** michael@0: * Fires a focus or blur event at aTarget. michael@0: * michael@0: * aType should be either NS_FOCUS_CONTENT or NS_BLUR_CONTENT. For blur michael@0: * events, aFocusMethod should normally be non-zero. michael@0: * michael@0: * aWindowRaised should only be true if called from WindowRaised. michael@0: */ michael@0: void SendFocusOrBlurEvent(uint32_t aType, michael@0: nsIPresShell* aPresShell, michael@0: nsIDocument* aDocument, michael@0: nsISupports* aTarget, michael@0: uint32_t aFocusMethod, michael@0: bool aWindowRaised, michael@0: bool aIsRefocus = false); michael@0: michael@0: /** michael@0: * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set. michael@0: */ michael@0: void ScrollIntoView(nsIPresShell* aPresShell, michael@0: nsIContent* aContent, michael@0: uint32_t aFlags); michael@0: michael@0: /** michael@0: * Raises the top-level window aWindow at the widget level. michael@0: */ michael@0: void RaiseWindow(nsPIDOMWindow* aWindow); michael@0: michael@0: /** michael@0: * Updates the caret positon and visibility to match the focus. michael@0: * michael@0: * aMoveCaretToFocus should be true to move the caret to aContent. michael@0: * michael@0: * aUpdateVisibility should be true to update whether the caret is michael@0: * visible or not. michael@0: */ michael@0: void UpdateCaret(bool aMoveCaretToFocus, michael@0: bool aUpdateVisibility, michael@0: nsIContent* aContent); michael@0: michael@0: /** michael@0: * Helper method to move the caret to the focused element aContent. michael@0: */ michael@0: void MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent); michael@0: michael@0: /** michael@0: * Makes the caret visible or not, depending on aVisible. michael@0: */ michael@0: nsresult SetCaretVisible(nsIPresShell* aPresShell, michael@0: bool aVisible, michael@0: nsIContent* aContent); michael@0: michael@0: michael@0: // the remaining functions are used for tab key and document-navigation michael@0: michael@0: /** michael@0: * Retrieves the start and end points of the current selection for michael@0: * aDocument and stores them in aStartContent and aEndContent. michael@0: */ michael@0: nsresult GetSelectionLocation(nsIDocument* aDocument, michael@0: nsIPresShell* aPresShell, michael@0: nsIContent **aStartContent, michael@0: nsIContent **aEndContent); michael@0: michael@0: /** michael@0: * Helper function for MoveFocus which determines the next element michael@0: * to move the focus to and returns it in aNextContent. michael@0: * michael@0: * aWindow is the window to adjust the focus within, and aStart is michael@0: * the element to start navigation from. For tab key navigation, michael@0: * this should be the currently focused element. michael@0: * michael@0: * aType is the type passed to MoveFocus. If aNoParentTraversal is set, michael@0: * navigation is not done to parent documents and iteration returns to the michael@0: * beginning (or end) of the starting document. michael@0: */ michael@0: nsresult DetermineElementToMoveFocus(nsPIDOMWindow* aWindow, michael@0: nsIContent* aStart, michael@0: int32_t aType, bool aNoParentTraversal, michael@0: nsIContent** aNextContent); michael@0: michael@0: /** michael@0: * Retrieve the next tabbable element within a document, using focusability michael@0: * and tabindex to determine the tab order. The element is returned in michael@0: * aResultContent. michael@0: * michael@0: * aRootContent is the root node -- nodes above this will not be examined. michael@0: * Typically this will be the root node of a document, but could also be michael@0: * a popup node. michael@0: * michael@0: * aOriginalStartContent is the content which was originally the starting michael@0: * node, in the case of recursive or looping calls. michael@0: * michael@0: * aStartContent is the starting point for this call of this method. michael@0: * If aStartContent doesn't have visual representation, the next content michael@0: * object, which does have a primary frame, will be used as a start. michael@0: * If that content object is focusable, the method may return it. michael@0: * michael@0: * aForward should be true for forward navigation or false for backward michael@0: * navigation. michael@0: * michael@0: * aCurrentTabIndex is the current tabindex. michael@0: * michael@0: * aIgnoreTabIndex to ignore the current tabindex and find the element michael@0: * irrespective or the tab index. This will be true when a selection is michael@0: * active, since we just want to focus the next element in tree order michael@0: * from where the selection is. Similarly, if the starting element isn't michael@0: * focusable, since it doesn't really have a defined tab index. michael@0: */ michael@0: nsresult GetNextTabbableContent(nsIPresShell* aPresShell, michael@0: nsIContent* aRootContent, michael@0: nsIContent* aOriginalStartContent, michael@0: nsIContent* aStartContent, michael@0: bool aForward, michael@0: int32_t aCurrentTabIndex, michael@0: bool aIgnoreTabIndex, michael@0: nsIContent** aResultContent); michael@0: michael@0: /** michael@0: * Get the next tabbable image map area and returns it. michael@0: * michael@0: * aForward should be true for forward navigation or false for backward michael@0: * navigation. michael@0: * michael@0: * aCurrentTabIndex is the current tabindex. michael@0: * michael@0: * aImageContent is the image. michael@0: * michael@0: * aStartContent is the current image map area. michael@0: */ michael@0: nsIContent* GetNextTabbableMapArea(bool aForward, michael@0: int32_t aCurrentTabIndex, michael@0: nsIContent* aImageContent, michael@0: nsIContent* aStartContent); michael@0: michael@0: /** michael@0: * Return the next valid tabindex value after aCurrentTabIndex, if aForward michael@0: * is true, or the previous tabindex value if aForward is false. aParent is michael@0: * the node from which to start looking for tab indicies. michael@0: */ michael@0: int32_t GetNextTabIndex(nsIContent* aParent, michael@0: int32_t aCurrentTabIndex, michael@0: bool aForward); michael@0: michael@0: /** michael@0: * Retrieves and returns the root node from aDocument to be focused. Will michael@0: * return null if the root node cannot be focused. There are several reasons michael@0: * for this: michael@0: * michael@0: * - if aIsForDocNavigation is true, and aWindow is in an