diff options
Diffstat (limited to 'dom/base/nsFocusManager.h')
-rw-r--r-- | dom/base/nsFocusManager.h | 1027 |
1 files changed, 1027 insertions, 0 deletions
diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h new file mode 100644 index 0000000000..4dde93c6ea --- /dev/null +++ b/dom/base/nsFocusManager.h @@ -0,0 +1,1027 @@ +/* -*- 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. + MOZ_CAN_RUN_SCRIPT static void FocusWindow( + nsPIDOMWindowOuter* aWindow, mozilla::dom::CallerType aCallerType); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY static void PrefChanged(const char* aPref, + void* aSelf); + MOZ_CAN_RUN_SCRIPT 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; } + static mozilla::dom::Element* GetFocusedElementStatic() { + return sInstance ? sInstance->GetFocusedElement() : nullptr; + } + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT nsresult ContentRemoved(Document* aDocument, + nsIContent* aContent); + + void NeedsFlushBeforeEventHandling(mozilla::dom::Element* aElement) { + if (mFocusedElement == aElement) { + mEventHandlingNeedsFlush = true; + } + } + + bool CanSkipFocus(nsIContent* aContent); + + MOZ_CAN_RUN_SCRIPT void FlushBeforeEventHandlingIfNeeded( + nsIContent* aContent) { + if (mEventHandlingNeedsFlush) { + nsCOMPtr<Document> 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). + */ + MOZ_CAN_RUN_SCRIPT void UpdateCaretForCaretBrowsingMode(); + + /** @see nsIFocusManager.getLastFocusMethod() */ + uint32_t GetLastFocusMethod(nsPIDOMWindowOuter*) const; + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT nsresult DetermineElementToMoveFocus( + nsPIDOMWindowOuter* aWindow, nsIContent* aStart, int32_t aType, + bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent); + + /** + * Setter for focusedWindow with CallerType + */ + MOZ_CAN_RUN_SCRIPT nsresult SetFocusedWindowWithCallerType( + mozIDOMWindowProxy* aWindowToFocus, mozilla::dom::CallerType aCallerType); + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT void RaiseWindow(nsPIDOMWindowOuter* aWindow, + mozilla::dom::CallerType aCallerType, + uint64_t aActionId); + + /** + * Called when a window has been raised. + */ + MOZ_CAN_RUN_SCRIPT void WindowRaised(mozIDOMWindowProxy* aWindow, + uint64_t aActionId); + + /** + * Called when a window has been lowered. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT void WindowShown(mozIDOMWindowProxy* aWindow, + bool aNeedsFocus); + + /** + * Called when a document in a window has been hidden or otherwise can no + * longer accept focus. + */ + MOZ_CAN_RUN_SCRIPT void WindowHidden(mozIDOMWindowProxy* aWindow, + uint64_t aActionId); + + /** + * Fire any events that have been delayed due to synchronized actions. + */ + MOZ_CAN_RUN_SCRIPT void FireDelayedEvents(Document* aDocument); + + void WasNuked(nsPIDOMWindowOuter* aWindow); + + static uint32_t ProgrammaticFocusFlags( + const mozilla::dom::FocusOptions& aOptions); + + /** + * Returns an InputContextAction cause for aFlags. + */ + static InputContextAction::Cause GetFocusMoveActionCause(uint32_t aFlags); + + /** + * Notify of re-focus to same element. + * + * aElement is focused element. + */ + MOZ_CAN_RUN_SCRIPT void NotifyOfReFocus(mozilla::dom::Element& aElement); + + static void MarkUncollectableForCCGeneration(uint32_t aGeneration); + + struct BlurredElementInfo { + const mozilla::OwningNonNull<mozilla::dom::Element> 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). + * + * Returns Nothing() if we end up not trying to focus the element, + * otherwise returns the generated action id. + */ + MOZ_CAN_RUN_SCRIPT mozilla::Maybe<uint64_t> SetFocusInner( + mozilla::dom::Element* aNewContent, int32_t aFlags, bool aFocusChanged, + bool aAdjustWidget); + + /** + * 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. + */ + MOZ_CAN_RUN_SCRIPT bool AdjustInProcessWindowFocus( + mozilla::dom::BrowsingContext* aBrowsingContext, bool aCheckPermission, + bool aIsVisible, uint64_t aActionId); + MOZ_CAN_RUN_SCRIPT 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 <area> elements, there is no + * frame, so only the IsFocusable method on the content node must be + * true. + */ + MOZ_CAN_RUN_SCRIPT 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 bool Blur( + mozilla::dom::BrowsingContext* aBrowsingContextToClear, + mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive, + uint64_t aActionId, mozilla::dom::Element* aElementToFocus = nullptr); + MOZ_CAN_RUN_SCRIPT 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 bool BlurImpl( + mozilla::dom::BrowsingContext* aBrowsingContextToClear, + mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, bool aRemainActive, + 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 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<BlurredElementInfo>& = 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. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT 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). + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT void UpdateCaret(bool aMoveCaretToFocus, + bool aUpdateVisibility, + nsIContent* aContent); + + /** + * Helper method to move the caret to the focused element aContent. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT nsIContent* GetNextTabbableContentInAncestorScopes( + nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */, + 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. + */ + MOZ_CAN_RUN_SCRIPT 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. + */ + MOZ_CAN_RUN_SCRIPT 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); + + MOZ_CAN_RUN_SCRIPT bool TryDocumentNavigation(nsIContent* aCurrentContent, + bool* aCheckSubDocument, + nsIContent** aResultContent); + + MOZ_CAN_RUN_SCRIPT 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(); + + // This function works very similar to + // https://html.spec.whatwg.org/#get-the-focusable-area + static mozilla::dom::Element* GetTheFocusableArea( + mozilla::dom::Element* aTarget, uint32_t aFlags); + + 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<nsPIDOMWindowOuter> 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<mozilla::dom::BrowsingContext> 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<mozilla::dom::BrowsingContext> 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<nsPIDOMWindowOuter> 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<mozilla::dom::BrowsingContext> 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<mozilla::dom::BrowsingContext> 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<mozilla::dom::Element> 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<mozilla::dom::Element> mFirstBlurEvent; + RefPtr<mozilla::dom::Element> mFirstFocusEvent; + + // keep track of a window while it is being lowered + nsCOMPtr<nsPIDOMWindowOuter> mWindowBeingLowered; + + // synchronized actions cannot be interrupted with events, so queue these up + // and fire them later. + nsTArray<nsDelayedBlurOrFocusEvent> 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<uint64_t> mPendingActiveBrowsingContextActions; + + // Like mPendingActiveBrowsingContextActions but for the focused + // browsing context. + nsTArray<uint64_t> 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<nsFocusManager> sInstance; +}; + +nsresult NS_NewFocusManager(nsIFocusManager** aResult); + +#endif |