diff options
Diffstat (limited to 'dom/base/nsFocusManager.cpp')
-rw-r--r-- | dom/base/nsFocusManager.cpp | 3659 |
1 files changed, 3659 insertions, 0 deletions
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp new file mode 100644 index 000000000..fb350fa12 --- /dev/null +++ b/dom/base/nsFocusManager.cpp @@ -0,0 +1,3659 @@ +/* -*- 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/. */ + +#include "mozilla/dom/TabParent.h" + +#include "nsFocusManager.h" + +#include "AccessibleCaretEventHub.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "nsIEditor.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMElement.h" +#include "nsIDOMDocument.h" +#include "nsIDOMRange.h" +#include "nsIHTMLDocument.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIFormControl.h" +#include "nsLayoutUtils.h" +#include "nsIPresShell.h" +#include "nsFrameTraversal.h" +#include "nsIWebNavigation.h" +#include "nsCaret.h" +#include "nsIBaseWindow.h" +#include "nsIXULWindow.h" +#include "nsViewManager.h" +#include "nsFrameSelection.h" +#include "mozilla/dom/Selection.h" +#include "nsXULPopupManager.h" +#include "nsMenuPopupFrame.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIPrincipal.h" +#include "nsIObserverService.h" +#include "nsIObjectFrame.h" +#include "nsBindingManager.h" +#include "nsStyleCoord.h" +#include "TabChild.h" +#include "nsFrameLoader.h" +#include "nsNumberControlFrame.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Unused.h" +#include <algorithm> + +#ifdef MOZ_XUL +#include "nsIDOMXULTextboxElement.h" +#include "nsIDOMXULMenuListElement.h" +#endif + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +#ifndef XP_MACOSX +#include "nsIScriptError.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::widget; + +// Two types of focus pr logging are available: +// 'Focus' for normal focus manager calls +// 'FocusNavigation' for tab and document navigation +LazyLogModule gFocusLog("Focus"); +LazyLogModule gFocusNavigationLog("FocusNavigation"); + +#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args) +#define LOGFOCUSNAVIGATION(args) MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args) + +#define LOGTAG(log, format, content) \ + if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \ + nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \ + if (content) { \ + content->NodeInfo()->NameAtom()->ToUTF8String(tag); \ + } \ + MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \ + } + +#define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content) +#define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content) + +struct nsDelayedBlurOrFocusEvent +{ + nsDelayedBlurOrFocusEvent(EventMessage aEventMessage, + nsIPresShell* aPresShell, + nsIDocument* aDocument, + EventTarget* aTarget, + EventTarget* aRelatedTarget) + : mPresShell(aPresShell) + , mDocument(aDocument) + , mTarget(aTarget) + , mEventMessage(aEventMessage) + , mRelatedTarget(aRelatedTarget) + { + } + + nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther) + : mPresShell(aOther.mPresShell) + , mDocument(aOther.mDocument) + , mTarget(aOther.mTarget) + , mEventMessage(aOther.mEventMessage) + { + } + + nsCOMPtr<nsIPresShell> mPresShell; + nsCOMPtr<nsIDocument> mDocument; + nsCOMPtr<EventTarget> mTarget; + EventMessage mEventMessage; + nsCOMPtr<EventTarget> mRelatedTarget; +}; + +inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField) +{ + aField.mPresShell = nullptr; + aField.mDocument = nullptr; + aField.mTarget = nullptr; + aField.mRelatedTarget = nullptr; +} + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsDelayedBlurOrFocusEvent& aField, + const char* aName, + uint32_t aFlags = 0) +{ + CycleCollectionNoteChild(aCallback, aField.mPresShell.get(), aName, aFlags); + CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags); + CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags); + CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName, aFlags); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager) + NS_INTERFACE_MAP_ENTRY(nsIFocusManager) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager) + +NS_IMPL_CYCLE_COLLECTION(nsFocusManager, + mActiveWindow, + mFocusedWindow, + mFocusedContent, + mFirstBlurEvent, + mFirstFocusEvent, + mWindowBeingLowered, + mDelayedBlurFocusEvents, + mMouseButtonEventHandlingDocument) + +nsFocusManager* nsFocusManager::sInstance = nullptr; +bool nsFocusManager::sMouseFocusesFormControl = false; +bool nsFocusManager::sTestMode = false; + +static const char* kObservedPrefs[] = { + "accessibility.browsewithcaret", + "accessibility.tabfocus_applies_to_xul", + "accessibility.mouse_focuses_formcontrol", + "focusmanager.testmode", + nullptr +}; + +nsFocusManager::nsFocusManager() +{ } + +nsFocusManager::~nsFocusManager() +{ + Preferences::RemoveObservers(this, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + } +} + +// static +nsresult +nsFocusManager::Init() +{ + nsFocusManager* fm = new nsFocusManager(); + NS_ADDREF(fm); + sInstance = fm; + + nsIContent::sTabFocusModelAppliesToXUL = + Preferences::GetBool("accessibility.tabfocus_applies_to_xul", + nsIContent::sTabFocusModelAppliesToXUL); + + sMouseFocusesFormControl = + Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false); + + sTestMode = Preferences::GetBool("focusmanager.testmode", false); + + Preferences::AddWeakObservers(fm, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(fm, "xpcom-shutdown", true); + } + + return NS_OK; +} + +// static +void +nsFocusManager::Shutdown() +{ + NS_IF_RELEASE(sInstance); +} + +NS_IMETHODIMP +nsFocusManager::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsDependentString data(aData); + if (data.EqualsLiteral("accessibility.browsewithcaret")) { + UpdateCaretForCaretBrowsingMode(); + } + else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) { + nsIContent::sTabFocusModelAppliesToXUL = + Preferences::GetBool("accessibility.tabfocus_applies_to_xul", + nsIContent::sTabFocusModelAppliesToXUL); + } + else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) { + sMouseFocusesFormControl = + Preferences::GetBool("accessibility.mouse_focuses_formcontrol", + false); + } + else if (data.EqualsLiteral("focusmanager.testmode")) { + sTestMode = Preferences::GetBool("focusmanager.testmode", false); + } + } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { + mActiveWindow = nullptr; + mFocusedWindow = nullptr; + mFocusedContent = nullptr; + mFirstBlurEvent = nullptr; + mFirstFocusEvent = nullptr; + mWindowBeingLowered = nullptr; + mDelayedBlurFocusEvents.Clear(); + mMouseButtonEventHandlingDocument = nullptr; + } + + return NS_OK; +} + +// given a frame content node, retrieve the nsIDOMWindow displayed in it +static nsPIDOMWindowOuter* +GetContentWindow(nsIContent* aContent) +{ + nsIDocument* doc = aContent->GetComposedDoc(); + if (doc) { + nsIDocument* subdoc = doc->GetSubDocumentFor(aContent); + if (subdoc) + return subdoc->GetWindow(); + } + + return nullptr; +} + +// get the current window for the given content node +static nsPIDOMWindowOuter* +GetCurrentWindow(nsIContent* aContent) +{ + nsIDocument* doc = aContent->GetComposedDoc(); + return doc ? doc->GetWindow() : nullptr; +} + +// static +nsIContent* +nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep, + nsPIDOMWindowOuter** aFocusedWindow) +{ + NS_ENSURE_TRUE(aWindow, nullptr); + + *aFocusedWindow = nullptr; + + nsIContent* currentContent = nullptr; + nsPIDOMWindowOuter* window = aWindow; + while (window) { + *aFocusedWindow = window; + currentContent = window->GetFocusedNode(); + if (!currentContent || !aDeep) + break; + + window = GetContentWindow(currentContent); + } + + NS_IF_ADDREF(*aFocusedWindow); + + return currentContent; +} + +// static +nsIContent* +nsFocusManager::GetRedirectedFocus(nsIContent* aContent) +{ + // For input number, redirect focus to our anonymous text control. + if (aContent->IsHTMLElement(nsGkAtoms::input)) { + bool typeIsNumber = + static_cast<dom::HTMLInputElement*>(aContent)->GetType() == + NS_FORM_INPUT_NUMBER; + + if (typeIsNumber) { + nsNumberControlFrame* numberControlFrame = + do_QueryFrame(aContent->GetPrimaryFrame()); + + if (numberControlFrame) { + HTMLInputElement* textControl = + numberControlFrame->GetAnonTextControl(); + return static_cast<nsIContent*>(textControl); + } + } + } + +#ifdef MOZ_XUL + if (aContent->IsXULElement()) { + nsCOMPtr<nsIDOMNode> inputField; + + nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent); + if (textbox) { + textbox->GetInputField(getter_AddRefs(inputField)); + } + else { + nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent); + if (menulist) { + menulist->GetInputField(getter_AddRefs(inputField)); + } + else if (aContent->IsXULElement(nsGkAtoms::scale)) { + nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc(); + if (!doc) + return nullptr; + + nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent); + if (children) { + nsIContent* child = children->Item(0); + if (child && child->IsXULElement(nsGkAtoms::slider)) + return child; + } + } + } + + if (inputField) { + nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField); + return retval; + } + } +#endif + + return nullptr; +} + +// static +InputContextAction::Cause +nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags) +{ + if (aFlags & nsIFocusManager::FLAG_BYTOUCH) { + return InputContextAction::CAUSE_TOUCH; + } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { + return InputContextAction::CAUSE_MOUSE; + } else if (aFlags & nsIFocusManager::FLAG_BYKEY) { + return InputContextAction::CAUSE_KEY; + } + return InputContextAction::CAUSE_UNKNOWN; +} + +NS_IMETHODIMP +nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) +{ + NS_IF_ADDREF(*aWindow = mActiveWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_STATE(aWindow); + + // only top-level windows can be made active + nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aWindow); + NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG); + + RaiseWindow(piWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) +{ + NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow); + return NS_OK; +} + +NS_IMETHODIMP nsFocusManager::SetFocusedWindow(mozIDOMWindowProxy* aWindowToFocus) +{ + LOGFOCUS(("<<SetFocusedWindow begin>>")); + + nsCOMPtr<nsPIDOMWindowOuter> windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus); + NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); + + windowToFocus = windowToFocus->GetOuterWindow(); + + nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal(); + if (frameElement) { + // pass false for aFocusChanged so that the caret does not get updated + // and scrolling does not occur. + SetFocusInner(frameElement, 0, false, true); + } + else { + // this is a top-level window. If the window has a child frame focused, + // clear the focus. Otherwise, focus should already be in this frame, or + // already cleared. This ensures that focus will be in this frame and not + // in a child. + nsIContent* content = windowToFocus->GetFocusedNode(); + if (content) { + if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content)) + ClearFocus(windowToFocus); + } + } + + nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot(); + if (rootWindow) + RaiseWindow(rootWindow); + + LOGFOCUS(("<<SetFocusedWindow end>>")); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement) +{ + if (mFocusedContent) + CallQueryInterface(mFocusedContent, aFocusedElement); + else + *aFocusedElement = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, uint32_t* aLastFocusMethod) +{ + // the focus method is stored on the inner window + nsCOMPtr<nsPIDOMWindowOuter> window; + if (aWindow) { + window = nsPIDOMWindowOuter::From(aWindow); + } + if (!window) + window = mFocusedWindow; + + *aLastFocusMethod = window ? window->GetFocusMethod() : 0; + + NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod, + "invalid focus method"); + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags) +{ + LOGFOCUS(("<<SetFocus begin>>")); + + nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement); + NS_ENSURE_ARG(newFocus); + + SetFocusInner(newFocus, aFlags, true, true); + + LOGFOCUS(("<<SetFocus end>>")); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags, + bool* aIsFocusable) +{ + NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG); + + nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement); + + *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, nsIDOMElement* aStartElement, + uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement) +{ + *aElement = nullptr; + + LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags)); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) { + nsIDocument* doc = mFocusedWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Focused Window: %p %s", + mFocusedWindow.get(), + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + + LOGCONTENT(" Current Focus: %s", mFocusedContent.get()); + + // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of + // the other focus methods is already set, or we're just moving to the root + // or caret position. + if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET && + (aFlags & FOCUSMETHOD_MASK) == 0) { + aFlags |= FLAG_BYMOVEFOCUS; + } + + nsCOMPtr<nsPIDOMWindowOuter> window; + nsCOMPtr<nsIContent> startContent; + if (aStartElement) { + startContent = do_QueryInterface(aStartElement); + NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG); + + window = GetCurrentWindow(startContent); + } + else { + window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + } + + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME; + nsCOMPtr<nsIContent> newFocus; + nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal, + getter_AddRefs(newFocus)); + if (rv == NS_SUCCESS_DOM_NO_OPERATION) { + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + + LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get()); + + if (newFocus) { + // for caret movement, pass false for the aFocusChanged argument, + // otherwise the caret will end up moving to the focus position. This + // would be a problem because the caret would move to the beginning of the + // focused link making it impossible to navigate the caret over a link. + SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET, true); + CallQueryInterface(newFocus, aElement); + } + else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) { + // no content was found, so clear the focus for these two types. + ClearFocus(window); + } + + LOGFOCUS(("<<MoveFocus end>>")); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) +{ + LOGFOCUS(("<<ClearFocus begin>>")); + + // if the window to clear is the focused window or an ancestor of the + // focused window, then blur the existing focused content. Otherwise, the + // focus is somewhere else so just update the current node. + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + if (IsSameOrAncestor(window, mFocusedWindow)) { + bool isAncestor = (window != mFocusedWindow); + if (Blur(window, nullptr, isAncestor, true)) { + // if we are clearing the focus on an ancestor of the focused window, + // the ancestor will become the new focused window, so focus it + if (isAncestor) + Focus(window, nullptr, 0, true, false, false, true); + } + } + else { + window->SetFocusedNode(nullptr); + } + + LOGFOCUS(("<<ClearFocus end>>")); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow, + bool aDeep, + mozIDOMWindowProxy** aFocusedWindow, + nsIDOMElement** aElement) +{ + *aElement = nullptr; + if (aFocusedWindow) + *aFocusedWindow = nullptr; + + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + nsCOMPtr<nsIContent> focusedContent = + GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow)); + if (focusedContent) + CallQueryInterface(focusedContent, aElement); + + if (aFocusedWindow) + NS_IF_ADDREF(*aFocusedWindow = focusedWindow); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) +{ + nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav); + if (dsti) { + if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + // don't move the caret for editable documents + bool isEditable; + docShell->GetEditable(&isEditable); + if (isEditable) + return NS_OK; + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + nsCOMPtr<nsIContent> content = window->GetFocusedNode(); + if (content) + MoveCaretToFocus(presShell, content); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { + LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); + nsIDocument* doc = window->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Raised Window: %p %s", aWindow, + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + if (mActiveWindow) { + doc = mActiveWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + } + + if (mActiveWindow == window) { + // The window is already active, so there is no need to focus anything, + // but make sure that the right widget is focused. This is a special case + // for Windows because when restoring a minimized window, a second + // activation will occur and the top-level widget could be focused instead + // of the child we want. We solve this by calling SetFocus to ensure that + // what the focus manager thinks should be the current widget is actually + // focused. + EnsureCurrentWidgetFocused(); + return NS_OK; + } + + // lower the existing window, if any. This shouldn't happen usually. + if (mActiveWindow) + WindowLowered(mActiveWindow); + + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell(); + // If there's no docShellAsItem, this window must have been closed, + // in that case there is no tree owner. + NS_ENSURE_TRUE(docShellAsItem, NS_OK); + + // set this as the active window + mActiveWindow = window; + + // ensure that the window is enabled and visible + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner); + if (baseWindow) { + bool isEnabled = true; + if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) { + return NS_ERROR_FAILURE; + } + + if (!sTestMode) { + baseWindow->SetVisibility(true); + } + } + + // If this is a parent or single process window, send the activate event. + // Events for child process windows will be sent when ParentActivated + // is called. + if (XRE_IsParentProcess()) { + ActivateOrDeactivate(window, true); + } + + // retrieve the last focused element within the window that was raised + nsCOMPtr<nsPIDOMWindowOuter> currentWindow; + nsCOMPtr<nsIContent> currentFocus = + GetFocusedDescendant(window, true, getter_AddRefs(currentWindow)); + + NS_ASSERTION(currentWindow, "window raised with no window current"); + if (!currentWindow) + return NS_OK; + + // If there is no nsIXULWindow, then this is an embedded or child process window. + // Pass false for aWindowRaised so that commands get updated. + nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow)); + Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { + LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); + nsIDocument* doc = window->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Lowered Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + if (mActiveWindow) { + doc = mActiveWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Active Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + } + + if (mActiveWindow != window) + return NS_OK; + + // clear the mouse capture as the active window has changed + nsIPresShell::SetCapturingContent(nullptr, 0); + + // In addition, reset the drag state to ensure that we are no longer in + // drag-select mode. + if (mFocusedWindow) { + nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell(); + if (docShell) { + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (presShell) { + RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection(); + frameSelection->SetDragState(false); + } + } + } + + // If this is a parent or single process window, send the deactivate event. + // Events for child process windows will be sent when ParentActivated + // is called. + if (XRE_IsParentProcess()) { + ActivateOrDeactivate(window, false); + } + + // keep track of the window being lowered, so that attempts to raise the + // window can be prevented until we return. Otherwise, focus can get into + // an unusual state. + mWindowBeingLowered = mActiveWindow; + mActiveWindow = nullptr; + + if (mFocusedWindow) + Blur(nullptr, nullptr, true, true); + + mWindowBeingLowered = nullptr; + + return NS_OK; +} + +nsresult +nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent) +{ + NS_ENSURE_ARG(aDocument); + NS_ENSURE_ARG(aContent); + + nsPIDOMWindowOuter *window = aDocument->GetWindow(); + if (!window) + return NS_OK; + + // if the content is currently focused in the window, or is an ancestor + // of the currently focused element, reset the focus within that window. + nsIContent* content = window->GetFocusedNode(); + if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) { + bool shouldShowFocusRing = window->ShouldShowFocusRing(); + window->SetFocusedNode(nullptr); + + // if this window is currently focused, clear the global focused + // element as well, but don't fire any events. + if (window == mFocusedWindow) { + mFocusedContent = nullptr; + } else { + // Check if the node that was focused is an iframe or similar by looking + // if it has a subdocument. This would indicate that this focused iframe + // and its descendants will be going away. We will need to move the + // focus somewhere else, so just clear the focus in the toplevel window + // so that no element is focused. + nsIDocument* subdoc = aDocument->GetSubDocumentFor(content); + if (subdoc) { + nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell(); + if (docShell) { + nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow(); + if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) { + ClearFocus(mActiveWindow); + } + } + } + } + + // Notify the editor in case we removed its ancestor limiter. + if (content->IsEditable()) { + nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell(); + if (docShell) { + nsCOMPtr<nsIEditor> editor; + docShell->GetEditor(getter_AddRefs(editor)); + if (editor) { + nsCOMPtr<nsISelection> s; + editor->GetSelection(getter_AddRefs(s)); + nsCOMPtr<nsISelectionPrivate> selection = do_QueryInterface(s); + if (selection) { + nsCOMPtr<nsIContent> limiter; + selection->GetAncestorLimiter(getter_AddRefs(limiter)); + if (limiter == content) { + editor->FinalizeSelection(); + } + } + } + } + } + + NotifyFocusStateChange(content, shouldShowFocusRing, false); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus) +{ + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { + LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get())); + nsIDocument* doc = window->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS(("Shown Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + + if (mFocusedWindow) { + doc = mFocusedWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Focused Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + } + + if (nsIDocShell* docShell = window->GetDocShell()) { + if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) { + bool active = static_cast<TabChild*>(child.get())->ParentIsActive(); + ActivateOrDeactivate(window, active); + } + } + + if (mFocusedWindow != window) + return NS_OK; + + if (aNeedsFocus) { + nsCOMPtr<nsPIDOMWindowOuter> currentWindow; + nsCOMPtr<nsIContent> currentFocus = + GetFocusedDescendant(window, true, getter_AddRefs(currentWindow)); + if (currentWindow) + Focus(currentWindow, currentFocus, 0, true, false, false, true); + } + else { + // Sometimes, an element in a window can be focused before the window is + // visible, which would mean that the widget may not be properly focused. + // When the window becomes visible, make sure the right widget is focused. + EnsureCurrentWidgetFocused(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow) +{ + // if there is no window or it is not the same or an ancestor of the + // currently focused window, just return, as the current focus will not + // be affected. + + NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { + LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get())); + nsAutoCString spec; + nsIDocument* doc = window->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Hide Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + + if (mFocusedWindow) { + doc = mFocusedWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Focused Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + + if (mActiveWindow) { + doc = mActiveWindow->GetExtantDoc(); + if (doc && doc->GetDocumentURI()) { + LOGFOCUS((" Active Window: %s", + doc->GetDocumentURI()->GetSpecOrDefault().get())); + } + } + } + + if (!IsSameOrAncestor(window, mFocusedWindow)) + return NS_OK; + + // at this point, we know that the window being hidden is either the focused + // window, or an ancestor of the focused window. Either way, the focus is no + // longer valid, so it needs to be updated. + + nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget(); + + nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell(); + nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell(); + + if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) { + NotifyFocusStateChange(oldFocusedContent, + mFocusedWindow->ShouldShowFocusRing(), + false); + window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + + if (presShell) { + SendFocusOrBlurEvent(eBlur, presShell, + oldFocusedContent->GetComposedDoc(), + oldFocusedContent, 1, false); + } + } + + nsPresContext* focusedPresContext = + presShell ? presShell->GetPresContext() : nullptr; + IMEStateManager::OnChangeFocus(focusedPresContext, nullptr, + GetFocusMoveActionCause(0)); + if (presShell) { + SetCaretVisible(presShell, false, nullptr); + } + + // if the docshell being hidden is being destroyed, then we want to move + // focus somewhere else. Call ClearFocus on the toplevel window, which + // will have the effect of clearing the focus and moving the focused window + // to the toplevel window. But if the window isn't being destroyed, we are + // likely just loading a new document in it, so we want to maintain the + // focused window so that the new document gets properly focused. + nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell(); + bool beingDestroyed = !docShellBeingHidden; + if (docShellBeingHidden) { + docShellBeingHidden->IsBeingDestroyed(&beingDestroyed); + } + if (beingDestroyed) { + // There is usually no need to do anything if a toplevel window is going + // away, as we assume that WindowLowered will be called. However, this may + // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause + // a leak. So if the active window is being destroyed, call WindowLowered + // directly. + NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected"); + if (mActiveWindow == mFocusedWindow || mActiveWindow == window) + WindowLowered(mActiveWindow); + else + ClearFocus(mActiveWindow); + return NS_OK; + } + + // if the window being hidden is an ancestor of the focused window, adjust + // the focused window so that it points to the one being hidden. This + // ensures that the focused window isn't in a chain of frames that doesn't + // exist any more. + if (window != mFocusedWindow) { + nsCOMPtr<nsIDocShellTreeItem> dsti = + mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr; + if (dsti) { + nsCOMPtr<nsIDocShellTreeItem> parentDsti; + dsti->GetParent(getter_AddRefs(parentDsti)); + if (parentDsti) { + if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parentDsti->GetWindow()) + parentWindow->SetFocusedNode(nullptr); + } + } + + SetFocusedWindowInternal(window); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::FireDelayedEvents(nsIDocument* aDocument) +{ + NS_ENSURE_ARG(aDocument); + + // fire any delayed focus and blur events in the same order that they were added + for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) { + if (mDelayedBlurFocusEvents[i].mDocument == aDocument) { + if (!aDocument->GetInnerWindow() || + !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) { + // If the document was navigated away from or is defunct, don't bother + // firing events on it. Note the symmetry between this condition and + // the similar one in nsDocument.cpp:FireOrClearDelayedEvents. + mDelayedBlurFocusEvents.RemoveElementAt(i); + --i; + } else if (!aDocument->EventHandlingSuppressed()) { + EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage; + nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget; + nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell; + nsCOMPtr<EventTarget> relatedTarget = mDelayedBlurFocusEvents[i].mRelatedTarget; + mDelayedBlurFocusEvents.RemoveElementAt(i); + SendFocusOrBlurEvent(message, presShell, aDocument, target, 0, + false, false, relatedTarget); + --i; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::FocusPlugin(nsIContent* aContent) +{ + NS_ENSURE_ARG(aContent); + SetFocusInner(aContent, 0, true, false); + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive) +{ + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); + + ActivateOrDeactivate(window, aActive); + return NS_OK; +} + +/* static */ +void +nsFocusManager::NotifyFocusStateChange(nsIContent* aContent, + bool aWindowShouldShowFocusRing, + bool aGettingFocus) +{ + if (!aContent->IsElement()) { + return; + } + EventStates eventState = NS_EVENT_STATE_FOCUS; + if (aWindowShouldShowFocusRing) { + eventState |= NS_EVENT_STATE_FOCUSRING; + } + + if (aGettingFocus) { + aContent->AsElement()->AddStates(eventState); + } else { + aContent->AsElement()->RemoveStates(eventState); + } + + for (Element* element = aContent->AsElement(); element; + element = element->GetParentElementCrossingShadowRoot()) { + if (aGettingFocus) { + element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN); + } else { + element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN); + } + } +} + +// static +void +nsFocusManager::EnsureCurrentWidgetFocused() +{ + if (!mFocusedWindow || sTestMode) + return; + + // get the main child widget for the focused window and ensure that the + // platform knows that this widget is focused. + nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell(); + if (docShell) { + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (presShell) { + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (widget) + widget->SetFocus(false); + } + } + } +} + +bool +ActivateOrDeactivateChild(TabParent* aParent, void* aArg) +{ + bool active = static_cast<bool>(aArg); + Unused << aParent->SendParentActivated(active); + return false; +} + +void +nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive) +{ + if (!aWindow) { + return; + } + + // Inform the DOM window that it has activated or deactivated, so that + // the active attribute is updated on the window. + aWindow->ActivateOrDeactivate(aActive); + + // Send the activate event. + if (aWindow->GetExtantDoc()) { + nsContentUtils::DispatchEventOnlyToChrome(aWindow->GetExtantDoc(), + aWindow->GetCurrentInnerWindow(), + aActive ? + NS_LITERAL_STRING("activate") : + NS_LITERAL_STRING("deactivate"), + true, true, nullptr); + } + + // Look for any remote child frames, iterate over them and send the activation notification. + nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild, + (void *)aActive); +} + +void +nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags, + bool aFocusChanged, bool aAdjustWidget) +{ + // if the element is not focusable, just return and leave the focus as is + nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags); + if (!contentToFocus) + return; + + // check if the element to focus is a frame (iframe) containing a child + // document. Frames are never directly focused; instead focusing a frame + // means focus what is inside the frame. To do this, the descendant content + // within the frame is retrieved and that will be focused instead. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(contentToFocus); + if (subWindow) { + contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow)); + // since a window is being refocused, clear aFocusChanged so that the + // caret position isn't updated. + aFocusChanged = false; + } + + // unless it was set above, retrieve the window for the element to focus + if (!newWindow) + newWindow = GetCurrentWindow(contentToFocus); + + // if the element is already focused, just return. Note that this happens + // after the frame check above so that we compare the element that will be + // focused rather than the frame it is in. + if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent)) + return; + + // don't allow focus to be placed in docshells or descendants of docshells + // that are being destroyed. Also, ensure that the page hasn't been + // unloaded. The prevents content from being refocused during an unload event. + nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell(); + nsCOMPtr<nsIDocShell> docShell = newDocShell; + while (docShell) { + bool inUnload; + docShell->GetIsInUnload(&inUnload); + if (inUnload) + return; + + bool beingDestroyed; + docShell->IsBeingDestroyed(&beingDestroyed); + if (beingDestroyed) + return; + + nsCOMPtr<nsIDocShellTreeItem> parentDsti; + docShell->GetParent(getter_AddRefs(parentDsti)); + docShell = do_QueryInterface(parentDsti); + } + + // if the new element is in the same window as the currently focused element + bool isElementInFocusedWindow = (mFocusedWindow == newWindow); + + if (!isElementInFocusedWindow && mFocusedWindow && newWindow && + nsContentUtils::IsHandlingKeyBoardEvent()) { + nsCOMPtr<nsIScriptObjectPrincipal> focused = + do_QueryInterface(mFocusedWindow); + nsCOMPtr<nsIScriptObjectPrincipal> newFocus = + do_QueryInterface(newWindow); + nsIPrincipal* focusedPrincipal = focused->GetPrincipal(); + nsIPrincipal* newPrincipal = newFocus->GetPrincipal(); + if (!focusedPrincipal || !newPrincipal) { + return; + } + bool subsumes = false; + focusedPrincipal->Subsumes(newPrincipal, &subsumes); + if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + NS_WARNING("Not allowed to focus the new window!"); + return; + } + } + + // to check if the new element is in the active window, compare the + // new root docshell for the new element with the active window's docshell. + bool isElementInActiveWindow = false; + + nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell(); + nsCOMPtr<nsPIDOMWindowOuter> newRootWindow; + if (dsti) { + nsCOMPtr<nsIDocShellTreeItem> root; + dsti->GetRootTreeItem(getter_AddRefs(root)); + newRootWindow = root ? root->GetWindow() : nullptr; + + isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow); + } + + // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX + // system. We don't control event dispatch to windowed plugins on non-MacOSX, + // so we can't display the "Press ESC to leave fullscreen mode" warning on + // key input if a windowed plugin is focused, so just exit fullscreen + // to guard against phishing. +#ifndef XP_MACOSX + if (contentToFocus && + nsContentUtils:: + GetRootDocument(contentToFocus->OwnerDoc())->GetFullscreenElement() && + nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), + contentToFocus->OwnerDoc(), + nsContentUtils::eDOM_PROPERTIES, + "FocusedWindowedPluginWhileFullscreen"); + nsIDocument::AsyncExitFullscreen(contentToFocus->OwnerDoc()); + } +#endif + + // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be + // shifted away from the current element if the new shell to focus is + // the same or an ancestor shell of the currently focused shell. + bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) || + IsSameOrAncestor(newWindow, mFocusedWindow); + + // if the element is in the active window, frame switching is allowed and + // the content is in a visible window, fire blur and focus events. + bool sendFocusEvent = + isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow); + + // When the following conditions are true: + // * an element has focus + // * isn't called by trusted event (i.e., called by untrusted event or by js) + // * the focus is moved to another document's element + // we need to check the permission. + if (sendFocusEvent && mFocusedContent && !nsContentUtils::LegacyIsCallerNativeCode() && + mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) { + // If the caller cannot access the current focused node, the caller should + // not be able to steal focus from it. E.g., When the current focused node + // is in chrome, any web contents should not be able to steal the focus. + nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent)); + sendFocusEvent = nsContentUtils::CanCallerAccess(domNode); + if (!sendFocusEvent && mMouseButtonEventHandlingDocument) { + // However, while mouse button event is handling, the handling document's + // script should be able to steal focus. + domNode = do_QueryInterface(mMouseButtonEventHandlingDocument); + sendFocusEvent = nsContentUtils::CanCallerAccess(domNode); + } + } + + LOGCONTENT("Shift Focus: %s", contentToFocus.get()); + LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p", + aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get())); + LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d", + isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent)); + + if (sendFocusEvent) { + nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent; + // return if blurring fails or the focus changes during the blur + if (mFocusedWindow) { + // if the focus is being moved to another element in the same document, + // or to a descendant, pass the existing window to Blur so that the + // current node in the existing window is cleared. If moving to a + // window elsewhere, we want to maintain the current node in the + // window but still blur it. + bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow); + // find the common ancestor of the currently focused window and the new + // window. The ancestor will need to have its currently focused node + // cleared once the document has been blurred. Otherwise, we'll be in a + // state where a document is blurred yet the chain of windows above it + // still points to that document. + // For instance, in the following frame tree: + // A + // B C + // D + // D is focused and we want to focus C. Once D has been blurred, we need + // to clear out the focus in A, otherwise A would still maintain that B + // was focused, and B that D was focused. + nsCOMPtr<nsPIDOMWindowOuter> commonAncestor; + if (!isElementInFocusedWindow) + commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow); + + if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr, + commonAncestor, !isElementInFocusedWindow, aAdjustWidget, + contentToFocus)) + return; + } + + Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow, + aFocusChanged, false, aAdjustWidget, oldFocusedContent); + } + else { + // otherwise, for inactive windows and when the caller cannot steal the + // focus, update the node in the window, and raise the window if desired. + if (allowFrameSwitch) + AdjustWindowFocus(newWindow, true); + + // set the focus node and method as needed + uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK : + newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING); + newWindow->SetFocusedNode(contentToFocus, focusMethod); + if (aFocusChanged) { + nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell(); + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (presShell && presShell->DidInitialize()) + ScrollIntoView(presShell, contentToFocus, aFlags); + } + + // update the commands even when inactive so that the attributes for that + // window are up to date. + if (allowFrameSwitch) + newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + + if (aFlags & FLAG_RAISE) + RaiseWindow(newRootWindow); + } +} + +bool +nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, + nsPIDOMWindowOuter* aWindow) +{ + if (!aWindow || !aPossibleAncestor) { + return false; + } + + nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell(); + nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell(); + while (dsti) { + if (dsti == ancestordsti) + return true; + nsCOMPtr<nsIDocShellTreeItem> parentDsti; + dsti->GetParent(getter_AddRefs(parentDsti)); + dsti.swap(parentDsti); + } + + return false; +} + +already_AddRefed<nsPIDOMWindowOuter> +nsFocusManager::GetCommonAncestor(nsPIDOMWindowOuter* aWindow1, + nsPIDOMWindowOuter* aWindow2) +{ + NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr); + + nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell(); + NS_ENSURE_TRUE(dsti1, nullptr); + + nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell(); + NS_ENSURE_TRUE(dsti2, nullptr); + + AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2; + do { + parents1.AppendElement(dsti1); + nsCOMPtr<nsIDocShellTreeItem> parentDsti1; + dsti1->GetParent(getter_AddRefs(parentDsti1)); + dsti1.swap(parentDsti1); + } while (dsti1); + do { + parents2.AppendElement(dsti2); + nsCOMPtr<nsIDocShellTreeItem> parentDsti2; + dsti2->GetParent(getter_AddRefs(parentDsti2)); + dsti2.swap(parentDsti2); + } while (dsti2); + + uint32_t pos1 = parents1.Length(); + uint32_t pos2 = parents2.Length(); + nsIDocShellTreeItem* parent = nullptr; + uint32_t len; + for (len = std::min(pos1, pos2); len > 0; --len) { + nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1); + nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2); + if (child1 != child2) { + break; + } + parent = child1; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = parent ? parent->GetWindow() : nullptr; + return window.forget(); +} + +void +nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow, + bool aCheckPermission) +{ + bool isVisible = IsWindowVisible(aWindow); + + nsCOMPtr<nsPIDOMWindowOuter> window(aWindow); + while (window) { + // get the containing <iframe> or equivalent element so that it can be + // focused below. + nsCOMPtr<Element> frameElement = window->GetFrameElementInternal(); + + nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell(); + if (!dsti) + return; + nsCOMPtr<nsIDocShellTreeItem> parentDsti; + dsti->GetParent(getter_AddRefs(parentDsti)); + if (!parentDsti) { + return; + } + + window = parentDsti->GetWindow(); + if (window) { + // if the parent window is visible but aWindow was not, then we have + // likely moved up and out from a hidden tab to the browser window, or a + // similar such arrangement. Stop adjusting the current nodes. + if (IsWindowVisible(window) != isVisible) + break; + + // When aCheckPermission is true, we should check whether the caller can + // access the window or not. If it cannot access, we should stop the + // adjusting. + if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) { + break; + } + + window->SetFocusedNode(frameElement); + } + } +} + +bool +nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow) +{ + if (!aWindow || aWindow->IsFrozen()) + return false; + + // Check if the inner window is frozen as well. This can happen when a focus change + // occurs while restoring a previous page. + nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow(); + if (!innerWindow || innerWindow->IsFrozen()) + return false; + + nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell)); + if (!baseWin) + return false; + + bool visible = false; + baseWin->GetVisibility(&visible); + return visible; +} + +bool +nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) +{ + NS_PRECONDITION(aContent, "aContent must not be NULL"); + NS_PRECONDITION(aContent->IsInComposedDoc(), "aContent must be in a document"); + + // If aContent is in designMode, the root element is not focusable. + // NOTE: in designMode, most elements are not focusable, just the document is + // focusable. + // Also, if aContent is not editable but it isn't in designMode, it's not + // focusable. + // And in userfocusignored context nothing is focusable. + nsIDocument* doc = aContent->GetComposedDoc(); + NS_ASSERTION(doc, "aContent must have current document"); + return aContent == doc->GetRootElement() && + (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() || + nsContentUtils::IsUserFocusIgnored(aContent)); +} + +nsIContent* +nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags) +{ + if (!aContent) + return nullptr; + + // this is a special case for some XUL elements or input number, where an + // anonymous child is actually focusable and not the element itself. + nsCOMPtr<nsIContent> redirectedFocus = GetRedirectedFocus(aContent); + if (redirectedFocus) + return CheckIfFocusable(redirectedFocus, aFlags); + + nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc(); + // can't focus elements that are not in documents + if (!doc) { + LOGCONTENT("Cannot focus %s because content not in document", aContent) + return nullptr; + } + + // Make sure that our frames are up to date + doc->FlushPendingNotifications(Flush_Layout); + + nsIPresShell *shell = doc->GetShell(); + if (!shell) + return nullptr; + + // the root content can always be focused, + // except in userfocusignored context. + if (aContent == doc->GetRootElement()) + return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent; + + // cannot focus content in print preview mode. Only the root can be focused. + nsPresContext* presContext = shell->GetPresContext(); + if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) { + LOGCONTENT("Cannot focus %s while in print preview", aContent) + return nullptr; + } + + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (!frame) { + LOGCONTENT("Cannot focus %s as it has no frame", aContent) + return nullptr; + } + + if (aContent->IsHTMLElement(nsGkAtoms::area)) { + // HTML areas do not have their own frame, and the img frame we get from + // GetPrimaryFrame() is not relevant as to whether it is focusable or + // not, so we have to do all the relevant checks manually for them. + return frame->IsVisibleConsideringAncestors() && + aContent->IsFocusable() ? aContent : nullptr; + } + + // if this is a child frame content node, check if it is visible and + // call the content node's IsFocusable method instead of the frame's + // IsFocusable method. This skips checking the style system and ensures that + // offscreen browsers can still be focused. + nsIDocument* subdoc = doc->GetSubDocumentFor(aContent); + if (subdoc && IsWindowVisible(subdoc->GetWindow())) { + const nsStyleUserInterface* ui = frame->StyleUserInterface(); + int32_t tabIndex = (ui->mUserFocus == StyleUserFocus::Ignore || + ui->mUserFocus == StyleUserFocus::None) ? -1 : 0; + return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr; + } + + return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr; +} + +bool +nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear, + nsPIDOMWindowOuter* aAncestorWindowToFocus, + bool aIsLeavingDocument, + bool aAdjustWidgets, + nsIContent* aContentToFocus) +{ + LOGFOCUS(("<<Blur begin>>")); + + // hold a reference to the focused content, which may be null + nsCOMPtr<nsIContent> content = mFocusedContent; + if (content) { + if (!content->IsInComposedDoc()) { + mFocusedContent = nullptr; + return true; + } + if (content == mFirstBlurEvent) + return true; + } + + // hold a reference to the focused window + nsCOMPtr<nsPIDOMWindowOuter> window = mFocusedWindow; + if (!window) { + mFocusedContent = nullptr; + return true; + } + + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + if (!docShell) { + mFocusedContent = nullptr; + return true; + } + + // Keep a ref to presShell since dispatching the DOM event may cause + // the document to be destroyed. + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (!presShell) { + mFocusedContent = nullptr; + return true; + } + + bool clearFirstBlurEvent = false; + if (!mFirstBlurEvent) { + mFirstBlurEvent = content; + clearFirstBlurEvent = true; + } + + nsPresContext* focusedPresContext = + mActiveWindow ? presShell->GetPresContext() : nullptr; + IMEStateManager::OnChangeFocus(focusedPresContext, nullptr, + GetFocusMoveActionCause(0)); + + // now adjust the actual focus, by clearing the fields in the focus manager + // and in the window. + mFocusedContent = nullptr; + bool shouldShowFocusRing = window->ShouldShowFocusRing(); + if (aWindowToClear) + aWindowToClear->SetFocusedNode(nullptr); + + LOGCONTENT("Element %s has been blurred", content.get()); + + // Don't fire blur event on the root content which isn't editable. + bool sendBlurEvent = + content && content->IsInComposedDoc() && !IsNonFocusableRoot(content); + if (content) { + if (sendBlurEvent) { + NotifyFocusStateChange(content, shouldShowFocusRing, false); + } + + // if an object/plug-in/remote browser is being blurred, move the system focus + // to the parent window, otherwise events will still get fired at the plugin. + // But don't do this if we are blurring due to the window being lowered, + // otherwise, the parent window can get raised again. + if (mActiveWindow) { + nsIFrame* contentFrame = content->GetPrimaryFrame(); + nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame); + if (aAdjustWidgets && objectFrame && !sTestMode) { + if (XRE_IsContentProcess()) { + // set focus to the top level window via the chrome process. + nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild(); + if (tabChild) { + static_cast<TabChild*>(tabChild.get())->SendDispatchFocusToTopLevelWindow(); + } + } else { + // note that the presshell's widget is being retrieved here, not the one + // for the object frame. + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (widget) { + // set focus to the top level window but don't raise it. + widget->SetFocus(false); + } + } + } + } + } + + // if the object being blurred is a remote browser, deactivate remote content + if (TabParent* remote = TabParent::GetFrom(content)) { + remote->Deactivate(); + LOGFOCUS(("Remote browser deactivated")); + } + } + + bool result = true; + if (sendBlurEvent) { + // if there is an active window, update commands. If there isn't an active + // window, then this was a blur caused by the active window being lowered, + // so there is no need to update the commands + if (mActiveWindow) + window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + + SendFocusOrBlurEvent(eBlur, presShell, + content->GetComposedDoc(), content, 1, + false, false, aContentToFocus); + } + + // if we are leaving the document or the window was lowered, make the caret + // invisible. + if (aIsLeavingDocument || !mActiveWindow) { + SetCaretVisible(presShell, false, nullptr); + } + + RefPtr<AccessibleCaretEventHub> eventHub = presShell->GetAccessibleCaretEventHub(); + if (eventHub) { + eventHub->NotifyBlur(aIsLeavingDocument || !mActiveWindow); + } + + // at this point, it is expected that this window will be still be + // focused, but the focused content will be null, as it was cleared before + // the event. If this isn't the case, then something else was focused during + // the blur event above and we should just return. However, if + // aIsLeavingDocument is set, a new document is desired, so make sure to + // blur the document and window. + if (mFocusedWindow != window || + (mFocusedContent != nullptr && !aIsLeavingDocument)) { + result = false; + } + else if (aIsLeavingDocument) { + window->TakeFocus(false, 0); + + // clear the focus so that the ancestor frame hierarchy is in the correct + // state. Pass true because aAncestorWindowToFocus is thought to be + // focused at this point. + if (aAncestorWindowToFocus) + aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true); + + SetFocusedWindowInternal(nullptr); + mFocusedContent = nullptr; + + // pass 1 for the focus method when calling SendFocusOrBlurEvent just so + // that the check is made for suppressed documents. Check to ensure that + // the document isn't null in case someone closed it during the blur above + nsIDocument* doc = window->GetExtantDoc(); + if (doc) + SendFocusOrBlurEvent(eBlur, presShell, doc, doc, 1, false); + if (mFocusedWindow == nullptr) + SendFocusOrBlurEvent(eBlur, presShell, doc, + window->GetCurrentInnerWindow(), 1, false); + + // check if a different window was focused + result = (mFocusedWindow == nullptr && mActiveWindow); + } + else if (mActiveWindow) { + // Otherwise, the blur of the element without blurring the document + // occurred normally. Call UpdateCaret to redisplay the caret at the right + // location within the document. This is needed to ensure that the caret + // used for caret browsing is made visible again when an input field is + // blurred. + UpdateCaret(false, true, nullptr); + } + + if (clearFirstBlurEvent) + mFirstBlurEvent = nullptr; + + return result; +} + +void +nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow, + nsIContent* aContent, + uint32_t aFlags, + bool aIsNewDocument, + bool aFocusChanged, + bool aWindowRaised, + bool aAdjustWidgets, + nsIContent* aContentLostFocus) +{ + LOGFOCUS(("<<Focus begin>>")); + + if (!aWindow) + return; + + if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent)) + return; + + // Keep a reference to the presShell since dispatching the DOM event may + // cause the document to be destroyed. + nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); + if (!docShell) + return; + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (!presShell) + return; + + // If the focus actually changed, set the focus method (mouse, keyboard, etc). + // Otherwise, just get the current focus method and use that. This ensures + // that the method is set during the document and window focus events. + uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK : + aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING); + + if (!IsWindowVisible(aWindow)) { + // if the window isn't visible, for instance because it is a hidden tab, + // update the current focus and scroll it into view but don't do anything else + if (CheckIfFocusable(aContent, aFlags)) { + aWindow->SetFocusedNode(aContent, focusMethod); + if (aFocusChanged) + ScrollIntoView(presShell, aContent, aFlags); + } + return; + } + + bool clearFirstFocusEvent = false; + if (!mFirstFocusEvent) { + mFirstFocusEvent = aContent; + clearFirstFocusEvent = true; + } + + LOGCONTENT("Element %s has been focused", aContent); + + if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { + nsIDocument* docm = aWindow->GetExtantDoc(); + if (docm) { + LOGCONTENT(" from %s", docm->GetRootElement()); + } + LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]", + aIsNewDocument, aFocusChanged, aWindowRaised, aFlags)); + } + + if (aIsNewDocument) { + // if this is a new document, update the parent chain of frames so that + // focus can be traversed from the top level down to the newly focused + // window. + AdjustWindowFocus(aWindow, false); + } + + // indicate that the window has taken focus. + if (aWindow->TakeFocus(true, focusMethod)) + aIsNewDocument = true; + + SetFocusedWindowInternal(aWindow); + + // Update the system focus by focusing the root widget. But avoid this + // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its + // own widget and is either already focused or is about to be focused. + nsCOMPtr<nsIWidget> objectFrameWidget; + if (aContent) { + nsIFrame* contentFrame = aContent->GetPrimaryFrame(); + nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame); + if (objectFrame) + objectFrameWidget = objectFrame->GetWidget(); + } + if (aAdjustWidgets && !objectFrameWidget && !sTestMode) { + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (widget) + widget->SetFocus(false); + } + } + + // if switching to a new document, first fire the focus event on the + // document and then the window. + if (aIsNewDocument) { + nsIDocument* doc = aWindow->GetExtantDoc(); + // The focus change should be notified to IMEStateManager from here if + // the focused content is a designMode editor since any content won't + // receive focus event. + if (doc && doc->HasFlag(NODE_IS_EDITABLE)) { + IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr, + GetFocusMoveActionCause(aFlags)); + } + if (doc) + SendFocusOrBlurEvent(eFocus, presShell, doc, + doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised); + if (mFocusedWindow == aWindow && mFocusedContent == nullptr) + SendFocusOrBlurEvent(eFocus, presShell, doc, + aWindow->GetCurrentInnerWindow(), + aFlags & FOCUSMETHOD_MASK, aWindowRaised); + } + + // check to ensure that the element is still focusable, and that nothing + // else was focused during the events above. + if (CheckIfFocusable(aContent, aFlags) && + mFocusedWindow == aWindow && mFocusedContent == nullptr) { + mFocusedContent = aContent; + + nsIContent* focusedNode = aWindow->GetFocusedNode(); + bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent); + + aWindow->SetFocusedNode(aContent, focusMethod); + + bool sendFocusEvent = + aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent); + nsPresContext* presContext = presShell->GetPresContext(); + if (sendFocusEvent) { + // if the focused element changed, scroll it into view + if (aFocusChanged) + ScrollIntoView(presShell, aContent, aFlags); + + NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true); + + // if this is an object/plug-in/remote browser, focus its widget. Note that we might + // no longer be in the same document, due to the events we fired above when + // aIsNewDocument. + if (presShell->GetDocument() == aContent->GetComposedDoc()) { + if (aAdjustWidgets && objectFrameWidget && !sTestMode) + objectFrameWidget->SetFocus(false); + + // if the object being focused is a remote browser, activate remote content + if (TabParent* remote = TabParent::GetFrom(aContent)) { + remote->Activate(); + LOGFOCUS(("Remote browser activated")); + } + } + + IMEStateManager::OnChangeFocus(presContext, aContent, + GetFocusMoveActionCause(aFlags)); + + // as long as this focus wasn't because a window was raised, update the + // commands + // XXXndeakin P2 someone could adjust the focus during the update + if (!aWindowRaised) + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + + SendFocusOrBlurEvent(eFocus, presShell, + aContent->GetComposedDoc(), + aContent, aFlags & FOCUSMETHOD_MASK, + aWindowRaised, isRefocus, aContentLostFocus); + } else { + IMEStateManager::OnChangeFocus(presContext, nullptr, + GetFocusMoveActionCause(aFlags)); + if (!aWindowRaised) { + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + } + } + } + else { + // If the window focus event (fired above when aIsNewDocument) caused + // the plugin not to be focusable, update the system focus by focusing + // the root widget. + if (aAdjustWidgets && objectFrameWidget && + mFocusedWindow == aWindow && mFocusedContent == nullptr && + !sTestMode) { + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (widget) + widget->SetFocus(false); + } + } + + if (!mFocusedContent) { + // When there is no focused content, IMEStateManager needs to adjust IME + // enabled state with the document. + nsPresContext* presContext = presShell->GetPresContext(); + IMEStateManager::OnChangeFocus(presContext, nullptr, + GetFocusMoveActionCause(aFlags)); + } + + if (!aWindowRaised) + aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); + } + + // update the caret visibility and position to match the newly focused + // element. However, don't update the position if this was a focus due to a + // mouse click as the selection code would already have moved the caret as + // needed. If this is a different document than was focused before, also + // update the caret's visibility. If this is the same document, the caret + // visibility should be the same as before so there is no need to update it. + if (mFocusedContent == aContent) + UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument, + mFocusedContent); + + if (clearFirstFocusEvent) + mFirstFocusEvent = nullptr; +} + +class FocusBlurEvent : public Runnable +{ +public: + FocusBlurEvent(nsISupports* aTarget, EventMessage aEventMessage, + nsPresContext* aContext, bool aWindowRaised, + bool aIsRefocus, EventTarget* aRelatedTarget) + : mTarget(aTarget) + , mContext(aContext) + , mEventMessage(aEventMessage) + , mWindowRaised(aWindowRaised) + , mIsRefocus(aIsRefocus) + , mRelatedTarget(aRelatedTarget) + { + } + + NS_IMETHOD Run() override + { + InternalFocusEvent event(true, mEventMessage); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + event.mFromRaise = mWindowRaised; + event.mIsRefocus = mIsRefocus; + event.mRelatedTarget = mRelatedTarget; + return EventDispatcher::Dispatch(mTarget, mContext, &event); + } + + nsCOMPtr<nsISupports> mTarget; + RefPtr<nsPresContext> mContext; + EventMessage mEventMessage; + bool mWindowRaised; + bool mIsRefocus; + nsCOMPtr<EventTarget> mRelatedTarget; +}; + +class FocusInOutEvent : public Runnable +{ +public: + FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage, + nsPresContext* aContext, + nsPIDOMWindowOuter* aOriginalFocusedWindow, + nsIContent* aOriginalFocusedContent, + EventTarget* aRelatedTarget) + : mTarget(aTarget) + , mContext(aContext) + , mEventMessage(aEventMessage) + , mOriginalFocusedWindow(aOriginalFocusedWindow) + , mOriginalFocusedContent(aOriginalFocusedContent) + , mRelatedTarget(aRelatedTarget) + { + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ? + mOriginalFocusedWindow->GetFocusedNode() : + nullptr; + // Blink does not check that focus is the same after blur, but WebKit does. + // Opt to follow Blink's behavior (see bug 687787). + if (mEventMessage == eFocusOut || + originalWindowFocus == mOriginalFocusedContent) { + InternalFocusEvent event(true, mEventMessage); + event.mFlags.mBubbles = true; + event.mFlags.mCancelable = false; + event.mRelatedTarget = mRelatedTarget; + return EventDispatcher::Dispatch(mTarget, mContext, &event); + } + return NS_OK; + } + + nsCOMPtr<nsISupports> mTarget; + RefPtr<nsPresContext> mContext; + EventMessage mEventMessage; + nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow; + nsCOMPtr<nsIContent> mOriginalFocusedContent; + nsCOMPtr<EventTarget> mRelatedTarget; +}; + +static nsIDocument* +GetDocumentHelper(EventTarget* aTarget) +{ + nsCOMPtr<nsINode> node = do_QueryInterface(aTarget); + if (!node) { + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget); + return win ? win->GetExtantDoc() : nullptr; + } + + return node->OwnerDoc(); +} + +void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage, + nsIPresShell* aPresShell, + nsISupports* aTarget, + nsPIDOMWindowOuter* aCurrentFocusedWindow, + nsIContent* aCurrentFocusedContent, + EventTarget* aRelatedTarget) +{ + NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut, + "Wrong event type for SendFocusInOrOutEvent"); + + nsContentUtils::AddScriptRunner( + new FocusInOutEvent( + aTarget, + aEventMessage, + aPresShell->GetPresContext(), + aCurrentFocusedWindow, + aCurrentFocusedContent, + aRelatedTarget)); +} + +void +nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage, + nsIPresShell* aPresShell, + nsIDocument* aDocument, + nsISupports* aTarget, + uint32_t aFocusMethod, + bool aWindowRaised, + bool aIsRefocus, + EventTarget* aRelatedTarget) +{ + NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur, + "Wrong event type for SendFocusOrBlurEvent"); + + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget); + nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget); + nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget); + nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow; + nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget); + nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget); + nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ? + currentWindow->GetFocusedNode() : nullptr; + + // set aRelatedTarget to null if it's not in the same document as eventTarget + if (eventTargetDoc != relatedTargetDoc) { + aRelatedTarget = nullptr; + } + + bool dontDispatchEvent = + eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc); + + // for focus events, if this event was from a mouse or key and event + // handling on the document is suppressed, queue the event and fire it + // later. For blur events, a non-zero value would be set for aFocusMethod. + if (aFocusMethod && !dontDispatchEvent && + aDocument && aDocument->EventHandlingSuppressed()) { + // aFlags is always 0 when aWindowRaised is true so this won't be called + // on a window raise. + NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set"); + + for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) { + // if this event was already queued, remove it and append it to the end + if (mDelayedBlurFocusEvents[i - 1].mEventMessage == aEventMessage && + mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell && + mDelayedBlurFocusEvents[i - 1].mDocument == aDocument && + mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget && + mDelayedBlurFocusEvents[i - 1].mRelatedTarget == aRelatedTarget) { + mDelayedBlurFocusEvents.RemoveElementAt(i - 1); + } + } + + mDelayedBlurFocusEvents.AppendElement( + nsDelayedBlurOrFocusEvent(aEventMessage, aPresShell, + aDocument, eventTarget, aRelatedTarget)); + return; + } + +#ifdef ACCESSIBILITY + nsAccessibilityService* accService = GetAccService(); + if (accService) { + if (aEventMessage == eFocus) { + accService->NotifyOfDOMFocus(aTarget); + } else { + accService->NotifyOfDOMBlur(aTarget); + } + } +#endif + + if (!dontDispatchEvent) { + nsContentUtils::AddScriptRunner( + new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(), + aWindowRaised, aIsRefocus, aRelatedTarget)); + + // Check that the target is not a window or document before firing + // focusin/focusout. Other browsers do not fire focusin/focusout on window, + // despite being required in the spec, so follow their behavior. + // + // As for document, we should not even fire focus/blur, but until then, we + // need this check. targetDocument should be removed once bug 1228802 is + // resolved. + if (!targetWindow && !targetDocument) { + EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut; + SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget, + currentWindow, currentFocusedContent, aRelatedTarget); + } + } +} + +void +nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell, + nsIContent* aContent, + uint32_t aFlags) +{ + // if the noscroll flag isn't set, scroll the newly focused element into view + if (!(aFlags & FLAG_NOSCROLL)) + aPresShell->ScrollContentIntoView(aContent, + nsIPresShell::ScrollAxis( + nsIPresShell::SCROLL_MINIMUM, + nsIPresShell::SCROLL_IF_NOT_VISIBLE), + nsIPresShell::ScrollAxis( + nsIPresShell::SCROLL_MINIMUM, + nsIPresShell::SCROLL_IF_NOT_VISIBLE), + nsIPresShell::SCROLL_OVERFLOW_HIDDEN); +} + + +void +nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow) +{ + // don't raise windows that are already raised or are in the process of + // being lowered + if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered) + return; + + if (sTestMode) { + // In test mode, emulate the existing window being lowered and the new + // window being raised. + if (mActiveWindow) + WindowLowered(mActiveWindow); + WindowRaised(aWindow); + return; + } + +#if defined(XP_WIN) + // Windows would rather we focus the child widget, otherwise, the toplevel + // widget will always end up being focused. Fortunately, focusing the child + // widget will also have the effect of raising the window this widget is in. + // But on other platforms, we can just focus the toplevel widget to raise + // the window. + nsCOMPtr<nsPIDOMWindowOuter> childWindow; + GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow)); + if (!childWindow) + childWindow = aWindow; + + nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); + if (!docShell) + return; + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (!presShell) + return; + + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + if (widget) + widget->SetFocus(true); + } +#else + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = + do_QueryInterface(aWindow->GetDocShell()); + if (treeOwnerAsWin) { + nsCOMPtr<nsIWidget> widget; + treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget)); + if (widget) + widget->SetFocus(true); + } +#endif +} + +void +nsFocusManager::UpdateCaretForCaretBrowsingMode() +{ + UpdateCaret(false, true, mFocusedContent); +} + +void +nsFocusManager::UpdateCaret(bool aMoveCaretToFocus, + bool aUpdateVisibility, + nsIContent* aContent) +{ + LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility)); + + if (!mFocusedWindow) + return; + + // this is called when a document is focused or when the caretbrowsing + // preference is changed + nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell(); + nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell); + if (!dsti) + return; + + if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) { + return; // Never browse with caret in chrome + } + + bool browseWithCaret = + Preferences::GetBool("accessibility.browsewithcaret"); + + nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell(); + if (!presShell) + return; + + // If this is an editable document which isn't contentEditable, or a + // contentEditable document and the node to focus is contentEditable, + // return, so that we don't mess with caret visibility. + bool isEditable = false; + focusedDocShell->GetEditable(&isEditable); + + if (isEditable) { + nsCOMPtr<nsIHTMLDocument> doc = + do_QueryInterface(presShell->GetDocument()); + + bool isContentEditableDoc = + doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable; + + bool isFocusEditable = + aContent && aContent->HasFlag(NODE_IS_EDITABLE); + if (!isContentEditableDoc || isFocusEditable) + return; + } + + if (!isEditable && aMoveCaretToFocus) + MoveCaretToFocus(presShell, aContent); + + if (!aUpdateVisibility) + return; + + // XXXndeakin this doesn't seem right. It should be checking for this only + // on the nearest ancestor frame which is a chrome frame. But this is + // what the existing code does, so just leave it for now. + if (!browseWithCaret) { + nsCOMPtr<Element> docElement = + mFocusedWindow->GetFrameElementInternal(); + if (docElement) + browseWithCaret = docElement->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::showcaret, + NS_LITERAL_STRING("true"), + eCaseMatters); + } + + SetCaretVisible(presShell, browseWithCaret, aContent); +} + +void +nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent) +{ + // domDoc is a document interface we can create a range with + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument()); + if (domDoc) { + RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection(); + nsCOMPtr<nsISelection> domSelection = + frameSelection->GetSelection(SelectionType::eNormal); + if (domSelection) { + nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent)); + // First clear the selection. This way, if there is no currently focused + // content, the selection will just be cleared. + domSelection->RemoveAllRanges(); + if (currentFocusNode) { + nsCOMPtr<nsIDOMRange> newRange; + nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange)); + if (NS_SUCCEEDED(rv)) { + // Set the range to the start of the currently focused node + // Make sure it's collapsed + newRange->SelectNodeContents(currentFocusNode); + nsCOMPtr<nsIDOMNode> firstChild; + currentFocusNode->GetFirstChild(getter_AddRefs(firstChild)); + if (!firstChild || + aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) { + // If current focus node is a leaf, set range to before the + // node by using the parent as a container. + // This prevents it from appearing as selected. + newRange->SetStartBefore(currentFocusNode); + newRange->SetEndBefore(currentFocusNode); + } + domSelection->AddRange(newRange); + domSelection->CollapseToStart(); + } + } + } + } +} + +nsresult +nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell, + bool aVisible, + nsIContent* aContent) +{ + // When browsing with caret, make sure caret is visible after new focus + // Return early if there is no caret. This can happen for the testcase + // for bug 308025 where a window is closed in a blur handler. + RefPtr<nsCaret> caret = aPresShell->GetCaret(); + if (!caret) + return NS_OK; + + bool caretVisible = caret->IsVisible(); + if (!aVisible && !caretVisible) + return NS_OK; + + RefPtr<nsFrameSelection> frameSelection; + if (aContent) { + NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(), + "Wrong document?"); + nsIFrame *focusFrame = aContent->GetPrimaryFrame(); + if (focusFrame) + frameSelection = focusFrame->GetFrameSelection(); + } + + RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection(); + + if (docFrameSelection && caret && + (frameSelection == docFrameSelection || !aContent)) { + nsISelection* domSelection = + docFrameSelection->GetSelection(SelectionType::eNormal); + if (domSelection) { + nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell)); + if (!selCon) { + return NS_ERROR_FAILURE; + } + // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection + selCon->SetCaretEnabled(false); + + // Caret must blink on non-editable elements + caret->SetIgnoreUserModify(true); + // Tell the caret which selection to use + caret->SetSelection(domSelection); + + // In content, we need to set the caret. The only special case is edit + // fields, which have a different frame selection from the document. + // They will take care of making the caret visible themselves. + + selCon->SetCaretReadOnly(false); + selCon->SetCaretEnabled(aVisible); + } + } + + return NS_OK; +} + +nsresult +nsFocusManager::GetSelectionLocation(nsIDocument* aDocument, + nsIPresShell* aPresShell, + nsIContent **aStartContent, + nsIContent **aEndContent) +{ + *aStartContent = *aEndContent = nullptr; + nsresult rv = NS_ERROR_FAILURE; + + nsPresContext* presContext = aPresShell->GetPresContext(); + NS_ASSERTION(presContext, "mPresContent is null!!"); + + RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection(); + + nsCOMPtr<nsISelection> domSelection; + if (frameSelection) { + domSelection = frameSelection->GetSelection(SelectionType::eNormal); + } + + nsCOMPtr<nsIDOMNode> startNode, endNode; + bool isCollapsed = false; + nsCOMPtr<nsIContent> startContent, endContent; + int32_t startOffset = 0; + if (domSelection) { + domSelection->GetIsCollapsed(&isCollapsed); + nsCOMPtr<nsIDOMRange> domRange; + rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange)); + if (domRange) { + domRange->GetStartContainer(getter_AddRefs(startNode)); + domRange->GetEndContainer(getter_AddRefs(endNode)); + domRange->GetStartOffset(&startOffset); + + nsIContent *childContent = nullptr; + + startContent = do_QueryInterface(startNode); + if (startContent && startContent->IsElement()) { + NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative"); + childContent = startContent->GetChildAt(startOffset); + if (childContent) { + startContent = childContent; + } + } + + endContent = do_QueryInterface(endNode); + if (endContent && endContent->IsElement()) { + int32_t endOffset = 0; + domRange->GetEndOffset(&endOffset); + NS_ASSERTION(endOffset >= 0, "End offset cannot be negative"); + childContent = endContent->GetChildAt(endOffset); + if (childContent) { + endContent = childContent; + } + } + } + } + else { + rv = NS_ERROR_INVALID_ARG; + } + + nsIFrame *startFrame = nullptr; + if (startContent) { + startFrame = startContent->GetPrimaryFrame(); + if (isCollapsed) { + // Next check to see if our caret is at the very end of a node + // If so, the caret is actually sitting in front of the next + // logical frame's primary node - so for this case we need to + // change caretContent to that node. + + if (startContent->NodeType() == nsIDOMNode::TEXT_NODE) { + nsAutoString nodeValue; + startContent->AppendTextTo(nodeValue); + + bool isFormControl = + startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL); + + if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl && + startContent != aDocument->GetRootElement()) { + // Yes, indeed we were at the end of the last node + nsCOMPtr<nsIFrameEnumerator> frameTraversal; + nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal), + presContext, startFrame, + eLeaf, + false, // aVisual + false, // aLockInScrollView + true, // aFollowOOFs + false // aSkipPopupChecks + ); + NS_ENSURE_SUCCESS(rv, rv); + + nsIFrame *newCaretFrame = nullptr; + nsCOMPtr<nsIContent> newCaretContent = startContent; + bool endOfSelectionInStartNode(startContent == endContent); + do { + // Continue getting the next frame until the primary content for the frame + // we are on changes - we don't want to be stuck in the same place + frameTraversal->Next(); + newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); + if (nullptr == newCaretFrame) + break; + newCaretContent = newCaretFrame->GetContent(); + } while (!newCaretContent || newCaretContent == startContent); + + if (newCaretFrame && newCaretContent) { + // If the caret is exactly at the same position of the new frame, + // then we can use the newCaretFrame and newCaretContent for our position + nsRect caretRect; + nsIFrame *frame = nsCaret::GetGeometry(domSelection, &caretRect); + if (frame) { + nsPoint caretWidgetOffset; + nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset); + caretRect.MoveBy(caretWidgetOffset); + nsPoint newCaretOffset; + nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset); + if (widget == newCaretWidget && caretRect.y == newCaretOffset.y && + caretRect.x == newCaretOffset.x) { + // The caret is at the start of the new element. + startFrame = newCaretFrame; + startContent = newCaretContent; + if (endOfSelectionInStartNode) { + endContent = newCaretContent; // Ensure end of selection is not before start + } + } + } + } + } + } + } + } + + *aStartContent = startContent; + *aEndContent = endContent; + NS_IF_ADDREF(*aStartContent); + NS_IF_ADDREF(*aEndContent); + + return rv; +} + +nsresult +nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow, + nsIContent* aStartContent, + int32_t aType, bool aNoParentTraversal, + nsIContent** aNextContent) +{ + *aNextContent = nullptr; + + // True if we are navigating by document (F6/Shift+F6) or false if we are + // navigating by element (Tab/Shift+Tab). + bool forDocumentNavigation = false; + + // This is used for document navigation only. It will be set to true if we + // start navigating from a starting point. If this starting point is near the + // end of the document (for example, an element on a statusbar), and there + // are no child documents or panels before the end of the document, then we + // will need to ensure that we don't consider the root chrome window when we + // loop around and instead find the next child document/panel, as focus is + // already in that window. This flag will be cleared once we navigate into + // another document. + bool mayFocusRoot = (aStartContent != nullptr); + + nsCOMPtr<nsIContent> startContent = aStartContent; + if (!startContent && aType != MOVEFOCUS_CARET) { + if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) { + // When moving between documents, make sure to get the right + // starting content in a descendant. + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow)); + } + else if (aType != MOVEFOCUS_LASTDOC) { + // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used, + // then we are document-navigating backwards from chrome to the content + // process, and we don't want to use this so that we start from the end + // of the document. + startContent = aWindow->GetFocusedNode(); + } + } + + nsCOMPtr<nsIDocument> doc; + if (startContent) + doc = startContent->GetComposedDoc(); + else + doc = aWindow->GetExtantDoc(); + if (!doc) + return NS_OK; + + LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel, + &nsIContent::sTabFocusModel); + + // These types are for document navigation using F6. + if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC || + aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) { + forDocumentNavigation = true; + } + + // If moving to the root or first document, find the root element and return. + if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) { + NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false)); + if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) { + // When looking for the first document, if the root wasn't focusable, + // find the next focusable document. + aType = MOVEFOCUS_FORWARDDOC; + } else { + return NS_OK; + } + } + + nsIContent* rootContent = doc->GetRootElement(); + NS_ENSURE_TRUE(rootContent, NS_OK); + + nsIPresShell *presShell = doc->GetShell(); + NS_ENSURE_TRUE(presShell, NS_OK); + + if (aType == MOVEFOCUS_FIRST) { + if (!aStartContent) + startContent = rootContent; + return GetNextTabbableContent(presShell, startContent, + nullptr, startContent, + true, 1, false, false, aNextContent); + } + if (aType == MOVEFOCUS_LAST) { + if (!aStartContent) + startContent = rootContent; + return GetNextTabbableContent(presShell, startContent, + nullptr, startContent, + false, 0, false, false, aNextContent); + } + + bool forward = (aType == MOVEFOCUS_FORWARD || + aType == MOVEFOCUS_FORWARDDOC || + aType == MOVEFOCUS_CARET); + bool doNavigation = true; + bool ignoreTabIndex = false; + // when a popup is open, we want to ensure that tab navigation occurs only + // within the most recently opened panel. If a popup is open, its frame will + // be stored in popupFrame. + nsIFrame* popupFrame = nullptr; + + int32_t tabIndex = forward ? 1 : 0; + if (startContent) { + nsIFrame* frame = startContent->GetPrimaryFrame(); + if (startContent->IsHTMLElement(nsGkAtoms::area)) + startContent->IsFocusable(&tabIndex); + else if (frame) + frame->IsFocusable(&tabIndex, 0); + else + startContent->IsFocusable(&tabIndex); + + // if the current element isn't tabbable, ignore the tabindex and just + // look for the next element. The root content won't have a tabindex + // so just treat this as the beginning of the tab order. + if (tabIndex < 0) { + tabIndex = 1; + if (startContent != rootContent) + ignoreTabIndex = true; + } + + // check if the focus is currently inside a popup. Elements such as the + // autocomplete widget use the noautofocus attribute to allow the focus to + // remain outside the popup when it is opened. + if (frame) { + popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame, + nsGkAtoms::menuPopupFrame); + } + + if (popupFrame && !forDocumentNavigation) { + // Don't navigate outside of a popup, so pretend that the + // root content is the popup itself + rootContent = popupFrame->GetContent(); + NS_ASSERTION(rootContent, "Popup frame doesn't have a content node"); + } + else if (!forward) { + // If focus moves backward and when current focused node is root + // content or <body> element which is editable by contenteditable + // attribute, focus should move to its parent document. + if (startContent == rootContent) { + doNavigation = false; + } else { + nsIDocument* doc = startContent->GetComposedDoc(); + if (startContent == + nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) { + doNavigation = false; + } + } + } + } + else { +#ifdef MOZ_XUL + if (aType != MOVEFOCUS_CARET) { + // if there is no focus, yet a panel is open, focus the first item in + // the panel + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + popupFrame = pm->GetTopPopup(ePopupTypePanel); + } +#endif + if (popupFrame) { + // When there is a popup open, and no starting content, start the search + // at the topmost popup. + startContent = popupFrame->GetContent(); + NS_ASSERTION(startContent, "Popup frame doesn't have a content node"); + // Unless we are searching for documents, set the root content to the + // popup as well, so that we don't tab-navigate outside the popup. + // When navigating by documents, we start at the popup but can navigate + // outside of it to look for other panels and documents. + if (!forDocumentNavigation) { + rootContent = startContent; + } + + doc = startContent ? startContent->GetComposedDoc() : nullptr; + } + else { + // Otherwise, for content shells, start from the location of the caret. + nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); + if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + nsCOMPtr<nsIContent> endSelectionContent; + GetSelectionLocation(doc, presShell, + getter_AddRefs(startContent), + getter_AddRefs(endSelectionContent)); + // If the selection is on the rootContent, then there is no selection + if (startContent == rootContent) { + startContent = nullptr; + } + + if (aType == MOVEFOCUS_CARET) { + // GetFocusInSelection finds a focusable link near the caret. + // If there is no start content though, don't do this to avoid + // focusing something unexpected. + if (startContent) { + GetFocusInSelection(aWindow, startContent, + endSelectionContent, aNextContent); + } + return NS_OK; + } + + if (startContent) { + // when starting from a selection, we always want to find the next or + // previous element in the document. So the tabindex on elements + // should be ignored. + ignoreTabIndex = true; + } + } + + if (!startContent) { + // otherwise, just use the root content as the starting point + startContent = rootContent; + NS_ENSURE_TRUE(startContent, NS_OK); + } + } + } + + // Check if the starting content is the same as the content assigned to the + // retargetdocumentfocus attribute. Is so, we don't want to start searching + // from there but instead from the beginning of the document. Otherwise, the + // content that appears before the retargetdocumentfocus element will never + // get checked as it will be skipped when the focus is retargetted to it. + if (forDocumentNavigation && doc->IsXULDocument()) { + nsAutoString retarget; + + if (rootContent->GetAttr(kNameSpaceID_None, + nsGkAtoms::retargetdocumentfocus, retarget)) { + nsIContent* retargetElement = doc->GetElementById(retarget); + // The common case here is the urlbar where focus is on the anonymous + // input inside the textbox, but the retargetdocumentfocus attribute + // refers to the textbox. The Contains check will return false and the + // ContentIsDescendantOf check will return true in this case. + if (retargetElement && (retargetElement == startContent || + (!retargetElement->Contains(startContent) && + nsContentUtils::ContentIsDescendantOf(startContent, retargetElement)))) { + startContent = rootContent; + } + } + } + + NS_ASSERTION(startContent, "starting content not set"); + + // keep a reference to the starting content. If we find that again, it means + // we've iterated around completely and we don't want to adjust the focus. + // The skipOriginalContentCheck will be set to true only for the first time + // GetNextTabbableContent is called. This ensures that we don't break out + // when nothing is focused to start with. Specifically, + // GetNextTabbableContent first checks the root content -- which happens to + // be the same as the start content -- when nothing is focused and tabbing + // forward. Without skipOriginalContentCheck set to true, we'd end up + // returning right away and focusing nothing. Luckily, GetNextTabbableContent + // will never wrap around on its own, and can only return the original + // content when it is called a second time or later. + bool skipOriginalContentCheck = true; + nsIContent* originalStartContent = startContent; + + LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get()); + LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d", + forward, tabIndex, ignoreTabIndex, forDocumentNavigation)); + + while (doc) { + if (doNavigation) { + nsCOMPtr<nsIContent> nextFocus; + nsresult rv = GetNextTabbableContent(presShell, rootContent, + skipOriginalContentCheck ? nullptr : originalStartContent, + startContent, forward, + tabIndex, ignoreTabIndex, + forDocumentNavigation, + getter_AddRefs(nextFocus)); + NS_ENSURE_SUCCESS(rv, rv); + if (rv == NS_SUCCESS_DOM_NO_OPERATION) { + // Navigation was redirected to a child process, so just return. + return NS_OK; + } + + // found a content node to focus. + if (nextFocus) { + LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get()); + + // as long as the found node was not the same as the starting node, + // set it as the return value. For document navigation, we can return + // the same element in case there is only one content node that could + // be returned, for example, in a child process document. + if (nextFocus != originalStartContent || forDocumentNavigation) { + nextFocus.forget(aNextContent); + } + return NS_OK; + } + + if (popupFrame && !forDocumentNavigation) { + // in a popup, so start again from the beginning of the popup. However, + // if we already started at the beginning, then there isn't anything to + // focus, so just return + if (startContent != rootContent) { + startContent = rootContent; + tabIndex = forward ? 1 : 0; + continue; + } + return NS_OK; + } + } + + doNavigation = true; + skipOriginalContentCheck = forDocumentNavigation; + ignoreTabIndex = false; + + if (aNoParentTraversal) { + if (startContent == rootContent) + return NS_OK; + + startContent = rootContent; + tabIndex = forward ? 1 : 0; + continue; + } + + // Reached the beginning or end of the document. Next, navigate up to the + // parent document and try again. + nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow(); + NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + // Get the frame element this window is inside and, from that, get the + // parent document and presshell. If there is no enclosing frame element, + // then this is a top-level, embedded or remote window. + startContent = piWindow->GetFrameElementInternal(); + if (startContent) { + doc = startContent->GetComposedDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + rootContent = doc->GetRootElement(); + presShell = doc->GetShell(); + + // We can focus the root element now that we have moved to another document. + mayFocusRoot = true; + + nsIFrame* frame = startContent->GetPrimaryFrame(); + if (!frame) { + return NS_OK; + } + + frame->IsFocusable(&tabIndex, 0); + if (tabIndex < 0) { + tabIndex = 1; + ignoreTabIndex = true; + } + + // if the frame is inside a popup, make sure to scan only within the + // popup. This handles the situation of tabbing amongst elements + // inside an iframe which is itself inside a popup. Otherwise, + // navigation would move outside the popup when tabbing outside the + // iframe. + if (!forDocumentNavigation) { + popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame, + nsGkAtoms::menuPopupFrame); + if (popupFrame) { + rootContent = popupFrame->GetContent(); + NS_ASSERTION(rootContent, "Popup frame doesn't have a content node"); + } + } + } + else { + // There is no parent, so call the tree owner. This will tell the + // embedder or parent process that it should take the focus. + bool tookFocus; + docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus); + // If the tree owner took the focus, blur the current content. + if (tookFocus) { + nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow(); + if (window->GetFocusedNode() == mFocusedContent) + Blur(mFocusedWindow, nullptr, true, true); + else + window->SetFocusedNode(nullptr); + return NS_OK; + } + + // If we have reached the end of the top-level document, focus the + // first element in the top-level document. This should always happen + // when navigating by document forwards but when navigating backwards, + // only do this if we started in another document or within a popup frame. + // If the focus started in this window outside a popup however, we should + // continue by looping around to the end again. + if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) { + // HTML content documents can have their root element focused (a focus + // ring appears around the entire content area frame). This root + // appears in the tab order before all of the elements in the document. + // Chrome documents however cannot be focused directly, so instead we + // focus the first focusable element within the window. + // For example, the urlbar. + nsIContent* root = GetRootForFocus(piWindow, doc, true, true); + return FocusFirst(root, aNextContent); + } + + // Once we have hit the top-level and have iterated to the end again, we + // just want to break out next time we hit this spot to prevent infinite + // iteration. + mayFocusRoot = true; + + // reset the tab index and start again from the beginning or end + startContent = rootContent; + tabIndex = forward ? 1 : 0; + } + + // wrapped all the way around and didn't find anything to move the focus + // to, so just break out + if (startContent == originalStartContent) + break; + } + + return NS_OK; +} + +nsresult +nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, + nsIContent* aRootContent, + nsIContent* aOriginalStartContent, + nsIContent* aStartContent, + bool aForward, + int32_t aCurrentTabIndex, + bool aIgnoreTabIndex, + bool aForDocumentNavigation, + nsIContent** aResultContent) +{ + *aResultContent = nullptr; + + nsCOMPtr<nsIContent> startContent = aStartContent; + if (!startContent) + return NS_OK; + + LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent); + LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex)); + + nsPresContext* presContext = aPresShell->GetPresContext(); + + bool getNextFrame = true; + nsCOMPtr<nsIContent> iterStartContent = aStartContent; + while (1) { + nsIFrame* startFrame = iterStartContent->GetPrimaryFrame(); + // if there is no frame, look for another content node that has a frame + if (!startFrame) { + // if the root content doesn't have a frame, just return + if (iterStartContent == aRootContent) + return NS_OK; + + // look for the next or previous content node in tree order + iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent(); + // we've already skipped over the initial focused content, so we + // don't want to traverse frames. + getNextFrame = false; + if (iterStartContent) + continue; + + // otherwise, as a last attempt, just look at the root content + iterStartContent = aRootContent; + continue; + } + + // For tab navigation, pass false for aSkipPopupChecks so that we don't + // iterate into or out of a popup. For document naviation pass true to + // ignore these boundaries. + nsCOMPtr<nsIFrameEnumerator> frameTraversal; + nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal), + presContext, startFrame, + ePreOrder, + false, // aVisual + false, // aLockInScrollView + true, // aFollowOOFs + aForDocumentNavigation // aSkipPopupChecks + ); + NS_ENSURE_SUCCESS(rv, rv); + + if (iterStartContent == aRootContent) { + if (!aForward) { + frameTraversal->Last(); + } else if (aRootContent->IsFocusable()) { + frameTraversal->Next(); + } + } + else if (getNextFrame && + (!iterStartContent || + !iterStartContent->IsHTMLElement(nsGkAtoms::area))) { + // Need to do special check in case we're in an imagemap which has multiple + // content nodes per frame, so don't skip over the starting frame. + if (aForward) + frameTraversal->Next(); + else + frameTraversal->Prev(); + } + + // Walk frames to find something tabbable matching mCurrentTabIndex + nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); + while (frame) { + nsIContent* currentContent = frame->GetContent(); + + // For document navigation, check if this element is an open panel. Since + // panels aren't focusable (tabIndex would be -1), we'll just assume that + // for document navigation, the tabIndex is 0. + if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) && + currentContent->IsXULElement(nsGkAtoms::panel)) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); + // Check if the panel is open. Closed panels are ignored since you can't + // focus anything in them. + if (popupFrame && popupFrame->IsOpen()) { + // When moving backward, skip the popup we started in otherwise it + // will be selected again. + bool validPopup = true; + if (!aForward) { + nsIContent* content = aStartContent; + while (content) { + if (content == currentContent) { + validPopup = false; + break; + } + + content = content->GetParent(); + } + } + + if (validPopup) { + // Since a panel isn't focusable itself, find the first focusable + // content within the popup. If there isn't any focusable content + // in the popup, skip this popup and continue iterating through the + // frames. We pass the panel itself (currentContent) as the starting + // and root content, so that we only find content within the panel. + // Note also that we pass false for aForDocumentNavigation since we + // want to locate the first content, not the first document. + rv = GetNextTabbableContent(aPresShell, currentContent, + nullptr, currentContent, + true, 1, false, false, + aResultContent); + if (NS_SUCCEEDED(rv) && *aResultContent) { + return rv; + } + } + } + } + + // TabIndex not set defaults to 0 for form elements, anchors and other + // elements that are normally focusable. Tabindex defaults to -1 + // for elements that are not normally focusable. + // The returned computed tabindex from IsFocusable() is as follows: + // < 0 not tabbable at all + // == 0 in normal tab order (last after positive tabindexed items) + // > 0 can be tabbed to in the order specified by this value + int32_t tabIndex; + frame->IsFocusable(&tabIndex, 0); + + LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent()); + LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex)); + + if (tabIndex >= 0) { + NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content"); + if (!aForDocumentNavigation && + currentContent->IsHTMLElement(nsGkAtoms::img) && + currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) { + // This is an image with a map. Image map areas are not traversed by + // nsIFrameTraversal so look for the next or previous area element. + nsIContent *areaContent = + GetNextTabbableMapArea(aForward, aCurrentTabIndex, + currentContent, iterStartContent); + if (areaContent) { + NS_ADDREF(*aResultContent = areaContent); + return NS_OK; + } + } + else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) { + // break out if we've wrapped around to the start again. + if (aOriginalStartContent && currentContent == aOriginalStartContent) { + NS_ADDREF(*aResultContent = currentContent); + return NS_OK; + } + + // If this is a remote child browser, call NavigateDocument to have + // the child process continue the navigation. Return a special error + // code to have the caller return early. If the child ends up not + // being focusable in some way, the child process will call back + // into document navigation again by calling MoveFocus. + TabParent* remote = TabParent::GetFrom(currentContent); + if (remote) { + remote->NavigateByKey(aForward, aForDocumentNavigation); + return NS_SUCCESS_DOM_NO_OPERATION; + } + + // Next, for document navigation, check if this a non-remote child document. + bool checkSubDocument = true; + if (aForDocumentNavigation) { + nsIContent* docRoot = GetRootForChildDocument(currentContent); + if (docRoot) { + // If GetRootForChildDocument returned something then call + // FocusFirst to find the root or first element to focus within + // the child document. If this is a frameset though, skip this and + // fall through to the checkSubDocument block below to iterate into + // the frameset's frames and locate the first focusable frame. + if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) { + return FocusFirst(docRoot, aResultContent); + } + } else { + // Set checkSubDocument to false, as this was neither a frame + // type element or a child document that was focusable. + checkSubDocument = false; + } + } + + if (checkSubDocument) { + // found a node with a matching tab index. Check if it is a child + // frame. If so, navigate into the child frame instead. + nsIDocument* doc = currentContent->GetComposedDoc(); + NS_ASSERTION(doc, "content not in document"); + nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent); + if (subdoc && !subdoc->EventHandlingSuppressed()) { + if (aForward) { + // when tabbing forward into a frame, return the root + // frame so that the canvas becomes focused. + nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow(); + if (subframe) { + *aResultContent = GetRootForFocus(subframe, subdoc, false, true); + if (*aResultContent) { + NS_ADDREF(*aResultContent); + return NS_OK; + } + } + } + Element* rootElement = subdoc->GetRootElement(); + nsIPresShell* subShell = subdoc->GetShell(); + if (rootElement && subShell) { + rv = GetNextTabbableContent(subShell, rootElement, + aOriginalStartContent, rootElement, + aForward, (aForward ? 1 : 0), + false, aForDocumentNavigation, aResultContent); + NS_ENSURE_SUCCESS(rv, rv); + if (*aResultContent) + return NS_OK; + } + } + // otherwise, use this as the next content node to tab to, unless + // this was the element we started on. This would happen for + // instance on an element with child frames, where frame navigation + // could return the original element again. In that case, just skip + // it. Also, if the next content node is the root content, then + // return it. This latter case would happen only if someone made a + // popup focusable. + // Also, when going backwards, check to ensure that the focus + // wouldn't be redirected. Otherwise, for example, when an input in + // a textbox is focused, the enclosing textbox would be found and + // the same inner input would be returned again. + else if (currentContent == aRootContent || + (currentContent != startContent && + (aForward || !GetRedirectedFocus(currentContent)))) { + NS_ADDREF(*aResultContent = currentContent); + return NS_OK; + } + } + } + } + else if (aOriginalStartContent && currentContent == aOriginalStartContent) { + // not focusable, so return if we have wrapped around to the original + // content. This is necessary in case the original starting content was + // not focusable. + NS_ADDREF(*aResultContent = currentContent); + return NS_OK; + } + + // Move to the next or previous frame, but ignore continuation frames + // since only the first frame should be involved in focusability. + // Otherwise, a loop will occur in the following example: + // <span tabindex="1">...<a/><a/>...</span> + // where the text wraps onto multiple lines. Tabbing from the second + // link can find one of the span's continuation frames between the link + // and the end of the span, and the span would end up getting focused + // again. + do { + if (aForward) + frameTraversal->Next(); + else + frameTraversal->Prev(); + frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); + } while (frame && frame->GetPrevContinuation()); + } + + // If already at lowest priority tab (0), end search completely. + // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0 + if (aCurrentTabIndex == (aForward ? 0 : 1)) { + // if going backwards, the canvas should be focused once the beginning + // has been reached, so get the root element. + if (!aForward) { + nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIContent> docRoot = + GetRootForFocus(window, aRootContent->GetComposedDoc(), false, true); + FocusFirst(docRoot, aResultContent); + } + break; + } + + // continue looking for next highest priority tabindex + aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward); + startContent = iterStartContent = aRootContent; + } + + return NS_OK; +} + +nsIContent* +nsFocusManager::GetNextTabbableMapArea(bool aForward, + int32_t aCurrentTabIndex, + nsIContent* aImageContent, + nsIContent* aStartContent) +{ + nsAutoString useMap; + aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap); + + nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc(); + if (doc) { + nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap); + if (!mapContent) + return nullptr; + uint32_t count = mapContent->GetChildCount(); + // First see if the the start content is in this map + + int32_t index = mapContent->IndexOf(aStartContent); + int32_t tabIndex; + if (index < 0 || (aStartContent->IsFocusable(&tabIndex) && + tabIndex != aCurrentTabIndex)) { + // If aStartContent is in this map we must start iterating past it. + // We skip the case where aStartContent has tabindex == aStartContent + // since the next tab ordered element might be before it + // (or after for backwards) in the child list. + index = aForward ? -1 : (int32_t)count; + } + + // GetChildAt will return nullptr if our index < 0 or index >= count + nsCOMPtr<nsIContent> areaContent; + while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) { + if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) { + return areaContent; + } + } + } + + return nullptr; +} + +int32_t +nsFocusManager::GetNextTabIndex(nsIContent* aParent, + int32_t aCurrentTabIndex, + bool aForward) +{ + int32_t tabIndex, childTabIndex; + + if (aForward) { + tabIndex = 0; + for (nsIContent* child = aParent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); + if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) { + tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex; + } + + nsAutoString tabIndexStr; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); + nsresult ec; + int32_t val = tabIndexStr.ToInteger(&ec); + if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) { + tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex; + } + } + } + else { /* !aForward */ + tabIndex = 1; + for (nsIContent* child = aParent->GetFirstChild(); + child; + child = child->GetNextSibling()) { + childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); + if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) || + (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) { + tabIndex = childTabIndex; + } + + nsAutoString tabIndexStr; + child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); + nsresult ec; + int32_t val = tabIndexStr.ToInteger(&ec); + if (NS_SUCCEEDED (ec)) { + if ((aCurrentTabIndex == 0 && val > tabIndex) || + (val < aCurrentTabIndex && val > tabIndex) ) { + tabIndex = val; + } + } + } + } + + return tabIndex; +} + +nsresult +nsFocusManager::FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent) +{ + if (!aRootContent) { + return NS_OK; + } + + nsIDocument* doc = aRootContent->GetComposedDoc(); + if (doc) { + if (doc->IsXULDocument()) { + // If the redirectdocumentfocus attribute is set, redirect the focus to a + // specific element. This is primarily used to retarget the focus to the + // urlbar during document navigation. + nsAutoString retarget; + + if (aRootContent->GetAttr(kNameSpaceID_None, + nsGkAtoms::retargetdocumentfocus, retarget)) { + nsCOMPtr<Element> element = doc->GetElementById(retarget); + nsCOMPtr<nsIContent> retargetElement = + CheckIfFocusable(element, 0); + if (retargetElement) { + retargetElement.forget(aNextContent); + return NS_OK; + } + } + } + + nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell(); + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + // If the found content is in a chrome shell, navigate forward one + // tabbable item so that the first item is focused. Note that we + // always go forward and not back here. + nsIPresShell* presShell = doc->GetShell(); + if (presShell) { + return GetNextTabbableContent(presShell, aRootContent, + nullptr, aRootContent, + true, 1, false, false, + aNextContent); + } + } + } + + NS_ADDREF(*aNextContent = aRootContent); + return NS_OK; +} + +nsIContent* +nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow, + nsIDocument* aDocument, + bool aForDocumentNavigation, + bool aCheckVisibility) +{ + if (!aForDocumentNavigation) { + nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); + if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { + return nullptr; + } + } + + if (aCheckVisibility && !IsWindowVisible(aWindow)) + return nullptr; + + // If the body is contenteditable, use the editor's root element rather than + // the actual root element. + nsCOMPtr<nsIContent> rootElement = + nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument); + if (!rootElement || !rootElement->GetPrimaryFrame()) { + rootElement = aDocument->GetRootElement(); + if (!rootElement) { + return nullptr; + } + } + + if (aCheckVisibility && !rootElement->GetPrimaryFrame()) { + return nullptr; + } + + // Finally, check if this is a frameset + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument); + if (htmlDoc) { + nsIContent* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset); + if (htmlChild) { + // In document navigation mode, return the frameset so that navigation + // descends into the child frames. + return aForDocumentNavigation ? htmlChild : nullptr; + } + } + + return rootElement; +} + +nsIContent* +nsFocusManager::GetRootForChildDocument(nsIContent* aContent) +{ + // Check for elements that represent child documents, that is, browsers, + // editors or frames from a frameset. We don't include iframes since we + // consider them to be an integral part of the same window or page. + if (!aContent || + !(aContent->IsXULElement(nsGkAtoms::browser) || + aContent->IsXULElement(nsGkAtoms::editor) || + aContent->IsHTMLElement(nsGkAtoms::frame))) { + return nullptr; + } + + nsIDocument* doc = aContent->GetComposedDoc(); + if (!doc) { + return nullptr; + } + + nsIDocument* subdoc = doc->GetSubDocumentFor(aContent); + if (!subdoc || subdoc->EventHandlingSuppressed()) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow(); + return GetRootForFocus(window, subdoc, true, true); +} + +void +nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow, + nsIContent* aStartSelection, + nsIContent* aEndSelection, + nsIContent** aFocusedContent) +{ + *aFocusedContent = nullptr; + + nsCOMPtr<nsIContent> testContent = aStartSelection; + nsCOMPtr<nsIContent> nextTestContent = aEndSelection; + + nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode(); + + // We now have the correct start node in selectionContent! + // Search for focusable elements, starting with selectionContent + + // Method #1: Keep going up while we look - an ancestor might be focusable + // We could end the loop earlier, such as when we're no longer + // in the same frame, by comparing selectionContent->GetPrimaryFrame() + // with a variable holding the starting selectionContent + while (testContent) { + // Keep testing while selectionContent is equal to something, + // eventually we'll run out of ancestors + + nsCOMPtr<nsIURI> uri; + if (testContent == currentFocus || + testContent->IsLink(getter_AddRefs(uri))) { + testContent.forget(aFocusedContent); + return; + } + + // Get the parent + testContent = testContent->GetParent(); + + if (!testContent) { + // We run this loop again, checking the ancestor chain of the selection's end point + testContent = nextTestContent; + nextTestContent = nullptr; + } + } + + // We couldn't find an anchor that was an ancestor of the selection start + // Method #2: look for anchor in selection's primary range (depth first search) + + // Turn into nodes so that we can use GetNextSibling() and GetFirstChild() + nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection)); + nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection)); + nsCOMPtr<nsIDOMNode> testNode; + + do { + testContent = do_QueryInterface(selectionNode); + + // We're looking for any focusable link that could be part of the + // main document's selection. + nsCOMPtr<nsIURI> uri; + if (testContent == currentFocus || + testContent->IsLink(getter_AddRefs(uri))) { + testContent.forget(aFocusedContent); + return; + } + + selectionNode->GetFirstChild(getter_AddRefs(testNode)); + if (testNode) { + selectionNode = testNode; + continue; + } + + if (selectionNode == endSelectionNode) + break; + selectionNode->GetNextSibling(getter_AddRefs(testNode)); + if (testNode) { + selectionNode = testNode; + continue; + } + + do { + selectionNode->GetParentNode(getter_AddRefs(testNode)); + if (!testNode || testNode == endSelectionNode) { + selectionNode = nullptr; + break; + } + testNode->GetNextSibling(getter_AddRefs(selectionNode)); + if (selectionNode) + break; + selectionNode = testNode; + } while (true); + } + while (selectionNode && selectionNode != endSelectionNode); +} + +class PointerUnlocker : public Runnable +{ +public: + PointerUnlocker() + { + MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker); + PointerUnlocker::sActiveUnlocker = this; + } + + ~PointerUnlocker() + { + if (PointerUnlocker::sActiveUnlocker == this) { + PointerUnlocker::sActiveUnlocker = nullptr; + } + } + + NS_IMETHOD Run() override + { + if (PointerUnlocker::sActiveUnlocker == this) { + PointerUnlocker::sActiveUnlocker = nullptr; + } + NS_ENSURE_STATE(nsFocusManager::GetFocusManager()); + nsPIDOMWindowOuter* focused = + nsFocusManager::GetFocusManager()->GetFocusedWindow(); + nsCOMPtr<nsIDocument> pointerLockedDoc = + do_QueryReferent(EventStateManager::sPointerLockedDoc); + if (pointerLockedDoc && + !nsContentUtils::IsInPointerLockContext(focused)) { + nsIDocument::UnlockPointer(); + } + return NS_OK; + } + + static PointerUnlocker* sActiveUnlocker; +}; + +PointerUnlocker* +PointerUnlocker::sActiveUnlocker = nullptr; + +void +nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow) +{ + if (!PointerUnlocker::sActiveUnlocker && + nsContentUtils::IsInPointerLockContext(mFocusedWindow) && + !nsContentUtils::IsInPointerLockContext(aWindow)) { + nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker(); + NS_DispatchToCurrentThread(runnable); + } + mFocusedWindow = aWindow; +} + +void +nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration) +{ + if (!sInstance) { + return; + } + + if (sInstance->mActiveWindow) { + sInstance->mActiveWindow-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mFocusedWindow) { + sInstance->mFocusedWindow-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mWindowBeingLowered) { + sInstance->mWindowBeingLowered-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mFocusedContent) { + sInstance->mFocusedContent->OwnerDoc()-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mFirstBlurEvent) { + sInstance->mFirstBlurEvent->OwnerDoc()-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mFirstFocusEvent) { + sInstance->mFirstFocusEvent->OwnerDoc()-> + MarkUncollectableForCCGeneration(aGeneration); + } + if (sInstance->mMouseButtonEventHandlingDocument) { + sInstance->mMouseButtonEventHandlingDocument-> + MarkUncollectableForCCGeneration(aGeneration); + } +} + +nsresult +NS_NewFocusManager(nsIFocusManager** aResult) +{ + NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager()); + return NS_OK; +} |