/* -*- 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/. */ #ifndef nsFocusManager_h___ #define nsFocusManager_h___ #include "nsCycleCollectionParticipant.h" #include "nsIContent.h" #include "mozilla/dom/Document.h" #include "nsIFocusManager.h" #include "nsIObserver.h" #include "nsWeakReference.h" #include "mozilla/Attributes.h" #include "mozilla/RefPtr.h" #include "mozilla/StaticPtr.h" #define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1" class nsIContent; class nsPIDOMWindowOuter; namespace mozilla { class PresShell; namespace dom { class Element; struct FocusOptions; class BrowserParent; class ContentChild; class ContentParent; } // namespace dom } // namespace mozilla struct nsDelayedBlurOrFocusEvent; /** * The focus manager keeps track of where the focus is, that is, the node * which receives key events. */ class nsFocusManager final : public nsIFocusManager, public nsIObserver, public nsSupportsWeakReference { using InputContextAction = mozilla::widget::InputContextAction; using Document = mozilla::dom::Document; friend class mozilla::dom::ContentChild; friend class mozilla::dom::ContentParent; public: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFocusManager, nsIFocusManager) NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIOBSERVER NS_DECL_NSIFOCUSMANAGER // called to initialize and stop the focus manager at startup and shutdown static nsresult Init(); static void Shutdown(); // Simple helper to call SetFocusedWindow on the instance. // // This raises the window and switches to the tab as needed. static void FocusWindow(nsPIDOMWindowOuter* aWindow, mozilla::dom::CallerType aCallerType); static void PrefChanged(const char* aPref, void* aSelf); void PrefChanged(const char* aPref); /** * Retrieve the single focus manager. */ static nsFocusManager* GetFocusManager() { return sInstance; } /** * A faster version of nsIFocusManager::GetFocusedElement, returning a * raw Element pointer (instead of having AddRef-ed Element * pointer filled in to an out-parameter). */ mozilla::dom::Element* GetFocusedElement() { return mFocusedElement; } /** * Returns true if aContent currently has focus. */ bool IsFocused(nsIContent* aContent); /** * Returns true if test mode is enabled. */ bool IsTestMode(); /** * Return a focused window. Version of nsIFocusManager::GetFocusedWindow. */ nsPIDOMWindowOuter* GetFocusedWindow() const { return mFocusedWindow; } /** * In the chrome process, retrieves the BrowsingContext corresponding * to GetFocusedWindow(). In a content process, retrieves the * focused BrowsingContext, which may not belong to this process. */ mozilla::dom::BrowsingContext* GetFocusedBrowsingContext() const { if (XRE_IsParentProcess()) { if (mFocusedWindow) { return mFocusedWindow->GetBrowsingContext(); } return nullptr; } return mFocusedBrowsingContextInContent; } /** * Returns whether the given browsing context is in the active window. */ bool IsInActiveWindow(mozilla::dom::BrowsingContext*) const; /** * Return an active window. Version of nsIFocusManager::GetActiveWindow. */ nsPIDOMWindowOuter* GetActiveWindow() const { return mActiveWindow; } /** * In the chrome process, retrieves the BrowsingContext corresponding * to GetActiveWindow(). In a content process, retrieves the * BrowsingContext of the top-level Web content in the active tab if * in the same process as the caller or nullptr otherwise. */ mozilla::dom::BrowsingContext* GetActiveBrowsingContext() const { if (XRE_IsParentProcess()) { if (mActiveWindow) { return mActiveWindow->GetBrowsingContext(); } return nullptr; } return mActiveBrowsingContextInContent; } /** * Called when content has been removed. */ nsresult ContentRemoved(Document* aDocument, nsIContent* aContent); void NeedsFlushBeforeEventHandling(mozilla::dom::Element* aElement) { if (mFocusedElement == aElement) { mEventHandlingNeedsFlush = true; } } bool CanSkipFocus(nsIContent* aContent); void FlushBeforeEventHandlingIfNeeded(nsIContent* aContent) { if (mEventHandlingNeedsFlush) { nsCOMPtr doc = aContent->GetComposedDoc(); if (doc) { mEventHandlingNeedsFlush = false; doc->FlushPendingNotifications(mozilla::FlushType::Layout); } } } /** * Update the caret with current mode (whether in caret browsing mode or not). */ void UpdateCaretForCaretBrowsingMode(); /** * Returns the content node that would be focused if aWindow was in an * active window. This will traverse down the frame hierarchy, starting at * the given window aWindow. Sets aFocusedWindow to the window with the * document containing aFocusedContent. If no element is focused, * aFocusedWindow may be still be set -- this means that the document is * focused but no element within it is focused. * * aWindow, aFocusIsOutOfProcess, aFocusedWindow must all be non-null. */ enum SearchRange { // Return focused content in aWindow. So, aFocusedWindow is always aWindow. eOnlyCurrentWindow, // Return focused content in aWindow or one of all sub windows. eIncludeAllDescendants, // Return focused content in aWindow or one of visible sub windows. eIncludeVisibleDescendants, }; static mozilla::dom::Element* GetFocusedDescendant( nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange, nsPIDOMWindowOuter** aFocusedWindow); /** * Helper function for MoveFocus which determines the next element * to move the focus to and returns it in aNextContent. * * aWindow is the window to adjust the focus within, and aStart is * the element to start navigation from. For tab key navigation, * this should be the currently focused element. * * aType is the type passed to MoveFocus. If aNoParentTraversal is set, * navigation is not done to parent documents and iteration returns to the * beginning (or end) of the starting document. * * aNavigateByKey to move focus by keyboard as a side effect of computing the * next target. */ nsresult DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow, nsIContent* aStart, int32_t aType, bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent); /** * Setter for focusedWindow with CallerType */ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult SetFocusedWindowWithCallerType( mozIDOMWindowProxy* aWindowToFocus, mozilla::dom::CallerType aCallerType, uint64_t aActionId); /** * Given an element, which must be the focused element, activate the remote * frame it embeds, if any. */ void ActivateRemoteFrameIfNeeded(mozilla::dom::Element&, uint64_t aActionId); /** * Raises the top-level window aWindow at the widget level. */ void RaiseWindow(nsPIDOMWindowOuter* aWindow, mozilla::dom::CallerType aCallerType, uint64_t aActionId); /** * Called when a window has been raised. */ void WindowRaised(mozIDOMWindowProxy* aWindow, uint64_t aActionId); /** * Called when a window has been lowered. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY void WindowLowered(mozIDOMWindowProxy* aWindow, uint64_t aActionId); /** * Called when a new document in a window is shown. * * If aNeedsFocus is true, then focus events are expected to be fired on the * window if this window is in the focused window chain. */ void WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus); /** * Called when a document in a window has been hidden or otherwise can no * longer accept focus. */ void WindowHidden(mozIDOMWindowProxy* aWindow, uint64_t aActionId); /** * Fire any events that have been delayed due to synchronized actions. */ void FireDelayedEvents(Document* aDocument); void WasNuked(nsPIDOMWindowOuter* aWindow); /** * Indicate that a plugin wishes to take the focus. This is similar to a * normal focus except that the widget focus is not changed. Updating the * widget focus state is the responsibility of the caller. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult FocusPlugin(mozilla::dom::Element* aPlugin); static uint32_t ProgrammaticFocusFlags( const mozilla::dom::FocusOptions& aOptions); /** * Returns the content node that focus will be redirected to if aContent was * focused. This is used for the special case of certain XUL elements such * as textboxes or input number which redirect focus to an anonymous child. * * aContent must be non-null. * * XXXndeakin this should be removed eventually but I want to do that as * followup work. */ static mozilla::dom::Element* GetRedirectedFocus(nsIContent* aContent); /** * Returns an InputContextAction cause for aFlags. */ static InputContextAction::Cause GetFocusMoveActionCause(uint32_t aFlags); /** * Notify of re-focus to same content. * * aContent is focused content. */ void NotifyOfReFocus(nsIContent& aContent); static void MarkUncollectableForCCGeneration(uint32_t aGeneration); struct BlurredElementInfo { const mozilla::OwningNonNull mElement; explicit BlurredElementInfo(mozilla::dom::Element&); ~BlurredElementInfo(); }; protected: nsFocusManager(); ~nsFocusManager(); /** * Ensure that the widget associated with the currently focused window is * focused at the widget level. */ void EnsureCurrentWidgetFocused(mozilla::dom::CallerType aCallerType); /** * Activate or deactivate the window and send the activate/deactivate events. */ void ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive); /** * Blur whatever is currently focused and focus aNewContent. aFlags is a * bitmask of the flags defined in nsIFocusManager. If aFocusChanged is * true, then the focus has actually shifted and the caret position will be * updated to the new focus, aNewContent will be scrolled into view (unless * a flag disables this) and the focus method for the window will be updated. * If aAdjustWidget is false, don't change the widget focus state. * * All actual focus changes must use this method to do so. (as opposed * to those that update the focus in an inactive window for instance). */ MOZ_CAN_RUN_SCRIPT void SetFocusInner(mozilla::dom::Element* aNewContent, int32_t aFlags, bool aFocusChanged, bool aAdjustWidget, uint64_t aActionId); /** * Returns true if aPossibleAncestor is the same as aWindow or an * ancestor of aWindow. */ bool IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, nsPIDOMWindowOuter* aWindow) const; bool IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, mozilla::dom::BrowsingContext* aContext) const; bool IsSameOrAncestor(mozilla::dom::BrowsingContext* aPossibleAncestor, nsPIDOMWindowOuter* aWindow) const; public: bool IsSameOrAncestor(mozilla::dom::BrowsingContext* aPossibleAncestor, mozilla::dom::BrowsingContext* aContext) const; protected: /** * Returns the window that is the lowest common ancestor of both aWindow * and aContext, or null if they share no common ancestor. */ mozilla::dom::BrowsingContext* GetCommonAncestor( nsPIDOMWindowOuter* aWindow, mozilla::dom::BrowsingContext* aContext); /** * When aBrowsingContext is focused, adjust the ancestors of aBrowsingContext * so that they also have their corresponding frames focused. Thus, one can * start at the active top-level window and navigate down the currently * focused elements for each frame in the tree to get to aBrowsingContext. */ bool AdjustInProcessWindowFocus( mozilla::dom::BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible, uint64_t aActionId); void AdjustWindowFocus(mozilla::dom::BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible, uint64_t aActionId); /** * Returns true if aWindow is visible. */ bool IsWindowVisible(nsPIDOMWindowOuter* aWindow); /** * Returns true if aContent is a root element and not focusable. * I.e., even if aContent is editable root element, this returns true when * the document is in designMode. * * @param aContent must not be null and must be in a document. */ bool IsNonFocusableRoot(nsIContent* aContent); /** * First flushes the pending notifications to ensure the PresShell and frames * are updated. * Checks and returns aElement if it may be focused, another element node if * the focus should be retargeted at another node, or null if the node * cannot be focused. aFlags are the flags passed to SetFocus and similar * methods. * * An element is focusable if it is in a document, the document isn't in * print preview mode and the element has an nsIFrame where the * IsFocusable method returns true. For elements, there is no * frame, so only the IsFocusable method on the content node must be * true. */ mozilla::dom::Element* FlushAndCheckIfFocusable( mozilla::dom::Element* aElement, uint32_t aFlags); /** * Blurs the currently focused element. Returns false if another element was * focused as a result. This would mean that the caller should not proceed * with a pending call to Focus. Normally, true would be returned. * * The currently focused element within aBrowsingContextToClear will be * cleared. aBrowsingContextToClear may be null, which means that no window is * cleared. This will be the case, for example, when lowering a window, as we * want to fire a blur, but not actually change what element would be focused, * so that the same element will be focused again when the window is raised. * * aAncestorBrowsingContextToFocus should be set to the common ancestor of the * window that is being blurred and the window that is going to focused, when * switching focus to a sibling window. * * aIsLeavingDocument should be set to true if the document/window is being * blurred as well. Document/window blur events will be fired. It should be * false if an element is the same document is about to be focused. * * If aAdjustWidget is false, don't change the widget focus state. */ // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now, until we annotate callers. MOZ_CAN_RUN_SCRIPT_BOUNDARY bool Blur(mozilla::dom::BrowsingContext* aBrowsingContextToClear, mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId, mozilla::dom::Element* aElementToFocus = nullptr); MOZ_CAN_RUN_SCRIPT_BOUNDARY void BlurFromOtherProcess( mozilla::dom::BrowsingContext* aFocusedBrowsingContext, mozilla::dom::BrowsingContext* aBrowsingContextToClear, mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId); MOZ_CAN_RUN_SCRIPT_BOUNDARY bool BlurImpl(mozilla::dom::BrowsingContext* aBrowsingContextToClear, mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, bool aIsLeavingDocument, bool aAdjustWidget, mozilla::dom::Element* aElementToFocus, uint64_t aActionId); /** * Focus an element in the active window and child frame. * * aWindow is the window containing the element aContent to focus. * * aFlags is the flags passed to the various focus methods in * nsIFocusManager. * * aIsNewDocument should be true if a new document is being focused. * Document/window focus events will be fired. * * aFocusChanged should be true if a new content node is being focused, so * the focused content will be scrolled into view and the caret position * will be updated. If false is passed, then a window is simply being * refocused, for instance, due to a window being raised, or a tab is being * switched to. * * If aFocusChanged is true, then the focus has moved to a new location. * Otherwise, the focus is just being updated because the window was * raised. * * aWindowRaised should be true if the window is being raised. In this case, * command updaters will not be called. * * If aAdjustWidget is false, don't change the widget focus state. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Focus(nsPIDOMWindowOuter* aWindow, mozilla::dom::Element* aContent, uint32_t aFlags, bool aIsNewDocument, bool aFocusChanged, bool aWindowRaised, bool aAdjustWidget, uint64_t aActionId, const mozilla::Maybe& = mozilla::Nothing()); /** * Send a focus or blur event at aTarget. It may be added to the delayed * event queue if the document is suppressing events. * * aEventMessage should be either eFocus or eBlur. * * aWindowRaised should only be true if called from WindowRaised. */ void SendFocusOrBlurEvent( mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell, Document* aDocument, nsISupports* aTarget, bool aWindowRaised, bool aIsRefocus = false, mozilla::dom::EventTarget* aRelatedTarget = nullptr); /** * Fire a focus or blur event at aTarget. * * aEventMessage should be either eFocus or eBlur. * For blur events, aFocusMethod should normally be non-zero. * * aWindowRaised should only be true if called from WindowRaised. */ void FireFocusOrBlurEvent( mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell, nsISupports* aTarget, bool aWindowRaised, bool aIsRefocus = false, mozilla::dom::EventTarget* aRelatedTarget = nullptr); /** * Fire a focusin or focusout event * * aEventMessage should be either eFocusIn or eFocusOut. * * aTarget is the content the event will fire on (the object that gained * focus for focusin, the object blurred for focusout). * * aCurrentFocusedWindow is the window focused before the focus/blur event * was fired. * * aCurrentFocusedContent is the content focused before the focus/blur event * was fired. * * aRelatedTarget is the content related to the event (the object * losing focus for focusin, the object getting focus for focusout). */ void FireFocusInOrOutEvent( mozilla::EventMessage aEventMessage, mozilla::PresShell* aPresShell, nsISupports* aTarget, nsPIDOMWindowOuter* aCurrentFocusedWindow, nsIContent* aCurrentFocusedContent, mozilla::dom::EventTarget* aRelatedTarget = nullptr); /** * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set. */ MOZ_CAN_RUN_SCRIPT void ScrollIntoView(mozilla::PresShell* aPresShell, nsIContent* aContent, uint32_t aFlags); /** * Updates the caret positon and visibility to match the focus. * * aMoveCaretToFocus should be true to move the caret to aContent. * * aUpdateVisibility should be true to update whether the caret is * visible or not. */ void UpdateCaret(bool aMoveCaretToFocus, bool aUpdateVisibility, nsIContent* aContent); /** * Helper method to move the caret to the focused element aContent. */ MOZ_CAN_RUN_SCRIPT_BOUNDARY void MoveCaretToFocus( mozilla::PresShell* aPresShell, nsIContent* aContent); /** * Makes the caret visible or not, depending on aVisible. */ nsresult SetCaretVisible(mozilla::PresShell* aPresShell, bool aVisible, nsIContent* aContent); // the remaining functions are used for tab key and document-navigation /** * Retrieves the start and end points of the current selection for * aDocument and stores them in aStartContent and aEndContent. */ nsresult GetSelectionLocation(Document* aDocument, mozilla::PresShell* aPresShell, nsIContent** aStartContent, nsIContent** aEndContent); /** * Retrieve the next tabbable element in scope owned by aOwner, using * focusability and tabindex to determine the tab order. * * aOwner is the owner of scope to search in. * * aStartContent is the starting point for this call of this method. * * aOriginalStartContent is the initial starting point for sequential * navigation. * * aForward should be true for forward navigation or false for backward * navigation. * * aCurrentTabIndex is the current tabindex. * * aIgnoreTabIndex to ignore the current tabindex and find the element * irrespective or the tab index. * * aForDocumentNavigation informs whether we're navigating only through * documents. * * aSkipOwner to skip owner while searching. The flag is set when caller is * |GetNextTabbableContent| in order to let caller handle owner. * * NOTE: * Consider the method searches downwards in flattened subtree * rooted at aOwner. */ nsIContent* GetNextTabbableContentInScope( nsIContent* aOwner, nsIContent* aStartContent, nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, bool aSkipOwner); /** * Retrieve the next tabbable element in scope including aStartContent * and the scope's ancestor scopes, using focusability and tabindex to * determine the tab order. * * aStartOwner is the scope owner of the aStartContent. * * aStartContent an in/out paremeter. It as input is the starting point * for this call of this method; as output it is the shadow host in * light DOM if the next tabbable element is not found in shadow DOM, * in order to continue searching in light DOM. * * aOriginalStartContent is the initial starting point for sequential * navigation. * * aForward should be true for forward navigation or false for backward * navigation. * * aCurrentTabIndex returns tab index of shadow host in light DOM if the * next tabbable element is not found in shadow DOM, in order to continue * searching in light DOM. * * aIgnoreTabIndex to ignore the current tabindex and find the element * irrespective or the tab index. * * aForDocumentNavigation informs whether we're navigating only through * documents. * * aNavigateByKey to move focus by keyboard as a side effect of computing the * next target. * * NOTE: * Consider the method searches upwards in all shadow host- or slot-rooted * flattened subtrees that contains aStartContent as non-root, except * the flattened subtree rooted at shadow host in light DOM. */ nsIContent* GetNextTabbableContentInAncestorScopes( nsIContent* aStartOwner, nsIContent** aStartContent, nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey); /** * Retrieve the next tabbable element within a document, using focusability * and tabindex to determine the tab order. The element is returned in * aResultContent. * * aRootContent is the root node -- nodes above this will not be examined. * Typically this will be the root node of a document, but could also be * a popup node. * * aOriginalStartContent is the content which was originally the starting * node, in the case of recursive or looping calls. * * aStartContent is the starting point for this call of this method. * If aStartContent doesn't have visual representation, the next content * object, which does have a primary frame, will be used as a start. * If that content object is focusable, the method may return it. * * aForward should be true for forward navigation or false for backward * navigation. * * aCurrentTabIndex is the current tabindex. * * aIgnoreTabIndex to ignore the current tabindex and find the element * irrespective or the tab index. This will be true when a selection is * active, since we just want to focus the next element in tree order * from where the selection is. Similarly, if the starting element isn't * focusable, since it doesn't really have a defined tab index. * * aNavigateByKey to move focus by keyboard as a side effect of computing the * next target. */ nsresult GetNextTabbableContent( mozilla::PresShell* aPresShell, nsIContent* aRootContent, nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward, int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, nsIContent** aResultContent); /** * Get the next tabbable image map area and returns it. * * aForward should be true for forward navigation or false for backward * navigation. * * aCurrentTabIndex is the current tabindex. * * aImageContent is the image. * * aStartContent is the current image map area. */ nsIContent* GetNextTabbableMapArea(bool aForward, int32_t aCurrentTabIndex, mozilla::dom::Element* aImageContent, nsIContent* aStartContent); /** * Return the next valid tabindex value after aCurrentTabIndex, if aForward * is true, or the previous tabindex value if aForward is false. aParent is * the node from which to start looking for tab indicies. */ int32_t GetNextTabIndex(nsIContent* aParent, int32_t aCurrentTabIndex, bool aForward); /** * Focus the first focusable content within the document with a root node of * aRootContent. For content documents, this will be aRootContent itself, but * for chrome documents, this will locate the next focusable content. */ nsresult FocusFirst(mozilla::dom::Element* aRootContent, nsIContent** aNextContent); /** * Retrieves and returns the root node from aDocument to be focused. Will * return null if the root node cannot be focused. There are several reasons * for this: * * - if aForDocumentNavigation is false and aWindow is a chrome shell. * - if aCheckVisibility is true and the aWindow is not visible. * - if aDocument is a frameset document. */ mozilla::dom::Element* GetRootForFocus(nsPIDOMWindowOuter* aWindow, Document* aDocument, bool aForDocumentNavigation, bool aCheckVisibility); /** * Retrieves and returns the root node as with GetRootForFocus but only if * aContent is a frame with a valid child document. */ mozilla::dom::Element* GetRootForChildDocument(nsIContent* aContent); /** * Retreives a focusable element within the current selection of aWindow. * Currently, this only detects links. * * This is used when MoveFocus is called with a type of MOVEFOCUS_CARET, * which is used, for example, to focus links as the caret is moved over * them. */ void GetFocusInSelection(nsPIDOMWindowOuter* aWindow, nsIContent* aStartSelection, nsIContent* aEndSelection, nsIContent** aFocusedContent); private: // Notify that the focus state of aElement has changed. Note that we need to // pass in whether the window should show a focus ring before the // SetFocusedNode call on it happened when losing focus and after the // SetFocusedNode call when gaining focus, which is why that information needs // to be an explicit argument instead of just passing in the window and asking // it whether it should show focus rings: in the losing focus case that // information could be wrong. // // aShouldShowFocusRing is only relevant if aGettingFocus is true. static void NotifyFocusStateChange(mozilla::dom::Element* aElement, mozilla::dom::Element* aElementToFocus, int32_t aFlags, bool aGettingFocus, bool aShouldShowFocusRing); void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow, uint64_t aActionId, bool aSyncBrowsingContext = true); bool TryDocumentNavigation(nsIContent* aCurrentContent, bool* aCheckSubDocument, nsIContent** aResultContent); bool TryToMoveFocusToSubDocument(nsIContent* aCurrentContent, nsIContent* aOriginalStartContent, bool aForward, bool aForDocumentNavigation, bool aNavigateByKey, nsIContent** aResultContent); // Sets the focused BrowsingContext and, if appropriate, syncs it to // other processes. void SetFocusedBrowsingContext(mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); // Content-only // Called when receiving an IPC message about another process setting // the focused BrowsingContext. void SetFocusedBrowsingContextFromOtherProcess( mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); // Chrome-only // When returning true, sets the chrome process notion of what // BrowsingContext is focused in content. When returning false, // ignores the attempt to set as out-of-sequence. bool SetFocusedBrowsingContextInChrome( mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); void InsertNewFocusActionId(uint64_t aActionId); bool ProcessPendingActiveBrowsingContextActionId(uint64_t aActionId, bool aSettingToNonNull); bool ProcessPendingFocusedBrowsingContextActionId(uint64_t aActionId); public: // Chrome-only // Gets the chrome process notion of what BrowsingContext is focused // in content. mozilla::dom::BrowsingContext* GetFocusedBrowsingContextInChrome(); // Chrome-only // Notifies the focus manager that BrowsingContext::Detach was called // on a BrowsingContext so that pointers to it can be forgotten. void BrowsingContextDetached(mozilla::dom::BrowsingContext* aContext); private: // Content-only // Sets the BrowsingContext corresponding to top-level Web content // in the frontmost tab if focus is in Web content. void SetActiveBrowsingContextInContent( mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); // Content-only // Receives notification of another process setting the top-level Web // content as being in the frontmost tab with focus in Web content. void SetActiveBrowsingContextFromOtherProcess( mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); // Content-only // Receives notification that another process determined that focus // moved to chrome so a particular BrowsingContext is no longer the // "active" one. void UnsetActiveBrowsingContextFromOtherProcess( mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); // Content-only // Receives a notification from parent that this content process's // attempt to set the active browsing context was late and the // prevailing browsing context is instead the second argument of // this method call. This should be ignored if the first argument // doesn't match the latest action id associated with setting the // active browsing context in this process, because in that case, // this revision is late. void ReviseActiveBrowsingContext(uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext, uint64_t aNewActionId); // Receives a notification from parent that this content process's // attempt to set the focused browsing context was late and the // prevailing browsing context is instead the second argument of // this method call. This should be ignored if the first argument // doesn't match the latest action id associated with setting the // active browsing context in this process, because in that case, // this revision is late. void ReviseFocusedBrowsingContext(uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext, uint64_t aNewActionId); // Chrome-only // Sets the chrome process notion of what content believes to be // the top-level BrowsingContext in the frontmost tab when focus // is in Web content. // Returns true if set and false if ignored. bool SetActiveBrowsingContextInChrome(mozilla::dom::BrowsingContext* aContext, uint64_t aActionId); public: // Chrome-only // Gets the chrome process notion of what content believes to be // the top-level BrowsingContext in the frontmost tab when focus // is in Web content. mozilla::dom::BrowsingContext* GetActiveBrowsingContextInChrome(); uint64_t GetActionIdForActiveBrowsingContextInChrome() const; uint64_t GetActionIdForFocusedBrowsingContextInChrome() const; static uint64_t GenerateFocusActionId(); private: // In the chrome process, the currently active and front-most top-most // window. Not supposed to be used in a meaningful way in content // processes. For legacy reasons, this exists as a separate field // instead of being derived from mFocusedWindow when needed, because // the defined relation that mActiveWindow is supposed to be the same // as or ancestor of mFocusedWindow is temporarily broken when a // window is being raised or lowered. nsCOMPtr mActiveWindow; // In a content process, the BrowsingContext corresponding to top-level // Web content in the active tab or nullptr if focus is not in a // BrowsingContextGroup that this process participates in. Synced // across processes in a BrowsingContextGroup. This field exists // separately from mFocusedBrowsingContextInContent instead of being being // derived from it, because for legacy reasons the relation // mFocusedBrowsingContextInContent->Top() == mActiveBrowsingContextInContent // is temporarily broken when a window is being raised or lowered. // Not supposed to be used in a meaningful way in the chrome process. RefPtr mActiveBrowsingContextInContent; // If this content process set mActiveBrowsingContextInContent, this // field holds the corresponding actionId so that // mActiveBrowsingContextInContent can be revised of the parent rejects // the update. This field is used for accepting revisions only if nothing // else has updated mActiveBrowsingContextInContent before the revision // arrives. uint64_t mActionIdForActiveBrowsingContextInContent; uint64_t mActionIdForActiveBrowsingContextInChrome; // If this content process set mFocusedBrowsingContextInContent, this // field holds the corresponding actionId so that // mFocusedBrowsingContextInContent can be revised of the parent rejects // the update. This field is used for accepting revisions only if nothing // else has updated mFocusedBrowsingContextInContent before the revision // arrives. uint64_t mActionIdForFocusedBrowsingContextInContent; uint64_t mActionIdForFocusedBrowsingContextInChrome; // Whether or not mActiveBrowsingContextInContent was set from another process // or from this process. bool mActiveBrowsingContextInContentSetFromOtherProcess; // This is the chrome process notion of content's // mActiveBrowsingContextInContent. Avoiding field reuse for different // semantics in different process types to make it easier to catch bugs. RefPtr mActiveBrowsingContextInChrome; // the child or top-level window that is currently focused. In the chrome // process, when a window isn't being raised or lowered, this window will // either be the same window as mActiveWindow or a descendant of it. // Except during shutdown use SetFocusedWindowInternal to set mFocusedWindow! nsCOMPtr mFocusedWindow; // The focused BrowsingContext if this is a chrome process and focus is // in chrome or if this is a content process and focus is in Web content // in this BrowsingContextGroup. nullptr otherwise. // Except during shutdown, must be set via SetFocusedWindowInternal which // calls SetFocusedBrowsingContext or if the value is coming in via IPC // via SetFocusedBrowsingContextFromOtherProcess. RefPtr mFocusedBrowsingContextInContent; // This is the chrome process notion of content's // mFocusedBrowsingContextInContent. Avoiding field reuse for different // semantics in different process types to make it easier to catch bugs. RefPtr mFocusedBrowsingContextInChrome; // the currently focused content if in-process or the XUL browser in which // Web content focus resides. Always inside mFocusedWindow. When a window // isn't being raised or lowered, this is a cached copy of the // mFocusedWindow's current content. This may be null if no content is // focused. RefPtr mFocusedElement; // these fields store a content node temporarily while it is being focused // or blurred to ensure that a recursive call doesn't refire the same event. // They will always be cleared afterwards. RefPtr mFirstBlurEvent; RefPtr mFirstFocusEvent; // keep track of a window while it is being lowered nsCOMPtr mWindowBeingLowered; // synchronized actions cannot be interrupted with events, so queue these up // and fire them later. nsTArray mDelayedBlurFocusEvents; // Array of focus action ids for which we haven't seen an active browsing // context set yet. As set is allowed to overwrite an unset. Therefore, // an unset removes earlier ids but not the matching id. A set removes // earlier ids and the matching id. // // Conceptually, active browsing context shouldn't have to exist as a // field, because it should be possible to always derive it from the // focused browsing context. Unfortunately, for legacy reasons, this // is not the case while a window is being raised or lowered. // // Conceptually, it should be possible for the parent to manage the // active browsing context. Unfortunately, for legacy reasons, the // code for setting the active browsing context needs to reside in // the content process to retain the existing and test-passing code // flow. // // This, obviously, raises the issue of content processes racing to // set the active browsing context. In particular, there is a pattern // that the parent initiates actions that cause multiple content // processes to mutate the active browsing context at almost the // same time. When two native browser windows change order, the // lowering isn't distinguished from the case of lowering the // entire app. For this reason, the owner of the previous active // browsing context tries to unset it and at almost the same time // the another content process sets a new active browsing context. // If the IPC messages for these unset and set actions were to // arrive in the wrong order, this could get in the wrong state. // // To address this issue, the parent manages an authortative order // of attempts to (un)set the active browsing context using the // array mPendingActiveBrowsingContextActions. // // A process reserves a slot in the order by calling // GenerateFocusActionId(). Per one call to GenerateFocusActionId(), // there may be at most one action to set the active browsing context // to a new value. There may be logically prior attempts to unset it // (i.e. set it to nullptr). That is, if there are both attempts to // unset and set the active browsing context with the same action id, // the attempt to set to a non-null value wins. // // The completion of an action from reserting the slot in the order // and actually performing the setting of the active browsing context // may span multiple processes and IPC messages. // // The at-most-once property is not asserted, because the process // claiming the position in the order and the process setting the // active browsing context with that actionId may be different, and // the act of using an actionId to set the active browsing context // is used to delete stale items from the array to avoid excessive // growth of the array. nsTArray mPendingActiveBrowsingContextActions; // Like mPendingActiveBrowsingContextActions but for the focused // browsing context. nsTArray mPendingFocusedBrowsingContextActions; // If set to true, layout of the document of the event target should be // flushed before handling focus depending events. bool mEventHandlingNeedsFlush; static bool sTestMode; // Process-specific counter for maintaining the prosess-specific // uniqueness of actionIds. static uint64_t sFocusActionCounter; // the single focus manager static mozilla::StaticRefPtr sInstance; }; nsresult NS_NewFocusManager(nsIFocusManager** aResult); #endif