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 "nsThreadUtils.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 "nsIXULRuntime.h"
#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/FocusModel.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/HTMLAreaElement.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_accessibility.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",
"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();
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("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) {
if (Document* doc = aContent->GetComposedDoc()) {
if (Document* subdoc = doc->GetSubDocumentFor(aContent)) {
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;
}
NS_IMETHODIMP
nsFocusManager::GetActiveContentBrowsingContext(
BrowsingContext** aBrowsingContext) {
MOZ_DIAGNOSTIC_ASSERT(
XRE_IsParentProcess(),
"We only have use cases for this in the parent process");
NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContextInChrome());
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> existingActionId;
if (frameElement) {
// pass false for aFocusChanged so that the caret does not get updated
// and scrolling does not occur.
existingActionId = SetFocusInner(frameElement, 0, false, true);
} else if (auto* bc = windowToFocus->GetBrowsingContext();
bc && !bc->IsTop()) {
// No frameElement means windowToFocus is an OOP iframe, so
// the above SetFocusInner is not called. That means the focus
// of the currently focused BC is not going to be cleared. So
// we do that manually here.
if (RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext()) {
// If focusedBC is an ancestor of bc, blur will be handled
// correctly by nsFocusManager::AdjustWindowFocus.
if (!IsSameOrAncestor(focusedBC, bc)) {
existingActionId.emplace(sInstance->GenerateFocusActionId());
Blur(focusedBC, nullptr, true, true, false, existingActionId.value());
}
}
} 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.
if (Element* el = windowToFocus->GetFocusedElement()) {
if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(el)) {
ClearFocus(windowToFocus);
}
}
}
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
const uint64_t actionId = existingActionId.isSome()
? existingActionId.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();
RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext();
const bool isAncestor = (focusedBC != bc);
RefPtr<BrowsingContext> ancestorBC = isAncestor ? bc : nullptr;
if (Blur(focusedBC, ancestorBC, isAncestor, true, false,
GenerateFocusActionId())) {
// 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) {
// Intentionally use a new actionId here because the above
// Blur() will clear the focus of the ancestors of focusedBC, and
// this Focus() call might need to update the focus of those ancestors,
// so it needs to have a newer actionId to make that happen.
Focus(window, nullptr, 0, true, false, false, true,
GenerateFocusActionId());
}
}
} 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,
false /* aIsEnteringBFCache */);
}
// ensure that the window is enabled and visible
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner)) {
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.
MoveFocusToWindowAfterRaise(window, aActionId);
}
void nsFocusManager::MoveFocusToWindowAfterRaise(nsPIDOMWindowOuter* aWindow,
uint64_t aActionId) {
nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
RefPtr<Element> currentFocus = GetFocusedDescendant(
aWindow, eIncludeAllDescendants, getter_AddRefs(currentWindow));
NS_ASSERTION(currentWindow, "window raised with no window current");
if (!currentWindow) {
return;
}
// 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, /* aFlags = */ 0,
/* aIsNewDocument = */ currentWindow != mFocusedWindow,
/* aFocusChanged = */ false,
/* aWindowRaised = */ true, /* aAdjustWidget = */ 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,
false /* aIsEnteringBFCache */);
}
}
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);
nsPIDOMWindowOuter* windowPtr = aDocument->GetWindow();
if (!windowPtr) {
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.
Element* previousFocusedElementPtr = windowPtr->GetFocusedElement();
if (!previousFocusedElementPtr) {
return NS_OK;
}
if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
previousFocusedElementPtr, aContent)) {
return NS_OK;
}
RefPtr<nsPIDOMWindowOuter> window = windowPtr;
RefPtr<Element> previousFocusedElement = previousFocusedElementPtr;
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 (nsIDocShell* const docShell = aDocument->GetDocShell()) {
if (HTMLEditor* const htmlEditor = docShell->GetHTMLEditor()) {
Selection* const selection = htmlEditor->GetSelection();
if (selection && selection->GetFrameSelection() &&
previousFocusedElement ==
selection->GetFrameSelection()->GetAncestorLimiter()) {
// The editing host may be being removed right now. So, it's already
// removed from the child chain of the parent node, but it still know
// the parent node. This could cause unexpected result at scheduling
// paint of the caret. Therefore, we should call FinalizeSelection
// after unblocking to run the script.
nsContentUtils::AddScriptRunner(
NewRunnableMethod("HTMLEditor::FinalizeSelection", htmlEditor,
&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()) {