mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
8ee4854efd
This prevents useless work which _could_ lead to visual artifacts, as well as assertions caused by us touching dirty frames,
3156 lines
111 KiB
C++
3156 lines
111 KiB
C++
/* -*- 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 "nsFocusManager.h"
|
|
|
|
#include "nsIInterfaceRequestor.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsIEnumerator.h"
|
|
#include "nsTPtrArray.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsIPrefBranch2.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMWindow.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMXULElement.h"
|
|
#include "nsIDOMNSHTMLFrameElement.h"
|
|
#include "nsIDOMHTMLInputElement.h"
|
|
#include "nsIDOMHTMLMapElement.h"
|
|
#include "nsIDOMHTMLLegendElement.h"
|
|
#include "nsIDOMDocumentRange.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 "nsIEventStateManager.h"
|
|
#include "nsIMEStateManager.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "nsCaret.h"
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsILookAndFeel.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsIBaseWindow.h"
|
|
#include "nsIViewManager.h"
|
|
#include "nsFrameSelection.h"
|
|
#include "nsXULPopupManager.h"
|
|
#include "nsImageMapUtils.h"
|
|
#include "nsTreeWalker.h"
|
|
#include "nsIDOMNodeFilter.h"
|
|
#include "nsIScriptObjectPrincipal.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "mozilla/dom/Element.h"
|
|
|
|
#ifdef MOZ_XUL
|
|
#include "nsIDOMXULTextboxElement.h"
|
|
#include "nsIDOMXULMenuListElement.h"
|
|
#endif
|
|
|
|
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,
|
|
nsPIDOMEventTarget* 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<nsIPresShell> mPresShell;
|
|
nsCOMPtr<nsIDocument> mDocument;
|
|
nsCOMPtr<nsPIDOMEventTarget> 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
|
|
|
|
static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
|
|
|
|
nsFocusManager* nsFocusManager::sInstance = nsnull;
|
|
PRBool nsFocusManager::sMouseFocusesFormControl = PR_FALSE;
|
|
|
|
nsFocusManager::nsFocusManager()
|
|
{ }
|
|
|
|
nsFocusManager::~nsFocusManager()
|
|
{
|
|
nsIPrefBranch2* prefBranch = nsContentUtils::GetPrefBranch();
|
|
|
|
if (prefBranch) {
|
|
prefBranch->RemoveObserver("accessibility.browsewithcaret", this);
|
|
prefBranch->RemoveObserver("accessibility.tabfocus_applies_to_xul", this);
|
|
prefBranch->RemoveObserver("accessibility.mouse_focuses_formcontrol", this);
|
|
}
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
nsFocusManager::Init()
|
|
{
|
|
nsFocusManager* fm = new nsFocusManager();
|
|
NS_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY);
|
|
NS_ADDREF(fm);
|
|
sInstance = fm;
|
|
|
|
nsIContent::sTabFocusModelAppliesToXUL =
|
|
nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul",
|
|
nsIContent::sTabFocusModelAppliesToXUL);
|
|
|
|
sMouseFocusesFormControl =
|
|
nsContentUtils::GetBoolPref("accessibility.mouse_focuses_formcontrol", PR_FALSE);
|
|
|
|
nsIPrefBranch2* prefBranch = nsContentUtils::GetPrefBranch();
|
|
prefBranch->AddObserver("accessibility.browsewithcaret", fm, PR_TRUE);
|
|
prefBranch->AddObserver("accessibility.tabfocus_applies_to_xul", fm, PR_TRUE);
|
|
prefBranch->AddObserver("accessibility.mouse_focuses_formcontrol", fm, PR_TRUE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
void
|
|
nsFocusManager::Shutdown()
|
|
{
|
|
NS_IF_RELEASE(sInstance);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::Observe(nsISupports *aSubject,
|
|
const char *aTopic,
|
|
const PRUnichar *aData)
|
|
{
|
|
nsDependentString data(aData);
|
|
if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
|
if (data.EqualsLiteral("accessibility.browsewithcaret")) {
|
|
UpdateCaret(PR_FALSE, PR_TRUE, mFocusedContent);
|
|
}
|
|
else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
|
|
nsIContent::sTabFocusModelAppliesToXUL =
|
|
nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul",
|
|
nsIContent::sTabFocusModelAppliesToXUL);
|
|
}
|
|
else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
|
|
sMouseFocusesFormControl =
|
|
nsContentUtils::GetBoolPref("accessibility.mouse_focuses_formcontrol", PR_FALSE);
|
|
}
|
|
}
|
|
|
|
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, PRBool 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<nsIDOMNode> inputField;
|
|
|
|
nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
|
|
if (textbox) {
|
|
textbox->GetInputField(getter_AddRefs(inputField));
|
|
}
|
|
else {
|
|
nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
|
|
if (menulist) {
|
|
menulist->GetInputField(getter_AddRefs(inputField));
|
|
}
|
|
else if (aContent->Tag() == nsGkAtoms::scale) {
|
|
nsCOMPtr<nsIDocument> 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<nsIContent> retval = do_QueryInterface(inputField);
|
|
return retval;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
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<nsPIDOMWindow> 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("<<SetFocusedWindow begin>>\n");
|
|
#endif
|
|
|
|
nsCOMPtr<nsPIDOMWindow> windowToFocus(do_QueryInterface(aWindowToFocus));
|
|
NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
|
|
|
|
windowToFocus = windowToFocus->GetOuterWindow();
|
|
|
|
nsCOMPtr<nsIContent> 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, PR_FALSE, PR_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<nsIDOMWindow> childWindow = GetContentWindow(content);
|
|
if (childWindow)
|
|
ClearFocus(windowToFocus);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindow> rootWindow = windowToFocus->GetPrivateRoot();
|
|
if (rootWindow)
|
|
RaiseWindow(rootWindow);
|
|
|
|
#ifdef DEBUG_FOCUS
|
|
printf("<<SetFocusedWindow end>>\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<nsPIDOMWindow> 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("<<SetFocus>>\n");
|
|
#endif
|
|
|
|
nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
|
|
NS_ENSURE_ARG(newFocus);
|
|
|
|
SetFocusInner(newFocus, aFlags, PR_TRUE, PR_TRUE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
|
|
PRUint32 aType, PRUint32 aFlags, nsIDOMElement** aElement)
|
|
{
|
|
*aElement = nsnull;
|
|
|
|
#ifdef DEBUG_FOCUS
|
|
printf("<<MoveFocus Type: %d Flags: %x>>\n<<", aType, aFlags);
|
|
|
|
nsCOMPtr<nsPIDOMWindow> focusedWindow = mFocusedWindow;
|
|
if (focusedWindow) {
|
|
nsCOMPtr<nsIDocument> 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<nsPIDOMWindow> window;
|
|
nsCOMPtr<nsIContent> 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<nsIContent> 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, PR_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("<<MoveFocus end>>\n");
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::ClearFocus(nsIDOMWindow* aWindow)
|
|
{
|
|
#ifdef DEBUG_FOCUS
|
|
printf("<<ClearFocus begin>>\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<nsPIDOMWindow> 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)) {
|
|
PRBool isAncestor = (window != mFocusedWindow);
|
|
if (Blur(window, nsnull, isAncestor, PR_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, PR_TRUE, PR_FALSE, PR_FALSE, PR_TRUE);
|
|
}
|
|
}
|
|
else {
|
|
window->SetFocusedNode(nsnull);
|
|
}
|
|
|
|
#ifdef DEBUG_FOCUS
|
|
printf("<<ClearFocus end>>\n");
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow,
|
|
PRBool aDeep,
|
|
nsIDOMWindow** aFocusedWindow,
|
|
nsIDOMElement** aElement)
|
|
{
|
|
*aElement = nsnull;
|
|
if (aFocusedWindow)
|
|
*aFocusedWindow = nsnull;
|
|
|
|
nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
|
|
NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
|
|
|
|
window = window->GetOuterWindow();
|
|
NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
|
|
|
|
nsCOMPtr<nsPIDOMWindow> focusedWindow;
|
|
nsCOMPtr<nsIContent> 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<nsIWebNavigation> webnav = do_GetInterface(aWindow);
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
|
|
if (dsti) {
|
|
dsti->GetItemType(&itemType);
|
|
if (itemType != nsIDocShellTreeItem::typeChrome) {
|
|
// don't move the caret for editable documents
|
|
nsCOMPtr<nsIEditorDocShell> editorDocShell(do_QueryInterface(dsti));
|
|
if (editorDocShell) {
|
|
PRBool isEditable;
|
|
editorDocShell->GetEditable(&isEditable);
|
|
if (isEditable)
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
|
|
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
docShell->GetPresShell(getter_AddRefs(presShell));
|
|
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
|
|
nsCOMPtr<nsIContent> content = window->GetFocusedNode();
|
|
if (content)
|
|
MoveCaretToFocus(presShell, content);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::WindowRaised(nsIDOMWindow* aWindow)
|
|
{
|
|
nsCOMPtr<nsPIDOMWindow> 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<nsIDocument> 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<nsIWebNavigation> webnav(do_GetInterface(aWindow));
|
|
nsCOMPtr<nsIDocShellTreeItem> 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<nsIDocShellTreeOwner> treeOwner;
|
|
docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
|
|
nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
|
|
if (baseWindow) {
|
|
PRBool isEnabled = PR_TRUE;
|
|
if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
baseWindow->SetVisibility(PR_TRUE);
|
|
}
|
|
|
|
// inform the DOM window that it has activated, so that the active attribute
|
|
// is updated on the window
|
|
window->ActivateOrDeactivate(PR_TRUE);
|
|
|
|
// send activate event
|
|
nsCOMPtr<nsIDocument> document = do_QueryInterface(window->GetExtantDocument());
|
|
nsContentUtils::DispatchTrustedEvent(document,
|
|
window,
|
|
NS_LITERAL_STRING("activate"),
|
|
PR_TRUE, PR_TRUE, nsnull);
|
|
|
|
// retrieve the last focused element within the window that was raised
|
|
nsCOMPtr<nsPIDOMWindow> currentWindow;
|
|
nsCOMPtr<nsIContent> currentFocus =
|
|
GetFocusedDescendant(window, PR_TRUE, getter_AddRefs(currentWindow));
|
|
|
|
NS_ASSERTION(currentWindow, "window raised with no window current");
|
|
if (!currentWindow)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIDocShell> currentDocShell = currentWindow->GetDocShell();
|
|
|
|
nsCOMPtr<nsIPresShell> 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
|
|
nsCOMPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
|
|
frameSelection->SetMouseDownState(PR_FALSE);
|
|
}
|
|
|
|
Focus(currentWindow, currentFocus, 0, PR_TRUE, PR_FALSE, PR_TRUE, PR_TRUE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::WindowLowered(nsIDOMWindow* aWindow)
|
|
{
|
|
nsCOMPtr<nsPIDOMWindow> 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<nsIDocument> 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(PR_FALSE);
|
|
|
|
// send deactivate event
|
|
nsCOMPtr<nsIDocument> document = do_QueryInterface(window->GetExtantDocument());
|
|
nsContentUtils::DispatchTrustedEvent(document,
|
|
window,
|
|
NS_LITERAL_STRING("deactivate"),
|
|
PR_TRUE, PR_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, PR_TRUE, PR_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)) {
|
|
window->SetFocusedNode(nsnull);
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
|
|
if (docShell) {
|
|
nsCOMPtr<nsIPresShell> 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<nsISupports> container = subdoc->GetContainer();
|
|
nsCOMPtr<nsPIDOMWindow> childWindow = do_GetInterface(container);
|
|
if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
|
|
ClearFocus(mActiveWindow);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::WindowShown(nsIDOMWindow* aWindow, PRBool aNeedsFocus)
|
|
{
|
|
nsCOMPtr<nsPIDOMWindow> 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<nsIDocument> 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<nsPIDOMWindow> currentWindow;
|
|
nsCOMPtr<nsIContent> currentFocus =
|
|
GetFocusedDescendant(window, PR_TRUE, getter_AddRefs(currentWindow));
|
|
if (currentWindow)
|
|
Focus(currentWindow, currentFocus, 0, PR_TRUE, PR_FALSE, PR_FALSE, PR_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<nsPIDOMWindow> 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<nsIDocument> 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.
|
|
|
|
nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
focusedDocShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (presShell) {
|
|
presShell->GetPresContext()->EventStateManager()->
|
|
SetContentState(mFocusedContent, NS_EVENT_STATE_FOCUS);
|
|
}
|
|
|
|
mFocusedContent = nsnull;
|
|
|
|
nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
|
|
if (presShell) {
|
|
nsIMEStateManager::OnChangeFocus(presShell->GetPresContext(), nsnull);
|
|
SetCaretVisible(presShell, PR_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.
|
|
PRBool beingDestroyed;
|
|
nsCOMPtr<nsIDocShell> 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<nsIWebNavigation> webnav(do_GetInterface(mFocusedWindow));
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
|
|
if (dsti) {
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti;
|
|
dsti->GetParent(getter_AddRefs(parentDsti));
|
|
nsCOMPtr<nsPIDOMWindow> 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<nsPIDOMEventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
|
|
nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
|
|
mDelayedBlurFocusEvents.RemoveElementAt(i);
|
|
SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, PR_FALSE);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFocusManager::FocusPlugin(nsIContent* aContent)
|
|
{
|
|
NS_ENSURE_ARG(aContent);
|
|
SetFocusInner(aContent, 0, PR_TRUE, PR_FALSE);
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
|
|
if (docShell) {
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
docShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (presShell) {
|
|
nsIViewManager* vm = presShell->GetViewManager();
|
|
if (vm) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFocusManager::SetFocusInner(nsIContent* aNewContent, PRInt32 aFlags,
|
|
PRBool aFocusChanged, PRBool aAdjustWidget)
|
|
{
|
|
// if the element is not focusable, just return and leave the focus as is
|
|
nsCOMPtr<nsIContent> 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<nsPIDOMWindow> newWindow;
|
|
nsCOMPtr<nsPIDOMWindow> subWindow = GetContentWindow(contentToFocus);
|
|
if (subWindow) {
|
|
contentToFocus = GetFocusedDescendant(subWindow, PR_TRUE, getter_AddRefs(newWindow));
|
|
// since a window is being refocused, clear aFocusChanged so that the
|
|
// caret position isn't updated.
|
|
aFocusChanged = PR_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<nsIDocShell> newDocShell = newWindow->GetDocShell();
|
|
nsCOMPtr<nsIDocShell> docShell = newDocShell;
|
|
while (docShell) {
|
|
PRBool inUnload;
|
|
docShell->GetIsInUnload(&inUnload);
|
|
if (inUnload)
|
|
return;
|
|
|
|
PRBool beingDestroyed;
|
|
docShell->IsBeingDestroyed(&beingDestroyed);
|
|
if (beingDestroyed)
|
|
return;
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(docShell);
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti;
|
|
dsti->GetParent(getter_AddRefs(parentDsti));
|
|
docShell = do_QueryInterface(parentDsti);
|
|
}
|
|
|
|
// if the new element is in the same window as the currently focused element
|
|
PRBool isElementInFocusedWindow = (mFocusedWindow == newWindow);
|
|
|
|
if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
|
|
nsContentUtils::IsHandlingKeyBoardEvent()) {
|
|
nsCOMPtr<nsIScriptObjectPrincipal> focused =
|
|
do_QueryInterface(mFocusedWindow);
|
|
nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
|
|
do_QueryInterface(newWindow);
|
|
nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
|
|
nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
|
|
if (!focusedPrincipal || !newPrincipal) {
|
|
return;
|
|
}
|
|
PRBool subsumes = PR_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.
|
|
PRBool isElementInActiveWindow = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(newWindow);
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
|
|
nsCOMPtr<nsPIDOMWindow> newRootWindow;
|
|
if (dsti) {
|
|
nsCOMPtr<nsIDocShellTreeItem> 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.
|
|
PRBool 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.
|
|
PRBool 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->GetOwnerDoc() != aNewContent->GetOwnerDoc()) {
|
|
// 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<nsIDOMNode> 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.
|
|
PRBool 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<nsPIDOMWindow> commonAncestor;
|
|
if (!isElementInFocusedWindow)
|
|
commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
|
|
|
|
if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nsnull,
|
|
commonAncestor, !isElementInFocusedWindow, aAdjustWidget))
|
|
return;
|
|
}
|
|
|
|
Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
|
|
aFocusChanged, PR_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, PR_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<nsIDocShell> docShell = newWindow->GetDocShell();
|
|
|
|
nsCOMPtr<nsIPresShell> 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);
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor,
|
|
nsPIDOMWindow* aWindow)
|
|
{
|
|
nsCOMPtr<nsIWebNavigation> awebnav(do_GetInterface(aPossibleAncestor));
|
|
nsCOMPtr<nsIDocShellTreeItem> ancestordsti = do_QueryInterface(awebnav);
|
|
|
|
nsCOMPtr<nsIWebNavigation> fwebnav(do_GetInterface(aWindow));
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(fwebnav);
|
|
while (dsti) {
|
|
if (dsti == ancestordsti)
|
|
return PR_TRUE;
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti;
|
|
dsti->GetParent(getter_AddRefs(parentDsti));
|
|
dsti.swap(parentDsti);
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindow>
|
|
nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1,
|
|
nsPIDOMWindow* aWindow2)
|
|
{
|
|
nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(aWindow1));
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti1 = do_QueryInterface(webnav);
|
|
NS_ENSURE_TRUE(dsti1, nsnull);
|
|
|
|
webnav = do_GetInterface(aWindow2);
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti2 = do_QueryInterface(webnav);
|
|
NS_ENSURE_TRUE(dsti2, nsnull);
|
|
|
|
nsAutoTPtrArray<nsIDocShellTreeItem, 30> parents1, parents2;
|
|
do {
|
|
parents1.AppendElement(dsti1);
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
|
|
dsti1->GetParent(getter_AddRefs(parentDsti1));
|
|
dsti1.swap(parentDsti1);
|
|
} while (dsti1);
|
|
do {
|
|
parents2.AppendElement(dsti2);
|
|
nsCOMPtr<nsIDocShellTreeItem> 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<nsPIDOMWindow> window = do_GetInterface(parent);
|
|
return window.forget();
|
|
}
|
|
|
|
void
|
|
nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
|
|
PRBool aCheckPermission)
|
|
{
|
|
PRBool isVisible = IsWindowVisible(aWindow);
|
|
|
|
nsCOMPtr<nsPIDOMWindow> window(aWindow);
|
|
while (window) {
|
|
// get the containing <iframe> or equivalent element so that it can be
|
|
// focused below.
|
|
nsCOMPtr<nsIContent> frameContent =
|
|
do_QueryInterface(window->GetFrameElementInternal());
|
|
|
|
nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(window));
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
|
|
if (!dsti)
|
|
return;
|
|
nsCOMPtr<nsIDocShellTreeItem> parentDsti;
|
|
dsti->GetParent(getter_AddRefs(parentDsti));
|
|
|
|
window = do_GetInterface(parentDsti);
|
|
if (window) {
|
|
// if the parent window is visible but aWindow was not, then we have
|
|
// likely moved up and out from a hidden tab to the browser window, or a
|
|
// similar such arrangement. Stop adjusting the current nodes.
|
|
if (IsWindowVisible(window) != isVisible)
|
|
break;
|
|
|
|
// When aCheckPermission is true, we should check whether the caller can
|
|
// access the window or not. If it cannot access, we should stop the
|
|
// adjusting.
|
|
if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
|
|
break;
|
|
|
|
window->SetFocusedNode(frameContent);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool
|
|
nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
|
|
{
|
|
if (!aWindow)
|
|
return PR_FALSE;
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
|
|
nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
|
|
if (!baseWin)
|
|
return PR_FALSE;
|
|
|
|
PRBool visible = PR_FALSE;
|
|
baseWin->GetVisibility(&visible);
|
|
return visible;
|
|
}
|
|
|
|
PRBool
|
|
nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
|
|
{
|
|
NS_PRECONDITION(aContent, "aContent must not be NULL");
|
|
NS_PRECONDITION(aContent->IsInDoc(), "aContent must be in a document");
|
|
|
|
// If aContent is in designMode, the root element is not focusable.
|
|
// NOTE: in designMode, most elements are not focusable, just the document is
|
|
// focusable.
|
|
// Also, if aContent is not editable but it isn't in designMode, it's not
|
|
// focusable.
|
|
nsIDocument* doc = aContent->GetCurrentDoc();
|
|
NS_ASSERTION(doc, "aContent must have current document");
|
|
return aContent == doc->GetRootElement() &&
|
|
(doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable());
|
|
}
|
|
|
|
nsIContent*
|
|
nsFocusManager::CheckIfFocusable(nsIContent* aContent, PRUint32 aFlags)
|
|
{
|
|
if (!aContent)
|
|
return nsnull;
|
|
|
|
// this is a special case for some XUL elements where an anonymous child is
|
|
// actually focusable and not the element itself.
|
|
nsIContent* redirectedFocus = GetRedirectedFocus(aContent);
|
|
if (redirectedFocus)
|
|
return CheckIfFocusable(redirectedFocus, aFlags);
|
|
|
|
nsCOMPtr<nsIDocument> doc = aContent->GetCurrentDoc();
|
|
// can't focus elements that are not in documents
|
|
if (!doc)
|
|
return nsnull;
|
|
|
|
// Make sure that our frames are up to date
|
|
doc->FlushPendingNotifications(Flush_Layout);
|
|
|
|
nsIPresShell *shell = doc->GetShell();
|
|
if (!shell)
|
|
return nsnull;
|
|
|
|
// the root content can always be focused
|
|
if (aContent == doc->GetRootElement())
|
|
return aContent;
|
|
|
|
// cannot focus content in print preview mode. Only the root can be focused.
|
|
nsPresContext* presContext = shell->GetPresContext();
|
|
if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview)
|
|
return nsnull;
|
|
|
|
nsIFrame* frame = aContent->GetPrimaryFrame();
|
|
if (!frame)
|
|
return nsnull;
|
|
|
|
if (aContent->Tag() == nsGkAtoms::area && aContent->IsHTML()) {
|
|
// HTML areas do not have their own frame, and the img frame we get from
|
|
// GetPrimaryFrame() is not relevant as to whether it is focusable or
|
|
// not, so we have to do all the relevant checks manually for them.
|
|
return frame->AreAncestorViewsVisible() &&
|
|
frame->GetStyleVisibility()->IsVisible() &&
|
|
aContent->IsFocusable() ? aContent : nsnull;
|
|
}
|
|
|
|
// if this is a child frame content node, check if it is visible and
|
|
// call the content node's IsFocusable method instead of the frame's
|
|
// IsFocusable method. This skips checking the style system and ensures that
|
|
// offscreen browsers can still be focused.
|
|
nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
|
|
if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
|
|
const nsStyleUserInterface* ui = frame->GetStyleUserInterface();
|
|
PRInt32 tabIndex = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE ||
|
|
ui->mUserFocus == NS_STYLE_USER_FOCUS_NONE) ? -1 : 0;
|
|
return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nsnull;
|
|
}
|
|
|
|
return frame->IsFocusable(nsnull, aFlags & FLAG_BYMOUSE) ? aContent : nsnull;
|
|
}
|
|
|
|
PRBool
|
|
nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
|
|
nsPIDOMWindow* aAncestorWindowToFocus,
|
|
PRBool aIsLeavingDocument,
|
|
PRBool aAdjustWidgets)
|
|
{
|
|
// hold a reference to the focused content, which may be null
|
|
nsCOMPtr<nsIContent> content = mFocusedContent;
|
|
if (content) {
|
|
if (!content->IsInDoc()) {
|
|
mFocusedContent = nsnull;
|
|
return PR_TRUE;
|
|
}
|
|
if (content == mFirstBlurEvent)
|
|
return PR_TRUE;
|
|
}
|
|
|
|
// hold a reference to the focused window
|
|
nsCOMPtr<nsPIDOMWindow> window = mFocusedWindow;
|
|
if (!window) {
|
|
mFocusedContent = nsnull;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
|
|
if (!docShell) {
|
|
mFocusedContent = nsnull;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
// Keep a ref to presShell since dispatching the DOM event may cause
|
|
// the document to be destroyed.
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
docShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (!presShell) {
|
|
mFocusedContent = nsnull;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool clearFirstBlurEvent = PR_FALSE;
|
|
if (!mFirstBlurEvent) {
|
|
mFirstBlurEvent = content;
|
|
clearFirstBlurEvent = PR_TRUE;
|
|
}
|
|
|
|
// if there is still an active window, adjust the IME state.
|
|
// This has to happen before the focus is cleared below, otherwise, the IME
|
|
// compositionend event won't get fired at the element being blurred.
|
|
nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
|
|
if (mActiveWindow)
|
|
nsIMEStateManager::OnChangeFocus(presShell->GetPresContext(), nsnull);
|
|
|
|
// now adjust the actual focus, by clearing the fields in the focus manager
|
|
// and in the window.
|
|
mFocusedContent = nsnull;
|
|
if (aWindowToClear)
|
|
aWindowToClear->SetFocusedNode(nsnull);
|
|
|
|
#ifdef DEBUG_FOCUS
|
|
PRINTTAGF("**Element %s has been blurred\n", content);
|
|
#endif
|
|
|
|
// Don't fire blur event on the root content which isn't editable.
|
|
PRBool sendBlurEvent =
|
|
content && content->IsInDoc() && !IsNonFocusableRoot(content);
|
|
if (content) {
|
|
if (sendBlurEvent) {
|
|
// unusual to pass a content node to SetContentState on a blur,
|
|
// but we are just calling it to get the ContentStatesChanged notifications
|
|
presShell->GetPresContext()->EventStateManager()->
|
|
SetContentState(content, NS_EVENT_STATE_FOCUS);
|
|
}
|
|
|
|
// if an object/plug-in is being blurred, move the system focus to the
|
|
// parent window, otherwise events will still get fired at the plugin.
|
|
// But don't do this if we are blurring due to the window being lowered,
|
|
// otherwise, the parent window can get raised again.
|
|
if (mActiveWindow && aAdjustWidgets) {
|
|
nsIFrame* contentFrame = content->GetPrimaryFrame();
|
|
nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
|
|
if (objectFrame) {
|
|
// note that the presshell's widget is being retrieved here, not the one
|
|
// for the object frame.
|
|
nsIViewManager* vm = presShell->GetViewManager();
|
|
if (vm) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool result = PR_TRUE;
|
|
if (sendBlurEvent) {
|
|
// if there is an active window, update commands. If there isn't an active
|
|
// window, then this was a blur caused by the active window being lowered,
|
|
// so there is no need to update the commands
|
|
if (mActiveWindow)
|
|
window->UpdateCommands(NS_LITERAL_STRING("focus"));
|
|
|
|
SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
|
|
content->GetCurrentDoc(), content, 1, PR_FALSE);
|
|
}
|
|
|
|
// if we are leaving the document or the window was lowered, make the caret
|
|
// invisible.
|
|
if (aIsLeavingDocument || !mActiveWindow)
|
|
SetCaretVisible(presShell, PR_FALSE, nsnull);
|
|
|
|
// at this point, it is expected that this window will be still be
|
|
// focused, but the focused content will be null, as it was cleared before
|
|
// the event. If this isn't the case, then something else was focused during
|
|
// the blur event above and we should just return. However, if
|
|
// aIsLeavingDocument is set, a new document is desired, so make sure to
|
|
// blur the document and window.
|
|
if (mFocusedWindow != window ||
|
|
(mFocusedContent != nsnull && !aIsLeavingDocument)) {
|
|
result = PR_FALSE;
|
|
}
|
|
else if (aIsLeavingDocument) {
|
|
window->TakeFocus(PR_FALSE, 0);
|
|
|
|
// clear the focus so that the ancestor frame hierarchy is in the correct
|
|
// state. Pass true because aAncestorWindowToFocus is thought to be
|
|
// focused at this point.
|
|
if (aAncestorWindowToFocus)
|
|
aAncestorWindowToFocus->SetFocusedNode(nsnull, 0, PR_TRUE);
|
|
|
|
mFocusedWindow = nsnull;
|
|
mFocusedContent = nsnull;
|
|
|
|
// pass 1 for the focus method when calling SendFocusOrBlurEvent just so
|
|
// that the check is made for suppressed documents. Check to ensure that
|
|
// the document isn't null in case someone closed it during the blur above
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
|
|
if (doc)
|
|
SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, doc, 1, PR_FALSE);
|
|
if (mFocusedWindow == nsnull)
|
|
SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, window, 1, PR_FALSE);
|
|
|
|
// check if a different window was focused
|
|
result = (mFocusedWindow == nsnull && mActiveWindow);
|
|
}
|
|
else if (mActiveWindow) {
|
|
// Otherwise, the blur of the element without blurring the document
|
|
// occurred normally. Call UpdateCaret to redisplay the caret at the right
|
|
// location within the document. This is needed to ensure that the caret
|
|
// used for caret browsing is made visible again when an input field is
|
|
// blurred.
|
|
UpdateCaret(PR_FALSE, PR_TRUE, nsnull);
|
|
}
|
|
|
|
if (clearFirstBlurEvent)
|
|
mFirstBlurEvent = nsnull;
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
nsFocusManager::Focus(nsPIDOMWindow* aWindow,
|
|
nsIContent* aContent,
|
|
PRUint32 aFlags,
|
|
PRBool aIsNewDocument,
|
|
PRBool aFocusChanged,
|
|
PRBool aWindowRaised,
|
|
PRBool aAdjustWidgets)
|
|
{
|
|
if (!aWindow)
|
|
return;
|
|
|
|
if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
|
|
return;
|
|
|
|
// Keep a reference to the presShell since dispatching the DOM event may
|
|
// cause the document to be destroyed.
|
|
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
|
|
if (!docShell)
|
|
return;
|
|
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
docShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (!presShell)
|
|
return;
|
|
|
|
// If the focus actually changed, set the focus method (mouse, keyboard, etc).
|
|
// Otherwise, just get the current focus method and use that. This ensures
|
|
// that the method is set during the document and window focus events.
|
|
PRUint32 focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
|
|
aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
|
|
|
|
if (!IsWindowVisible(aWindow)) {
|
|
// if the window isn't visible, for instance because it is a hidden tab,
|
|
// update the current focus and scroll it into view but don't do anything else
|
|
if (CheckIfFocusable(aContent, aFlags)) {
|
|
aWindow->SetFocusedNode(aContent, focusMethod);
|
|
if (aFocusChanged)
|
|
ScrollIntoView(presShell, aContent, aFlags);
|
|
}
|
|
return;
|
|
}
|
|
|
|
PRBool clearFirstFocusEvent = PR_FALSE;
|
|
if (!mFirstFocusEvent) {
|
|
mFirstFocusEvent = aContent;
|
|
clearFirstFocusEvent = PR_TRUE;
|
|
}
|
|
|
|
#ifdef DEBUG_FOCUS
|
|
PRINTTAGF("**Element %s has been focused", aContent);
|
|
nsCOMPtr<nsIDocument> docm = do_QueryInterface(aWindow->GetExtantDocument());
|
|
if (docm)
|
|
PRINTTAGF(" from %s", docm->GetRootElement());
|
|
printf(" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]\n",
|
|
aIsNewDocument, aFocusChanged, aWindowRaised, aFlags);
|
|
#endif
|
|
|
|
if (aIsNewDocument) {
|
|
// if this is a new document, update the parent chain of frames so that
|
|
// focus can be traversed from the top level down to the newly focused
|
|
// window.
|
|
AdjustWindowFocus(aWindow, PR_FALSE);
|
|
|
|
// Update the window touch registration to reflect the state of
|
|
// the new document that got focus
|
|
aWindow->UpdateTouchState();
|
|
}
|
|
|
|
// indicate that the window has taken focus.
|
|
if (aWindow->TakeFocus(PR_TRUE, focusMethod))
|
|
aIsNewDocument = PR_TRUE;
|
|
|
|
mFocusedWindow = aWindow;
|
|
|
|
// Update the system focus by focusing the root widget. But avoid this
|
|
// if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
|
|
// own widget and is either already focused or is about to be focused.
|
|
nsCOMPtr<nsIWidget> objectFrameWidget;
|
|
if (aContent) {
|
|
nsIFrame* contentFrame = aContent->GetPrimaryFrame();
|
|
nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
|
|
if (objectFrame)
|
|
objectFrameWidget = objectFrame->GetWidget();
|
|
}
|
|
if (aAdjustWidgets && !objectFrameWidget) {
|
|
nsIViewManager* vm = presShell->GetViewManager();
|
|
if (vm) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
// if switching to a new document, first fire the focus event on the
|
|
// document and then the window.
|
|
if (aIsNewDocument) {
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aWindow->GetExtantDocument());
|
|
if (doc)
|
|
SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
|
|
doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
|
|
if (mFocusedWindow == aWindow && mFocusedContent == nsnull)
|
|
SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
|
|
aWindow, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
|
|
}
|
|
|
|
// check to ensure that the element is still focusable, and that nothing
|
|
// else was focused during the events above.
|
|
if (CheckIfFocusable(aContent, aFlags) &&
|
|
mFocusedWindow == aWindow && mFocusedContent == nsnull) {
|
|
mFocusedContent = aContent;
|
|
|
|
nsIContent* focusedNode = aWindow->GetFocusedNode();
|
|
PRBool isRefocus = focusedNode && focusedNode->IsEqual(aContent);
|
|
|
|
aWindow->SetFocusedNode(aContent, focusMethod);
|
|
|
|
PRBool sendFocusEvent =
|
|
aContent && aContent->IsInDoc() && !IsNonFocusableRoot(aContent);
|
|
if (sendFocusEvent) {
|
|
// if the focused element changed, scroll it into view
|
|
if (aFocusChanged)
|
|
ScrollIntoView(presShell, aContent, aFlags);
|
|
|
|
// inform the EventStateManager so that content state change notifications
|
|
// are made.
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
presContext->EventStateManager()->
|
|
SetContentState(aContent, NS_EVENT_STATE_FOCUS);
|
|
|
|
// if this is an object/plug-in, focus the plugin's widget. Note that we might
|
|
// no longer be in the same document, due to the events we fired above when
|
|
// aIsNewDocument.
|
|
if (aAdjustWidgets && presShell->GetDocument() == aContent->GetDocument()) {
|
|
if (objectFrameWidget)
|
|
objectFrameWidget->SetFocus(PR_FALSE);
|
|
}
|
|
|
|
nsIMEStateManager::OnChangeFocus(presContext, aContent);
|
|
|
|
// as long as this focus wasn't because a window was raised, update the
|
|
// commands
|
|
// XXXndeakin P2 someone could adjust the focus during the update
|
|
if (!aWindowRaised)
|
|
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
|
|
|
|
SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell,
|
|
aContent->GetCurrentDoc(),
|
|
aContent, aFlags & FOCUSMETHOD_MASK,
|
|
aWindowRaised, isRefocus);
|
|
|
|
nsIMEStateManager::OnTextStateFocus(presContext, aContent);
|
|
} else {
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
nsIMEStateManager::OnTextStateBlur(presContext, nsnull);
|
|
nsIMEStateManager::OnChangeFocus(presContext, nsnull);
|
|
if (!aWindowRaised) {
|
|
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// If the window focus event (fired above when aIsNewDocument) caused
|
|
// the plugin not to be focusable, update the system focus by focusing
|
|
// the root widget.
|
|
if (aAdjustWidgets && objectFrameWidget &&
|
|
mFocusedWindow == aWindow && mFocusedContent == nsnull) {
|
|
nsIViewManager* vm = presShell->GetViewManager();
|
|
if (vm) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_FALSE);
|
|
}
|
|
}
|
|
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
nsIMEStateManager::OnTextStateBlur(presContext, nsnull);
|
|
nsIMEStateManager::OnChangeFocus(presContext, nsnull);
|
|
|
|
if (!aWindowRaised)
|
|
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
|
|
}
|
|
|
|
// update the caret visibility and position to match the newly focused
|
|
// element. However, don't update the position if this was a focus due to a
|
|
// mouse click as the selection code would already have moved the caret as
|
|
// needed. If this is a different document than was focused before, also
|
|
// update the caret's visibility. If this is the same document, the caret
|
|
// visibility should be the same as before so there is no need to update it.
|
|
if (mFocusedContent == aContent)
|
|
UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
|
|
mFocusedContent);
|
|
|
|
if (clearFirstFocusEvent)
|
|
mFirstFocusEvent = nsnull;
|
|
}
|
|
|
|
class FocusBlurEvent : public nsRunnable
|
|
{
|
|
public:
|
|
FocusBlurEvent(nsISupports* aTarget, PRUint32 aType,
|
|
nsPresContext* aContext, PRBool aWindowRaised,
|
|
PRBool aIsRefocus)
|
|
: mTarget(aTarget), mType(aType), mContext(aContext),
|
|
mWindowRaised(aWindowRaised), mIsRefocus(aIsRefocus) {}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
nsFocusEvent event(PR_TRUE, mType);
|
|
event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
|
|
event.fromRaise = mWindowRaised;
|
|
event.isRefocus = mIsRefocus;
|
|
return nsEventDispatcher::Dispatch(mTarget, mContext, &event);
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> mTarget;
|
|
PRUint32 mType;
|
|
nsRefPtr<nsPresContext> mContext;
|
|
PRBool mWindowRaised;
|
|
PRBool mIsRefocus;
|
|
};
|
|
|
|
void
|
|
nsFocusManager::SendFocusOrBlurEvent(PRUint32 aType,
|
|
nsIPresShell* aPresShell,
|
|
nsIDocument* aDocument,
|
|
nsISupports* aTarget,
|
|
PRUint32 aFocusMethod,
|
|
PRBool aWindowRaised,
|
|
PRBool aIsRefocus)
|
|
{
|
|
NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
|
|
"Wrong event type for SendFocusOrBlurEvent");
|
|
|
|
nsCOMPtr<nsPIDOMEventTarget> eventTarget = do_QueryInterface(aTarget);
|
|
|
|
// for focus events, if this event was from a mouse or key and event
|
|
// handling on the document is suppressed, queue the event and fire it
|
|
// later. For blur events, a non-zero value would be set for aFocusMethod.
|
|
if (aFocusMethod && aDocument && aDocument->EventHandlingSuppressed()) {
|
|
// aFlags is always 0 when aWindowRaised is true so this won't be called
|
|
// on a window raise.
|
|
NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
|
|
|
|
for (PRUint32 i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
|
|
// if this event was already queued, remove it and append it to the end
|
|
if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
|
|
mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
|
|
mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
|
|
mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget) {
|
|
mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
|
|
}
|
|
}
|
|
|
|
mDelayedBlurFocusEvents.AppendElement(
|
|
nsDelayedBlurOrFocusEvent(aType, aPresShell, aDocument, eventTarget));
|
|
return;
|
|
}
|
|
|
|
nsContentUtils::AddScriptRunner(
|
|
new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
|
|
aWindowRaised, aIsRefocus));
|
|
}
|
|
|
|
void
|
|
nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
|
|
nsIContent* aContent,
|
|
PRUint32 aFlags)
|
|
{
|
|
// if the noscroll flag isn't set, scroll the newly focused element into view
|
|
if (!(aFlags & FLAG_NOSCROLL))
|
|
aPresShell->ScrollContentIntoView(aContent,
|
|
NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
|
|
NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
|
|
nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
|
|
}
|
|
|
|
|
|
void
|
|
nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
|
|
{
|
|
// don't raise windows that are already raised or are in the process of
|
|
// being lowered
|
|
if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
|
|
return;
|
|
|
|
#if defined(XP_WIN) || defined(XP_OS2)
|
|
// Windows would rather we focus the child widget, otherwise, the toplevel
|
|
// widget will always end up being focused. Fortunately, focusing the child
|
|
// widget will also have the effect of raising the window this widget is in.
|
|
// But on other platforms, we can just focus the toplevel widget to raise
|
|
// the window.
|
|
nsCOMPtr<nsPIDOMWindow> childWindow;
|
|
GetFocusedDescendant(aWindow, PR_TRUE, getter_AddRefs(childWindow));
|
|
if (!childWindow)
|
|
childWindow = aWindow;
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
|
|
if (!docShell)
|
|
return;
|
|
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
docShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (!presShell)
|
|
return;
|
|
|
|
nsIViewManager* vm = presShell->GetViewManager();
|
|
if (vm) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
vm->GetRootWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_TRUE);
|
|
}
|
|
#else
|
|
nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
|
|
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(webnav);
|
|
if (treeOwnerAsWin) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
|
|
if (widget)
|
|
widget->SetFocus(PR_TRUE);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
nsFocusManager::UpdateCaret(PRBool aMoveCaretToFocus,
|
|
PRBool aUpdateVisibility,
|
|
nsIContent* aContent)
|
|
{
|
|
#ifdef DEBUG_FOCUS
|
|
printf("Update Caret: %d %d\n", aMoveCaretToFocus, aUpdateVisibility);
|
|
#endif
|
|
|
|
if (!mFocusedWindow)
|
|
return;
|
|
|
|
// this is called when a document is focused or when the caretbrowsing
|
|
// preference is changed
|
|
nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
|
|
if (!dsti)
|
|
return;
|
|
|
|
PRInt32 itemType;
|
|
dsti->GetItemType(&itemType);
|
|
if (itemType == nsIDocShellTreeItem::typeChrome)
|
|
return; // Never browse with caret in chrome
|
|
|
|
PRPackedBool browseWithCaret =
|
|
nsContentUtils::GetBoolPref("accessibility.browsewithcaret");
|
|
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
focusedDocShell->GetPresShell(getter_AddRefs(presShell));
|
|
if (!presShell)
|
|
return;
|
|
|
|
// If this is an editable document which isn't contentEditable, or a
|
|
// contentEditable document and the node to focus is contentEditable,
|
|
// return, so that we don't mess with caret visibility.
|
|
PRBool isEditable = PR_FALSE;
|
|
nsCOMPtr<nsIEditorDocShell> editorDocShell(do_QueryInterface(dsti));
|
|
if (editorDocShell) {
|
|
editorDocShell->GetEditable(&isEditable);
|
|
|
|
if (isEditable) {
|
|
nsCOMPtr<nsIHTMLDocument> doc =
|
|
do_QueryInterface(presShell->GetDocument());
|
|
|
|
PRBool isContentEditableDoc =
|
|
doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
|
|
|
|
PRBool isFocusEditable =
|
|
aContent && aContent->HasFlag(NODE_IS_EDITABLE);
|
|
if (!isContentEditableDoc || isFocusEditable)
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!isEditable && aMoveCaretToFocus)
|
|
MoveCaretToFocus(presShell, aContent);
|
|
|
|
if (!aUpdateVisibility)
|
|
return;
|
|
|
|
// XXXndeakin this doesn't seem right. It should be checking for this only
|
|
// on the nearest ancestor frame which is a chrome frame. But this is
|
|
// what the existing code does, so just leave it for now.
|
|
if (!browseWithCaret) {
|
|
nsCOMPtr<nsIContent> docContent =
|
|
do_QueryInterface(mFocusedWindow->GetFrameElementInternal());
|
|
if (docContent)
|
|
browseWithCaret = docContent->AttrValueIs(kNameSpaceID_None,
|
|
nsGkAtoms::showcaret,
|
|
NS_LITERAL_STRING("true"),
|
|
eCaseMatters);
|
|
}
|
|
|
|
SetCaretVisible(presShell, browseWithCaret, aContent);
|
|
}
|
|
|
|
void
|
|
nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
|
|
{
|
|
// rangeDoc is a document interface we can create a range with
|
|
nsCOMPtr<nsIDOMDocumentRange> rangeDoc(do_QueryInterface(aPresShell->GetDocument()));
|
|
if (rangeDoc) {
|
|
nsCOMPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
|
|
nsCOMPtr<nsISelection> domSelection = frameSelection->
|
|
GetSelection(nsISelectionController::SELECTION_NORMAL);
|
|
if (domSelection) {
|
|
nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
|
|
// First clear the selection. This way, if there is no currently focused
|
|
// content, the selection will just be cleared.
|
|
domSelection->RemoveAllRanges();
|
|
if (currentFocusNode) {
|
|
nsCOMPtr<nsIDOMRange> newRange;
|
|
nsresult rv = rangeDoc->CreateRange(getter_AddRefs(newRange));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// Set the range to the start of the currently focused node
|
|
// Make sure it's collapsed
|
|
newRange->SelectNodeContents(currentFocusNode);
|
|
nsCOMPtr<nsIDOMNode> firstChild;
|
|
currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
|
|
if (!firstChild ||
|
|
aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
|
|
// If current focus node is a leaf, set range to before the
|
|
// node by using the parent as a container.
|
|
// This prevents it from appearing as selected.
|
|
newRange->SetStartBefore(currentFocusNode);
|
|
newRange->SetEndBefore(currentFocusNode);
|
|
}
|
|
domSelection->AddRange(newRange);
|
|
domSelection->CollapseToStart();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
|
|
PRBool aVisible,
|
|
nsIContent* aContent)
|
|
{
|
|
// When browsing with caret, make sure caret is visible after new focus
|
|
// Return early if there is no caret. This can happen for the testcase
|
|
// for bug 308025 where a window is closed in a blur handler.
|
|
nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
|
|
if (!caret)
|
|
return NS_OK;
|
|
|
|
PRBool caretVisible = PR_FALSE;
|
|
caret->GetCaretVisible(&caretVisible);
|
|
if (!aVisible && !caretVisible)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsFrameSelection> frameSelection;
|
|
if (aContent) {
|
|
NS_ASSERTION(aContent->GetDocument() == aPresShell->GetDocument(),
|
|
"Wrong document?");
|
|
nsIFrame *focusFrame = aContent->GetPrimaryFrame();
|
|
if (focusFrame)
|
|
frameSelection = focusFrame->GetFrameSelection();
|
|
}
|
|
|
|
nsCOMPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
|
|
|
|
if (docFrameSelection && caret &&
|
|
(frameSelection == docFrameSelection || !aContent)) {
|
|
nsISelection* domSelection = docFrameSelection->
|
|
GetSelection(nsISelectionController::SELECTION_NORMAL);
|
|
if (domSelection) {
|
|
// First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
|
|
caret->SetCaretVisible(PR_FALSE);
|
|
|
|
// Tell the caret which selection to use
|
|
caret->SetCaretDOMSelection(domSelection);
|
|
|
|
// In content, we need to set the caret. The only special case is edit
|
|
// fields, which have a different frame selection from the document.
|
|
// They will take care of making the caret visible themselves.
|
|
|
|
nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
|
|
if (!selCon)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
selCon->SetCaretEnabled(aVisible);
|
|
caret->SetCaretVisible(aVisible);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
|
|
nsIPresShell* aPresShell,
|
|
nsIContent **aStartContent,
|
|
nsIContent **aEndContent)
|
|
{
|
|
*aStartContent = *aEndContent = nsnull;
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
|
|
nsPresContext* presContext = aPresShell->GetPresContext();
|
|
NS_ASSERTION(presContext, "mPresContent is null!!");
|
|
|
|
nsCOMPtr<nsFrameSelection> frameSelection;
|
|
frameSelection = aPresShell->FrameSelection();
|
|
|
|
nsCOMPtr<nsISelection> domSelection;
|
|
if (frameSelection) {
|
|
domSelection = frameSelection->
|
|
GetSelection(nsISelectionController::SELECTION_NORMAL);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRBool isCollapsed = PR_FALSE;
|
|
nsCOMPtr<nsIContent> startContent, endContent;
|
|
PRInt32 startOffset = 0;
|
|
if (domSelection) {
|
|
domSelection->GetIsCollapsed(&isCollapsed);
|
|
nsCOMPtr<nsIDOMRange> domRange;
|
|
rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
|
|
if (domRange) {
|
|
domRange->GetStartContainer(getter_AddRefs(startNode));
|
|
domRange->GetEndContainer(getter_AddRefs(endNode));
|
|
domRange->GetStartOffset(&startOffset);
|
|
|
|
nsIContent *childContent = nsnull;
|
|
|
|
startContent = do_QueryInterface(startNode);
|
|
if (startContent && startContent->IsElement()) {
|
|
NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
|
|
childContent = startContent->GetChildAt(startOffset);
|
|
if (childContent) {
|
|
startContent = childContent;
|
|
}
|
|
}
|
|
|
|
endContent = do_QueryInterface(endNode);
|
|
if (endContent && endContent->IsElement()) {
|
|
PRInt32 endOffset = 0;
|
|
domRange->GetEndOffset(&endOffset);
|
|
NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
|
|
childContent = endContent->GetChildAt(endOffset);
|
|
if (childContent) {
|
|
endContent = childContent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
rv = NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsIFrame *startFrame = nsnull;
|
|
if (startContent) {
|
|
startFrame = startContent->GetPrimaryFrame();
|
|
if (isCollapsed) {
|
|
// Next check to see if our caret is at the very end of a node
|
|
// If so, the caret is actually sitting in front of the next
|
|
// logical frame's primary node - so for this case we need to
|
|
// change caretContent to that node.
|
|
|
|
nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(startContent));
|
|
PRUint16 nodeType;
|
|
domNode->GetNodeType(&nodeType);
|
|
|
|
if (nodeType == nsIDOMNode::TEXT_NODE) {
|
|
nsAutoString nodeValue;
|
|
domNode->GetNodeValue(nodeValue);
|
|
|
|
PRBool isFormControl =
|
|
startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
|
|
|
|
if (nodeValue.Length() == (PRUint32)startOffset && !isFormControl &&
|
|
startContent != aDocument->GetRootElement()) {
|
|
// Yes, indeed we were at the end of the last node
|
|
nsCOMPtr<nsIFrameEnumerator> frameTraversal;
|
|
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
|
|
presContext, startFrame,
|
|
eLeaf,
|
|
PR_FALSE, // aVisual
|
|
PR_FALSE, // aLockInScrollView
|
|
PR_TRUE // aFollowOOFs
|
|
);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsIFrame *newCaretFrame = nsnull;
|
|
nsCOMPtr<nsIContent> newCaretContent = startContent;
|
|
PRBool endOfSelectionInStartNode(startContent == endContent);
|
|
do {
|
|
// Continue getting the next frame until the primary content for the frame
|
|
// we are on changes - we don't want to be stuck in the same place
|
|
frameTraversal->Next();
|
|
newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
|
|
if (nsnull == newCaretFrame)
|
|
break;
|
|
newCaretContent = newCaretFrame->GetContent();
|
|
} while (!newCaretContent || newCaretContent == startContent);
|
|
|
|
if (newCaretFrame && newCaretContent) {
|
|
// If the caret is exactly at the same position of the new frame,
|
|
// then we can use the newCaretFrame and newCaretContent for our position
|
|
nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
|
|
nsRect caretRect;
|
|
nsIFrame *frame = caret->GetGeometry(domSelection, &caretRect);
|
|
if (frame) {
|
|
nsPoint caretWidgetOffset;
|
|
nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
|
|
caretRect.MoveBy(caretWidgetOffset);
|
|
nsPoint newCaretOffset;
|
|
nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
|
|
if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
|
|
caretRect.x == newCaretOffset.x) {
|
|
// The caret is at the start of the new element.
|
|
startFrame = newCaretFrame;
|
|
startContent = newCaretContent;
|
|
if (endOfSelectionInStartNode) {
|
|
endContent = newCaretContent; // Ensure end of selection is not before start
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*aStartContent = startContent;
|
|
*aEndContent = endContent;
|
|
NS_IF_ADDREF(*aStartContent);
|
|
NS_IF_ADDREF(*aEndContent);
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
|
|
nsIContent* aStartContent,
|
|
PRInt32 aType,
|
|
nsIContent** aNextContent)
|
|
{
|
|
*aNextContent = nsnull;
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
|
|
if (!docShell)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIContent> startContent = aStartContent;
|
|
if (!startContent && aType != MOVEFOCUS_CARET)
|
|
startContent = aWindow->GetFocusedNode();
|
|
|
|
nsCOMPtr<nsIDocument> doc;
|
|
if (startContent)
|
|
doc = startContent->GetCurrentDoc();
|
|
else
|
|
doc = do_QueryInterface(aWindow->GetExtantDocument());
|
|
if (!doc)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsILookAndFeel> lookNFeel(do_GetService(kLookAndFeelCID));
|
|
lookNFeel->GetMetric(nsILookAndFeel::eMetric_TabFocusModel,
|
|
nsIContent::sTabFocusModel);
|
|
|
|
if (aType == MOVEFOCUS_ROOT) {
|
|
NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, PR_FALSE, PR_FALSE));
|
|
return NS_OK;
|
|
}
|
|
if (aType == MOVEFOCUS_FORWARDDOC) {
|
|
NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(PR_TRUE));
|
|
return NS_OK;
|
|
}
|
|
if (aType == MOVEFOCUS_BACKWARDDOC) {
|
|
NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(PR_FALSE));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIContent* rootContent = doc->GetRootElement();
|
|
NS_ENSURE_TRUE(rootContent, NS_OK);
|
|
|
|
nsIPresShell *presShell = doc->GetShell();
|
|
NS_ENSURE_TRUE(presShell, NS_OK);
|
|
|
|
if (aType == MOVEFOCUS_FIRST) {
|
|
if (!aStartContent)
|
|
startContent = rootContent;
|
|
return GetNextTabbableContent(presShell, startContent,
|
|
nsnull, startContent,
|
|
PR_TRUE, 1, PR_FALSE, aNextContent);
|
|
}
|
|
if (aType == MOVEFOCUS_LAST) {
|
|
if (!aStartContent)
|
|
startContent = rootContent;
|
|
return GetNextTabbableContent(presShell, startContent,
|
|
nsnull, startContent,
|
|
PR_FALSE, 0, PR_FALSE, aNextContent);
|
|
}
|
|
|
|
PRBool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
|
|
PRBool doNavigation = PR_TRUE;
|
|
PRBool ignoreTabIndex = PR_FALSE;
|
|
// when a popup is open, we want to ensure that tab navigation occurs only
|
|
// within the most recently opened panel. If a popup is open, its frame will
|
|
// be stored in popupFrame.
|
|
nsIFrame* popupFrame = nsnull;
|
|
|
|
PRInt32 tabIndex = forward ? 1 : 0;
|
|
if (startContent) {
|
|
nsIFrame* frame = startContent->GetPrimaryFrame();
|
|
if (startContent->Tag() == nsGkAtoms::area &&
|
|
startContent->IsHTML())
|
|
startContent->IsFocusable(&tabIndex);
|
|
else if (frame)
|
|
frame->IsFocusable(&tabIndex, 0);
|
|
else
|
|
startContent->IsFocusable(&tabIndex);
|
|
|
|
// if the current element isn't tabbable, ignore the tabindex and just
|
|
// look for the next element. The root content won't have a tabindex
|
|
// so just treat this as the beginning of the tab order.
|
|
if (tabIndex < 0) {
|
|
tabIndex = 1;
|
|
if (startContent != rootContent)
|
|
ignoreTabIndex = PR_TRUE;
|
|
}
|
|
|
|
// check if the focus is currently inside a popup. Elements such as the
|
|
// autocomplete widget use the noautofocus attribute to allow the focus to
|
|
// remain outside the popup when it is opened.
|
|
if (frame) {
|
|
popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
|
|
nsGkAtoms::menuPopupFrame);
|
|
}
|
|
|
|
if (popupFrame) {
|
|
// Don't navigate outside of a popup, so pretend that the
|
|
// root content is the popup itself
|
|
rootContent = popupFrame->GetContent();
|
|
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
|
|
}
|
|
else if (!forward) {
|
|
// If focus moves backward and when current focused node is root
|
|
// content or <body> element which is editable by contenteditable
|
|
// attribute, focus should move to its parent document.
|
|
if (startContent == rootContent) {
|
|
doNavigation = PR_FALSE;
|
|
} else {
|
|
nsIDocument* doc = startContent->GetCurrentDoc();
|
|
if (startContent ==
|
|
nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
|
|
doNavigation = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
#ifdef MOZ_XUL
|
|
// if there is no focus, yet a panel is open, focus the first item in
|
|
// the panel
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
if (pm)
|
|
popupFrame = pm->GetTopPopup(ePopupTypePanel);
|
|
#endif
|
|
if (popupFrame) {
|
|
rootContent = popupFrame->GetContent();
|
|
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
|
|
startContent = rootContent;
|
|
}
|
|
else {
|
|
// Otherwise, for content shells, start from the location of the caret.
|
|
PRInt32 itemType;
|
|
nsCOMPtr<nsIDocShellTreeItem> shellItem = do_QueryInterface(docShell);
|
|
shellItem->GetItemType(&itemType);
|
|
if (itemType != nsIDocShellTreeItem::typeChrome) {
|
|
nsCOMPtr<nsIContent> endSelectionContent;
|
|
GetSelectionLocation(doc, presShell,
|
|
getter_AddRefs(startContent),
|
|
getter_AddRefs(endSelectionContent));
|
|
// If the selection is on the rootContent, then there is no selection
|
|
if (startContent == rootContent) {
|
|
startContent = nsnull;
|
|
}
|
|
else if (startContent && startContent->HasFlag(NODE_IS_EDITABLE)) {
|
|
// Don't start from the selection if the selection is in a
|
|
// contentEditable region.
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
|
|
if (htmlDoc &&
|
|
htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable)
|
|
startContent = nsnull;
|
|
}
|
|
|
|
if (aType == MOVEFOCUS_CARET) {
|
|
// GetFocusInSelection finds a focusable link near the caret.
|
|
// If there is no start content though, don't do this to avoid
|
|
// focusing something unexpected.
|
|
if (startContent) {
|
|
GetFocusInSelection(aWindow, startContent,
|
|
endSelectionContent, aNextContent);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (startContent) {
|
|
// when starting from a selection, we always want to find the next or
|
|
// previous element in the document. So the tabindex on elements
|
|
// should be ignored.
|
|
ignoreTabIndex = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
if (!startContent) {
|
|
// otherwise, just use the root content as the starting point
|
|
startContent = rootContent;
|
|
NS_ENSURE_TRUE(startContent, NS_OK);
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(startContent, "starting content not set");
|
|
|
|
// keep a reference to the starting content. If we find that again, it means
|
|
// we've iterated around completely and we don't want to adjust the focus.
|
|
// The skipOriginalContentCheck will be set to true only for the first time
|
|
// GetNextTabbableContent is called. This ensures that we don't break out
|
|
// when nothing is focused to start with. Specifically,
|
|
// GetNextTabbableContent first checks the root content -- which happens to
|
|
// be the same as the start content -- when nothing is focused and tabbing
|
|
// forward. Without skipOriginalContentCheck set to true, we'd end up
|
|
// returning right away and focusing nothing. Luckily, GetNextTabbableContent
|
|
// will never wrap around on its own, and can only return the original
|
|
// content when it is called a second time or later.
|
|
PRBool skipOriginalContentCheck = PR_TRUE;
|
|
nsIContent* originalStartContent = startContent;
|
|
|
|
#ifdef DEBUG_FOCUS_NAVIGATION
|
|
PRINTTAGF("Focus Navigation Start Content %s\n", startContent);
|
|
printf("[Tabindex: %d Ignore: %d]", tabIndex, ignoreTabIndex);
|
|
#endif
|
|
|
|
while (doc) {
|
|
if (doNavigation) {
|
|
nsCOMPtr<nsIContent> nextFocus;
|
|
nsresult rv = GetNextTabbableContent(presShell, rootContent,
|
|
skipOriginalContentCheck ? nsnull : originalStartContent,
|
|
startContent, forward,
|
|
tabIndex, ignoreTabIndex,
|
|
getter_AddRefs(nextFocus));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// found a content node to focus.
|
|
if (nextFocus) {
|
|
#ifdef DEBUG_FOCUS_NAVIGATION
|
|
PRINTTAGF("Next Content: %s\n", nextFocus);
|
|
#endif
|
|
// as long as the found node was not the same as the starting node,
|
|
// set it as the return value.
|
|
if (nextFocus != originalStartContent)
|
|
NS_ADDREF(*aNextContent = nextFocus);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (popupFrame) {
|
|
// in a popup, so start again from the beginning of the popup. However,
|
|
// if we already started at the beginning, then there isn't anything to
|
|
// focus, so just return
|
|
if (startContent != rootContent) {
|
|
startContent = rootContent;
|
|
tabIndex = forward ? 1 : 0;
|
|
continue;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
doNavigation = PR_TRUE;
|
|
skipOriginalContentCheck = PR_FALSE;
|
|
ignoreTabIndex = PR_FALSE;
|
|
|
|
// reached the beginning or end of the document. Traverse up to the parent
|
|
// document and try again.
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(docShell);
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> docShellParent;
|
|
dsti->GetParent(getter_AddRefs(docShellParent));
|
|
if (docShellParent) {
|
|
// move up to the parent shell and try again from there.
|
|
|
|
// first, get the frame element this window is inside.
|
|
nsCOMPtr<nsPIDOMWindow> piWindow = do_GetInterface(docShell);
|
|
NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
|
|
|
|
// Next, retrieve the parent docshell, document and presshell.
|
|
docShell = do_QueryInterface(docShellParent);
|
|
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsPIDOMWindow> piParentWindow = do_GetInterface(docShellParent);
|
|
NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
|
|
doc = do_QueryInterface(piParentWindow->GetExtantDocument());
|
|
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
|
|
|
|
presShell = doc->GetShell();
|
|
|
|
rootContent = doc->GetRootElement();
|
|
startContent = do_QueryInterface(piWindow->GetFrameElementInternal());
|
|
if (startContent) {
|
|
nsIFrame* frame = startContent->GetPrimaryFrame();
|
|
if (!frame)
|
|
return NS_OK;
|
|
|
|
frame->IsFocusable(&tabIndex, 0);
|
|
if (tabIndex < 0) {
|
|
tabIndex = 1;
|
|
ignoreTabIndex = PR_TRUE;
|
|
}
|
|
|
|
// if the frame is inside a popup, make sure to scan only within the
|
|
// popup. This handles the situation of tabbing amongst elements
|
|
// inside an iframe which is itself inside a popup. Otherwise,
|
|
// navigation would move outside the popup when tabbing outside the
|
|
// iframe.
|
|
popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
|
|
nsGkAtoms::menuPopupFrame);
|
|
if (popupFrame) {
|
|
rootContent = popupFrame->GetContent();
|
|
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
|
|
}
|
|
}
|
|
else {
|
|
startContent = rootContent;
|
|
tabIndex = forward ? 1 : 0;
|
|
}
|
|
}
|
|
else {
|
|
// no parent, so call the tree owner. This will tell the embedder that
|
|
// it should take the focus.
|
|
PRBool tookFocus;
|
|
docShell->TabToTreeOwner(forward, &tookFocus);
|
|
// if the tree owner, took the focus, blur the current content
|
|
if (tookFocus) {
|
|
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(docShell);
|
|
if (window->GetFocusedNode() == mFocusedContent)
|
|
Blur(mFocusedWindow, nsnull, PR_TRUE, PR_TRUE);
|
|
else
|
|
window->SetFocusedNode(nsnull);
|
|
return NS_OK;
|
|
}
|
|
|
|
// reset the tab index and start again from the beginning or end
|
|
startContent = rootContent;
|
|
tabIndex = forward ? 1 : 0;
|
|
}
|
|
|
|
// wrapped all the way around and didn't find anything to move the focus
|
|
// to, so just break out
|
|
if (startContent == originalStartContent)
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
|
|
nsIContent* aRootContent,
|
|
nsIContent* aOriginalStartContent,
|
|
nsIContent* aStartContent,
|
|
PRBool aForward,
|
|
PRInt32 aCurrentTabIndex,
|
|
PRBool aIgnoreTabIndex,
|
|
nsIContent** aResultContent)
|
|
{
|
|
*aResultContent = nsnull;
|
|
|
|
nsCOMPtr<nsIContent> startContent = aStartContent;
|
|
if (!startContent)
|
|
return NS_OK;
|
|
|
|
#ifdef DEBUG_FOCUS_NAVIGATION
|
|
PRINTTAGF("GetNextTabbable: %s", aStartContent);
|
|
printf(" tabindex: %d\n", aCurrentTabIndex);
|
|
#endif
|
|
|
|
nsPresContext* presContext = aPresShell->GetPresContext();
|
|
|
|
PRBool getNextFrame = PR_TRUE;
|
|
nsCOMPtr<nsIContent> iterStartContent = aStartContent;
|
|
while (1) {
|
|
nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
|
|
// if there is no frame, look for another content node that has a frame
|
|
if (!startFrame) {
|
|
// if the root content doesn't have a frame, just return
|
|
if (iterStartContent == aRootContent)
|
|
return NS_OK;
|
|
|
|
// look for the next or previous content node in tree order
|
|
nsTreeWalker walker(aRootContent, nsIDOMNodeFilter::SHOW_ALL, nsnull, PR_TRUE);
|
|
nsCOMPtr<nsIDOMNode> nextNode = do_QueryInterface(iterStartContent);
|
|
walker.SetCurrentNode(nextNode);
|
|
if (NS_SUCCEEDED(aForward ? walker.NextNode(getter_AddRefs(nextNode)) :
|
|
walker.PreviousNode(getter_AddRefs(nextNode)))) {
|
|
iterStartContent = do_QueryInterface(nextNode);
|
|
// we've already skipped over the initial focused content, so we
|
|
// don't want to traverse frames.
|
|
getNextFrame = PR_FALSE;
|
|
if (iterStartContent)
|
|
continue;
|
|
}
|
|
|
|
// otherwise, as a last attempt, just look at the root content
|
|
iterStartContent = aRootContent;
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIFrameEnumerator> frameTraversal;
|
|
nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
|
|
presContext, startFrame,
|
|
ePreOrder,
|
|
PR_FALSE, // aVisual
|
|
PR_FALSE, // aLockInScrollView
|
|
PR_TRUE // aFollowOOFs
|
|
);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (iterStartContent == aRootContent) {
|
|
if (!aForward) {
|
|
frameTraversal->Last();
|
|
} else if (aRootContent->IsFocusable()) {
|
|
frameTraversal->Next();
|
|
}
|
|
}
|
|
else if (getNextFrame &&
|
|
(!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
|
|
!iterStartContent->IsHTML())) {
|
|
// Need to do special check in case we're in an imagemap which has multiple
|
|
// content nodes per frame, so don't skip over the starting frame.
|
|
if (aForward)
|
|
frameTraversal->Next();
|
|
else
|
|
frameTraversal->Prev();
|
|
}
|
|
|
|
// Walk frames to find something tabbable matching mCurrentTabIndex
|
|
nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
|
|
while (frame) {
|
|
// TabIndex not set defaults to 0 for form elements, anchors and other
|
|
// elements that are normally focusable. Tabindex defaults to -1
|
|
// for elements that are not normally focusable.
|
|
// The returned computed tabindex from IsFocusable() is as follows:
|
|
// < 0 not tabbable at all
|
|
// == 0 in normal tab order (last after positive tabindexed items)
|
|
// > 0 can be tabbed to in the order specified by this value
|
|
|
|
PRInt32 tabIndex;
|
|
frame->IsFocusable(&tabIndex, 0);
|
|
|
|
#ifdef DEBUG_FOCUS_NAVIGATION
|
|
if (frame->GetContent()) {
|
|
PRINTTAGF("Next Tabbable %s:", frame->GetContent());
|
|
printf(" with tabindex: %d expected: %d\n", tabIndex, aCurrentTabIndex);
|
|
}
|
|
#endif
|
|
|
|
nsIContent* currentContent = frame->GetContent();
|
|
if (tabIndex >= 0) {
|
|
NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
|
|
if (currentContent->Tag() == nsGkAtoms::img &&
|
|
currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
|
|
// This is an image with a map. Image map areas are not traversed by
|
|
// nsIFrameTraversal so look for the next or previous area element.
|
|
nsIContent *areaContent =
|
|
GetNextTabbableMapArea(aForward, aCurrentTabIndex,
|
|
currentContent, iterStartContent);
|
|
if (areaContent) {
|
|
NS_ADDREF(*aResultContent = areaContent);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
|
|
// break out if we've wrapped around to the start again.
|
|
if (aOriginalStartContent && currentContent == aOriginalStartContent) {
|
|
NS_ADDREF(*aResultContent = currentContent);
|
|
return NS_OK;
|
|
}
|
|
|
|
// found a node with a matching tab index. Check if it is a child
|
|
// frame. If so, navigate into the child frame instead.
|
|
nsIDocument* doc = currentContent->GetCurrentDoc();
|
|
NS_ASSERTION(doc, "content not in document");
|
|
nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
|
|
if (subdoc) {
|
|
if (!subdoc->EventHandlingSuppressed()) {
|
|
if (aForward) {
|
|
// when tabbing forward into a frame, return the root
|
|
// frame so that the canvas becomes focused.
|
|
nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
|
|
if (subframe) {
|
|
// If the subframe body is editable by contenteditable,
|
|
// we should set the editor's root element rather than the
|
|
// actual root element. Otherwise, we should set the focus
|
|
// to the root content.
|
|
*aResultContent =
|
|
nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
|
|
if (!*aResultContent ||
|
|
!((*aResultContent)->GetPrimaryFrame())) {
|
|
*aResultContent =
|
|
GetRootForFocus(subframe, subdoc, PR_FALSE, PR_TRUE);
|
|
}
|
|
if (*aResultContent) {
|
|
NS_ADDREF(*aResultContent);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
Element* rootElement = subdoc->GetRootElement();
|
|
nsIPresShell* subShell = subdoc->GetShell();
|
|
if (rootElement && subShell) {
|
|
rv = GetNextTabbableContent(subShell, rootElement,
|
|
aOriginalStartContent, rootElement,
|
|
aForward, (aForward ? 1 : 0),
|
|
PR_FALSE, aResultContent);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (*aResultContent)
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
// otherwise, use this as the next content node to tab to, unless
|
|
// this was the element we started on. This would happen for
|
|
// instance on an element with child frames, where frame navigation
|
|
// could return the original element again. In that case, just skip
|
|
// it. Also, if the next content node is the root content, then
|
|
// return it. This latter case would happen only if someone made a
|
|
// popup focusable.
|
|
// Also, when going backwards, check to ensure that the focus
|
|
// wouldn't be redirected. Otherwise, for example, when an input in
|
|
// a textbox is focused, the enclosing textbox would be found and
|
|
// the same inner input would be returned again.
|
|
else if (currentContent == aRootContent ||
|
|
(currentContent != startContent &&
|
|
(aForward || !GetRedirectedFocus(currentContent)))) {
|
|
NS_ADDREF(*aResultContent = currentContent);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
|
|
// not focusable, so return if we have wrapped around to the original
|
|
// content. This is necessary in case the original starting content was
|
|
// not focusable.
|
|
NS_ADDREF(*aResultContent = currentContent);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Move to the next or previous frame, but ignore continuation frames
|
|
// since only the first frame should be involved in focusability.
|
|
// Otherwise, a loop will occur in the following example:
|
|
// <span tabindex="1">...<a/><a/>...</span>
|
|
// where the text wraps onto multiple lines. Tabbing from the second
|
|
// link can find one of the span's continuation frames between the link
|
|
// and the end of the span, and the span would end up getting focused
|
|
// again.
|
|
do {
|
|
if (aForward)
|
|
frameTraversal->Next();
|
|
else
|
|
frameTraversal->Prev();
|
|
frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
|
|
} while (frame && frame->GetPrevContinuation());
|
|
}
|
|
|
|
// If already at lowest priority tab (0), end search completely.
|
|
// A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
|
|
if (aCurrentTabIndex == (aForward ? 0 : 1)) {
|
|
// if going backwards, the canvas should be focused once the beginning
|
|
// has been reached.
|
|
if (!aForward) {
|
|
nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
|
|
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
|
|
NS_IF_ADDREF(*aResultContent =
|
|
GetRootForFocus(window, aRootContent->GetCurrentDoc(), PR_FALSE, PR_TRUE));
|
|
}
|
|
break;
|
|
}
|
|
|
|
// continue looking for next highest priority tabindex
|
|
aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
|
|
startContent = iterStartContent = aRootContent;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIContent*
|
|
nsFocusManager::GetNextTabbableMapArea(PRBool aForward,
|
|
PRInt32 aCurrentTabIndex,
|
|
nsIContent* aImageContent,
|
|
nsIContent* aStartContent)
|
|
{
|
|
nsAutoString useMap;
|
|
aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
|
|
|
|
nsCOMPtr<nsIDocument> doc = aImageContent->GetDocument();
|
|
if (doc) {
|
|
nsCOMPtr<nsIDOMHTMLMapElement> imageMap = nsImageMapUtils::FindImageMap(doc, useMap);
|
|
if (!imageMap)
|
|
return nsnull;
|
|
nsCOMPtr<nsIContent> mapContent = do_QueryInterface(imageMap);
|
|
PRUint32 count = mapContent->GetChildCount();
|
|
// First see if the the start content is in this map
|
|
|
|
PRInt32 index = mapContent->IndexOf(aStartContent);
|
|
PRInt32 tabIndex;
|
|
if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
|
|
tabIndex != aCurrentTabIndex)) {
|
|
// If aStartContent is in this map we must start iterating past it.
|
|
// We skip the case where aStartContent has tabindex == aStartContent
|
|
// since the next tab ordered element might be before it
|
|
// (or after for backwards) in the child list.
|
|
index = aForward ? -1 : (PRInt32)count;
|
|
}
|
|
|
|
// GetChildAt will return nsnull if our index < 0 or index >= count
|
|
nsCOMPtr<nsIContent> areaContent;
|
|
while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nsnull) {
|
|
if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
|
|
return areaContent;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
PRInt32
|
|
nsFocusManager::GetNextTabIndex(nsIContent* aParent,
|
|
PRInt32 aCurrentTabIndex,
|
|
PRBool aForward)
|
|
{
|
|
PRInt32 tabIndex, childTabIndex;
|
|
nsIContent *child;
|
|
|
|
PRUint32 count = aParent->GetChildCount();
|
|
|
|
if (aForward) {
|
|
tabIndex = 0;
|
|
for (PRUint32 index = 0; index < count; index++) {
|
|
child = aParent->GetChildAt(index);
|
|
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
|
|
if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
|
|
tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
|
|
}
|
|
|
|
nsAutoString tabIndexStr;
|
|
child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
|
|
PRInt32 ec, val = tabIndexStr.ToInteger(&ec);
|
|
if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
|
|
tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
|
|
}
|
|
}
|
|
}
|
|
else { /* !aForward */
|
|
tabIndex = 1;
|
|
for (PRUint32 index = 0; index < count; index++) {
|
|
child = aParent->GetChildAt(index);
|
|
childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
|
|
if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
|
|
(childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
|
|
tabIndex = childTabIndex;
|
|
}
|
|
|
|
nsAutoString tabIndexStr;
|
|
child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
|
|
PRInt32 ec, val = tabIndexStr.ToInteger(&ec);
|
|
if (NS_SUCCEEDED (ec)) {
|
|
if ((aCurrentTabIndex == 0 && val > tabIndex) ||
|
|
(val < aCurrentTabIndex && val > tabIndex) ) {
|
|
tabIndex = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return tabIndex;
|
|
}
|
|
|
|
nsIContent*
|
|
nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
|
|
nsIDocument* aDocument,
|
|
PRBool aIsForDocNavigation,
|
|
PRBool aCheckVisibility)
|
|
{
|
|
// the root element's canvas may be focused as long as the document is in a
|
|
// a non-chrome shell and does not contain a frameset.
|
|
if (aIsForDocNavigation) {
|
|
nsCOMPtr<nsIContent> docContent =
|
|
do_QueryInterface(aWindow->GetFrameElementInternal());
|
|
if (docContent && docContent->Tag() == nsGkAtoms::iframe)
|
|
return nsnull;
|
|
}
|
|
else {
|
|
PRInt32 itemType;
|
|
nsCOMPtr<nsIDocShellTreeItem> shellItem = do_QueryInterface(aWindow->GetDocShell());
|
|
shellItem->GetItemType(&itemType);
|
|
|
|
if (itemType == nsIDocShellTreeItem::typeChrome)
|
|
return nsnull;
|
|
}
|
|
|
|
if (aCheckVisibility && !IsWindowVisible(aWindow))
|
|
return nsnull;
|
|
|
|
Element *rootElement = aDocument->GetRootElement();
|
|
if (rootElement) {
|
|
if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
|
|
return nsnull;
|
|
}
|
|
|
|
// Finally, check if this is a frameset
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
|
|
if (htmlDoc) {
|
|
PRUint32 childCount = rootElement->GetChildCount();
|
|
for (PRUint32 i = 0; i < childCount; ++i) {
|
|
nsIContent *childContent = rootElement->GetChildAt(i);
|
|
nsINodeInfo *ni = childContent->NodeInfo();
|
|
if (childContent->IsHTML() &&
|
|
ni->Equals(nsGkAtoms::frameset))
|
|
return nsnull;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rootElement;
|
|
}
|
|
|
|
void
|
|
nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
|
|
nsIDocShellTreeItem** aResult)
|
|
{
|
|
*aResult = nsnull;
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
|
|
while (curItem) {
|
|
PRInt32 childCount = 0;
|
|
curItem->GetChildCount(&childCount);
|
|
if (!childCount) {
|
|
*aResult = curItem;
|
|
NS_ADDREF(*aResult);
|
|
return;
|
|
}
|
|
|
|
|
|
curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
|
|
nsIDocShellTreeItem** aResult)
|
|
{
|
|
*aResult = nsnull;
|
|
|
|
PRInt32 childCount = 0;
|
|
aItem->GetChildCount(&childCount);
|
|
if (childCount) {
|
|
aItem->GetChildAt(0, aResult);
|
|
if (*aResult)
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
|
|
while (curItem) {
|
|
nsCOMPtr<nsIDocShellTreeItem> parentItem;
|
|
curItem->GetParent(getter_AddRefs(parentItem));
|
|
if (!parentItem)
|
|
return;
|
|
|
|
// Note that we avoid using GetChildOffset() here because docshell
|
|
// child offsets can't be trusted to be correct. bug 162283.
|
|
nsCOMPtr<nsIDocShellTreeItem> iterItem;
|
|
childCount = 0;
|
|
parentItem->GetChildCount(&childCount);
|
|
for (PRInt32 index = 0; index < childCount; ++index) {
|
|
parentItem->GetChildAt(index, getter_AddRefs(iterItem));
|
|
if (iterItem == curItem) {
|
|
++index;
|
|
if (index < childCount) {
|
|
parentItem->GetChildAt(index, aResult);
|
|
if (*aResult)
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
curItem = parentItem;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
|
|
nsIDocShellTreeItem** aResult)
|
|
{
|
|
*aResult = nsnull;
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> parentItem;
|
|
aItem->GetParent(getter_AddRefs(parentItem));
|
|
if (!parentItem)
|
|
return;
|
|
|
|
// Note that we avoid using GetChildOffset() here because docshell
|
|
// child offsets can't be trusted to be correct. bug 162283.
|
|
PRInt32 childCount = 0;
|
|
parentItem->GetChildCount(&childCount);
|
|
nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
|
|
for (PRInt32 index = 0; index < childCount; ++index) {
|
|
parentItem->GetChildAt(index, getter_AddRefs(iterItem));
|
|
if (iterItem == aItem)
|
|
break;
|
|
prevItem = iterItem;
|
|
}
|
|
|
|
if (prevItem)
|
|
GetLastDocShell(prevItem, aResult);
|
|
else
|
|
NS_ADDREF(*aResult = parentItem);
|
|
}
|
|
|
|
nsIContent*
|
|
nsFocusManager::GetNextTabbableDocument(PRBool aForward)
|
|
{
|
|
nsCOMPtr<nsIDocShellTreeItem> startItem;
|
|
if (mFocusedWindow) {
|
|
startItem = do_QueryInterface(mFocusedWindow->GetDocShell());
|
|
}
|
|
else {
|
|
nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(mActiveWindow);
|
|
startItem = do_QueryInterface(webnav);
|
|
}
|
|
if (!startItem)
|
|
return nsnull;
|
|
|
|
// perform a depth first search (preorder) of the docshell tree
|
|
// looking for an HTML Frame or a chrome document
|
|
nsIContent* content = nsnull;
|
|
nsCOMPtr<nsIDocShellTreeItem> curItem = startItem;
|
|
nsCOMPtr<nsIDocShellTreeItem> nextItem;
|
|
do {
|
|
if (aForward) {
|
|
GetNextDocShell(curItem, getter_AddRefs(nextItem));
|
|
if (!nextItem) {
|
|
// wrap around to the beginning, which is the top of the tree
|
|
startItem->GetRootTreeItem(getter_AddRefs(nextItem));
|
|
}
|
|
}
|
|
else {
|
|
GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
|
|
if (!nextItem) {
|
|
// wrap around to the end, which is the last item in the tree
|
|
nsCOMPtr<nsIDocShellTreeItem> rootItem;
|
|
startItem->GetRootTreeItem(getter_AddRefs(rootItem));
|
|
GetLastDocShell(rootItem, getter_AddRefs(nextItem));
|
|
}
|
|
}
|
|
|
|
curItem = nextItem;
|
|
nsCOMPtr<nsPIDOMWindow> nextFrame = do_GetInterface(nextItem);
|
|
if (!nextFrame)
|
|
return nsnull;
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(nextFrame->GetExtantDocument());
|
|
if (doc && !doc->EventHandlingSuppressed()) {
|
|
content = GetRootForFocus(nextFrame, doc, PR_TRUE, PR_TRUE);
|
|
if (content && !GetRootForFocus(nextFrame, doc, PR_FALSE, PR_FALSE)) {
|
|
// if the found content is in a chrome shell or a frameset, navigate
|
|
// forward one tabbable item so that the first item is focused. Note
|
|
// that we always go forward and not back here.
|
|
nsCOMPtr<nsIContent> nextFocus;
|
|
Element* rootElement = doc->GetRootElement();
|
|
nsIPresShell* presShell = doc->GetShell();
|
|
if (presShell) {
|
|
nsresult rv = GetNextTabbableContent(presShell, rootElement,
|
|
nsnull, rootElement,
|
|
PR_TRUE, 1, PR_FALSE,
|
|
getter_AddRefs(nextFocus));
|
|
return NS_SUCCEEDED(rv) ? nextFocus.get() : nsnull;
|
|
}
|
|
}
|
|
}
|
|
} while (!content);
|
|
|
|
return content;
|
|
}
|
|
|
|
void
|
|
nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
|
|
nsIContent* aStartSelection,
|
|
nsIContent* aEndSelection,
|
|
nsIContent** aFocusedContent)
|
|
{
|
|
*aFocusedContent = nsnull;
|
|
|
|
nsCOMPtr<nsIContent> testContent = aStartSelection;
|
|
nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
|
|
|
|
nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
|
|
|
|
// We now have the correct start node in selectionContent!
|
|
// Search for focusable elements, starting with selectionContent
|
|
|
|
// Method #1: Keep going up while we look - an ancestor might be focusable
|
|
// We could end the loop earlier, such as when we're no longer
|
|
// in the same frame, by comparing selectionContent->GetPrimaryFrame()
|
|
// with a variable holding the starting selectionContent
|
|
while (testContent) {
|
|
// Keep testing while selectionContent is equal to something,
|
|
// eventually we'll run out of ancestors
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (testContent == currentFocus ||
|
|
testContent->IsLink(getter_AddRefs(uri))) {
|
|
NS_ADDREF(*aFocusedContent = testContent);
|
|
return;
|
|
}
|
|
|
|
// Get the parent
|
|
testContent = testContent->GetParent();
|
|
|
|
if (!testContent) {
|
|
// We run this loop again, checking the ancestor chain of the selection's end point
|
|
testContent = nextTestContent;
|
|
nextTestContent = nsnull;
|
|
}
|
|
}
|
|
|
|
// We couldn't find an anchor that was an ancestor of the selection start
|
|
// Method #2: look for anchor in selection's primary range (depth first search)
|
|
|
|
// Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
|
|
nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
|
|
nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
|
|
nsCOMPtr<nsIDOMNode> testNode;
|
|
|
|
do {
|
|
testContent = do_QueryInterface(selectionNode);
|
|
|
|
// We're looking for any focusable link that could be part of the
|
|
// main document's selection.
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (testContent == currentFocus ||
|
|
testContent->IsLink(getter_AddRefs(uri))) {
|
|
NS_ADDREF(*aFocusedContent = testContent);
|
|
return;
|
|
}
|
|
|
|
selectionNode->GetFirstChild(getter_AddRefs(testNode));
|
|
if (testNode) {
|
|
selectionNode = testNode;
|
|
continue;
|
|
}
|
|
|
|
if (selectionNode == endSelectionNode)
|
|
break;
|
|
selectionNode->GetNextSibling(getter_AddRefs(testNode));
|
|
if (testNode) {
|
|
selectionNode = testNode;
|
|
continue;
|
|
}
|
|
|
|
do {
|
|
selectionNode->GetParentNode(getter_AddRefs(testNode));
|
|
if (!testNode || testNode == endSelectionNode) {
|
|
selectionNode = nsnull;
|
|
break;
|
|
}
|
|
testNode->GetNextSibling(getter_AddRefs(selectionNode));
|
|
if (selectionNode)
|
|
break;
|
|
selectionNode = testNode;
|
|
} while (PR_TRUE);
|
|
}
|
|
while (selectionNode && selectionNode != endSelectionNode);
|
|
}
|
|
|
|
nsresult
|
|
NS_NewFocusManager(nsIFocusManager** aResult)
|
|
{
|
|
NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
|
|
return NS_OK;
|
|
}
|