/* -*- Mode: C++; tab-width: 2; 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 "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIServiceManager.h" #include "nsGkAtoms.h" #include "nsContentUtils.h" #include "nsIDocument.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" #include "nsIDOMElement.h" #include "nsIDOMXULElement.h" #include "nsIDOMHTMLFrameElement.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMHTMLMapElement.h" #include "nsIDOMHTMLLegendElement.h" #include "nsIDOMDocument.h" #include "nsIDOMRange.h" #include "nsIHTMLDocument.h" #include "nsIDocShell.h" #include "nsIDocShellTreeOwner.h" #include "nsLayoutUtils.h" #include "nsIPresShell.h" #include "nsIContentViewer.h" #include "nsFrameTraversal.h" #include "nsObjectFrame.h" #include "nsEventDispatcher.h" #include "nsEventStateManager.h" #include "nsIMEStateManager.h" #include "nsIWebNavigation.h" #include "nsCaret.h" #include "nsIBaseWindow.h" #include "nsViewManager.h" #include "nsFrameSelection.h" #include "mozilla/Selection.h" #include "nsXULPopupManager.h" #include "nsIDOMNodeFilter.h" #include "nsIScriptObjectPrincipal.h" #include "nsIPrincipal.h" #include "mozAutoDocUpdate.h" #include "nsFrameLoader.h" #include "nsIObserverService.h" #include "nsIScriptError.h" #include "mozilla/dom/Element.h" #include "mozilla/LookAndFeel.h" #include "mozilla/Preferences.h" #include #ifdef MOZ_XUL #include "nsIDOMXULTextboxElement.h" #include "nsIDOMXULMenuListElement.h" #endif #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::widget; #ifdef PR_LOGGING // Two types of focus pr logging are available: // 'Focus' for normal focus manager calls // 'FocusNavigation' for tab and document navigation PRLogModuleInfo* gFocusLog; PRLogModuleInfo* gFocusNavigationLog; #define LOGFOCUS(args) PR_LOG(gFocusLog, 4, args) #define LOGFOCUSNAVIGATION(args) PR_LOG(gFocusNavigationLog, 4, args) #define LOGTAG(log, format, content) \ { \ nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \ if (content) { \ content->Tag()->ToUTF8String(tag); \ } \ PR_LOG(log, 4, (format, tag.get())); \ } #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content) #define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content) #else #define LOGFOCUS(args) #define LOGFOCUSNAVIGATION(args) #define LOGCONTENT(format, content) #define LOGCONTENTNAVIGATION(format, content) #endif struct nsDelayedBlurOrFocusEvent { nsDelayedBlurOrFocusEvent(uint32_t aType, nsIPresShell* aPresShell, nsIDocument* aDocument, EventTarget* aTarget) : mType(aType), mPresShell(aPresShell), mDocument(aDocument), mTarget(aTarget) { } nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther) : mType(aOther.mType), mPresShell(aOther.mPresShell), mDocument(aOther.mDocument), mTarget(aOther.mTarget) { } uint32_t mType; nsCOMPtr mPresShell; nsCOMPtr mDocument; nsCOMPtr mTarget; }; 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_6(nsFocusManager, mActiveWindow, mFocusedWindow, mFocusedContent, mFirstBlurEvent, mFirstFocusEvent, mWindowBeingLowered) 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", NULL }; 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_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY); NS_ADDREF(fm); sInstance = fm; #ifdef PR_LOGGING gFocusLog = PR_NewLogModule("Focus"); gFocusNavigationLog = PR_NewLogModule("FocusNavigation"); #endif 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 PRUnichar *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(); mMouseDownEventHandlingDocument = nullptr; } return NS_OK; } // given a frame content node, retrieve the nsIDOMWindow displayed in it static nsPIDOMWindow* GetContentWindow(nsIContent* aContent) { nsIDocument* doc = aContent->GetCurrentDoc(); if (doc) { nsIDocument* subdoc = doc->GetSubDocumentFor(aContent); if (subdoc) return subdoc->GetWindow(); } return nullptr; } // get the current window for the given content node static nsPIDOMWindow* GetCurrentWindow(nsIContent* aContent) { nsIDocument *doc = aContent->GetCurrentDoc(); return doc ? doc->GetWindow() : nullptr; } // static nsIContent* nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep, nsPIDOMWindow** aFocusedWindow) { NS_ENSURE_TRUE(aWindow, nullptr); *aFocusedWindow = nullptr; nsIContent* currentContent = nullptr; nsPIDOMWindow* window = aWindow->GetOuterWindow(); 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) { #ifdef MOZ_XUL if (aContent->IsXUL()) { 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->Tag() == nsGkAtoms::scale) { nsCOMPtr doc = aContent->GetCurrentDoc(); if (!doc) return nullptr; nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent); if (children) { nsIContent* child = children->Item(0); if (child && child->Tag() == nsGkAtoms::slider) return child; } } } if (inputField) { nsCOMPtr retval = do_QueryInterface(inputField); return retval; } } #endif return nullptr; } // static InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags) { 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(nsIDOMWindow** aWindow) { NS_IF_ADDREF(*aWindow = mActiveWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetActiveWindow(nsIDOMWindow* aWindow) { // only top-level windows can be made active nsCOMPtr piWindow = do_QueryInterface(aWindow); if (piWindow) piWindow = piWindow->GetOuterWindow(); NS_ENSURE_TRUE(piWindow && (piWindow == piWindow->GetPrivateRoot()), NS_ERROR_INVALID_ARG); RaiseWindow(piWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedWindow(nsIDOMWindow** aFocusedWindow) { NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow); return NS_OK; } NS_IMETHODIMP nsFocusManager::SetFocusedWindow(nsIDOMWindow* aWindowToFocus) { LOGFOCUS(("<>")); nsCOMPtr windowToFocus(do_QueryInterface(aWindowToFocus)); NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); windowToFocus = windowToFocus->GetOuterWindow(); nsCOMPtr frameContent = do_QueryInterface(windowToFocus->GetFrameElementInternal()); if (frameContent) { // pass false for aFocusChanged so that the caret does not get updated // and scrolling does not occur. SetFocusInner(frameContent, 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) { nsCOMPtr childWindow = GetContentWindow(content); if (childWindow) 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(nsIDOMWindow* aWindow, uint32_t* aLastFocusMethod) { // the focus method is stored on the inner window nsCOMPtr window(do_QueryInterface(aWindow)); if (window) window = window->GetCurrentInnerWindow(); 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(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement, uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement) { *aElement = nullptr; #ifdef PR_LOGGING LOGFOCUS(("<>", aType, aFlags)); if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG) && mFocusedWindow) { nsIDocument* doc = mFocusedWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { nsAutoCString spec; doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), spec.get())); } } LOGCONTENT(" Current Focus: %s", mFocusedContent.get()); #endif // 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 ? do_QueryInterface(aWindow) : mFocusedWindow; NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); window = window->GetOuterWindow(); } NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME; nsCOMPtr newFocus; nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal, getter_AddRefs(newFocus)); 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(nsIDOMWindow* 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. nsCOMPtr window(do_QueryInterface(aWindow)); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); window = window->GetOuterWindow(); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); 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(nsIDOMWindow* aWindow, bool aDeep, nsIDOMWindow** aFocusedWindow, nsIDOMElement** aElement) { *aElement = nullptr; if (aFocusedWindow) *aFocusedWindow = nullptr; nsCOMPtr window(do_QueryInterface(aWindow)); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); window = window->GetOuterWindow(); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); 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(nsIDOMWindow* aWindow) { int32_t itemType = nsIDocShellTreeItem::typeChrome; nsCOMPtr webnav = do_GetInterface(aWindow); nsCOMPtr dsti = do_QueryInterface(webnav); if (dsti) { dsti->GetItemType(&itemType); if (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(do_QueryInterface(aWindow)); nsCOMPtr content = window->GetFocusedNode(); if (content) MoveCaretToFocus(presShell, content); } } return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowRaised(nsIDOMWindow* aWindow) { nsCOMPtr window = do_QueryInterface(aWindow); NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG); #ifdef PR_LOGGING if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) { LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); nsAutoCString spec; nsIDocument* doc = window->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Raised Window: %p %s", aWindow, spec.get())); } if (mActiveWindow) { doc = mActiveWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), spec.get())); } } } #endif 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 webnav(do_GetInterface(aWindow)); nsCOMPtr docShellAsItem(do_QueryInterface(webnav)); // 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); } } // inform the DOM window that it has activated, so that the active attribute // is updated on the window window->ActivateOrDeactivate(true); // send activate event nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(), window, NS_LITERAL_STRING("activate"), true, true, nullptr); // 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; nsCOMPtr currentDocShell = currentWindow->GetDocShell(); nsCOMPtr presShell = currentDocShell->GetPresShell(); if (presShell) { // disable selection mousedown state on activation // XXXndeakin P3 not sure if this is necessary, but it doesn't hurt nsRefPtr frameSelection = presShell->FrameSelection(); frameSelection->SetMouseDownState(false); } Focus(currentWindow, currentFocus, 0, true, false, true, true); return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowLowered(nsIDOMWindow* aWindow) { nsCOMPtr window = do_QueryInterface(aWindow); NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG); #ifdef PR_LOGGING if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) { LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get())); nsAutoCString spec; nsIDocument* doc = window->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Lowered Window: %s", spec.get())); } if (mActiveWindow) { doc = mActiveWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Active Window: %s", spec.get())); } } } #endif if (mActiveWindow != window) return NS_OK; // clear the mouse capture as the active window has changed nsIPresShell::SetCapturingContent(nullptr, 0); // inform the DOM window that it has deactivated, so that the active // attribute is updated on the window window->ActivateOrDeactivate(false); // send deactivate event nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(), window, NS_LITERAL_STRING("deactivate"), true, true, nullptr); // 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); nsPIDOMWindow *window = aDocument->GetWindow(); if (!window) return NS_OK; // if the content is currently focused in the window, or is an ancestor // of the currently focused element, reset the focus within that window. nsIContent* content = window->GetFocusedNode(); if (content && nsContentUtils::ContentIsDescendantOf(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 container = subdoc->GetContainer(); nsCOMPtr childWindow = do_GetInterface(container); if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) { ClearFocus(mActiveWindow); } } } NotifyFocusStateChange(content, shouldShowFocusRing, false); } return NS_OK; } NS_IMETHODIMP nsFocusManager::WindowShown(nsIDOMWindow* aWindow, bool aNeedsFocus) { nsCOMPtr window = do_QueryInterface(aWindow); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); window = window->GetOuterWindow(); #ifdef PR_LOGGING if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) { LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get())); nsAutoCString spec; nsIDocument* doc = window->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS(("Shown Window: %s", spec.get())); } if (mFocusedWindow) { doc = mFocusedWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Focused Window: %s", spec.get())); } } } #endif 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(nsIDOMWindow* 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. nsCOMPtr window = do_QueryInterface(aWindow); NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG); window = window->GetOuterWindow(); #ifdef PR_LOGGING if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) { LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get())); nsAutoCString spec; nsIDocument* doc = window->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Hide Window: %s", spec.get())); } if (mFocusedWindow) { doc = mFocusedWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Focused Window: %s", spec.get())); } } if (mActiveWindow) { doc = mActiveWindow->GetExtantDoc(); if (doc && doc->GetDocumentURI()) { doc->GetDocumentURI()->GetSpec(spec); LOGFOCUS((" Active Window: %s", spec.get())); } } } #endif 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->IsInDoc()) { NotifyFocusStateChange(oldFocusedContent, mFocusedWindow->ShouldShowFocusRing(), false); window->UpdateCommands(NS_LITERAL_STRING("focus")); if (presShell) { SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, oldFocusedContent->GetCurrentDoc(), oldFocusedContent, 1, false); } } nsPresContext* focusedPresContext = presShell ? presShell->GetPresContext() : nullptr; nsIMEStateManager::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. bool beingDestroyed; nsCOMPtr docShellBeingHidden = window->GetDocShell(); 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 webnav(do_GetInterface(mFocusedWindow)); nsCOMPtr dsti = do_QueryInterface(webnav); if (dsti) { nsCOMPtr parentDsti; dsti->GetParent(getter_AddRefs(parentDsti)); nsCOMPtr parentWindow = do_GetInterface(parentDsti); if (parentWindow) 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 && !aDocument->EventHandlingSuppressed()) { uint32_t type = mDelayedBlurFocusEvents[i].mType; nsCOMPtr target = mDelayedBlurFocusEvents[i].mTarget; nsCOMPtr presShell = mDelayedBlurFocusEvents[i].mPresShell; mDelayedBlurFocusEvents.RemoveElementAt(i); SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false); --i; } } return NS_OK; } NS_IMETHODIMP nsFocusManager::FocusPlugin(nsIContent* aContent) { NS_ENSURE_ARG(aContent); SetFocusInner(aContent, 0, true, false); return NS_OK; } /* static */ void nsFocusManager::NotifyFocusStateChange(nsIContent* aContent, bool aWindowShouldShowFocusRing, bool aGettingFocus) { if (!aContent->IsElement()) { return; } nsEventStates eventState = NS_EVENT_STATE_FOCUS; if (aWindowShouldShowFocusRing) { eventState |= NS_EVENT_STATE_FOCUSRING; } if (aGettingFocus) { aContent->AsElement()->AddStates(eventState); } else { aContent->AsElement()->RemoveStates(eventState); } } // 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); } } } } 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::IsCallerChrome()) { 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 webnav = do_GetInterface(newWindow); nsCOMPtr dsti = do_QueryInterface(webnav); nsCOMPtr newRootWindow; if (dsti) { nsCOMPtr root; dsti->GetRootTreeItem(getter_AddRefs(root)); newRootWindow = do_GetInterface(root); isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow); } // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX // system. We don't control event dispatch to windowed plugins on non-MacOSX, // so we can't display the "Press ESC to leave fullscreen mode" warning on // key input if a windowed plugin is focused, so just exit fullscreen // to guard against phishing. #ifndef XP_MACOSX nsIDocument* fullscreenAncestor; if (contentToFocus && (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) && nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM", contentToFocus->OwnerDoc(), nsContentUtils::eDOM_PROPERTIES, "FocusedWindowedPluginWhileFullScreen"); nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true); } #endif // 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 && 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 && mMouseDownEventHandlingDocument) { // However, while mouse down event is handling, the handling document's // script should be able to steal focus. domNode = do_QueryInterface(mMouseDownEventHandlingDocument); 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) { // 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)) return; } Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow, aFocusChanged, false, aAdjustWidget); } 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) 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")); if (aFlags & FLAG_RAISE) RaiseWindow(newRootWindow); } } bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor, nsPIDOMWindow* aWindow) { nsCOMPtr awebnav(do_GetInterface(aPossibleAncestor)); nsCOMPtr ancestordsti = do_QueryInterface(awebnav); nsCOMPtr fwebnav(do_GetInterface(aWindow)); nsCOMPtr dsti = do_QueryInterface(fwebnav); while (dsti) { if (dsti == ancestordsti) return true; nsCOMPtr parentDsti; dsti->GetParent(getter_AddRefs(parentDsti)); dsti.swap(parentDsti); } return false; } already_AddRefed nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1, nsPIDOMWindow* aWindow2) { nsCOMPtr webnav(do_GetInterface(aWindow1)); nsCOMPtr dsti1 = do_QueryInterface(webnav); NS_ENSURE_TRUE(dsti1, nullptr); webnav = do_GetInterface(aWindow2); nsCOMPtr dsti2 = do_QueryInterface(webnav); NS_ENSURE_TRUE(dsti2, nullptr); nsAutoTArray 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 = do_GetInterface(parent); return window.forget(); } void nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow, bool aCheckPermission) { bool isVisible = IsWindowVisible(aWindow); nsCOMPtr window(aWindow); while (window) { // get the containing