/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozila.org code. * * The Initial Developer of the Original Code is Mozilla Foundation * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/dom/TabParent.h" #include "nsFocusManager.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIServiceManager.h" #include "nsIEnumerator.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 "nsIFormControlFrame.h" #include "nsGenericHTMLElement.h" #include "nsIDocShell.h" #include "nsIEditorDocShell.h" #include "nsIDocShellTreeItem.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 "nsIWidget.h" #include "nsIBaseWindow.h" #include "nsIViewManager.h" #include "nsFrameSelection.h" #include "nsXULPopupManager.h" #include "nsIDOMNodeFilter.h" #include "nsIScriptObjectPrincipal.h" #include "nsIPrincipal.h" #include "mozilla/dom/Element.h" #include "mozAutoDocUpdate.h" #include "mozilla/Preferences.h" #include "mozilla/LookAndFeel.h" #ifdef MOZ_XUL #include "nsIDOMXULTextboxElement.h" #include "nsIDOMXULMenuListElement.h" #endif #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif using namespace mozilla; using namespace mozilla::dom; //#define DEBUG_FOCUS 1 //#define DEBUG_FOCUS_NAVIGATION 1 #define PRINTTAGF(format, content) \ { \ nsAutoString tag(NS_LITERAL_STRING("(none)")); \ if (content) \ content->Tag()->ToString(tag); \ printf(format, NS_ConvertUTF16toUTF8(tag).get()); \ } struct nsDelayedBlurOrFocusEvent { nsDelayedBlurOrFocusEvent(PRUint32 aType, nsIPresShell* aPresShell, nsIDocument* aDocument, nsIDOMEventTarget* 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) { } PRUint32 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_CLASS(nsFocusManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFocusManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mActiveWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFocusedWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFocusedContent) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstBlurEvent) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstFocusEvent) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindowBeingLowered) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFocusManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mActiveWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFocusedWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFocusedContent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBlurEvent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstFocusEvent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindowBeingLowered) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END nsFocusManager* nsFocusManager::sInstance = nsnull; bool nsFocusManager::sMouseFocusesFormControl = false; static const char* kObservedPrefs[] = { "accessibility.browsewithcaret", "accessibility.tabfocus_applies_to_xul", "accessibility.mouse_focuses_formcontrol", 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; nsIContent::sTabFocusModelAppliesToXUL = Preferences::GetBool("accessibility.tabfocus_applies_to_xul", nsIContent::sTabFocusModelAppliesToXUL); sMouseFocusesFormControl = Preferences::GetBool("accessibility.mouse_focuses_formcontrol", 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")) { UpdateCaret(false, true, mFocusedContent); } 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 (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { mActiveWindow = nsnull; mFocusedWindow = nsnull; mFocusedContent = nsnull; mFirstBlurEvent = nsnull; mFirstFocusEvent = nsnull; mWindowBeingLowered = nsnull; mDelayedBlurFocusEvents.Clear(); mMouseDownEventHandlingDocument = nsnull; } 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 nsnull; } // get the current window for the given content node static nsPIDOMWindow* GetCurrentWindow(nsIContent* aContent) { nsIDocument *doc = aContent->GetCurrentDoc(); return doc ? doc->GetWindow() : nsnull; } // static nsIContent* nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep, nsPIDOMWindow** aFocusedWindow) { NS_ENSURE_TRUE(aWindow, nsnull); *aFocusedWindow = nsnull; nsIContent* currentContent = nsnull; 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 nsnull; nsINodeList* children = doc->BindingManager()->GetXBLChildNodesFor(aContent); if (children) { nsIContent* child = children->GetNodeAt(0); if (child && child->Tag() == nsGkAtoms::slider) return child; } } } if (inputField) { nsCOMPtr retval = do_QueryInterface(inputField); return retval; } } #endif return nsnull; } // static PRUint32 nsFocusManager::GetFocusMoveReason(PRUint32 aFlags) { PRUint32 reason = IMEContext::FOCUS_MOVED_UNKNOWN; if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { reason = IMEContext::FOCUS_MOVED_BY_MOUSE; } else if (aFlags & nsIFocusManager::FLAG_BYKEY) { reason = IMEContext::FOCUS_MOVED_BY_KEY; } else if (aFlags & nsIFocusManager::FLAG_BYMOVEFOCUS) { reason = IMEContext::FOCUS_MOVED_BY_MOVEFOCUS; } return reason; } 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) { #ifdef DEBUG_FOCUS printf("<>\n"); #endif 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); #ifdef DEBUG_FOCUS printf("<>\n"); #endif return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement) { if (mFocusedContent) CallQueryInterface(mFocusedContent, aFocusedElement); else *aFocusedElement = nsnull; return NS_OK; } NS_IMETHODIMP nsFocusManager::GetLastFocusMethod(nsIDOMWindow* aWindow, PRUint32* 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, PRUint32 aFlags) { #ifdef DEBUG_FOCUS printf("<>\n"); #endif nsCOMPtr newFocus = do_QueryInterface(aElement); NS_ENSURE_ARG(newFocus); SetFocusInner(newFocus, aFlags, true, true); return NS_OK; } NS_IMETHODIMP nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, PRUint32 aFlags, bool* aIsFocusable) { NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG); nsCOMPtr aContent = do_QueryInterface(aElement); *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nsnull; return NS_OK; } NS_IMETHODIMP nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement, PRUint32 aType, PRUint32 aFlags, nsIDOMElement** aElement) { *aElement = nsnull; #ifdef DEBUG_FOCUS printf("<>\n<<", aType, aFlags); nsCOMPtr focusedWindow = mFocusedWindow; if (focusedWindow) { nsCOMPtr doc = do_QueryInterface(focusedWindow->GetExtantDocument()); if (doc) { nsCAutoString spec; doc->GetDocumentURI()->GetSpec(spec); printf(" [%p] Focused Window: %s", mFocusedWindow.get(), spec.get()); } } PRINTTAGF(">> $[[%s]]\n", mFocusedContent); #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); nsCOMPtr newFocus; nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, getter_AddRefs(newFocus)); NS_ENSURE_SUCCESS(rv, rv); #ifdef DEBUG_FOCUS_NAVIGATION PRINTTAGF("-> Element to be focused: %s\n", newFocus); #endif 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); } #ifdef DEBUG_FOCUS printf("<>\n"); #endif return NS_OK; } NS_IMETHODIMP nsFocusManager::ClearFocus(nsIDOMWindow* aWindow) { #ifdef DEBUG_FOCUS printf("<>\n"); #endif // 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, nsnull, 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, nsnull, 0, true, false, false, true); } } else { window->SetFocusedNode(nsnull); } #ifdef DEBUG_FOCUS printf("<>\n"); #endif return NS_OK; } NS_IMETHODIMP nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow, bool aDeep, nsIDOMWindow** aFocusedWindow, nsIDOMElement** aElement) { *aElement = nsnull; if (aFocusedWindow) *aFocusedWindow = nsnull; 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) { PRInt32 itemType = nsIDocShellTreeItem::typeChrome; nsCOMPtr webnav = do_GetInterface(aWindow); nsCOMPtr dsti = do_QueryInterface(webnav); if (dsti) { dsti->GetItemType(&itemType); if (itemType != nsIDocShellTreeItem::typeChrome) { // don't move the caret for editable documents nsCOMPtr editorDocShell(do_QueryInterface(dsti)); if (editorDocShell) { bool isEditable; editorDocShell->GetEditable(&isEditable); if (isEditable) return NS_OK; } nsCOMPtr docShell = do_QueryInterface(dsti); NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); nsCOMPtr presShell; docShell->GetPresShell(getter_AddRefs(presShell)); 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 DEBUG_FOCUS printf("Window %p Raised [Currently: %p %p] <<", aWindow, mActiveWindow.get(), mFocusedWindow.get()); nsCAutoString spec; nsCOMPtr doc = do_QueryInterface(window->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf("[%p] Raised Window: %s", aWindow, spec.get()); } if (mActiveWindow) { doc = do_QueryInterface(mActiveWindow->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf(" [%p] Active Window: %s", mActiveWindow.get(), spec.get()); } } printf(">>\n"); #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; } 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 nsCOMPtr document = do_QueryInterface(window->GetExtantDocument()); nsContentUtils::DispatchTrustedEvent(document, window, NS_LITERAL_STRING("activate"), true, true, nsnull); // 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(getter_AddRefs(presShell)); 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 DEBUG_FOCUS printf("Window %p Lowered [Currently: %p %p] <<", aWindow, mActiveWindow.get(), mFocusedWindow.get()); nsCAutoString spec; nsCOMPtr doc = do_QueryInterface(window->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf("[%p] Lowered Window: %s", aWindow, spec.get()); } if (mActiveWindow) { doc = do_QueryInterface(mActiveWindow->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf(" [%p] Active Window: %s", mActiveWindow.get(), spec.get()); } } printf(">>\n"); #endif if (mActiveWindow != window) return NS_OK; // clear the mouse capture as the active window has changed nsIPresShell::SetCapturingContent(nsnull, 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 nsCOMPtr document = do_QueryInterface(window->GetExtantDocument()); nsContentUtils::DispatchTrustedEvent(document, window, NS_LITERAL_STRING("deactivate"), true, true, nsnull); // 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 = nsnull; if (mFocusedWindow) Blur(nsnull, nsnull, true, true); mWindowBeingLowered = nsnull; 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(nsnull); nsCOMPtr docShell = window->GetDocShell(); if (docShell) { nsCOMPtr presShell; docShell->GetPresShell(getter_AddRefs(presShell)); nsIMEStateManager::OnRemoveContent(presShell->GetPresContext(), content); } // if this window is currently focused, clear the global focused // element as well, but don't fire any events. if (window == mFocusedWindow) { mFocusedContent = nsnull; } 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(aContent, 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 DEBUG_FOCUS printf("Window %p Shown [Currently: %p %p] <<", window.get(), mActiveWindow.get(), mFocusedWindow.get()); nsCAutoString spec; nsCOMPtr doc = do_QueryInterface(window->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf("Shown Window: %s", spec.get()); } if (mFocusedWindow) { doc = do_QueryInterface(mFocusedWindow->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf(" Focused Window: %s", spec.get()); } } printf(">>\n"); #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 DEBUG_FOCUS printf("Window %p Hidden [Currently: %p %p] <<", window.get(), mActiveWindow.get(), mFocusedWindow.get()); nsCAutoString spec; nsCOMPtr doc = do_QueryInterface(window->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf("Hide Window: %s", spec.get()); } if (mFocusedWindow) { doc = do_QueryInterface(mFocusedWindow->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf(" Focused Window: %s", spec.get()); } } if (mActiveWindow) { doc = do_QueryInterface(mActiveWindow->GetExtantDocument()); if (doc) { doc->GetDocumentURI()->GetSpec(spec); printf(" Active Window: %s", spec.get()); } } printf(">>\n"); #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. nsIContent* oldFocusedContent = mFocusedContent; mFocusedContent = nsnull; if (oldFocusedContent && oldFocusedContent->IsInDoc()) { NotifyFocusStateChange(oldFocusedContent, mFocusedWindow->ShouldShowFocusRing(), false); } nsCOMPtr focusedDocShell = mFocusedWindow->GetDocShell(); nsCOMPtr presShell; focusedDocShell->GetPresShell(getter_AddRefs(presShell)); nsIMEStateManager::OnTextStateBlur(nsnull, nsnull); if (presShell) { nsIMEStateManager::OnChangeFocus(presShell->GetPresContext(), nsnull, IMEContext::FOCUS_REMOVED); SetCaretVisible(presShell, false, nsnull); } // 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(nsnull); } mFocusedWindow = 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 (PRUint32 i = 0; i < mDelayedBlurFocusEvents.Length(); i++) { if (mDelayedBlurFocusEvents[i].mDocument == aDocument && !aDocument->EventHandlingSuppressed()) { PRUint32 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) 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(getter_AddRefs(presShell)); if (presShell) { nsIViewManager* vm = presShell->GetViewManager(); if (vm) { nsCOMPtr widget; vm->GetRootWidget(getter_AddRefs(widget)); if (widget) widget->SetFocus(false); } } } } void nsFocusManager::SetFocusInner(nsIContent* aNewContent, PRInt32 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 dsti = do_QueryInterface(docShell); nsCOMPtr parentDsti; dsti->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::IsCallerTrustedForWrite()) { 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); } #ifdef DEBUG_FOCUS PRINTTAGF("Shift Focus: %s", contentToFocus); printf(" Flags: %x Current Window: %p New Window: %p Current Element: %p", aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()); printf(" In Active Window: %d In Focused Window: %d\n", isElementInActiveWindow, isElementInFocusedWindow); #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); } } 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() : nsnull, 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 PRUint32 focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK : newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING); newWindow->SetFocusedNode(contentToFocus, focusMethod); if (aFocusChanged) { nsCOMPtr docShell = newWindow->GetDocShell(); nsCOMPtr presShell; docShell->GetPresShell(getter_AddRefs(presShell)); 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, nsnull); webnav = do_GetInterface(aWindow2); nsCOMPtr dsti2 = do_QueryInterface(webnav); NS_ENSURE_TRUE(dsti2, nsnull); 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); PRUint32 pos1 = parents1.Length(); PRUint32 pos2 = parents2.Length(); nsIDocShellTreeItem* parent = nsnull; PRUint32 len; for (len = NS_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