/* -*- 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 "nsFocusManager.h" #include #include "AncestorIterator.h" #include "BrowserChild.h" #include "ChildIterator.h" #include "ContentParent.h" #include "LayoutConstants.h" #include "mozilla/AccessibleCaretEventHub.h" #include "mozilla/ContentEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/FocusModel.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/StaticPrefs_accessibility.h" #include "mozilla/StaticPrefs_full_screen_api.h" #include "mozilla/dom/BrowserBridgeChild.h" #include "mozilla/dom/BrowserParent.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/HTMLAreaElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/Navigation.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/XULPopupElement.h" #include "mozilla/widget/IMEData.h" #include "nsCaret.h" #include "nsContentUtils.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsFrameSelection.h" #include "nsFrameTraversal.h" #include "nsGkAtoms.h" #include "nsHTMLDocument.h" #include "nsIAppWindow.h" #include "nsIBaseWindow.h" #include "nsIContentInlines.h" #include "nsIDOMXULMenuListElement.h" #include "nsIDocShell.h" #include "nsIDocShellTreeOwner.h" #include "nsIFormControl.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIObserverService.h" #include "nsIPrincipal.h" #include "nsIScriptError.h" #include "nsIScriptObjectPrincipal.h" #include "nsIWebNavigation.h" #include "nsIXULRuntime.h" #include "nsLayoutUtils.h" #include "nsMenuPopupFrame.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsQueryObject.h" #include "nsRange.h" #include "nsTextControlFrame.h" #include "nsThreadUtils.h" #include "nsXULPopupManager.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, mWindowBeingLowered, mDelayedBlurFocusEvents) StaticRefPtr 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 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 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("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; 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 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; } 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(("<>")); nsCOMPtr windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus); NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); nsCOMPtr frameElement = windowToFocus->GetFrameElementInternal(); Maybe 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 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 childWindow = GetContentWindow(el)) { ClearFocus(windowToFocus); } } } nsCOMPtr rootWindow = windowToFocus->GetPrivateRoot(); const uint64_t actionId = existingActionId.isSome() ? existingActionId.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(); RefPtr focusedBC = GetFocusedBrowsingContext(); const bool isAncestor = (focusedBC != bc); RefPtr 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(("<>")); 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, false /* aIsEnteringBFCache */); } // ensure that the window is enabled and visible nsCOMPtr treeOwner; docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); if (nsCOMPtr 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 currentWindow; RefPtr 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 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, false /* aIsEnteringBFCache */); } } if (mFocusedWindow) { Blur(nullptr, nullptr, true, true, false, aActionId); } mWindowBeingLowered = nullptr; } void nsFocusManager::FocusedElementMayHaveMoved(nsIContent* aContent, nsINode* aOldParent) { if (!aOldParent) { return; } if (aOldParent->IsElement() && !aOldParent->AsElement()->State().HasState(ElementState::FOCUS_WITHIN)) { return; } nsPIDOMWindowOuter* window = aContent->OwnerDoc()->GetWindow(); if (!window) { return; } Element* focusedElement = window->GetFocusedElement(); if (!focusedElement) { return; } if (!nsContentUtils::ContentIsHostIncludingDescendantOf(focusedElement, aContent)) { return; } if (aOldParent->IsElement()) { // Clear the old ancestor chain. NotifyFocusStateChange(aOldParent->AsElement(), nullptr, 0, false, false); } // XXX This is not very optimal. // Clear the ancestor chain of focused element. NotifyFocusStateChange(focusedElement, nullptr, 0, false, false); // And set the correct states. NotifyFocusStateChange(focusedElement, nullptr, 0, true, false); } void nsFocusManager::ContentInserted(nsIContent* aChild, const ContentInsertInfo& aInfo) { FocusedElementMayHaveMoved(aChild, aInfo.mOldParent); } void nsFocusManager::ContentAppended(nsIContent* aFirstNewContent, const ContentAppendInfo& aInfo) { FocusedElementMayHaveMoved(aFirstNewContent, aInfo.mOldParent); } static void UpdateFocusWithinState(Element* aElement, nsIContent* aCommonAncestor, bool aGettingFocus) { Element* focusedElement = nullptr; Document* document = aElement->GetComposedDoc(); if (aElement && document) { if (nsPIDOMWindowOuter* window = document->GetWindow()) { focusedElement = window->GetFocusedElement(); } } bool focusChanged = false; for (nsIContent* content = aElement; content && content != aCommonAncestor; 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); } focusChanged = focusChanged || element == focusedElement; } if (focusChanged && document->GetInnerWindow()) { if (RefPtr navigation = document->GetInnerWindow()->Navigation()) { navigation->SetFocusedChangedDuringOngoingNavigation( /* aFocusChangedDuringOngoingNavigation */ true); } } } static void MaybeFixUpFocusWithinState(Element* aElementToFocus, Element* aFocusedElement) { if (!aElementToFocus || aElementToFocus == aFocusedElement || !aElementToFocus->IsInComposedDoc()) { return; } // Focus was redirected, make sure the :focus-within state remains consistent. auto* commonAncestor = [&]() -> nsIContent* { if (!aFocusedElement || aElementToFocus->OwnerDoc() != aFocusedElement->OwnerDoc()) { return nullptr; } return nsContentUtils::GetCommonFlattenedTreeAncestor(aFocusedElement, aElementToFocus); }(); UpdateFocusWithinState(aElementToFocus, commonAncestor, false); } nsresult nsFocusManager::ContentRemoved(Document* aDocument, nsIContent* aContent, const ContentRemoveInfo& aInfo) { MOZ_ASSERT(aDocument); MOZ_ASSERT(aContent); if (aInfo.mNewParent) { // Handled upon insertion in ContentAppended/Inserted. return NS_OK; } nsPIDOMWindowOuter* windowPtr = aDocument->GetWindow(); if (!windowPtr) { return NS_OK; } Element* focusWithinElement = [&]() -> Element* { if (auto* el = Element::FromNode(aContent)) { return el; } if (auto* shadow = ShadowRoot::FromNode(aContent)) { // Note that we only get here with ShadowRoots for shadow roots of form // controls that we can un-attach. So if there's a focused element it must // be inside our shadow tree already. return shadow->Host(); } // Removing text / comments / etc can't affect the focus state. return nullptr; }(); if (!focusWithinElement) { return NS_OK; } const bool hasFocusWithinInThisDocument = focusWithinElement->State().HasAtLeastOneOfStates( ElementState::FOCUS | ElementState::FOCUS_WITHIN); // 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) { if (hasFocusWithinInThisDocument) { // If we're in-between a blur and an incoming focus, we might have stale // :focus-within in our ancestor chain. Fix it up now. UpdateFocusWithinState(focusWithinElement, nullptr, false); } return NS_OK; } if (previousFocusedElementPtr->State().HasState(ElementState::FOCUS)) { if (!hasFocusWithinInThisDocument) { // If the focused element has :focus, that means our ancestor should have // focus-within. return NS_OK; } } else if (!nsContentUtils::ContentIsFlattenedTreeDescendantOf( previousFocusedElementPtr, focusWithinElement)) { // Otherwise, previousFocusedElementPtr could be an