/* -*- 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 "ChildIterator.h" #include "nsIInterfaceRequestorUtils.h" #include "nsGkAtoms.h" #include "nsGlobalWindow.h" #include "nsContentUtils.h" #include "ContentParent.h" #include "nsPIDOMWindow.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 "nsIObjectFrame.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/WindowGlobalParent.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/HTMLEditor.h" #include "mozilla/IMEStateManager.h" #include "mozilla/LookAndFeel.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 #ifdef MOZ_XUL # include "nsIDOMXULMenuListElement.h" #endif #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::sMouseFocusesFormControl = false; bool nsFocusManager::sTestMode = false; uint64_t nsFocusManager::sFocusActionCounter = 0; static const char* kObservedPrefs[] = { "accessibility.browsewithcaret", "accessibility.tabfocus_applies_to_xul", "accessibility.mouse_focuses_formcontrol", "focusmanager.testmode", nullptr}; nsFocusManager::nsFocusManager() : mActionIdForActiveBrowsingContextInContent(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); sMouseFocusesFormControl = Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false); 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) { static_cast(aSelf)->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("accessibility.mouse_focuses_formcontrol")) { sMouseFocusesFormControl = Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false); } 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; mActiveBrowsingContextInChrome = nullptr; mFocusedWindow = nullptr; mFocusedBrowsingContextInContent = nullptr; mFocusedBrowsingContextInChrome = nullptr; mFocusedElement = nullptr; mFirstBlurEvent = nullptr; mFirstFocusEvent = nullptr; mWindowBeingLowered = nullptr; mDelayedBlurFocusEvents.Clear(); } return NS_OK; } // 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 Element* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) { #ifdef MOZ_XUL if (aContent->IsXULElement()) { nsCOMPtr menulist = aContent->AsElement()->AsXULMenuList(); if (menulist) { RefPtr inputField; menulist->GetInputField(getter_AddRefs(inputField)); return inputField; } } #endif return nullptr; } // static InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause( uint32_t aFlags) { if (aFlags & nsIFocusManager::FLAG_BYTOUCH) { return InputContextAction::CAUSE_TOUCH; } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { return InputContextAction::CAUSE_MOUSE; } else if (aFlags & nsIFocusManager::FLAG_BYKEY) { return InputContextAction::CAUSE_KEY; } 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 (sInstance) { sInstance->SetFocusedWindowWithCallerType( aWindow, aCallerType, sInstance->GenerateFocusActionId()); } } 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, uint64_t aActionId) { LOGFOCUS(("<>")); nsCOMPtr windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus); NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); nsCOMPtr frameElement = windowToFocus->GetFrameElementInternal(); if (frameElement) { // pass false for aFocusChanged so that the caret does not get updated // and scrolling does not occur. SetFocusInner(frameElement, 0, false, true, aActionId); } 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(); if (rootWindow) { RaiseWindow(rootWindow, aCallerType, aActionId); } LOGFOCUS(("<>")); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetFocusedWindow( mozIDOMWindowProxy* aWindowToFocus) { return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System, GenerateFocusActionId()); } NS_IMETHODIMP nsFocusManager::GetFocusedElement(Element** aFocusedElement) { RefPtr focusedElement = mFocusedElement; focusedElement.forget(aFocusedElement); return NS_OK; } NS_IMETHODIMP nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, uint32_t* aLastFocusMethod) { // the focus method is stored on the inner window nsCOMPtr window; if (aWindow) { window = nsPIDOMWindowOuter::From(aWindow); } if (!window) { window = mFocusedWindow; } *aLastFocusMethod = window ? window->GetFocusMethod() : 0; NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod, "invalid focus method"); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) { LOGFOCUS(("<>")); NS_ENSURE_ARG(aElement); SetFocusInner(aElement, aFlags, true, true, GenerateFocusActionId()); 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) != nullptr; 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 & FOCUSMETHOD_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); 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, GenerateFocusActionId()); *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())) { BrowsingContext* bc = window->GetBrowsingContext(); bool isAncestor = (GetFocusedBrowsingContext() != bc); uint64_t actionId = GenerateFocusActionId(); if (Blur(bc, nullptr, isAncestor, true, 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); nsCOMPtr content = window->GetFocusedElement(); if (content) { MoveCaretToFocus(presShell, content); } } } 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]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); 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 (mActiveWindow) { WindowLowered(mActiveWindow, 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()) { WindowLowered(active->GetDOMWindow(), 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, 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 && nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) { bool shouldShowFocusRing = window->ShouldShowFocusRing(); window->SetFocusedElement(nullptr); // if this window is currently focused, clear the global focused // element as well, but don't fire any events. if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) { mFocusedElement = nullptr; } else { // Check if the node that was focused is an iframe or similar by looking // if it has a subdocument. This would indicate that this focused iframe // and its descendants will be going away. We will need to move the // focus somewhere else, so just clear the focus in the toplevel window // so that no element is focused. // // This check does not work correctly in Fission: // https://bugzilla.mozilla.org/show_bug.cgi?id=1613054 Document* subdoc = aDocument->GetSubDocumentFor(content); if (subdoc) { nsCOMPtr docShell = subdoc->GetDocShell(); if (docShell) { nsCOMPtr childWindow = docShell->GetWindow(); if (childWindow && IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) { if (XRE_IsParentProcess()) { ClearFocus(mActiveWindow); } else { BrowsingContext* active = GetActiveBrowsingContext(); if (active) { if (active->IsInProcess()) { ClearFocus(active->GetDOMWindow()); } 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()) { nsCOMPtr docShell = aDocument->GetDocShell(); if (docShell) { RefPtr htmlEditor = docShell->GetHTMLEditor(); if (htmlEditor) { RefPtr selection = htmlEditor->GetSelection(); if (selection && selection->GetFrameSelection() && content == selection->GetFrameSelection()->GetAncestorLimiter()) { htmlEditor->FinalizeSelection(); } } } } NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, 0, /* aGettingFocus = */ false); } 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 (mFocusedWindow != window) { return; } 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]", window.get(), mActiveWindow.get(), mFocusedWindow.get())); 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. RefPtr oldFocusedElement = std::move(mFocusedElement); nsCOMPtr focusedDocShell = mFocusedWindow->GetDocShell(); if (!focusedDocShell) { return; } RefPtr presShell = focusedDocShell->GetPresShell(); if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) { NotifyFocusStateChange(oldFocusedElement, nullptr, mFocusedWindow->ShouldShowFocusRing(), 0, false); window->UpdateCommands(u"focus"_ns, nullptr, 0); if (presShell) { SendFocusOrBlurEvent(eBlur, presShell, oldFocusedElement->GetComposedDoc(), oldFocusedElement, 1, false); } } nsPresContext* focusedPresContext = presShell ? presShell->GetPresContext() : nullptr; IMEStateManager::OnChangeFocus(focusedPresContext, nullptr, GetFocusMoveActionCause(0)); if (presShell) { SetCaretVisible(presShell, false, nullptr); } // if the docshell being hidden is being destroyed, then we want to move // focus somewhere else. Call ClearFocus on the toplevel window, which // will have the effect of clearing the focus and moving the focused window // to the toplevel window. But if the window isn't being destroyed, we are // likely just loading a new document in it, so we want to maintain the // focused window so that the new document gets properly focused. nsCOMPtr docShellBeingHidden = window->GetDocShell(); bool beingDestroyed = !docShellBeingHidden; if (docShellBeingHidden) { docShellBeingHidden->IsBeingDestroyed(&beingDestroyed); } if (beingDestroyed) { // There is usually no need to do anything if a toplevel window is going // away, as we assume that WindowLowered will be called. However, this may // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause // a leak. So if the active window is being destroyed, call WindowLowered // directly. if (XRE_IsParentProcess()) { if (mActiveWindow == mFocusedWindow || mActiveWindow == window) { WindowLowered(mActiveWindow, aActionId); } else { ClearFocus(mActiveWindow); } } else { BrowsingContext* active = GetActiveBrowsingContext(); if (active) { nsPIDOMWindowOuter* activeWindow = active->GetDOMWindow(); if (activeWindow) { 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 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); } } 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; } } } } nsresult nsFocusManager::FocusPlugin(Element* aPlugin) { NS_ENSURE_ARG(aPlugin); SetFocusInner(aPlugin, 0, true, false, GenerateFocusActionId()); return NS_OK; } nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement) : mElement(aElement), mHadRing(aElement.State().HasState(NS_EVENT_STATE_FOCUSRING)) {} nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default; // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo static bool ShouldMatchFocusVisible( const Element& aElement, int32_t aFocusFlags, const Maybe& aBlurredElementInfo) { // 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: // If the active element matches :focus-visible, and a script causes focus // to move elsewhere, the newly focused element should match // :focus-visible. // // Conversely, if the active element does not match :focus-visible, and a // script causes focus to move elsewhere, the newly focused element should // not match :focus-visible. return !aBlurredElementInfo || aBlurredElementInfo->mHadRing; 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 bool ShouldFocusRingBeVisible( Element& aElement, int32_t aFlags, const Maybe& aBlurredElementInfo) { if (aFlags & nsIFocusManager::FLAG_SHOWRING) { return true; } const bool focusVisibleEnabled = StaticPrefs::layout_css_focus_visible_enabled(); #if defined(XP_MACOSX) || defined(ANDROID) if (!focusVisibleEnabled) { // Preserve historical behavior if the focus visible pseudo-class is not // enabled. if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { return !nsContentUtils::ContentIsLink(&aElement) && !aElement.IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio); } return true; } #endif return focusVisibleEnabled && ShouldMatchFocusVisible(aElement, aFlags, aBlurredElementInfo); } /* static */ void nsFocusManager::NotifyFocusStateChange( Element* aElement, Element* aElementToFocus, bool aWindowShouldShowFocusRing, int32_t aFlags, bool aGettingFocus, const Maybe& aBlurredElementInfo) { MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus); nsIContent* commonAncestor = nullptr; if (aElementToFocus) { commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor( aElement, aElementToFocus); } if (aGettingFocus) { EventStates eventStateToAdd = NS_EVENT_STATE_FOCUS; if (aWindowShouldShowFocusRing || ShouldFocusRingBeVisible(*aElement, aFlags, aBlurredElementInfo)) { eventStateToAdd |= NS_EVENT_STATE_FOCUSRING; } aElement->AddStates(eventStateToAdd); } else { EventStates eventStateToRemove = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING; aElement->RemoveStates(eventStateToRemove); } for (nsIContent* content = aElement; content && content != commonAncestor; content = content->GetFlattenedTreeParent()) { Element* element = Element::FromNode(content); if (!element) { continue; } if (aGettingFocus) { if (element->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN)) { break; } element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN); } else { element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN); } } } // static void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) { if (!mFocusedWindow || sTestMode) return; // get the main child widget for the focused window and ensure that the // platform knows that this widget is focused. nsCOMPtr docShell = mFocusedWindow->GetDocShell(); if (!docShell) { return; } RefPtr presShell = docShell->GetPresShell(); if (!presShell) { return; } nsViewManager* vm = presShell->GetViewManager(); if (!vm) { return; } nsCOMPtr widget; vm->GetRootWidget(getter_AddRefs(widget)); if (!widget) { return; } widget->SetFocus(nsIWidget::Raise::No, aCallerType); } void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive) { MOZ_ASSERT(XRE_IsParentProcess()); if (!aWindow) { return; } if (BrowsingContext* bc = aWindow->GetBrowsingContext()) { MOZ_ASSERT(bc->IsTop()); RefPtr chromeTop = bc->Canonical()->TopCrossChromeBoundary(); MOZ_ASSERT(bc == chromeTop); chromeTop->SetIsActiveBrowserWindow(aActive); chromeTop->CallOnAllTopDescendants( [aActive](CanonicalBrowsingContext* aBrowsingContext) -> CallState { aBrowsingContext->SetIsActiveBrowserWindow(aActive); return CallState::Continue; }); } if (aWindow->GetExtantDoc()) { nsContentUtils::DispatchEventOnlyToChrome( aWindow->GetExtantDoc(), aWindow->GetCurrentInnerWindow(), aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes, Cancelable::eYes, nullptr); } } // Retrieves innerWindowId of the window of the last focused element to // log a warning to the website console. void LogWarningFullscreenWindowRaise(Element* aElement) { nsCOMPtr frameLoaderOwner(do_QueryInterface(aElement)); NS_ENSURE_TRUE_VOID(frameLoaderOwner); RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); NS_ENSURE_TRUE_VOID(frameLoaderOwner); RefPtr browsingContext = frameLoader->GetBrowsingContext(); NS_ENSURE_TRUE_VOID(browsingContext); WindowGlobalParent* windowGlobalParent = browsingContext->Canonical()->GetCurrentWindowGlobal(); NS_ENSURE_TRUE_VOID(windowGlobalParent); // Log to console nsAutoString localizedMsg; nsTArray params; nsresult rv = nsContentUtils::FormatLocalizedString( nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params, localizedMsg); NS_ENSURE_SUCCESS_VOID(rv); Unused << nsContentUtils::ReportToConsoleByWindowID( localizedMsg, nsIScriptError::warningFlag, "DOM"_ns, windowGlobalParent->InnerWindowId(), windowGlobalParent->GetDocumentURI()); } void nsFocusManager::SetFocusInner(Element* aNewContent, int32_t aFlags, bool aFocusChanged, bool aAdjustWidget, uint64_t aActionId) { // if the element is not focusable, just return and leave the focus as is RefPtr elementToFocus = FlushAndCheckIfFocusable(aNewContent, aFlags); if (!elementToFocus) { return; } RefPtr focusedBrowsingContext = GetFocusedBrowsingContext(); // check if the element to focus is a frame (iframe) containing a child // document. Frames are never directly focused; instead focusing a frame // means focus what is inside the frame. To do this, the descendant content // within the frame is retrieved and that will be focused instead. nsCOMPtr newWindow; nsCOMPtr subWindow = GetContentWindow(elementToFocus); if (subWindow) { // XXX What if this is an out-of-process iframe? // https://bugzilla.mozilla.org/show_bug.cgi?id=1613054 elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants, getter_AddRefs(newWindow)); // since a window is being refocused, clear aFocusChanged so that the // caret position isn't updated. aFocusChanged = false; } // unless it was set above, retrieve the window for the element to focus if (!newWindow) { newWindow = GetCurrentWindow(elementToFocus); } BrowsingContext* newBrowsingContext = nullptr; if (newWindow) { newBrowsingContext = newWindow->GetBrowsingContext(); } // if the element is already focused, just return. Note that this happens // after the frame check above so that we compare the element that will be // focused rather than the frame it is in. if (!newWindow || (newBrowsingContext == GetFocusedBrowsingContext() && elementToFocus == mFocusedElement)) { return; } MOZ_ASSERT(newBrowsingContext); BrowsingContext* browsingContextToFocus = newBrowsingContext; if (RefPtr flo = do_QueryObject(elementToFocus)) { // Only look at pre-existing browsing contexts. If this function is // called during reflow, calling GetBrowsingContext() could cause frame // loader initialization at a time when it isn't safe. if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) { browsingContextToFocus = bc; } } // don't allow focus to be placed in docshells or descendants of docshells // that are being destroyed. Also, ensure that the page hasn't been // unloaded. The prevents content from being refocused during an unload event. nsCOMPtr newDocShell = newWindow->GetDocShell(); nsCOMPtr docShell = newDocShell; while (docShell) { bool inUnload; docShell->GetIsInUnload(&inUnload); if (inUnload) { return; } bool beingDestroyed; docShell->IsBeingDestroyed(&beingDestroyed); if (beingDestroyed) { return; } BrowsingContext* bc = docShell->GetBrowsingContext(); nsCOMPtr parentDsti; docShell->GetInProcessParent(getter_AddRefs(parentDsti)); docShell = do_QueryInterface(parentDsti); if (!docShell && !XRE_IsParentProcess()) { // We don't have an in-process parent, but let's see if we have // an in-process ancestor or if an out-of-process ancestor // is discarded. do { bc = bc->GetParent(); if (bc && bc->IsDiscarded()) { return; } } while (bc && !bc->IsInProcess()); if (bc) { docShell = bc->GetDocShell(); } else { docShell = nullptr; } } } bool focusMovesToDifferentBC = (focusedBrowsingContext != browsingContextToFocus); if (focusedBrowsingContext && focusMovesToDifferentBC && nsContentUtils::IsHandlingKeyBoardEvent() && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { MOZ_ASSERT(browsingContextToFocus, "BrowsingContext to focus should be non-null."); nsIPrincipal* focusedPrincipal = nullptr; nsIPrincipal* newPrincipal = nullptr; if (XRE_IsParentProcess()) { if (WindowGlobalParent* focusedWindowGlobalParent = focusedBrowsingContext->Canonical()->GetCurrentWindowGlobal()) { focusedPrincipal = focusedWindowGlobalParent->DocumentPrincipal(); } if (WindowGlobalParent* newWindowGlobalParent = browsingContextToFocus->Canonical()->GetCurrentWindowGlobal()) { newPrincipal = newWindowGlobalParent->DocumentPrincipal(); } } else if (focusedBrowsingContext->IsInProcess() && browsingContextToFocus->IsInProcess()) { nsCOMPtr focused = do_QueryInterface(focusedBrowsingContext->GetDOMWindow()); nsCOMPtr newFocus = do_QueryInterface(browsingContextToFocus->GetDOMWindow()); MOZ_ASSERT(focused && newFocus, "BrowsingContext should always have a window here."); focusedPrincipal = focused->GetPrincipal(); newPrincipal = newFocus->GetPrincipal(); } if (!focusedPrincipal || !newPrincipal) { return; } if (!focusedPrincipal->Subsumes(newPrincipal)) { NS_WARNING("Not allowed to focus the new window!"); return; } } // to check if the new element is in the active window, compare the // new root docshell for the new element with the active window's docshell. RefPtr newRootBrowsingContext = nullptr; bool isElementInActiveWindow = false; if (XRE_IsParentProcess()) { nsCOMPtr newRootWindow = nullptr; nsCOMPtr dsti = newWindow->GetDocShell(); if (dsti) { nsCOMPtr root; dsti->GetInProcessRootTreeItem(getter_AddRefs(root)); newRootWindow = root ? root->GetWindow() : nullptr; isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow); } if (newRootWindow) { newRootBrowsingContext = newRootWindow->GetBrowsingContext(); } } else { // XXX This is wrong for `