diff -r 000000000000 -r 6474c204b198 dom/base/nsFocusManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/base/nsFocusManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,3486 @@ +/* -*- 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 "nsIInterfaceRequestorUtils.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsIDocument.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMElement.h" +#include "nsIDOMDocument.h" +#include "nsIDOMRange.h" +#include "nsIHTMLDocument.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsLayoutUtils.h" +#include "nsIPresShell.h" +#include "nsFrameTraversal.h" +#include "nsIWebNavigation.h" +#include "nsCaret.h" +#include "nsIBaseWindow.h" +#include "nsViewManager.h" +#include "nsFrameSelection.h" +#include "mozilla/dom/Selection.h" +#include "nsXULPopupManager.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIPrincipal.h" +#include "nsIObserverService.h" +#include "nsIObjectFrame.h" +#include "nsBindingManager.h" +#include "nsStyleCoord.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/Element.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 + +#ifdef MOZ_XUL +#include "nsIDOMXULTextboxElement.h" +#include "nsIDOMXULMenuListElement.h" +#endif + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +#ifndef XP_MACOSX +#include "nsIScriptError.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(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", + 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_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 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(); + 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) +{ + 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(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; + 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. + 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) { + 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()) { + 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; + } + EventStates 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, + NS_LITERAL_CSTRING("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