From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/base/nsFocusManager.cpp | 5443 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5443 insertions(+) create mode 100644 dom/base/nsFocusManager.cpp (limited to 'dom/base/nsFocusManager.cpp') diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp new file mode 100644 index 0000000000..ead05d9103 --- /dev/null +++ b/dom/base/nsFocusManager.cpp @@ -0,0 +1,5443 @@ +/* -*- 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 "nsGlobalWindow.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/Document.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/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 + +#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 mPresShell; + nsCOMPtr mDocument; + nsCOMPtr mTarget; + EventMessage mEventMessage; + nsCOMPtr 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(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::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 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 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 fm = static_cast(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 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 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(("<>")); + + nsCOMPtr windowToFocus = + nsPIDOMWindowOuter::From(aWindowToFocus); + NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); + + nsCOMPtr frameElement = windowToFocus->GetFrameElementInternal(); + Maybe 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 childWindow = GetContentWindow(content)) + ClearFocus(windowToFocus); + } + } + + nsCOMPtr rootWindow = windowToFocus->GetPrivateRoot(); + const uint64_t actionId = actionIdFromSetFocusInner.isSome() + ? actionIdFromSetFocusInner.value() + : sInstance->GenerateFocusActionId(); + if (rootWindow) { + RaiseWindow(rootWindow, aCallerType, actionId); + } + + LOGFOCUS(("<>", actionId)); + + return NS_OK; +} + +NS_IMETHODIMP nsFocusManager::SetFocusedWindow( + mozIDOMWindowProxy* aWindowToFocus) { + return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System); +} + +NS_IMETHODIMP +nsFocusManager::GetFocusedElement(Element** aFocusedElement) { + RefPtr 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(("<>")); + + NS_ENSURE_ARG(aElement); + + SetFocusInner(aElement, aFlags, true, true); + + LOGFOCUS(("<>")); + + 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(("<>", 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 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 doc = window->GetExtantDoc()) { + doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames); + } + + bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME; + nsCOMPtr 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(("<>")); + + return NS_OK; +} + +NS_IMETHODIMP +nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) { + LOGFOCUS(("<>")); + + // 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 window = nsPIDOMWindowOuter::From(aWindow); + + if (IsSameOrAncestor(window, GetFocusedBrowsingContext())) { + RefPtr 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(("<>")); + + 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 window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr focusedWindow; + RefPtr 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 webnav = do_GetInterface(aWindow); + nsCOMPtr dsti = do_QueryInterface(webnav); + if (dsti) { + if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { + nsCOMPtr 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 = docShell->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); + if (RefPtr focusedElement = window->GetFocusedElement()) { + MoveCaretToFocus(presShell, focusedElement); + } + } + } + + return NS_OK; +} + +void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow, + uint64_t aActionId) { + if (!aWindow) { + return; + } + + nsCOMPtr 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 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 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 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 treeOwner; + docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); + nsCOMPtr 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 currentWindow; + RefPtr currentFocus = GetFocusedDescendant( + window, eIncludeAllDescendants, getter_AddRefs(currentWindow)); + + NS_ASSERTION(currentWindow, "window raised with no window current"); + if (!currentWindow) { + return; + } + + nsCOMPtr 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 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 docShell = mFocusedWindow->GetDocShell(); + if (docShell) { + if (PresShell* presShell = docShell->GetPresShell()) { + RefPtr 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); + + 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. + Element* content = window->GetFocusedElement(); + if (!content) { + return NS_OK; + } + + if (!nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) { + return NS_OK; + } + + 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(content)) { + // 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 docShell = subdoc->GetDocShell()) { + nsCOMPtr childWindow = docShell->GetWindow(); + if (childWindow && + IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) { + if (XRE_IsParentProcess()) { + nsCOMPtr activeWindow = mActiveWindow; + ClearFocus(activeWindow); + } else { + BrowsingContext* active = GetActiveBrowsingContext(); + if (active) { + if (active->IsInProcess()) { + nsCOMPtr 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 (content->IsEditable()) { + if (nsCOMPtr docShell = aDocument->GetDocShell()) { + if (RefPtr htmlEditor = docShell->GetHTMLEditor()) { + RefPtr selection = htmlEditor->GetSelection(); + if (selection && selection->GetFrameSelection() && + content == selection->GetFrameSelection()->GetAncestorLimiter()) { + htmlEditor->FinalizeSelection(); + } + } + } + } + + if (!newFocusedElement) { + NotifyFocusStateChange(content, newFocusedElement, 0, + /* aGettingFocus = */ false, false); + } else { + // We should already have the right state, which is managed by the + // widget. + MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS)); + } + return NS_OK; +} + +void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, + bool aNeedsFocus) { + if (!aWindow) { + return; + } + + nsCOMPtr 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 currentWindow; + RefPtr 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 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 oldFocusedElement = std::move(mFocusedElement); + + nsCOMPtr focusedDocShell = mFocusedWindow->GetDocShell(); + if (!focusedDocShell) { + return; + } + + const RefPtr presShell = focusedDocShell->GetPresShell(); + + if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) { + NotifyFocusStateChange(oldFocusedElement, nullptr, 0, false, false); + window->UpdateCommands(u"focus"_ns, nullptr, 0); + + if (presShell) { + RefPtr composedDoc = oldFocusedElement->GetComposedDoc(); + SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement, + false); + } + } + + const RefPtr 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 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 activeWindow = mActiveWindow; + if (activeWindow == mFocusedWindow || activeWindow == window) { + WindowLowered(activeWindow, aActionId); + } else { + ClearFocus(activeWindow); + } + } else { + BrowsingContext* active = GetActiveBrowsingContext(); + if (active) { + if (nsCOMPtr 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 dsti = + mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr; + if (dsti) { + nsCOMPtr parentDsti; + dsti->GetInProcessParent(getter_AddRefs(parentDsti)); + if (parentDsti) { + if (nsCOMPtr 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 target = mDelayedBlurFocusEvents[i].mTarget; + RefPtr presShell = mDelayedBlurFocusEvents[i].mPresShell; + nsCOMPtr 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; + +// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo +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 and . + // The other browsers cancel active state when they gets lost focus, but + // does not do it for the other elements such as