/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/TabParent.h" #include "nsFocusManager.h" #include "AccessibleCaretEventHub.h" #include "nsIInterfaceRequestorUtils.h" #include "nsGkAtoms.h" #include "nsContentUtils.h" #include "nsIDocument.h" #include "nsIEditor.h" #include "nsPIDOMWindow.h" #include "nsIDOMChromeWindow.h" #include "nsIDOMElement.h" #include "nsIDOMDocument.h" #include "nsIDOMRange.h" #include "nsIHTMLDocument.h" #include "nsIDocShell.h" #include "nsIDocShellTreeOwner.h" #include "nsIFormControl.h" #include "nsLayoutUtils.h" #include "nsIPresShell.h" #include "nsFrameTraversal.h" #include "nsIWebNavigation.h" #include "nsCaret.h" #include "nsIBaseWindow.h" #include "nsIXULWindow.h" #include "nsViewManager.h" #include "nsFrameSelection.h" #include "mozilla/dom/Selection.h" #include "nsXULPopupManager.h" #include "nsMenuPopupFrame.h" #include "nsIScriptObjectPrincipal.h" #include "nsIPrincipal.h" #include "nsIObserverService.h" #include "nsIObjectFrame.h" #include "nsBindingManager.h" #include "nsStyleCoord.h" #include "TabChild.h" #include "nsFrameLoader.h" #include "nsNumberControlFrame.h" #include "mozilla/ContentEvents.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/IMEStateManager.h" #include "mozilla/LookAndFeel.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/Unused.h" #include #include "nsIDOMXULTextboxElement.h" #include "nsIDOMXULMenuListElement.h" #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif #include "nsIScriptError.h" 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(NS_LITERAL_CSTRING("(none)")); \ 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, nsIPresShell* aPresShell, nsIDocument* 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) { } nsCOMPtr 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, 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(nsFocusManager, mActiveWindow, mFocusedWindow, mFocusedContent, mFirstBlurEvent, mFirstFocusEvent, mWindowBeingLowered, mDelayedBlurFocusEvents, mMouseButtonEventHandlingDocument) nsFocusManager* nsFocusManager::sInstance = nullptr; bool nsFocusManager::sMouseFocusesFormControl = false; bool nsFocusManager::sTestMode = false; static const char* kObservedPrefs[] = { "accessibility.browsewithcaret", "accessibility.tabfocus_applies_to_xul", "accessibility.mouse_focuses_formcontrol", "focusmanager.testmode", nullptr }; nsFocusManager::nsFocusManager() { } nsFocusManager::~nsFocusManager() { Preferences::RemoveObservers(this, kObservedPrefs); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(this, "xpcom-shutdown"); } } // static nsresult nsFocusManager::Init() { nsFocusManager* fm = new nsFocusManager(); NS_ADDREF(fm); sInstance = fm; 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::AddWeakObservers(fm, kObservedPrefs); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->AddObserver(fm, "xpcom-shutdown", true); } return NS_OK; } // static void nsFocusManager::Shutdown() { NS_IF_RELEASE(sInstance); } NS_IMETHODIMP nsFocusManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { nsDependentString data(aData); if (data.EqualsLiteral("accessibility.browsewithcaret")) { UpdateCaretForCaretBrowsingMode(); } else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) { nsIContent::sTabFocusModelAppliesToXUL = Preferences::GetBool("accessibility.tabfocus_applies_to_xul", nsIContent::sTabFocusModelAppliesToXUL); } else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) { sMouseFocusesFormControl = Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false); } else if (data.EqualsLiteral("focusmanager.testmode")) { sTestMode = Preferences::GetBool("focusmanager.testmode", false); } } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { mActiveWindow = nullptr; mFocusedWindow = nullptr; mFocusedContent = nullptr; mFirstBlurEvent = nullptr; mFirstFocusEvent = nullptr; mWindowBeingLowered = nullptr; mDelayedBlurFocusEvents.Clear(); mMouseButtonEventHandlingDocument = nullptr; } return NS_OK; } // given a frame content node, retrieve the nsIDOMWindow displayed in it static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) { nsIDocument* doc = aContent->GetComposedDoc(); if (doc) { nsIDocument* subdoc = doc->GetSubDocumentFor(aContent); if (subdoc) return subdoc->GetWindow(); } return nullptr; } // get the current window for the given content node static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) { nsIDocument* doc = aContent->GetComposedDoc(); return doc ? doc->GetWindow() : nullptr; } // static nsIContent* nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep, nsPIDOMWindowOuter** aFocusedWindow) { NS_ENSURE_TRUE(aWindow, nullptr); *aFocusedWindow = nullptr; nsIContent* currentContent = nullptr; nsPIDOMWindowOuter* window = aWindow; while (window) { *aFocusedWindow = window; currentContent = window->GetFocusedNode(); if (!currentContent || !aDeep) break; window = GetContentWindow(currentContent); } NS_IF_ADDREF(*aFocusedWindow); return currentContent; } // static nsIContent* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) { // For input number, redirect focus to our anonymous text control. if (aContent->IsHTMLElement(nsGkAtoms::input)) { bool typeIsNumber = static_cast(aContent)->GetType() == NS_FORM_INPUT_NUMBER; if (typeIsNumber) { nsNumberControlFrame* numberControlFrame = do_QueryFrame(aContent->GetPrimaryFrame()); if (numberControlFrame) { HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl(); return static_cast(textControl); } } } if (aContent->IsXULElement()) { nsCOMPtr inputField; nsCOMPtr textbox = do_QueryInterface(aContent); if (textbox) { textbox->GetInputField(getter_AddRefs(inputField)); } else { nsCOMPtr menulist = do_QueryInterface(aContent); if (menulist) { menulist->GetInputField(getter_AddRefs(inputField)); } else if (aContent->IsXULElement(nsGkAtoms::scale)) { nsCOMPtr doc = aContent->GetComposedDoc(); if (!doc) return nullptr; nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent); if (children) { nsIContent* child = children->Item(0); if (child && child->IsXULElement(nsGkAtoms::slider)) return child; } } } if (inputField) { nsCOMPtr retval = do_QueryInterface(inputField); return retval; } } 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; } return InputContextAction::CAUSE_UNKNOWN; } NS_IMETHODIMP nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) { NS_IF_ADDREF(*aWindow = mActiveWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow) { NS_ENSURE_STATE(aWindow); // only top-level windows can be made active nsCOMPtr piWindow = nsPIDOMWindowOuter::From(aWindow); NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG); RaiseWindow(piWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) { NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetFocusedWindow(mozIDOMWindowProxy* aWindowToFocus) { LOGFOCUS(("<>")); nsCOMPtr windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus); NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); windowToFocus = windowToFocus->GetOuterWindow(); 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); } 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->GetFocusedNode(); if (content) { if (nsCOMPtr childWindow = GetContentWindow(content)) ClearFocus(windowToFocus); } } nsCOMPtr rootWindow = windowToFocus->GetPrivateRoot(); if (rootWindow) RaiseWindow(rootWindow); LOGFOCUS(("<>")); return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement) { if (mFocusedContent) CallQueryInterface(mFocusedContent, aFocusedElement); else *aFocusedElement = nullptr; 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(nsIDOMElement* aElement, uint32_t aFlags) { LOGFOCUS(("<>")); nsCOMPtr newFocus = do_QueryInterface(aElement); NS_ENSURE_ARG(newFocus); SetFocusInner(newFocus, aFlags, true, true); LOGFOCUS(("<>")); return NS_OK; } NS_IMETHODIMP nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags, bool* aIsFocusable) { NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG); nsCOMPtr aContent = do_QueryInterface(aElement); *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr; return NS_OK; } NS_IMETHODIMP nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, nsIDOMElement* aStartElement, uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement) { *aElement = nullptr; LOGFOCUS(("<>", aType, aFlags)); if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) { nsIDocument* doc = mFocusedWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), doc->GetDocumentURI()->GetSpecOrDefault().get())); } } LOGCONTENT(" Current Focus: %s", mFocusedContent.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; nsCOMPtr startContent; if (aStartElement) { startContent = do_QueryInterface(aStartElement); NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG); window = GetCurrentWindow(startContent); } else { window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get(); NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); } NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME; nsCOMPtr newFocus; nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal, 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) { // 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(newFocus, aFlags, aType != MOVEFOCUS_CARET, true); CallQueryInterface(newFocus, aElement); } 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, mFocusedWindow)) { bool isAncestor = (window != mFocusedWindow); if (Blur(window, nullptr, isAncestor, true)) { // 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); } } else { window->SetFocusedNode(nullptr); } LOGFOCUS(("<>")); return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow, bool aDeep, mozIDOMWindowProxy** aFocusedWindow, nsIDOMElement** aElement) { *aElement = nullptr; if (aFocusedWindow) *aFocusedWindow = nullptr; NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); nsCOMPtr focusedWindow; nsCOMPtr focusedContent = GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow)); if (focusedContent) CallQueryInterface(focusedContent, 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; nsCOMPtr presShell = docShell->GetPresShell(); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); nsCOMPtr content = window->GetFocusedNode(); if (content) MoveCaretToFocus(presShell, content); } } return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow) { NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); nsIDocument* 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 (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(); return NS_OK; } // lower the existing window, if any. This shouldn't happen usually. if (mActiveWindow) WindowLowered(mActiveWindow); nsCOMPtr docShellAsItem = window->GetDocShell(); // If there's no docShellAsItem, this window must have been closed, // in that case there is no tree owner. NS_ENSURE_TRUE(docShellAsItem, NS_OK); // set this as the active window mActiveWindow = window; // 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 NS_ERROR_FAILURE; } if (!sTestMode) { baseWindow->SetVisibility(true); } } // If this is a parent or single process window, send the activate event. // Events for child process windows will be sent when ParentActivated // is called. if (XRE_IsParentProcess()) { ActivateOrDeactivate(window, true); } // retrieve the last focused element within the window that was raised nsCOMPtr currentWindow; nsCOMPtr currentFocus = GetFocusedDescendant(window, true, getter_AddRefs(currentWindow)); NS_ASSERTION(currentWindow, "window raised with no window current"); if (!currentWindow) return NS_OK; // If there is no nsIXULWindow, then this is an embedded or child process window. // Pass false for aWindowRaised so that commands get updated. nsCOMPtr xulWin(do_GetInterface(baseWindow)); Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true); return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow) { NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); nsIDocument* 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 (mActiveWindow != window) return NS_OK; // clear the mouse capture as the active window has changed nsIPresShell::SetCapturingContent(nullptr, 0); // 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) { nsCOMPtr presShell = docShell->GetPresShell(); if (presShell) { RefPtr frameSelection = presShell->FrameSelection(); frameSelection->SetDragState(false); } } } // If this is a parent or single process window, send the deactivate event. // Events for child process windows will be sent when ParentActivated // is called. 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 = mActiveWindow; mActiveWindow = nullptr; if (mFocusedWindow) Blur(nullptr, nullptr, true, true); mWindowBeingLowered = nullptr; return NS_OK; } nsresult nsFocusManager::ContentRemoved(nsIDocument* 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. nsIContent* content = window->GetFocusedNode(); if (content && nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) { bool shouldShowFocusRing = window->ShouldShowFocusRing(); window->SetFocusedNode(nullptr); // if this window is currently focused, clear the global focused // element as well, but don't fire any events. if (window == mFocusedWindow) { mFocusedContent = 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. nsIDocument* subdoc = aDocument->GetSubDocumentFor(content); if (subdoc) { nsCOMPtr docShell = subdoc->GetDocShell(); if (docShell) { nsCOMPtr childWindow = docShell->GetWindow(); if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) { ClearFocus(mActiveWindow); } } } } // Notify the editor in case we removed its ancestor limiter. if (content->IsEditable()) { nsCOMPtr docShell = aDocument->GetDocShell(); if (docShell) { nsCOMPtr editor; docShell->GetEditor(getter_AddRefs(editor)); if (editor) { nsCOMPtr s; editor->GetSelection(getter_AddRefs(s)); nsCOMPtr selection = do_QueryInterface(s); if (selection) { nsCOMPtr limiter; selection->GetAncestorLimiter(getter_AddRefs(limiter)); if (limiter == content) { editor->FinalizeSelection(); } } } } } NotifyFocusStateChange(content, shouldShowFocusRing, false); } return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus) { NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); 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())); nsIDocument* 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 (nsIDocShell* docShell = window->GetDocShell()) { if (nsCOMPtr child = docShell->GetTabChild()) { bool active = static_cast(child.get())->ParentIsActive(); ActivateOrDeactivate(window, active); } } if (mFocusedWindow != window) return NS_OK; if (aNeedsFocus) { nsCOMPtr currentWindow; nsCOMPtr currentFocus = GetFocusedDescendant(window, true, getter_AddRefs(currentWindow)); if (currentWindow) Focus(currentWindow, currentFocus, 0, true, false, false, true); } 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(); } return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow) { // 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. NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); 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; nsIDocument* 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 NS_OK; // 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. nsCOMPtr oldFocusedContent = mFocusedContent.forget(); nsCOMPtr focusedDocShell = mFocusedWindow->GetDocShell(); nsCOMPtr presShell = focusedDocShell->GetPresShell(); if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) { NotifyFocusStateChange(oldFocusedContent, mFocusedWindow->ShouldShowFocusRing(), false); window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); if (presShell) { SendFocusOrBlurEvent(eBlur, presShell, oldFocusedContent->GetComposedDoc(), oldFocusedContent, 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. NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected"); if (mActiveWindow == mFocusedWindow || mActiveWindow == window) WindowLowered(mActiveWindow); else ClearFocus(mActiveWindow); return NS_OK; } // 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->GetParent(getter_AddRefs(parentDsti)); if (parentDsti) { if (nsCOMPtr parentWindow = parentDsti->GetWindow()) parentWindow->SetFocusedNode(nullptr); } } SetFocusedWindowInternal(window); } return NS_OK; } NS_IMETHODIMP nsFocusManager::FireDelayedEvents(nsIDocument* aDocument) { NS_ENSURE_ARG(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 nsDocument.cpp:FireOrClearDelayedEvents. mDelayedBlurFocusEvents.RemoveElementAt(i); --i; } else if (!aDocument->EventHandlingSuppressed()) { EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage; nsCOMPtr target = mDelayedBlurFocusEvents[i].mTarget; nsCOMPtr presShell = mDelayedBlurFocusEvents[i].mPresShell; nsCOMPtr relatedTarget = mDelayedBlurFocusEvents[i].mRelatedTarget; mDelayedBlurFocusEvents.RemoveElementAt(i); SendFocusOrBlurEvent(message, presShell, aDocument, target, 0, false, false, relatedTarget); --i; } } } return NS_OK; } NS_IMETHODIMP nsFocusManager::FocusPlugin(nsIContent* aContent) { NS_ENSURE_ARG(aContent); SetFocusInner(aContent, 0, true, false); return NS_OK; } NS_IMETHODIMP nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive) { nsCOMPtr window = nsPIDOMWindowOuter::From(aWindow); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); ActivateOrDeactivate(window, aActive); return NS_OK; } /* static */ void nsFocusManager::NotifyFocusStateChange(nsIContent* aContent, bool aWindowShouldShowFocusRing, bool aGettingFocus) { if (!aContent->IsElement()) { return; } EventStates eventState = NS_EVENT_STATE_FOCUS; if (aWindowShouldShowFocusRing) { eventState |= NS_EVENT_STATE_FOCUSRING; } if (aGettingFocus) { aContent->AsElement()->AddStates(eventState); } else { aContent->AsElement()->RemoveStates(eventState); } for (Element* element = aContent->AsElement(); element; element = element->GetParentElementCrossingShadowRoot()) { if (aGettingFocus) { element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN); } else { element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN); } } } // static void nsFocusManager::EnsureCurrentWidgetFocused() { 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) { nsCOMPtr presShell = docShell->GetPresShell(); if (presShell) { nsViewManager* vm = presShell->GetViewManager(); if (vm) { nsCOMPtr widget; vm->GetRootWidget(getter_AddRefs(widget)); if (widget) widget->SetFocus(false); } } } } bool ActivateOrDeactivateChild(TabParent* aParent, void* aArg) { bool active = static_cast(aArg); Unused << aParent->SendParentActivated(active); return false; } void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive) { if (!aWindow) { return; } // Inform the DOM window that it has activated or deactivated, so that // the active attribute is updated on the window. aWindow->ActivateOrDeactivate(aActive); // Send the activate event. if (aWindow->GetExtantDoc()) { nsContentUtils::DispatchEventOnlyToChrome(aWindow->GetExtantDoc(), aWindow->GetCurrentInnerWindow(), aActive ? NS_LITERAL_STRING("activate") : NS_LITERAL_STRING("deactivate"), true, true, nullptr); } // Look for any remote child frames, iterate over them and send the activation notification. nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild, (void *)aActive); } void nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags, bool aFocusChanged, bool aAdjustWidget) { // if the element is not focusable, just return and leave the focus as is nsCOMPtr contentToFocus = CheckIfFocusable(aNewContent, aFlags); if (!contentToFocus) return; // 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(contentToFocus); if (subWindow) { contentToFocus = GetFocusedDescendant(subWindow, true, 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(contentToFocus); // 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 || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent)) return; // 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; nsCOMPtr parentDsti; docShell->GetParent(getter_AddRefs(parentDsti)); docShell = do_QueryInterface(parentDsti); } // if the new element is in the same window as the currently focused element bool isElementInFocusedWindow = (mFocusedWindow == newWindow); if (!isElementInFocusedWindow && mFocusedWindow && newWindow && nsContentUtils::IsHandlingKeyBoardEvent()) { nsCOMPtr focused = do_QueryInterface(mFocusedWindow); nsCOMPtr newFocus = do_QueryInterface(newWindow); nsIPrincipal* focusedPrincipal = focused->GetPrincipal(); nsIPrincipal* newPrincipal = newFocus->GetPrincipal(); if (!focusedPrincipal || !newPrincipal) { return; } bool subsumes = false; focusedPrincipal->Subsumes(newPrincipal, &subsumes); if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { 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. bool isElementInActiveWindow = false; nsCOMPtr dsti = newWindow->GetDocShell(); nsCOMPtr newRootWindow; if (dsti) { nsCOMPtr root; dsti->GetRootTreeItem(getter_AddRefs(root)); newRootWindow = root ? root->GetWindow() : nullptr; isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow); } // Exit fullscreen if a website focuses another window if (!isElementInActiveWindow && aFlags & FLAG_RAISE) { if (nsIDocument* doc = mActiveWindow ? mActiveWindow->GetDoc() : nullptr) { if (doc && doc->GetFullscreenElement()) { nsIDocument::AsyncExitFullscreen(doc); } } } // Exit fullscreen if we're focusing a windowed plugin. We don't control // event dispatch to windowed plugins, so we can't display the warning on key input if a windowed plugin is // focused, so just exit fullscreen to guard against phishing. if (contentToFocus && nsContentUtils:: GetRootDocument(contentToFocus->OwnerDoc())->GetFullscreenElement() && nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), contentToFocus->OwnerDoc(), nsContentUtils::eDOM_PROPERTIES, "FocusedWindowedPluginWhileFullscreen"); nsIDocument::AsyncExitFullscreen(contentToFocus->OwnerDoc()); } // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be // shifted away from the current element if the new shell to focus is // the same or an ancestor shell of the currently focused shell. bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) || IsSameOrAncestor(newWindow, mFocusedWindow); // if the element is in the active window, frame switching is allowed and // the content is in a visible window, fire blur and focus events. bool sendFocusEvent = isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow); // When the following conditions are true: // * an element has focus // * isn't called by trusted event (i.e., called by untrusted event or by js) // * the focus is moved to another document's element // we need to check the permission. if (sendFocusEvent && mFocusedContent && !nsContentUtils::LegacyIsCallerNativeCode() && mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) { // If the caller cannot access the current focused node, the caller should // not be able to steal focus from it. E.g., When the current focused node // is in chrome, any web contents should not be able to steal the focus. nsCOMPtr domNode(do_QueryInterface(mFocusedContent)); sendFocusEvent = nsContentUtils::CanCallerAccess(domNode); if (!sendFocusEvent && mMouseButtonEventHandlingDocument) { // However, while mouse button event is handling, the handling document's // script should be able to steal focus. domNode = do_QueryInterface(mMouseButtonEventHandlingDocument); sendFocusEvent = nsContentUtils::CanCallerAccess(domNode); } } LOGCONTENT("Shift Focus: %s", contentToFocus.get()); LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p", aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get())); LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d", isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent)); if (sendFocusEvent) { nsCOMPtr oldFocusedContent = mFocusedContent; // return if blurring fails or the focus changes during the blur if (mFocusedWindow) { // if the focus is being moved to another element in the same document, // or to a descendant, pass the existing window to Blur so that the // current node in the existing window is cleared. If moving to a // window elsewhere, we want to maintain the current node in the // window but still blur it. bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow); // find the common ancestor of the currently focused window and the new // window. The ancestor will need to have its currently focused node // cleared once the document has been blurred. Otherwise, we'll be in a // state where a document is blurred yet the chain of windows above it // still points to that document. // For instance, in the following frame tree: // A // B C // D // D is focused and we want to focus C. Once D has been blurred, we need // to clear out the focus in A, otherwise A would still maintain that B // was focused, and B that D was focused. nsCOMPtr commonAncestor; if (!isElementInFocusedWindow) commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow); if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr, commonAncestor, !isElementInFocusedWindow, aAdjustWidget, contentToFocus)) return; } Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow, aFocusChanged, false, aAdjustWidget, oldFocusedContent); } else { // otherwise, for inactive windows and when the caller cannot steal the // focus, update the node in the window, and raise the window if desired. if (allowFrameSwitch) AdjustWindowFocus(newWindow, true); // set the focus node and method as needed uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK : newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING); newWindow->SetFocusedNode(contentToFocus, focusMethod); if (aFocusChanged) { nsCOMPtr docShell = newWindow->GetDocShell(); nsCOMPtr presShell = docShell->GetPresShell(); if (presShell && presShell->DidInitialize()) ScrollIntoView(presShell, contentToFocus, aFlags); } // update the commands even when inactive so that the attributes for that // window are up to date. if (allowFrameSwitch) newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0); if (aFlags & FLAG_RAISE) RaiseWindow(newRootWindow); } } bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, nsPIDOMWindowOuter* aWindow) { if (!aWindow || !aPossibleAncestor) { return false; } nsCOMPtr ancestordsti = aPossibleAncestor->GetDocShell(); nsCOMPtr dsti = aWindow->GetDocShell(); while (dsti) { if (dsti == ancestordsti) return true; nsCOMPtr parentDsti; dsti->GetParent(getter_AddRefs(parentDsti)); dsti.swap(parentDsti); } return false; } already_AddRefed nsFocusManager::GetCommonAncestor(nsPIDOMWindowOuter* aWindow1, nsPIDOMWindowOuter* aWindow2) { NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr); nsCOMPtr dsti1 = aWindow1->GetDocShell(); NS_ENSURE_TRUE(dsti1, nullptr); nsCOMPtr dsti2 = aWindow2->GetDocShell(); NS_ENSURE_TRUE(dsti2, nullptr); AutoTArray parents1, parents2; do { parents1.AppendElement(dsti1); nsCOMPtr parentDsti1; dsti1->GetParent(getter_AddRefs(parentDsti1)); dsti1.swap(parentDsti1); } while (dsti1); do { parents2.AppendElement(dsti2); nsCOMPtr parentDsti2; dsti2->GetParent(getter_AddRefs(parentDsti2)); dsti2.swap(parentDsti2); } while (dsti2); uint32_t pos1 = parents1.Length(); uint32_t pos2 = parents2.Length(); nsIDocShellTreeItem* parent = nullptr; uint32_t len; for (len = std::min(pos1, pos2); len > 0; --len) { nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1); nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2); if (child1 != child2) { break; } parent = child1; } nsCOMPtr window = parent ? parent->GetWindow() : nullptr; return window.forget(); } void nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow, bool aCheckPermission) { bool isVisible = IsWindowVisible(aWindow); nsCOMPtr window(aWindow); while (window) { // get the containing