Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/BrowserParent.h"
#include "nsFocusManager.h"
#include "LayoutConstants.h"
#include "ChildIterator.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "ContentParent.h"
#include "nsPIDOMWindow.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIFormControl.h"
#include "nsLayoutUtils.h"
#include "nsFrameTraversal.h"
#include "nsIWebNavigation.h"
#include "nsCaret.h"
#include "nsIBaseWindow.h"
#include "nsIAppWindow.h"
#include "nsTextControlFrame.h"
#include "nsViewManager.h"
#include "nsFrameSelection.h"
#include "mozilla/dom/Selection.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "nsIScriptError.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIPrincipal.h"
#include "nsIObserverService.h"
#include "BrowserChild.h"
#include "nsFrameLoader.h"
#include "nsHTMLDocument.h"
#include "nsNetUtil.h"
#include "nsRange.h"
#include "nsFrameLoaderOwner.h"
#include "nsQueryObject.h"
#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/XULPopupElement.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/Try.h"
#include "mozilla/widget/IMEData.h"
#include <algorithm>
#include "nsIDOMXULMenuListElement.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.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("(none)"_ns); \
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, PresShell* aPresShell,
Document* 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) {}
RefPtr<PresShell> mPresShell;
nsCOMPtr<Document> 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, static_cast<nsIDocumentObserver*>(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_WEAK(nsFocusManager, mActiveWindow,
mActiveBrowsingContextInContent,
mActiveBrowsingContextInChrome, mFocusedWindow,
mFocusedBrowsingContextInContent,
mFocusedBrowsingContextInChrome, mFocusedElement,
mFirstBlurEvent, mFirstFocusEvent,
mWindowBeingLowered, mDelayedBlurFocusEvents)
StaticRefPtr<nsFocusManager> nsFocusManager::sInstance;
bool nsFocusManager::sTestMode = false;
uint64_t nsFocusManager::sFocusActionCounter = 0;
static const char* kObservedPrefs[] = {"accessibility.browsewithcaret",
"accessibility.tabfocus_applies_to_xul",
"focusmanager.testmode", nullptr};
nsFocusManager::nsFocusManager()
: mActionIdForActiveBrowsingContextInContent(0),
mActionIdForActiveBrowsingContextInChrome(0),
mActionIdForFocusedBrowsingContextInContent(0),
mActionIdForFocusedBrowsingContextInChrome(0),
mActiveBrowsingContextInContentSetFromOtherProcess(false),
mEventHandlingNeedsFlush(false) {}
nsFocusManager::~nsFocusManager() {
Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
this);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "xpcom-shutdown");
}
}
// static
nsresult nsFocusManager::Init() {
sInstance = new nsFocusManager();
nsIContent::sTabFocusModelAppliesToXUL =
Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
nsIContent::sTabFocusModelAppliesToXUL);
sTestMode = Preferences::GetBool("focusmanager.testmode", false);
Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
sInstance.get());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(sInstance, "xpcom-shutdown", true);
}
return NS_OK;
}
// static
void nsFocusManager::Shutdown() { sInstance = nullptr; }
// static
void nsFocusManager::PrefChanged(const char* aPref, void* aSelf) {
if (RefPtr<nsFocusManager> fm = static_cast<nsFocusManager*>(aSelf)) {
fm->PrefChanged(aPref);
}
}
void nsFocusManager::PrefChanged(const char* aPref) {
nsDependentCString pref(aPref);
if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
UpdateCaretForCaretBrowsingMode();
} else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
nsIContent::sTabFocusModelAppliesToXUL =
Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
nsIContent::sTabFocusModelAppliesToXUL);
} else if (pref.EqualsLiteral("focusmanager.testmode")) {
sTestMode = Preferences::GetBool("focusmanager.testmode", false);
}
}
NS_IMETHODIMP
nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
mActiveWindow = nullptr;
mActiveBrowsingContextInContent = nullptr;
mActionIdForActiveBrowsingContextInContent = 0;
mActionIdForFocusedBrowsingContextInContent = 0;
mActiveBrowsingContextInChrome = nullptr;
mActionIdForActiveBrowsingContextInChrome = 0;
mActionIdForFocusedBrowsingContextInChrome = 0;
mFocusedWindow = nullptr;
mFocusedBrowsingContextInContent = nullptr;
mFocusedBrowsingContextInChrome = nullptr;
mFocusedElement = nullptr;
mFirstBlurEvent = nullptr;
mFirstFocusEvent = nullptr;
mWindowBeingLowered = nullptr;
mDelayedBlurFocusEvents.Clear();
}
return NS_OK;
}
static bool ActionIdComparableAndLower(uint64_t aActionId,
uint64_t aReference) {
MOZ_ASSERT(aActionId, "Uninitialized action id");
auto [actionProc, actionId] =
nsContentUtils::SplitProcessSpecificId(aActionId);
auto [refProc, refId] = nsContentUtils::SplitProcessSpecificId(aReference);
return actionProc == refProc && actionId < refId;
}
// given a frame content node, retrieve the nsIDOMWindow displayed in it
static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) {
Document* doc = aContent->GetComposedDoc();
if (doc) {
Document* subdoc = doc->GetSubDocumentFor(aContent);
if (subdoc) {
return subdoc->GetWindow();
}
}
return nullptr;
}
bool nsFocusManager::IsFocused(nsIContent* aContent) {
if (!aContent || !mFocusedElement) {
return false;
}
return aContent == mFocusedElement;
}
bool nsFocusManager::IsTestMode() { return sTestMode; }
bool nsFocusManager::IsInActiveWindow(BrowsingContext* aBC) const {
RefPtr<BrowsingContext> top = aBC->Top();
if (XRE_IsParentProcess()) {
top = top->Canonical()->TopCrossChromeBoundary();
}
return IsSameOrAncestor(top, GetActiveBrowsingContext());
}
// get the current window for the given content node
static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) {
Document* doc = aContent->GetComposedDoc();
return doc ? doc->GetWindow() : nullptr;
}
// static
Element* nsFocusManager::GetFocusedDescendant(
nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
nsPIDOMWindowOuter** aFocusedWindow) {
NS_ENSURE_TRUE(aWindow, nullptr);
*aFocusedWindow = nullptr;
Element* currentElement = nullptr;
nsPIDOMWindowOuter* window = aWindow;
for (;;) {
*aFocusedWindow = window;
currentElement = window->GetFocusedElement();
if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
break;
}
window = GetContentWindow(currentElement);
if (!window) {
break;
}
if (aSearchRange == eIncludeAllDescendants) {
continue;
}
MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
// If the child window doesn't have PresShell, it means the window is
// invisible.
nsIDocShell* docShell = window->GetDocShell();
if (!docShell) {
break;
}
if (!docShell->GetPresShell()) {
break;
}
}
NS_IF_ADDREF(*aFocusedWindow);
return currentElement;
}
// 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;
} else if (aFlags & nsIFocusManager::FLAG_BYLONGPRESS) {
return InputContextAction::CAUSE_LONGPRESS;
}
return InputContextAction::CAUSE_UNKNOWN;
}
NS_IMETHODIMP
nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) {
MOZ_ASSERT(XRE_IsParentProcess(),
"Must not be called outside the parent process.");
NS_IF_ADDREF(*aWindow = mActiveWindow);
return NS_OK;
}
NS_IMETHODIMP
nsFocusManager::GetActiveBrowsingContext(BrowsingContext** aBrowsingContext) {
NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContext());
return NS_OK;
}
void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow,
CallerType aCallerType) {
if (RefPtr<nsFocusManager> fm = sInstance) {
fm->SetFocusedWindowWithCallerType(aWindow, aCallerType);
}
}
NS_IMETHODIMP
nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) {
NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
return NS_OK;
}
NS_IMETHODIMP
nsFocusManager::GetFocusedContentBrowsingContext(
BrowsingContext** aBrowsingContext) {
MOZ_DIAGNOSTIC_ASSERT(
XRE_IsParentProcess(),
"We only have use cases for this in the parent process");
NS_IF_ADDREF(*aBrowsingContext = GetFocusedBrowsingContextInChrome());
return NS_OK;
}
nsresult nsFocusManager::SetFocusedWindowWithCallerType(
mozIDOMWindowProxy* aWindowToFocus, CallerType aCallerType) {
LOGFOCUS(("<<SetFocusedWindow begin>>"));
nsCOMPtr<nsPIDOMWindowOuter> windowToFocus =
nsPIDOMWindowOuter::From(aWindowToFocus);
NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
Maybe<uint64_t> actionIdFromSetFocusInner;
if (frameElement) {
// pass false for aFocusChanged so that the caret does not get updated
// and scrolling does not occur.
actionIdFromSetFocusInner = 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->GetFocusedElement();
if (content) {
if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
ClearFocus(windowToFocus);
}
}
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
const uint64_t actionId = actionIdFromSetFocusInner.isSome()
? actionIdFromSetFocusInner.value()
: sInstance->GenerateFocusActionId();
if (rootWindow) {
RaiseWindow(rootWindow, aCallerType, actionId);
}
LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64 ">>", actionId));
return NS_OK;
}
NS_IMETHODIMP nsFocusManager::SetFocusedWindow(
mozIDOMWindowProxy* aWindowToFocus) {
return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System);
}
NS_IMETHODIMP
nsFocusManager::GetFocusedElement(Element** aFocusedElement) {
RefPtr<Element> focusedElement = mFocusedElement;
focusedElement.forget(aFocusedElement);
return NS_OK;
}
uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter* aWindow) const {
nsPIDOMWindowOuter* window = aWindow ? aWindow : mFocusedWindow.get();
uint32_t method = window ? window->GetFocusMethod() : 0;
NS_ASSERTION((method & METHOD_MASK) == method, "invalid focus method");
return method;
}
NS_IMETHODIMP
nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow,
uint32_t* aLastFocusMethod) {
*aLastFocusMethod = GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow));
return NS_OK;
}
NS_IMETHODIMP
nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) {
LOGFOCUS(("<<SetFocus begin>>"));
NS_ENSURE_ARG(aElement);
SetFocusInner(aElement, aFlags, true, true);
LOGFOCUS(("<<SetFocus end>>"));
return NS_OK;
}
NS_IMETHODIMP
nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
bool* aIsFocusable) {
NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
*aIsFocusable = !!FlushAndCheckIfFocusable(aElement, aFlags);
return NS_OK;
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
uint32_t aType, uint32_t aFlags, Element** aElement) {
*aElement = nullptr;
LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
Document* doc = mFocusedWindow->GetExtantDoc();
if (doc && doc->GetDocumentURI()) {
LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(),
doc->GetDocumentURI()->GetSpecOrDefault().get()));
}
}
LOGCONTENT(" Current Focus: %s", mFocusedElement.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 & METHOD_MASK) == 0) {
aFlags |= FLAG_BYMOVEFOCUS;
}
nsCOMPtr<nsPIDOMWindowOuter> window;
if (aStartElement) {
window = GetCurrentWindow(aStartElement);
} else {
window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
}
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
// Flush to ensure that focusability of descendants is computed correctly.
if (RefPtr<Document> doc = window->GetExtantDoc()) {
doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
}
bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
nsCOMPtr<nsIContent> newFocus;
nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType,
noParentTraversal, true,
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 && newFocus->IsElement()) {
// 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(MOZ_KnownLive(newFocus->AsElement()), aFlags,
aType != MOVEFOCUS_CARET, true);
*aElement = do_AddRef(newFocus->AsElement()).take();
} 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, GetFocusedBrowsingContext())) {
RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
bool isAncestor = (GetFocusedBrowsingContext() != bc);
uint64_t actionId = GenerateFocusActionId();
if (Blur(bc, nullptr, isAncestor, true, false, actionId)) {
// 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, actionId);
}
}
} else {
window->SetFocusedElement(nullptr);
}
LOGFOCUS(("<<ClearFocus end>>"));
return NS_OK;
}
NS_IMETHODIMP
nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
bool aDeep,
mozIDOMWindowProxy** aFocusedWindow,
Element** aElement) {
*aElement = nullptr;
if (aFocusedWindow) {
*aFocusedWindow = nullptr;
}
NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
RefPtr<Element> focusedElement =
GetFocusedDescendant(window,
aDeep ? nsFocusManager::eIncludeAllDescendants
: nsFocusManager::eOnlyCurrentWindow,
getter_AddRefs(focusedWindow));
focusedElement.forget(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;
}
RefPtr<PresShell> presShell = docShell->GetPresShell();
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
if (RefPtr<Element> focusedElement = window->GetFocusedElement()) {
MoveCaretToFocus(presShell, focusedElement);
}
}
}
return NS_OK;
}
void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow,
uint64_t aActionId) {
if (!aWindow) {
return;
}
nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
BrowsingContext* bc = window->GetBrowsingContext();
if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64, aWindow,
mActiveWindow.get(), mFocusedWindow.get(), aActionId));
Document* 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 (XRE_IsParentProcess()) {
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(CallerType::System);
return;
}
// lower the existing window, if any. This shouldn't happen usually.
if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow) {
WindowLowered(activeWindow, aActionId);
}
} else if (bc->IsTop()) {
BrowsingContext* active = GetActiveBrowsingContext();
if (active == bc && !mActiveBrowsingContextInContentSetFromOtherProcess) {
// EnsureCurrentWidgetFocused() should not be necessary with
// PuppetWidget.
return;
}
if (active && active != bc) {
if (active->IsInProcess()) {
nsCOMPtr<nsPIDOMWindowOuter> activeWindow = active->GetDOMWindow();
WindowLowered(activeWindow, aActionId);
}
// No else, because trying to lower other-process windows
// from here can result in the BrowsingContext no longer
// existing in the parent process by the time it deserializes
// the IPC message.
}
}
nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
// If there's no docShellAsItem, this window must have been closed,
// in that case there is no tree owner.
if (!docShellAsItem) {
return;
}
// set this as the active window
if (XRE_IsParentProcess()) {
mActiveWindow = window;
} else if (bc->IsTop()) {
SetActiveBrowsingContextInContent(bc, aActionId);
}
// 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;
}
baseWindow->SetVisibility(true);
}
if (XRE_IsParentProcess()) {
// Unsetting top-level focus upon lowering was inhibited to accommodate
// ATOK, so we need to do it here.
BrowserParent::UnsetTopLevelWebFocusAll();
ActivateOrDeactivate(window, true);
}
// retrieve the last focused element within the window that was raised
nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
RefPtr<Element> currentFocus = GetFocusedDescendant(
window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
NS_ASSERTION(currentWindow, "window raised with no window current");
if (!currentWindow) {
return;
}
nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(baseWindow));
// We use mFocusedWindow here is basically for the case that iframe navigate
// from a.com to b.com for example, so it ends up being loaded in a different
// process after Fission, but
// currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
// still be true because focused browsing context is synced, and we won't
// fire a focus event while focusing if we use it as condition.
Focus(currentWindow, currentFocus, 0, currentWindow != mFocusedWindow, false,
appWin != nullptr, true, aActionId);
}
void nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow,
uint64_t aActionId) {
if (!aWindow) {
return;
}
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()));
Document* 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 (XRE_IsParentProcess()) {
if (mActiveWindow != window) {
return;
}
} else {
BrowsingContext* bc = window->GetBrowsingContext();
BrowsingContext* active = GetActiveBrowsingContext();
if (active != bc->Top()) {
return;
}
}
// clear the mouse capture as the active window has changed
PresShell::ReleaseCapturingContent();
// 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) {
if (PresShell* presShell = docShell->GetPresShell()) {
RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
frameSelection->SetDragState(false);
}
}
}
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 = window;
if (XRE_IsParentProcess()) {
mActiveWindow = nullptr;
} else {
BrowsingContext* bc = window->GetBrowsingContext();
if (bc == bc->Top()) {
SetActiveBrowsingContextInContent(nullptr, aActionId);
}
}
if (mFocusedWindow) {
Blur(nullptr, nullptr, true, true, false, aActionId);
}
mWindowBeingLowered = nullptr;
}
nsresult nsFocusManager::ContentRemoved(Document* aDocument,
nsIContent* aContent) {
NS_ENSURE_ARG(aDocument);
NS_ENSURE_ARG(aContent);
RefPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow();
if (!window) {
return NS_OK;
}
// if the content is currently focused in the window, or is an
// shadow-including inclusive ancestor of the currently focused element,
// reset the focus within that window.
RefPtr<Element> previousFocusedElement = window->GetFocusedElement();
if (!previousFocusedElement) {
return NS_OK;
}
if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
previousFocusedElement, aContent)) {
return NS_OK;
}
RefPtr<Element> newFocusedElement = [&]() -> Element* {
if (auto* sr = ShadowRoot::FromNode(aContent)) {
if (sr->IsUAWidget() && sr->Host()->IsHTMLElement(nsGkAtoms::input)) {
return sr->Host();
}
}
return nullptr;
}();
window->SetFocusedElement(newFocusedElement);
// if this window is currently focused, clear the global focused
// element as well, but don't fire any events.
if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) {
mFocusedElement = newFocusedElement;
} else if (Document* subdoc =
aDocument->GetSubDocumentFor(previousFocusedElement)) {
// 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.
//
// The Fission case is handled in FlushAndCheckIfFocusable().
if (nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell()) {
nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
if (childWindow &&
IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) {
if (XRE_IsParentProcess()) {
nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
ClearFocus(activeWindow);
} else {
BrowsingContext* active = GetActiveBrowsingContext();
if (active) {
if (active->IsInProcess()) {
nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
active->GetDOMWindow();
ClearFocus(activeWindow);
} else {
mozilla::dom::ContentChild* contentChild =
mozilla::dom::ContentChild::GetSingleton();
MOZ_ASSERT(contentChild);
contentChild->SendClearFocus(active);
}
} // no else, because ClearFocus does nothing with nullptr
}
}
}
}
// Notify the editor in case we removed its ancestor limiter.
if (previousFocusedElement->IsEditable()) {
if (nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell()) {
if (RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor()) {
RefPtr<Selection> selection = htmlEditor->GetSelection();
if (selection && selection->GetFrameSelection() &&
previousFocusedElement ==
selection->GetFrameSelection()->GetAncestorLimiter()) {
htmlEditor->FinalizeSelection();
}
}
}
}
if (!newFocusedElement) {
NotifyFocusStateChange(previousFocusedElement, newFocusedElement, 0,
/* aGettingFocus = */ false, false);
} else {
// We should already have the right state, which is managed by the <input>
// widget.
MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS));
}
// If we changed focused element and the element still has focus, let's
// notify IME of focus. Note that if new focus move has already occurred
// by running script, we should not let IMEStateManager of outdated focus
// change.
if (mFocusedElement == newFocusedElement && mFocusedWindow == window) {
RefPtr<nsPresContext> presContext(aDocument->GetPresContext());
IMEStateManager::OnChangeFocus(presContext, newFocusedElement,
InputContextAction::Cause::CAUSE_UNKNOWN);
}
return NS_OK;
}
void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow,
bool aNeedsFocus) {
if (!aWindow) {
return;
}
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()));
Document* 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 (XRE_IsParentProcess()) {
if (BrowsingContext* bc = window->GetBrowsingContext()) {
if (bc->IsTop()) {
bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
}
}
}
if (XRE_IsParentProcess()) {
if (mFocusedWindow != window) {
return;
}
} else {
BrowsingContext* bc = window->GetBrowsingContext();
if (!bc || mFocusedBrowsingContextInContent != bc) {
return;
}
// Sync the window for a newly-created OOP iframe
// Set actionId to zero to signify that it should be ignored.
SetFocusedWindowInternal(window, 0, false);
}
if (aNeedsFocus) {
nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
RefPtr<Element> currentFocus = GetFocusedDescendant(
window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
if (currentWindow) {
Focus(currentWindow, currentFocus, 0, true, false, false, true,
GenerateFocusActionId());
}
} 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(CallerType::System);
}
}
void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow,
uint64_t aActionId) {
// 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.
if (!aWindow) {
return;
}
nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64,
window.get(), mActiveWindow.get(), mFocusedWindow.get(),
aActionId));
nsAutoCString spec;
Document* 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;
}
// 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.
const RefPtr<Element> oldFocusedElement = std::move(mFocusedElement);
nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
if (!focusedDocShell) {
return;
}
const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
NotifyFocusStateChange(oldFocusedElement, nullptr, 0, false, false);
window->UpdateCommands(u"focus"_ns);
if (presShell) {
RefPtr<Document> composedDoc = oldFocusedElement->GetComposedDoc();
SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement,
false);
}
}
const RefPtr<nsPresContext> focusedPresContext =
presShell ? presShell->GetPresContext() : nullptr;
IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
GetFocusMoveActionCause(0));
if (presShell) {
SetCaretVisible(presShell, false, nullptr);
}
// If a window is being "hidden" because its BrowsingContext is changing
// remoteness, we don't want to handle docshell destruction by moving focus.
// Instead, the focused browsing context should stay the way it is (so that
// the newly "shown" window in the other process knows to take focus) and
// we should just null out the process-local field.
nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
// Check if we're currently hiding a non-remote nsDocShell due to its
// BrowsingContext navigating to become remote. Normally, when a focused
// subframe is hidden, focus is moved to the frame element, but focus should
// stay with the BrowsingContext when performing a process switch. We don't
// need to consider process switches where the hiding docshell is already
// remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
// frame element is handled elsewhere.
if (docShellBeingHidden &&
nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() &&
docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) {
if (mFocusedWindow != window) {
// The window being hidden is an ancestor of the focused window.
#ifdef DEBUG
BrowsingContext* ancestor = window->GetBrowsingContext();
BrowsingContext* bc = mFocusedWindow->GetBrowsingContext();
for (;;) {
if (!bc) {
MOZ_ASSERT(false, "Should have found ancestor");
}
bc = bc->GetParent();
if (ancestor == bc) {
break;
}
}
#endif
// This call adjusts the focused browsing context and window.
// The latter gets nulled out immediately below.
SetFocusedWindowInternal(window, aActionId);
}
mFocusedWindow = nullptr;
window->SetFocusedElement(nullptr);
return;
}
// 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.
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.
if (XRE_IsParentProcess()) {
nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
if (activeWindow == mFocusedWindow || activeWindow == window) {
WindowLowered(activeWindow, aActionId);
} else {
ClearFocus(activeWindow);
}
} else {
BrowsingContext* active = GetActiveBrowsingContext();
if (active) {
if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
active->GetDOMWindow()) {
if ((mFocusedWindow &&
mFocusedWindow->GetBrowsingContext() == active) ||
(window->GetBrowsingContext() == active)) {
WindowLowered(activeWindow, aActionId);
} else {
ClearFocus(activeWindow);
}
} // else do nothing when an out-of-process iframe is torn down
}
}
return;
}
if (!XRE_IsParentProcess() &&
mActiveBrowsingContextInContent ==
docShellBeingHidden->GetBrowsingContext() &&
mActiveBrowsingContextInContent->GetIsInBFCache()) {
SetActiveBrowsingContextInContent(nullptr, aActionId);
}
// 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->GetInProcessParent(getter_AddRefs(parentDsti));
if (parentDsti) {
if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
parentDsti->GetWindow()) {
parentWindow->SetFocusedElement(nullptr);
}
}
}
SetFocusedWindowInternal(window, aActionId);
}
}
void nsFocusManager::FireDelayedEvents(Document* aDocument) {
MOZ_ASSERT(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 Document.cpp:FireOrClearDelayedEvents.
mDelayedBlurFocusEvents.RemoveElementAt(i);
--i;
} else if (!aDocument->EventHandlingSuppressed()) {
EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
nsCOMPtr<EventTarget> relatedTarget =
mDelayedBlurFocusEvents[i].mRelatedTarget;
mDelayedBlurFocusEvents.RemoveElementAt(i);
FireFocusOrBlurEvent(message, presShell, target, false, false,
relatedTarget);
--i;
}
}
}
}
void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) {
MOZ_ASSERT(aWindow, "Expected non-null window.");
MOZ_ASSERT(aWindow != mActiveWindow,
"How come we're nuking a window that's still active?");
if (aWindow == mFocusedWindow) {
mFocusedWindow = nullptr;
SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
mFocusedElement = nullptr;
}
}
nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement)
: mElement(aElement) {}
nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter* aWindow,
const Element& aElement,
int32_t aFocusFlags) {
// If we were explicitly requested to show the ring, do it.
if (aFocusFlags & nsIFocusManager::FLAG_SHOWRING) {
return true;
}
if (aFocusFlags & nsIFocusManager::FLAG_NOSHOWRING) {
return false;
}
if (aWindow->ShouldShowFocusRing()) {
// The window decision also trumps any other heuristic.
return true;
}
// Any element which supports keyboard input (such as an input element, or any
// other element which may trigger a virtual keyboard to be shown on focus if
// a physical keyboard is not present) should always match :focus-visible when
// focused.
{
if (aElement.IsHTMLElement(nsGkAtoms::textarea) || aElement.IsEditable()) {
return true;
}
if (auto* input = HTMLInputElement::FromNode(aElement)) {
if (input->IsSingleLineTextControl()) {
return true;
}
}
}
switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) {
case InputContextAction::CAUSE_KEY:
// If the user interacts with the page via the keyboard, the currently
// focused element should match :focus-visible (i.e. keyboard usage may
// change whether this pseudo-class matches even if it doesn't affect
// :focus).
return true;
case InputContextAction::CAUSE_UNKNOWN:
// We render outlines if the last "known" focus method was by key or there
// was no previous known focus method, otherwise we don't.
return aWindow->UnknownFocusMethodShouldShowOutline();
case InputContextAction::CAUSE_MOUSE:
case InputContextAction::CAUSE_TOUCH:
case InputContextAction::CAUSE_LONGPRESS:
// If the user interacts with the page via a pointing device, such that
// the focus is moved to a new element which does not support user input,
// the newly focused element should not match :focus-visible.
return false;
case InputContextAction::CAUSE_UNKNOWN_CHROME:
case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
// TODO(emilio): We could return some of these though, looking at
// UserActivation. We may want to suppress focus rings for unknown /
// programatic focus if the user is interacting with the page but not
// during keyboard input, or such.
MOZ_ASSERT_UNREACHABLE(
"These don't get returned by GetFocusMoveActionCause");
break;
}
return false;
}
/* static */
void nsFocusManager::NotifyFocusStateChange(Element* aElement,
Element* aElementToFocus,
int32_t aFlags, bool aGettingFocus,
bool aShouldShowFocusRing) {
MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus);
nsIContent* commonAncestor = nullptr;
if (aElementToFocus) {
commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor(
aElement, aElementToFocus);
}
if (aGettingFocus) {
ElementState stateToAdd = ElementState::FOCUS;
if (aShouldShowFocusRing) {
stateToAdd |= ElementState::FOCUSRING;
}
aElement->AddStates(stateToAdd);
for (nsIContent* host = aElement->GetContainingShadowHost(); host;
host = host->GetContainingShadowHost()) {
host->AsElement()->AddStates(ElementState::FOCUS);
}
} else {
constexpr auto kStatesToRemove =
ElementState::FOCUS | ElementState::FOCUSRING;
aElement->RemoveStates(kStatesToRemove);
for (nsIContent* host = aElement->GetContainingShadowHost(); host;
host = host->GetContainingShadowHost()) {
host->AsElement()->RemoveStates(kStatesToRemove);
}
}
// Special case for <input type="checkbox"> and <input type="radio">.
// The other browsers cancel active state when they gets lost focus, but
// does not do it for the other elements such as <button> and <a href="...">.
// Additionally, they may be activated with <label>, but they will get focus
// at `click`, but activated at `mousedown`. Therefore, we need to cancel
// active state at moving focus.
if (RefPtr<nsPresContext> presContext =
aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)) {
RefPtr<EventStateManager> esm = presContext->EventStateManager();
auto* activeInputElement =
HTMLInputElement::FromNodeOrNull(esm->GetActiveContent());
if (activeInputElement &&
(activeInputElement->ControlType() == FormControlType::InputCheckbox ||
activeInputElement->ControlType() == FormControlType::InputRadio) &&
!activeInputElement->State().HasState(ElementState::FOCUS)) {
esm->SetContentState(nullptr, ElementState::ACTIVE);
}
}
for (nsIContent* content = aElement; content && content != commonAncestor;
content = content->GetFlattenedTreeParent()) {
Element* element = Element::FromNode(content);
if (!element) {
continue;
}
if (aGettingFocus) {
if (element->State().HasState(ElementState::FOCUS_WITHIN)) {
break;
}
element->AddStates(ElementState::FOCUS_WITHIN);
} else {
element->RemoveStates(ElementState::FOCUS_WITHIN);
}
}
}
// static
void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) {
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) {
return;
}
RefPtr<PresShell> presShell = docShell->GetPresShell();
if (!presShell) {
return;
}
nsViewManager* vm = presShell->GetViewManager();
if (!vm) {
return;
}
nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
if (!widget) {
return;
}
widget->SetFocus(nsIWidget::Raise::No, aCallerType);
}
void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow,
bool aActive) {
MOZ_ASSERT(XRE_IsParentProcess());
if (!aWindow) {
return;
}
if (BrowsingContext* bc = aWindow->GetBrowsingContext()) {
MOZ_ASSERT(bc->IsTop());
RefPtr<CanonicalBrowsingContext> chromeTop =
bc->Canonical()->TopCrossChromeBoundary();
MOZ_ASSERT(bc == chromeTop);
chromeTop->SetIsActiveBrowserWindow(aActive);
chromeTop->CallOnAllTopDescendants(
[aActive](CanonicalBrowsingContext* aBrowsingContext) -> CallState {
aBrowsingContext->SetIsActiveBrowserWindow(aActive);
return CallState::Continue;
});
}
if (aWindow->GetExtantDoc()) {
nsContentUtils::DispatchEventOnlyToChrome(
aWindow->GetExtantDoc(),
nsGlobalWindowInner::Cast(aWindow->GetCurrentInnerWindow()),
aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes,
Cancelable::eYes, nullptr);
}
}
// Retrieves innerWindowId of the window of the last focused element to
// log a warning to the website console.
void LogWarningFullscreenWindowRaise(Element* aElement) {
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner(do_QueryInterface(aElement));
NS_ENSURE_TRUE_VOID(frameLoaderOwner);
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
NS_ENSURE_TRUE_VOID(frameLoaderOwner);
RefPtr<BrowsingContext> browsingContext = frameLoader->GetBrowsingContext();
NS_ENSURE_TRUE_VOID(browsingContext);
WindowGlobalParent* windowGlobalParent =
browsingContext->Canonical()->GetCurrentWindowGlobal();
NS_ENSURE_TRUE_VOID(windowGlobalParent);
// Log to console
nsAutoString localizedMsg;
nsTArray<nsString> params;
nsresult rv = nsContentUtils::FormatLocalizedString(
nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params,
localizedMsg);
NS_ENSURE_SUCCESS_VOID(rv);
Unused << nsContentUtils::ReportToConsoleByWindowID(
localizedMsg, nsIScriptError::warningFlag, "DOM"_ns,
windowGlobalParent->InnerWindowId(),
windowGlobalParent->GetDocumentURI());
}
// Ensure that when an embedded popup with a noautofocus attribute
// like a date picker is opened and focused, the parent page does not blur
static bool IsEmeddededInNoautofocusPopup(BrowsingContext& aBc) {
auto* embedder = aBc.GetEmbedderElement();
if (!embedder) {
return false;
}
nsIFrame* f = embedder->GetPrimaryFrame();
if (!f || !f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
return false;
}
nsIFrame* menuPopup =
nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::MenuPopup);
MOZ_ASSERT(menuPopup, "NS_FRAME_IN_POPUP lied?");
return static_cast<nsMenuPopupFrame*>(menuPopup)
->PopupElement()
.GetXULBoolAttr(nsGkAtoms::noautofocus);
}
Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
int32_t aFlags,
bool aFocusChanged,
bool aAdjustWidget) {
// if the element is not focusable, just return and leave the focus as is
RefPtr<Element> elementToFocus =
FlushAndCheckIfFocusable(aNewContent, aFlags);
if (!elementToFocus) {
return Nothing();
}
const RefPtr<BrowsingContext> focusedBrowsingContext =
GetFocusedBrowsingContext();
// 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(elementToFocus);
if (subWindow) {
elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
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(elementToFocus);
}
RefPtr<BrowsingContext> newBrowsingContext;
if (newWindow) {
newBrowsingContext = newWindow->GetBrowsingContext();
}
// 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 || (newBrowsingContext == GetFocusedBrowsingContext() &&
elementToFocus == mFocusedElement)) {
return Nothing();
}
MOZ_ASSERT(newBrowsingContext);
BrowsingContext* browsingContextToFocus = newBrowsingContext;
if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(elementToFocus)) {
// Only look at pre-existing browsing contexts. If this function is
// called during reflow, calling GetBrowsingContext() could cause frame
// loader initialization at a time when it isn't safe.
if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
// If focus is already in the subtree rooted at bc, return early
// to match the single-process focus semantics. Otherwise, we'd
// blur and immediately refocus whatever is focused.
BrowsingContext* walk = focusedBrowsingContext;
while (walk) {
if (walk == bc) {
return Nothing();
}
walk = walk->GetParent();
}
browsingContextToFocus = bc;
}
}
// 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 Nothing();
}
bool beingDestroyed;
docShell->IsBeingDestroyed(&beingDestroyed);
<