follow up to bug 588735, missing sdk ifdefing. a=nobug.
[mozilla-central.git] / dom / base / nsFocusManager.cpp
blob7cdc4cd6a0835096f0357823207456db83c6c4f8
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozila.org code.
17 * The Initial Developer of the Original Code is Mozilla Foundation
18 * Portions created by the Initial Developer are Copyright (C) 2008
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
35 * ***** END LICENSE BLOCK ***** */
37 #include "nsFocusManager.h"
39 #include "nsIInterfaceRequestor.h"
40 #include "nsIInterfaceRequestorUtils.h"
41 #include "nsIServiceManager.h"
42 #include "nsIEnumerator.h"
43 #include "nsTPtrArray.h"
44 #include "nsGkAtoms.h"
45 #include "nsIPrefBranch2.h"
46 #include "nsContentUtils.h"
47 #include "nsIDocument.h"
48 #include "nsIDOMWindow.h"
49 #include "nsPIDOMWindow.h"
50 #include "nsIDOMElement.h"
51 #include "nsIDOMXULElement.h"
52 #include "nsIDOMNSHTMLFrameElement.h"
53 #include "nsIDOMHTMLInputElement.h"
54 #include "nsIDOMHTMLMapElement.h"
55 #include "nsIDOMHTMLLegendElement.h"
56 #include "nsIDOMDocumentRange.h"
57 #include "nsIDOMRange.h"
58 #include "nsIHTMLDocument.h"
59 #include "nsIFormControlFrame.h"
60 #include "nsGenericHTMLElement.h"
61 #include "nsIDocShell.h"
62 #include "nsIEditorDocShell.h"
63 #include "nsIDocShellTreeItem.h"
64 #include "nsIDocShellTreeOwner.h"
65 #include "nsLayoutUtils.h"
66 #include "nsIPresShell.h"
67 #include "nsIContentViewer.h"
68 #include "nsFrameTraversal.h"
69 #include "nsObjectFrame.h"
70 #include "nsEventDispatcher.h"
71 #include "nsIEventStateManager.h"
72 #include "nsIMEStateManager.h"
73 #include "nsIWebNavigation.h"
74 #include "nsCaret.h"
75 #include "nsWidgetsCID.h"
76 #include "nsILookAndFeel.h"
77 #include "nsIWidget.h"
78 #include "nsIBaseWindow.h"
79 #include "nsIViewManager.h"
80 #include "nsFrameSelection.h"
81 #include "nsXULPopupManager.h"
82 #include "nsImageMapUtils.h"
83 #include "nsTreeWalker.h"
84 #include "nsIDOMNodeFilter.h"
85 #include "nsIScriptObjectPrincipal.h"
86 #include "nsIPrincipal.h"
87 #include "mozilla/dom/Element.h"
89 #ifdef MOZ_XUL
90 #include "nsIDOMXULTextboxElement.h"
91 #include "nsIDOMXULMenuListElement.h"
92 #endif
94 using namespace mozilla::dom;
96 //#define DEBUG_FOCUS 1
97 //#define DEBUG_FOCUS_NAVIGATION 1
98 #define PRINTTAGF(format, content) \
99 { \
100 nsAutoString tag(NS_LITERAL_STRING("(none)")); \
101 if (content) \
102 content->Tag()->ToString(tag); \
103 printf(format, NS_ConvertUTF16toUTF8(tag).get()); \
106 struct nsDelayedBlurOrFocusEvent
108 nsDelayedBlurOrFocusEvent(PRUint32 aType,
109 nsIPresShell* aPresShell,
110 nsIDocument* aDocument,
111 nsPIDOMEventTarget* aTarget)
112 : mType(aType),
113 mPresShell(aPresShell),
114 mDocument(aDocument),
115 mTarget(aTarget) { }
117 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
118 : mType(aOther.mType),
119 mPresShell(aOther.mPresShell),
120 mDocument(aOther.mDocument),
121 mTarget(aOther.mTarget) { }
123 PRUint32 mType;
124 nsCOMPtr<nsIPresShell> mPresShell;
125 nsCOMPtr<nsIDocument> mDocument;
126 nsCOMPtr<nsPIDOMEventTarget> mTarget;
129 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
130 NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
131 NS_INTERFACE_MAP_ENTRY(nsIObserver)
132 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
133 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
134 NS_INTERFACE_MAP_END
136 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
137 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
139 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFocusManager)
140 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFocusManager)
141 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mActiveWindow)
142 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFocusedWindow)
143 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFocusedContent)
144 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstBlurEvent)
145 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFirstFocusEvent)
146 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindowBeingLowered)
147 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
148 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFocusManager)
149 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mActiveWindow)
150 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFocusedWindow)
151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFocusedContent)
152 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBlurEvent)
153 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstFocusEvent)
154 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindowBeingLowered)
155 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
157 static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
159 nsFocusManager* nsFocusManager::sInstance = nsnull;
160 PRBool nsFocusManager::sMouseFocusesFormControl = PR_FALSE;
162 nsFocusManager::nsFocusManager()
165 nsFocusManager::~nsFocusManager()
167 nsIPrefBranch2* prefBranch = nsContentUtils::GetPrefBranch();
169 if (prefBranch) {
170 prefBranch->RemoveObserver("accessibility.browsewithcaret", this);
171 prefBranch->RemoveObserver("accessibility.tabfocus_applies_to_xul", this);
172 prefBranch->RemoveObserver("accessibility.mouse_focuses_formcontrol", this);
176 // static
177 nsresult
178 nsFocusManager::Init()
180 nsFocusManager* fm = new nsFocusManager();
181 NS_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY);
182 NS_ADDREF(fm);
183 sInstance = fm;
185 nsIContent::sTabFocusModelAppliesToXUL =
186 nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul",
187 nsIContent::sTabFocusModelAppliesToXUL);
189 sMouseFocusesFormControl =
190 nsContentUtils::GetBoolPref("accessibility.mouse_focuses_formcontrol", PR_FALSE);
192 nsIPrefBranch2* prefBranch = nsContentUtils::GetPrefBranch();
193 prefBranch->AddObserver("accessibility.browsewithcaret", fm, PR_TRUE);
194 prefBranch->AddObserver("accessibility.tabfocus_applies_to_xul", fm, PR_TRUE);
195 prefBranch->AddObserver("accessibility.mouse_focuses_formcontrol", fm, PR_TRUE);
197 return NS_OK;
200 // static
201 void
202 nsFocusManager::Shutdown()
204 NS_IF_RELEASE(sInstance);
207 NS_IMETHODIMP
208 nsFocusManager::Observe(nsISupports *aSubject,
209 const char *aTopic,
210 const PRUnichar *aData)
212 nsDependentString data(aData);
213 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
214 if (data.EqualsLiteral("accessibility.browsewithcaret")) {
215 UpdateCaret(PR_FALSE, PR_TRUE, mFocusedContent);
217 else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
218 nsIContent::sTabFocusModelAppliesToXUL =
219 nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul",
220 nsIContent::sTabFocusModelAppliesToXUL);
222 else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
223 sMouseFocusesFormControl =
224 nsContentUtils::GetBoolPref("accessibility.mouse_focuses_formcontrol", PR_FALSE);
228 return NS_OK;
231 // given a frame content node, retrieve the nsIDOMWindow displayed in it
232 static nsPIDOMWindow*
233 GetContentWindow(nsIContent* aContent)
235 nsIDocument* doc = aContent->GetCurrentDoc();
236 if (doc) {
237 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
238 if (subdoc)
239 return subdoc->GetWindow();
242 return nsnull;
245 // get the current window for the given content node
246 static nsPIDOMWindow*
247 GetCurrentWindow(nsIContent* aContent)
249 nsIDocument *doc = aContent->GetCurrentDoc();
250 return doc ? doc->GetWindow() : nsnull;
253 // static
254 nsIContent*
255 nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, PRBool aDeep,
256 nsPIDOMWindow** aFocusedWindow)
258 NS_ENSURE_TRUE(aWindow, nsnull);
260 *aFocusedWindow = nsnull;
262 nsIContent* currentContent = nsnull;
263 nsPIDOMWindow* window = aWindow->GetOuterWindow();
264 while (window) {
265 *aFocusedWindow = window;
266 currentContent = window->GetFocusedNode();
267 if (!currentContent || !aDeep)
268 break;
270 window = GetContentWindow(currentContent);
273 NS_IF_ADDREF(*aFocusedWindow);
275 return currentContent;
278 // static
279 nsIContent*
280 nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
282 #ifdef MOZ_XUL
283 if (aContent->IsXUL()) {
284 nsCOMPtr<nsIDOMNode> inputField;
286 nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
287 if (textbox) {
288 textbox->GetInputField(getter_AddRefs(inputField));
290 else {
291 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
292 if (menulist) {
293 menulist->GetInputField(getter_AddRefs(inputField));
295 else if (aContent->Tag() == nsGkAtoms::scale) {
296 nsCOMPtr<nsIDocument> doc = aContent->GetCurrentDoc();
297 if (!doc)
298 return nsnull;
300 nsINodeList* children = doc->BindingManager()->GetXBLChildNodesFor(aContent);
301 if (children) {
302 nsIContent* child = children->GetNodeAt(0);
303 if (child && child->Tag() == nsGkAtoms::slider)
304 return child;
309 if (inputField) {
310 nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
311 return retval;
314 #endif
316 return nsnull;
319 NS_IMETHODIMP
320 nsFocusManager::GetActiveWindow(nsIDOMWindow** aWindow)
322 NS_IF_ADDREF(*aWindow = mActiveWindow);
323 return NS_OK;
326 NS_IMETHODIMP
327 nsFocusManager::SetActiveWindow(nsIDOMWindow* aWindow)
329 // only top-level windows can be made active
330 nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow);
331 NS_ASSERTION(!piWindow || piWindow->IsOuterWindow(), "outer window expected");
333 NS_ENSURE_TRUE(piWindow && (piWindow == piWindow->GetPrivateRoot()),
334 NS_ERROR_INVALID_ARG);
336 RaiseWindow(piWindow);
337 return NS_OK;
340 NS_IMETHODIMP
341 nsFocusManager::GetFocusedWindow(nsIDOMWindow** aFocusedWindow)
343 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
344 return NS_OK;
347 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(nsIDOMWindow* aWindowToFocus)
349 #ifdef DEBUG_FOCUS
350 printf("<<SetFocusedWindow begin>>\n");
351 #endif
353 nsCOMPtr<nsPIDOMWindow> windowToFocus(do_QueryInterface(aWindowToFocus));
354 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
356 windowToFocus = windowToFocus->GetOuterWindow();
358 nsCOMPtr<nsIContent> frameContent =
359 do_QueryInterface(windowToFocus->GetFrameElementInternal());
360 if (frameContent) {
361 // pass false so that the caret does not get updated and scrolling does
362 // not occur.
363 SetFocusInner(frameContent, 0, PR_FALSE);
365 else {
366 // this is a top-level window. If the window has a child frame focused,
367 // clear the focus. Otherwise, focus should already be in this frame, or
368 // already cleared. This ensures that focus will be in this frame and not
369 // in a child.
370 nsIContent* content = windowToFocus->GetFocusedNode();
371 if (content) {
372 nsCOMPtr<nsIDOMWindow> childWindow = GetContentWindow(content);
373 if (childWindow)
374 ClearFocus(windowToFocus);
378 nsCOMPtr<nsPIDOMWindow> rootWindow = windowToFocus->GetPrivateRoot();
379 if (rootWindow)
380 RaiseWindow(rootWindow);
382 #ifdef DEBUG_FOCUS
383 printf("<<SetFocusedWindow end>>\n");
384 #endif
386 return NS_OK;
389 NS_IMETHODIMP
390 nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
392 if (mFocusedContent)
393 CallQueryInterface(mFocusedContent, aFocusedElement);
394 else
395 *aFocusedElement = nsnull;
396 return NS_OK;
399 NS_IMETHODIMP
400 nsFocusManager::GetLastFocusMethod(nsIDOMWindow* aWindow, PRUint32* aLastFocusMethod)
402 // the focus method is stored on the inner window
403 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
404 if (window)
405 window = window->GetCurrentInnerWindow();
406 if (!window)
407 window = mFocusedWindow;
409 *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
411 NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
412 "invalid focus method");
413 return NS_OK;
416 NS_IMETHODIMP
417 nsFocusManager::SetFocus(nsIDOMElement* aElement, PRUint32 aFlags)
419 #ifdef DEBUG_FOCUS
420 printf("<<SetFocus>>\n");
421 #endif
423 nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
424 NS_ENSURE_ARG(newFocus);
426 SetFocusInner(newFocus, aFlags, PR_TRUE);
428 return NS_OK;
431 NS_IMETHODIMP
432 nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
433 PRUint32 aType, PRUint32 aFlags, nsIDOMElement** aElement)
435 *aElement = nsnull;
437 #ifdef DEBUG_FOCUS
438 printf("<<MoveFocus Type: %d Flags: %x>>\n<<", aType, aFlags);
440 nsCOMPtr<nsPIDOMWindow> focusedWindow = mFocusedWindow;
441 if (focusedWindow) {
442 nsCOMPtr<nsIDocument> doc = do_QueryInterface(focusedWindow->GetExtantDocument());
443 if (doc) {
444 nsCAutoString spec;
445 doc->GetDocumentURI()->GetSpec(spec);
446 printf(" [%p] Focused Window: %s", mFocusedWindow.get(), spec.get());
449 PRINTTAGF(">> $[[%s]]\n", mFocusedContent);
450 #endif
452 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
453 // the other focus methods is already set, or we're just moving to the root
454 // or caret position.
455 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
456 (aFlags & FOCUSMETHOD_MASK) == 0) {
457 aFlags |= FLAG_BYMOVEFOCUS;
460 nsCOMPtr<nsPIDOMWindow> window;
461 nsCOMPtr<nsIContent> startContent;
462 if (aStartElement) {
463 startContent = do_QueryInterface(aStartElement);
464 NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
466 window = GetCurrentWindow(startContent);
468 else {
469 window = aWindow ? do_QueryInterface(aWindow) : mFocusedWindow;
470 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
471 window = window->GetOuterWindow();
474 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
476 nsCOMPtr<nsIContent> newFocus;
477 nsresult rv = DetermineElementToMoveFocus(window, startContent, aType,
478 getter_AddRefs(newFocus));
479 NS_ENSURE_SUCCESS(rv, rv);
481 #ifdef DEBUG_FOCUS_NAVIGATION
482 PRINTTAGF("-> Element to be focused: %s\n", newFocus);
483 #endif
485 if (newFocus) {
486 // for caret movement, pass false for the aFocusChanged argument,
487 // otherwise the caret will end up moving to the focus position. This
488 // would be a problem because the caret would move to the beginning of the
489 // focused link making it impossible to navigate the caret over a link.
490 SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET);
491 CallQueryInterface(newFocus, aElement);
493 else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
494 // no content was found, so clear the focus for these two types.
495 ClearFocus(window);
498 #ifdef DEBUG_FOCUS
499 printf("<<MoveFocus end>>\n");
500 #endif
502 return NS_OK;
505 NS_IMETHODIMP
506 nsFocusManager::ClearFocus(nsIDOMWindow* aWindow)
508 #ifdef DEBUG_FOCUS
509 printf("<<ClearFocus begin>>\n");
510 #endif
512 // if the window to clear is the focused window or an ancestor of the
513 // focused window, then blur the existing focused content. Otherwise, the
514 // focus is somewhere else so just update the current node.
515 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
516 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
518 window = window->GetOuterWindow();
519 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
521 if (IsSameOrAncestor(window, mFocusedWindow)) {
522 PRBool isAncestor = (window != mFocusedWindow);
523 if (Blur(window, nsnull, isAncestor)) {
524 // if we are clearing the focus on an ancestor of the focused window,
525 // the ancestor will become the new focused window, so focus it
526 if (isAncestor)
527 Focus(window, nsnull, 0, PR_TRUE, PR_FALSE, PR_FALSE);
530 else {
531 window->SetFocusedNode(nsnull);
534 #ifdef DEBUG_FOCUS
535 printf("<<ClearFocus end>>\n");
536 #endif
538 return NS_OK;
541 NS_IMETHODIMP
542 nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow,
543 PRBool aDeep,
544 nsIDOMWindow** aFocusedWindow,
545 nsIDOMElement** aElement)
547 *aElement = nsnull;
548 if (aFocusedWindow)
549 *aFocusedWindow = nsnull;
551 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
552 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
554 window = window->GetOuterWindow();
555 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
557 nsCOMPtr<nsPIDOMWindow> focusedWindow;
558 nsCOMPtr<nsIContent> focusedContent =
559 GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
560 if (focusedContent)
561 CallQueryInterface(focusedContent, aElement);
563 if (aFocusedWindow)
564 NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
566 return NS_OK;
569 NS_IMETHODIMP
570 nsFocusManager::MoveCaretToFocus(nsIDOMWindow* aWindow)
572 PRInt32 itemType = nsIDocShellTreeItem::typeChrome;
574 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
575 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
576 if (dsti) {
577 dsti->GetItemType(&itemType);
578 if (itemType != nsIDocShellTreeItem::typeChrome) {
579 // don't move the caret for editable documents
580 nsCOMPtr<nsIEditorDocShell> editorDocShell(do_QueryInterface(dsti));
581 if (editorDocShell) {
582 PRBool isEditable;
583 editorDocShell->GetEditable(&isEditable);
584 if (isEditable)
585 return NS_OK;
588 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
589 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
591 nsCOMPtr<nsIPresShell> presShell;
592 docShell->GetPresShell(getter_AddRefs(presShell));
593 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
595 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
596 nsCOMPtr<nsIContent> content = window->GetFocusedNode();
597 if (content)
598 MoveCaretToFocus(presShell, content);
602 return NS_OK;
605 NS_IMETHODIMP
606 nsFocusManager::WindowRaised(nsIDOMWindow* aWindow)
608 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
609 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
611 #ifdef DEBUG_FOCUS
612 printf("Window %p Raised [Currently: %p %p] <<", aWindow, mActiveWindow.get(), mFocusedWindow.get());
613 nsCAutoString spec;
614 nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
615 if (doc) {
616 doc->GetDocumentURI()->GetSpec(spec);
617 printf("[%p] Raised Window: %s", aWindow, spec.get());
619 if (mActiveWindow) {
620 doc = do_QueryInterface(mActiveWindow->GetExtantDocument());
621 if (doc) {
622 doc->GetDocumentURI()->GetSpec(spec);
623 printf(" [%p] Active Window: %s", mActiveWindow.get(), spec.get());
626 printf(">>\n");
627 #endif
629 if (mActiveWindow == window) {
630 // The window is already active, so there is no need to focus anything,
631 // but make sure that the right widget is focused. This is a special case
632 // for Windows because when restoring a minimized window, a second
633 // activation will occur and the top-level widget could be focused instead
634 // of the child we want. We solve this by calling SetFocus to ensure that
635 // what the focus manager thinks should be the current widget is actually
636 // focused.
637 EnsureCurrentWidgetFocused();
638 return NS_OK;
641 // lower the existing window, if any. This shouldn't happen usually.
642 if (mActiveWindow)
643 WindowLowered(mActiveWindow);
645 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(aWindow));
646 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(webnav));
647 // If there's no docShellAsItem, this window must have been closed,
648 // in that case there is no tree owner.
649 NS_ENSURE_TRUE(docShellAsItem, NS_OK);
651 // set this as the active window
652 mActiveWindow = window;
654 // ensure that the window is enabled and visible
655 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
656 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
657 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
658 if (baseWindow) {
659 PRBool isEnabled = PR_TRUE;
660 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
661 return NS_ERROR_FAILURE;
664 baseWindow->SetVisibility(PR_TRUE);
667 // inform the DOM window that it has activated, so that the active attribute
668 // is updated on the window
669 window->ActivateOrDeactivate(PR_TRUE);
671 // send activate event
672 nsCOMPtr<nsIDocument> document = do_QueryInterface(window->GetExtantDocument());
673 nsContentUtils::DispatchTrustedEvent(document,
674 window,
675 NS_LITERAL_STRING("activate"),
676 PR_TRUE, PR_TRUE, nsnull);
678 // retrieve the last focused element within the window that was raised
679 nsCOMPtr<nsPIDOMWindow> currentWindow;
680 nsCOMPtr<nsIContent> currentFocus =
681 GetFocusedDescendant(window, PR_TRUE, getter_AddRefs(currentWindow));
683 NS_ASSERTION(currentWindow, "window raised with no window current");
684 if (!currentWindow)
685 return NS_OK;
687 nsCOMPtr<nsIDocShell> currentDocShell = currentWindow->GetDocShell();
689 nsCOMPtr<nsIPresShell> presShell;
690 currentDocShell->GetPresShell(getter_AddRefs(presShell));
691 if (presShell) {
692 // disable selection mousedown state on activation
693 // XXXndeakin P3 not sure if this is necessary, but it doesn't hurt
694 nsCOMPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
695 frameSelection->SetMouseDownState(PR_FALSE);
698 Focus(currentWindow, currentFocus, 0, PR_TRUE, PR_FALSE, PR_TRUE);
700 return NS_OK;
703 NS_IMETHODIMP
704 nsFocusManager::WindowLowered(nsIDOMWindow* aWindow)
706 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
707 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
709 #ifdef DEBUG_FOCUS
710 printf("Window %p Lowered [Currently: %p %p] <<", aWindow, mActiveWindow.get(), mFocusedWindow.get());
711 nsCAutoString spec;
712 nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
713 if (doc) {
714 doc->GetDocumentURI()->GetSpec(spec);
715 printf("[%p] Lowered Window: %s", aWindow, spec.get());
717 if (mActiveWindow) {
718 doc = do_QueryInterface(mActiveWindow->GetExtantDocument());
719 if (doc) {
720 doc->GetDocumentURI()->GetSpec(spec);
721 printf(" [%p] Active Window: %s", mActiveWindow.get(), spec.get());
724 printf(">>\n");
725 #endif
727 if (mActiveWindow != window)
728 return NS_OK;
730 // clear the mouse capture as the active window has changed
731 nsIPresShell::SetCapturingContent(nsnull, 0);
733 // inform the DOM window that it has deactivated, so that the active
734 // attribute is updated on the window
735 window->ActivateOrDeactivate(PR_FALSE);
737 // send deactivate event
738 nsCOMPtr<nsIDocument> document = do_QueryInterface(window->GetExtantDocument());
739 nsContentUtils::DispatchTrustedEvent(document,
740 window,
741 NS_LITERAL_STRING("deactivate"),
742 PR_TRUE, PR_TRUE, nsnull);
744 // keep track of the window being lowered, so that attempts to raise the
745 // window can be prevented until we return. Otherwise, focus can get into
746 // an unusual state.
747 mWindowBeingLowered = mActiveWindow;
748 mActiveWindow = nsnull;
750 if (mFocusedWindow)
751 Blur(nsnull, nsnull, PR_TRUE);
753 mWindowBeingLowered = nsnull;
755 return NS_OK;
758 nsresult
759 nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
761 NS_ENSURE_ARG(aDocument);
762 NS_ENSURE_ARG(aContent);
764 nsPIDOMWindow *window = aDocument->GetWindow();
765 if (!window)
766 return NS_OK;
768 // if the content is currently focused in the window, or is an ancestor
769 // of the currently focused element, reset the focus within that window.
770 nsIContent* content = window->GetFocusedNode();
771 if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
772 window->SetFocusedNode(nsnull);
774 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
775 if (docShell) {
776 nsCOMPtr<nsIPresShell> presShell;
777 docShell->GetPresShell(getter_AddRefs(presShell));
778 nsIMEStateManager::OnRemoveContent(presShell->GetPresContext(), content);
781 // if this window is currently focused, clear the global focused
782 // element as well, but don't fire any events.
783 if (window == mFocusedWindow) {
784 mFocusedContent = nsnull;
786 else {
787 // Check if the node that was focused is an iframe or similar by looking
788 // if it has a subdocument. This would indicate that this focused iframe
789 // and its descendants will be going away. We will need to move the
790 // focus somewhere else, so just clear the focus in the toplevel window
791 // so that no element is focused.
792 nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
793 if (subdoc) {
794 nsCOMPtr<nsISupports> container = subdoc->GetContainer();
795 nsCOMPtr<nsPIDOMWindow> childWindow = do_GetInterface(container);
796 if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
797 ClearFocus(mActiveWindow);
803 return NS_OK;
806 NS_IMETHODIMP
807 nsFocusManager::WindowShown(nsIDOMWindow* aWindow, PRBool aNeedsFocus)
809 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
810 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
812 window = window->GetOuterWindow();
814 #ifdef DEBUG_FOCUS
815 printf("Window %p Shown [Currently: %p %p] <<", window.get(), mActiveWindow.get(), mFocusedWindow.get());
816 nsCAutoString spec;
817 nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
818 if (doc) {
819 doc->GetDocumentURI()->GetSpec(spec);
820 printf("Shown Window: %s", spec.get());
823 if (mFocusedWindow) {
824 doc = do_QueryInterface(mFocusedWindow->GetExtantDocument());
825 if (doc) {
826 doc->GetDocumentURI()->GetSpec(spec);
827 printf(" Focused Window: %s", spec.get());
830 printf(">>\n");
831 #endif
833 if (mFocusedWindow != window)
834 return NS_OK;
836 if (aNeedsFocus) {
837 nsCOMPtr<nsPIDOMWindow> currentWindow;
838 nsCOMPtr<nsIContent> currentFocus =
839 GetFocusedDescendant(window, PR_TRUE, getter_AddRefs(currentWindow));
840 if (currentWindow)
841 Focus(currentWindow, currentFocus, 0, PR_TRUE, PR_FALSE, PR_FALSE);
843 else {
844 // Sometimes, an element in a window can be focused before the window is
845 // visible, which would mean that the widget may not be properly focused.
846 // When the window becomes visible, make sure the right widget is focused.
847 EnsureCurrentWidgetFocused();
850 return NS_OK;
853 NS_IMETHODIMP
854 nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
856 // if there is no window or it is not the same or an ancestor of the
857 // currently focused window, just return, as the current focus will not
858 // be affected.
860 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
861 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
863 window = window->GetOuterWindow();
865 #ifdef DEBUG_FOCUS
866 printf("Window %p Hidden [Currently: %p %p] <<", window.get(), mActiveWindow.get(), mFocusedWindow.get());
867 nsCAutoString spec;
868 nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
869 if (doc) {
870 doc->GetDocumentURI()->GetSpec(spec);
871 printf("Hide Window: %s", spec.get());
874 if (mFocusedWindow) {
875 doc = do_QueryInterface(mFocusedWindow->GetExtantDocument());
876 if (doc) {
877 doc->GetDocumentURI()->GetSpec(spec);
878 printf(" Focused Window: %s", spec.get());
882 if (mActiveWindow) {
883 doc = do_QueryInterface(mActiveWindow->GetExtantDocument());
884 if (doc) {
885 doc->GetDocumentURI()->GetSpec(spec);
886 printf(" Active Window: %s", spec.get());
889 printf(">>\n");
890 #endif
892 if (!IsSameOrAncestor(window, mFocusedWindow))
893 return NS_OK;
895 // at this point, we know that the window being hidden is either the focused
896 // window, or an ancestor of the focused window. Either way, the focus is no
897 // longer valid, so it needs to be updated.
899 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
900 nsCOMPtr<nsIPresShell> presShell;
901 focusedDocShell->GetPresShell(getter_AddRefs(presShell));
902 if (presShell) {
903 presShell->GetPresContext()->EventStateManager()->
904 SetContentState(mFocusedContent, NS_EVENT_STATE_FOCUS);
907 mFocusedContent = nsnull;
909 nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
910 if (presShell) {
911 nsIMEStateManager::OnChangeFocus(presShell->GetPresContext(), nsnull);
912 SetCaretVisible(presShell, PR_FALSE, nsnull);
915 // if the docshell being hidden is being destroyed, then we want to move
916 // focus somewhere else. Call ClearFocus on the toplevel window, which
917 // will have the effect of clearing the focus and moving the focused window
918 // to the toplevel window. But if the window isn't being destroyed, we are
919 // likely just loading a new document in it, so we want to maintain the
920 // focused window so that the new document gets properly focused.
921 PRBool beingDestroyed;
922 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
923 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
924 if (beingDestroyed) {
925 // There is usually no need to do anything if a toplevel window is going
926 // away, as we assume that WindowLowered will be called. However, this may
927 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
928 // a leak. So if the active window is being destroyed, call WindowLowered
929 // directly.
930 NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
931 if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
932 WindowLowered(mActiveWindow);
933 else
934 ClearFocus(mActiveWindow);
935 return NS_OK;
938 // if the window being hidden is an ancestor of the focused window, adjust
939 // the focused window so that it points to the one being hidden. This
940 // ensures that the focused window isn't in a chain of frames that doesn't
941 // exist any more.
942 if (window != mFocusedWindow) {
943 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(mFocusedWindow));
944 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
945 if (dsti) {
946 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
947 dsti->GetParent(getter_AddRefs(parentDsti));
948 nsCOMPtr<nsPIDOMWindow> parentWindow = do_GetInterface(parentDsti);
949 if (parentWindow)
950 parentWindow->SetFocusedNode(nsnull);
953 mFocusedWindow = window;
956 return NS_OK;
959 NS_IMETHODIMP
960 nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
962 NS_ENSURE_ARG(aDocument);
964 // fire any delayed focus and blur events in the same order that they were added
965 for (PRUint32 i = 0; i < mDelayedBlurFocusEvents.Length(); i++)
967 if (mDelayedBlurFocusEvents[i].mDocument == aDocument &&
968 !aDocument->EventHandlingSuppressed()) {
969 PRUint32 type = mDelayedBlurFocusEvents[i].mType;
970 nsCOMPtr<nsPIDOMEventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
971 nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
972 mDelayedBlurFocusEvents.RemoveElementAt(i);
973 SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, PR_FALSE);
974 --i;
978 return NS_OK;
981 // static
982 void
983 nsFocusManager::EnsureCurrentWidgetFocused()
985 if (!mFocusedWindow)
986 return;
988 // get the main child widget for the focused window and ensure that the
989 // platform knows that this widget is focused.
990 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
991 if (docShell) {
992 nsCOMPtr<nsIPresShell> presShell;
993 docShell->GetPresShell(getter_AddRefs(presShell));
994 if (presShell) {
995 nsIViewManager* vm = presShell->GetViewManager();
996 if (vm) {
997 nsCOMPtr<nsIWidget> widget;
998 vm->GetRootWidget(getter_AddRefs(widget));
999 if (widget)
1000 widget->SetFocus(PR_FALSE);
1006 void
1007 nsFocusManager::SetFocusInner(nsIContent* aNewContent, PRInt32 aFlags,
1008 PRBool aFocusChanged)
1010 // if the element is not focusable, just return and leave the focus as is
1011 nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
1012 if (!contentToFocus)
1013 return;
1015 // check if the element to focus is a frame (iframe) containing a child
1016 // document. Frames are never directly focused; instead focusing a frame
1017 // means focus what is inside the frame. To do this, the descendant content
1018 // within the frame is retrieved and that will be focused instead.
1019 nsCOMPtr<nsPIDOMWindow> newWindow;
1020 nsCOMPtr<nsPIDOMWindow> subWindow = GetContentWindow(contentToFocus);
1021 if (subWindow) {
1022 contentToFocus = GetFocusedDescendant(subWindow, PR_TRUE, getter_AddRefs(newWindow));
1023 // since a window is being refocused, clear aFocusChanged so that the
1024 // caret position isn't updated.
1025 aFocusChanged = PR_FALSE;
1028 // unless it was set above, retrieve the window for the element to focus
1029 if (!newWindow)
1030 newWindow = GetCurrentWindow(contentToFocus);
1032 // if the element is already focused, just return. Note that this happens
1033 // after the frame check above so that we compare the element that will be
1034 // focused rather than the frame it is in.
1035 if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
1036 return;
1038 // don't allow focus to be placed in docshells or descendants of docshells
1039 // that are being destroyed. Also, ensure that the page hasn't been
1040 // unloaded. The prevents content from being refocused during an unload event.
1041 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1042 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1043 while (docShell) {
1044 PRBool inUnload;
1045 docShell->GetIsInUnload(&inUnload);
1046 if (inUnload)
1047 return;
1049 PRBool beingDestroyed;
1050 docShell->IsBeingDestroyed(&beingDestroyed);
1051 if (beingDestroyed)
1052 return;
1054 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(docShell);
1055 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1056 dsti->GetParent(getter_AddRefs(parentDsti));
1057 docShell = do_QueryInterface(parentDsti);
1060 // if the new element is in the same window as the currently focused element
1061 PRBool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1063 if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1064 nsContentUtils::IsHandlingKeyBoardEvent()) {
1065 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1066 do_QueryInterface(mFocusedWindow);
1067 nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1068 do_QueryInterface(newWindow);
1069 nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1070 nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1071 if (!focusedPrincipal || !newPrincipal) {
1072 return;
1074 PRBool subsumes = PR_FALSE;
1075 focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1076 if (!subsumes && !nsContentUtils::IsCallerTrustedForWrite()) {
1077 NS_WARNING("Not allowed to focus the new window!");
1078 return;
1082 // to check if the new element is in the active window, compare the
1083 // new root docshell for the new element with the active window's docshell.
1084 PRBool isElementInActiveWindow = PR_FALSE;
1086 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(newWindow);
1087 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
1088 nsCOMPtr<nsPIDOMWindow> newRootWindow;
1089 if (dsti) {
1090 nsCOMPtr<nsIDocShellTreeItem> root;
1091 dsti->GetRootTreeItem(getter_AddRefs(root));
1092 newRootWindow = do_GetInterface(root);
1094 isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1097 #ifdef DEBUG_FOCUS
1098 PRINTTAGF("Shift Focus: %s", contentToFocus);
1099 printf(" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1100 aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get());
1101 printf(" In Active Window: %d In Focused Window: %d\n",
1102 isElementInActiveWindow, isElementInFocusedWindow);
1103 #endif
1105 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1106 // shifted away from the current element if the new shell to focus is
1107 // the same or an ancestor shell of the currently focused shell.
1108 PRBool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1109 IsSameOrAncestor(newWindow, mFocusedWindow);
1111 // if the element is in the active window, frame switching is allowed and
1112 // the content is in a visible window, fire blur and focus events.
1113 PRBool sendFocusEvent =
1114 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1116 // When the following conditions are true:
1117 // * an element has focus
1118 // * isn't called by trusted event (i.e., called by untrusted event or by js)
1119 // * the focus is moved to another document's element
1120 // we need to check the permission.
1121 if (sendFocusEvent && mFocusedContent &&
1122 mFocusedContent->GetOwnerDoc() != aNewContent->GetOwnerDoc()) {
1123 // If the caller cannot access the current focused node, the caller should
1124 // not be able to steal focus from it. E.g., When the current focused node
1125 // is in chrome, any web contents should not be able to steal the focus.
1126 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
1127 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1130 if (sendFocusEvent) {
1131 // return if blurring fails or the focus changes during the blur
1132 if (mFocusedWindow) {
1133 // if the focus is being moved to another element in the same document,
1134 // or to a descendant, pass the existing window to Blur so that the
1135 // current node in the existing window is cleared. If moving to a
1136 // window elsewhere, we want to maintain the current node in the
1137 // window but still blur it.
1138 PRBool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
1139 // find the common ancestor of the currently focused window and the new
1140 // window. The ancestor will need to have its currently focused node
1141 // cleared once the document has been blurred. Otherwise, we'll be in a
1142 // state where a document is blurred yet the chain of windows above it
1143 // still points to that document.
1144 // For instance, in the following frame tree:
1145 // A
1146 // B C
1147 // D
1148 // D is focused and we want to focus C. Once D has been blurred, we need
1149 // to clear out the focus in A, otherwise A would still maintain that B
1150 // was focused, and B that D was focused.
1151 nsCOMPtr<nsPIDOMWindow> commonAncestor;
1152 if (!isElementInFocusedWindow)
1153 commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1155 if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nsnull,
1156 commonAncestor, !isElementInFocusedWindow))
1157 return;
1160 Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
1161 aFocusChanged, PR_FALSE);
1163 else {
1164 // otherwise, for inactive windows and when the caller cannot steal the
1165 // focus, update the node in the window, and raise the window if desired.
1166 if (allowFrameSwitch)
1167 AdjustWindowFocus(newWindow, PR_TRUE);
1169 // set the focus node and method as needed
1170 PRUint32 focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1171 newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1172 newWindow->SetFocusedNode(contentToFocus, focusMethod);
1173 if (aFocusChanged) {
1174 nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1176 nsCOMPtr<nsIPresShell> presShell;
1177 docShell->GetPresShell(getter_AddRefs(presShell));
1178 if (presShell)
1179 ScrollIntoView(presShell, contentToFocus, aFlags);
1182 // update the commands even when inactive so that the attributes for that
1183 // window are up to date.
1184 if (allowFrameSwitch)
1185 newWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1187 if (aFlags & FLAG_RAISE)
1188 RaiseWindow(newRootWindow);
1192 PRBool
1193 nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor,
1194 nsPIDOMWindow* aWindow)
1196 nsCOMPtr<nsIWebNavigation> awebnav(do_GetInterface(aPossibleAncestor));
1197 nsCOMPtr<nsIDocShellTreeItem> ancestordsti = do_QueryInterface(awebnav);
1199 nsCOMPtr<nsIWebNavigation> fwebnav(do_GetInterface(aWindow));
1200 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(fwebnav);
1201 while (dsti) {
1202 if (dsti == ancestordsti)
1203 return PR_TRUE;
1204 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1205 dsti->GetParent(getter_AddRefs(parentDsti));
1206 dsti.swap(parentDsti);
1209 return PR_FALSE;
1212 already_AddRefed<nsPIDOMWindow>
1213 nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1,
1214 nsPIDOMWindow* aWindow2)
1216 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(aWindow1));
1217 nsCOMPtr<nsIDocShellTreeItem> dsti1 = do_QueryInterface(webnav);
1218 NS_ENSURE_TRUE(dsti1, nsnull);
1220 webnav = do_GetInterface(aWindow2);
1221 nsCOMPtr<nsIDocShellTreeItem> dsti2 = do_QueryInterface(webnav);
1222 NS_ENSURE_TRUE(dsti2, nsnull);
1224 nsAutoTPtrArray<nsIDocShellTreeItem, 30> parents1, parents2;
1225 do {
1226 parents1.AppendElement(dsti1);
1227 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1228 dsti1->GetParent(getter_AddRefs(parentDsti1));
1229 dsti1.swap(parentDsti1);
1230 } while (dsti1);
1231 do {
1232 parents2.AppendElement(dsti2);
1233 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1234 dsti2->GetParent(getter_AddRefs(parentDsti2));
1235 dsti2.swap(parentDsti2);
1236 } while (dsti2);
1238 PRUint32 pos1 = parents1.Length();
1239 PRUint32 pos2 = parents2.Length();
1240 nsIDocShellTreeItem* parent = nsnull;
1241 PRUint32 len;
1242 for (len = NS_MIN(pos1, pos2); len > 0; --len) {
1243 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1244 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1245 if (child1 != child2) {
1246 break;
1248 parent = child1;
1251 nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(parent);
1252 return window.forget();
1255 void
1256 nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
1257 PRBool aCheckPermission)
1259 PRBool isVisible = IsWindowVisible(aWindow);
1261 nsCOMPtr<nsPIDOMWindow> window(aWindow);
1262 while (window) {
1263 // get the containing <iframe> or equivalent element so that it can be
1264 // focused below.
1265 nsCOMPtr<nsIContent> frameContent =
1266 do_QueryInterface(window->GetFrameElementInternal());
1268 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(window));
1269 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
1270 if (!dsti)
1271 return;
1272 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1273 dsti->GetParent(getter_AddRefs(parentDsti));
1275 window = do_GetInterface(parentDsti);
1276 if (window) {
1277 // if the parent window is visible but aWindow was not, then we have
1278 // likely moved up and out from a hidden tab to the browser window, or a
1279 // similar such arrangement. Stop adjusting the current nodes.
1280 if (IsWindowVisible(window) != isVisible)
1281 break;
1283 // When aCheckPermission is true, we should check whether the caller can
1284 // access the window or not. If it cannot access, we should stop the
1285 // adjusting.
1286 if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
1287 break;
1289 window->SetFocusedNode(frameContent);
1294 PRBool
1295 nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
1297 if (!aWindow)
1298 return PR_FALSE;
1300 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1301 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1302 if (!baseWin)
1303 return PR_FALSE;
1305 PRBool visible = PR_FALSE;
1306 baseWin->GetVisibility(&visible);
1307 return visible;
1310 PRBool
1311 nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
1313 NS_PRECONDITION(aContent, "aContent must not be NULL");
1314 NS_PRECONDITION(aContent->IsInDoc(), "aContent must be in a document");
1316 // If aContent is in designMode, the root element is not focusable.
1317 // NOTE: in designMode, most elements are not focusable, just the document is
1318 // focusable.
1319 // Also, if aContent is not editable but it isn't in designMode, it's not
1320 // focusable.
1321 nsIDocument* doc = aContent->GetCurrentDoc();
1322 NS_ASSERTION(doc, "aContent must have current document");
1323 return aContent == doc->GetRootElement() &&
1324 (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable());
1327 nsIContent*
1328 nsFocusManager::CheckIfFocusable(nsIContent* aContent, PRUint32 aFlags)
1330 if (!aContent)
1331 return nsnull;
1333 // this is a special case for some XUL elements where an anonymous child is
1334 // actually focusable and not the element itself.
1335 nsIContent* redirectedFocus = GetRedirectedFocus(aContent);
1336 if (redirectedFocus)
1337 return CheckIfFocusable(redirectedFocus, aFlags);
1339 nsCOMPtr<nsIDocument> doc = aContent->GetCurrentDoc();
1340 // can't focus elements that are not in documents
1341 if (!doc)
1342 return nsnull;
1344 // Make sure that our frames are up to date
1345 if (doc)
1346 doc->FlushPendingNotifications(Flush_Frames);
1348 nsIPresShell *shell = doc->GetShell();
1349 if (!shell)
1350 return nsnull;
1352 // the root content can always be focused
1353 if (aContent == doc->GetRootElement())
1354 return aContent;
1356 // cannot focus content in print preview mode. Only the root can be focused.
1357 nsPresContext* presContext = shell->GetPresContext();
1358 if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview)
1359 return nsnull;
1361 nsIFrame* frame = aContent->GetPrimaryFrame();
1362 if (!frame)
1363 return nsnull;
1365 if (aContent->Tag() == nsGkAtoms::area && aContent->IsHTML()) {
1366 // HTML areas do not have their own frame, and the img frame we get from
1367 // GetPrimaryFrame() is not relevant as to whether it is focusable or
1368 // not, so we have to do all the relevant checks manually for them.
1369 return frame->AreAncestorViewsVisible() &&
1370 frame->GetStyleVisibility()->IsVisible() &&
1371 aContent->IsFocusable() ? aContent : nsnull;
1374 // if this is a child frame content node, check if it is visible and
1375 // call the content node's IsFocusable method instead of the frame's
1376 // IsFocusable method. This skips checking the style system and ensures that
1377 // offscreen browsers can still be focused.
1378 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
1379 if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1380 const nsStyleUserInterface* ui = frame->GetStyleUserInterface();
1381 PRInt32 tabIndex = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE ||
1382 ui->mUserFocus == NS_STYLE_USER_FOCUS_NONE) ? -1 : 0;
1383 return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nsnull;
1386 return frame->IsFocusable(nsnull, aFlags & FLAG_BYMOUSE) ? aContent : nsnull;
1389 PRBool
1390 nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
1391 nsPIDOMWindow* aAncestorWindowToFocus,
1392 PRBool aIsLeavingDocument)
1394 // hold a reference to the focused content, which may be null
1395 nsCOMPtr<nsIContent> content = mFocusedContent;
1396 if (content) {
1397 if (!content->IsInDoc()) {
1398 mFocusedContent = nsnull;
1399 return PR_TRUE;
1401 if (content == mFirstBlurEvent)
1402 return PR_TRUE;
1405 // hold a reference to the focused window
1406 nsCOMPtr<nsPIDOMWindow> window = mFocusedWindow;
1407 if (!window) {
1408 mFocusedContent = nsnull;
1409 return PR_TRUE;
1412 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1413 if (!docShell) {
1414 mFocusedContent = nsnull;
1415 return PR_TRUE;
1418 // Keep a ref to presShell since dispatching the DOM event may cause
1419 // the document to be destroyed.
1420 nsCOMPtr<nsIPresShell> presShell;
1421 docShell->GetPresShell(getter_AddRefs(presShell));
1422 if (!presShell) {
1423 mFocusedContent = nsnull;
1424 return PR_TRUE;
1427 PRBool clearFirstBlurEvent = PR_FALSE;
1428 if (!mFirstBlurEvent) {
1429 mFirstBlurEvent = content;
1430 clearFirstBlurEvent = PR_TRUE;
1433 // if there is still an active window, adjust the IME state.
1434 // This has to happen before the focus is cleared below, otherwise, the IME
1435 // compositionend event won't get fired at the element being blurred.
1436 nsIMEStateManager::OnTextStateBlur(nsnull, nsnull);
1437 if (mActiveWindow)
1438 nsIMEStateManager::OnChangeFocus(presShell->GetPresContext(), nsnull);
1440 // now adjust the actual focus, by clearing the fields in the focus manager
1441 // and in the window.
1442 mFocusedContent = nsnull;
1443 if (aWindowToClear)
1444 aWindowToClear->SetFocusedNode(nsnull);
1446 #ifdef DEBUG_FOCUS
1447 PRINTTAGF("**Element %s has been blurred\n", content);
1448 #endif
1450 // Don't fire blur event on the root content which isn't editable.
1451 PRBool sendBlurEvent =
1452 content && content->IsInDoc() && !IsNonFocusableRoot(content);
1453 if (content) {
1454 if (sendBlurEvent) {
1455 // unusual to pass a content node to SetContentState on a blur,
1456 // but we are just calling it to get the ContentStatesChanged notifications
1457 presShell->GetPresContext()->EventStateManager()->
1458 SetContentState(content, NS_EVENT_STATE_FOCUS);
1461 // if an object/plug-in is being blurred, move the system focus to the
1462 // parent window, otherwise events will still get fired at the plugin.
1463 // But don't do this if we are blurring due to the window being lowered,
1464 // otherwise, the parent window can get raised again.
1465 if (mActiveWindow) {
1466 nsIFrame* contentFrame = content->GetPrimaryFrame();
1467 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1468 if (objectFrame) {
1469 // note that the presshell's widget is being retrieved here, not the one
1470 // for the object frame.
1471 nsIViewManager* vm = presShell->GetViewManager();
1472 if (vm) {
1473 nsCOMPtr<nsIWidget> widget;
1474 vm->GetRootWidget(getter_AddRefs(widget));
1475 if (widget)
1476 widget->SetFocus(PR_FALSE);
1482 PRBool result = PR_TRUE;
1483 if (sendBlurEvent) {
1484 // if there is an active window, update commands. If there isn't an active
1485 // window, then this was a blur caused by the active window being lowered,
1486 // so there is no need to update the commands
1487 if (mActiveWindow)
1488 window->UpdateCommands(NS_LITERAL_STRING("focus"));
1490 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
1491 content->GetCurrentDoc(), content, 1, PR_FALSE);
1494 // if we are leaving the document or the window was lowered, make the caret
1495 // invisible.
1496 if (aIsLeavingDocument || !mActiveWindow)
1497 SetCaretVisible(presShell, PR_FALSE, nsnull);
1499 // at this point, it is expected that this window will be still be
1500 // focused, but the focused content will be null, as it was cleared before
1501 // the event. If this isn't the case, then something else was focused during
1502 // the blur event above and we should just return. However, if
1503 // aIsLeavingDocument is set, a new document is desired, so make sure to
1504 // blur the document and window.
1505 if (mFocusedWindow != window ||
1506 (mFocusedContent != nsnull && !aIsLeavingDocument)) {
1507 result = PR_FALSE;
1509 else if (aIsLeavingDocument) {
1510 window->TakeFocus(PR_FALSE, 0);
1512 // clear the focus so that the ancestor frame hierarchy is in the correct
1513 // state. Pass true because aAncestorWindowToFocus is thought to be
1514 // focused at this point.
1515 if (aAncestorWindowToFocus)
1516 aAncestorWindowToFocus->SetFocusedNode(nsnull, 0, PR_TRUE);
1518 mFocusedWindow = nsnull;
1519 mFocusedContent = nsnull;
1521 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1522 // that the check is made for suppressed documents. Check to ensure that
1523 // the document isn't null in case someone closed it during the blur above
1524 nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
1525 if (doc)
1526 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, doc, 1, PR_FALSE);
1527 if (mFocusedWindow == nsnull)
1528 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, window, 1, PR_FALSE);
1530 // check if a different window was focused
1531 result = (mFocusedWindow == nsnull && mActiveWindow);
1533 else if (mActiveWindow) {
1534 // Otherwise, the blur of the element without blurring the document
1535 // occurred normally. Call UpdateCaret to redisplay the caret at the right
1536 // location within the document. This is needed to ensure that the caret
1537 // used for caret browsing is made visible again when an input field is
1538 // blurred.
1539 UpdateCaret(PR_FALSE, PR_TRUE, nsnull);
1542 if (clearFirstBlurEvent)
1543 mFirstBlurEvent = nsnull;
1545 return result;
1548 void
1549 nsFocusManager::Focus(nsPIDOMWindow* aWindow,
1550 nsIContent* aContent,
1551 PRUint32 aFlags,
1552 PRBool aIsNewDocument,
1553 PRBool aFocusChanged,
1554 PRBool aWindowRaised)
1556 if (!aWindow)
1557 return;
1559 if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
1560 return;
1562 // Keep a reference to the presShell since dispatching the DOM event may
1563 // cause the document to be destroyed.
1564 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1565 if (!docShell)
1566 return;
1568 nsCOMPtr<nsIPresShell> presShell;
1569 docShell->GetPresShell(getter_AddRefs(presShell));
1570 if (!presShell)
1571 return;
1573 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1574 // Otherwise, just get the current focus method and use that. This ensures
1575 // that the method is set during the document and window focus events.
1576 PRUint32 focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1577 aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1579 if (!IsWindowVisible(aWindow)) {
1580 // if the window isn't visible, for instance because it is a hidden tab,
1581 // update the current focus and scroll it into view but don't do anything else
1582 if (CheckIfFocusable(aContent, aFlags)) {
1583 aWindow->SetFocusedNode(aContent, focusMethod);
1584 if (aFocusChanged)
1585 ScrollIntoView(presShell, aContent, aFlags);
1587 return;
1590 PRBool clearFirstFocusEvent = PR_FALSE;
1591 if (!mFirstFocusEvent) {
1592 mFirstFocusEvent = aContent;
1593 clearFirstFocusEvent = PR_TRUE;
1596 #ifdef DEBUG_FOCUS
1597 PRINTTAGF("**Element %s has been focused", aContent);
1598 nsCOMPtr<nsIDocument> docm = do_QueryInterface(aWindow->GetExtantDocument());
1599 if (docm)
1600 PRINTTAGF(" from %s", docm->GetRootElement());
1601 printf(" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]\n",
1602 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags);
1603 #endif
1605 if (aIsNewDocument) {
1606 // if this is a new document, update the parent chain of frames so that
1607 // focus can be traversed from the top level down to the newly focused
1608 // window.
1609 AdjustWindowFocus(aWindow, PR_FALSE);
1611 // Update the window touch registration to reflect the state of
1612 // the new document that got focus
1613 aWindow->UpdateTouchState();
1616 // indicate that the window has taken focus.
1617 if (aWindow->TakeFocus(PR_TRUE, focusMethod))
1618 aIsNewDocument = PR_TRUE;
1620 mFocusedWindow = aWindow;
1622 // update the system focus.
1623 nsIViewManager* vm = presShell->GetViewManager();
1624 if (vm) {
1625 nsCOMPtr<nsIWidget> widget;
1626 vm->GetRootWidget(getter_AddRefs(widget));
1627 if (widget)
1628 widget->SetFocus(PR_FALSE);
1631 // if switching to a new document, first fire the focus event on the
1632 // document and then the window.
1633 if (aIsNewDocument) {
1634 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aWindow->GetExtantDocument());
1635 if (doc)
1636 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1637 doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1638 if (mFocusedWindow == aWindow && mFocusedContent == nsnull)
1639 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1640 aWindow, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1643 // check to ensure that the element is still focusable, and that nothing
1644 // else was focused during the events above.
1645 if (CheckIfFocusable(aContent, aFlags) &&
1646 mFocusedWindow == aWindow && mFocusedContent == nsnull) {
1647 mFocusedContent = aContent;
1648 aWindow->SetFocusedNode(aContent, focusMethod);
1650 PRBool sendFocusEvent =
1651 aContent && aContent->IsInDoc() && !IsNonFocusableRoot(aContent);
1652 if (sendFocusEvent) {
1653 // if the focused element changed, scroll it into view
1654 if (aFocusChanged)
1655 ScrollIntoView(presShell, aContent, aFlags);
1657 // inform the EventStateManager so that content state change notifications
1658 // are made.
1659 nsPresContext* presContext = presShell->GetPresContext();
1660 presContext->EventStateManager()->
1661 SetContentState(aContent, NS_EVENT_STATE_FOCUS);
1663 // if this is an object/plug-in, focus the plugin's widget. Note that we might
1664 // no longer be in the same document, due to the events we fired above when
1665 // aIsNewDocument.
1666 if (presShell->GetDocument() == aContent->GetDocument()) {
1667 nsIFrame* contentFrame = aContent->GetPrimaryFrame();
1668 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1669 if (objectFrame) {
1670 nsIWidget* widget = objectFrame->GetWidget();
1671 if (widget)
1672 widget->SetFocus(PR_FALSE);
1676 nsIMEStateManager::OnChangeFocus(presContext, aContent);
1678 // as long as this focus wasn't because a window was raised, update the
1679 // commands
1680 // XXXndeakin P2 someone could adjust the focus during the update
1681 if (!aWindowRaised)
1682 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1684 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, aContent->GetCurrentDoc(),
1685 aContent, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1687 nsIMEStateManager::OnTextStateFocus(presContext, aContent);
1688 } else {
1689 nsPresContext* presContext = presShell->GetPresContext();
1690 nsIMEStateManager::OnTextStateBlur(presContext, nsnull);
1691 nsIMEStateManager::OnChangeFocus(presContext, nsnull);
1692 if (!aWindowRaised) {
1693 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1697 else {
1698 nsPresContext* presContext = presShell->GetPresContext();
1699 nsIMEStateManager::OnTextStateBlur(presContext, nsnull);
1700 nsIMEStateManager::OnChangeFocus(presContext, nsnull);
1702 if (!aWindowRaised)
1703 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1706 // update the caret visibility and position to match the newly focused
1707 // element. However, don't update the position if this was a focus due to a
1708 // mouse click as the selection code would already have moved the caret as
1709 // needed. If this is a different document than was focused before, also
1710 // update the caret's visibility. If this is the same document, the caret
1711 // visibility should be the same as before so there is no need to update it.
1712 if (mFocusedContent == aContent)
1713 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
1714 mFocusedContent);
1716 if (clearFirstFocusEvent)
1717 mFirstFocusEvent = nsnull;
1720 class FocusBlurEvent : public nsRunnable
1722 public:
1723 FocusBlurEvent(nsISupports* aTarget, PRUint32 aType,
1724 nsPresContext* aContext, PRBool aWindowRaised)
1725 : mTarget(aTarget), mType(aType), mContext(aContext),
1726 mWindowRaised(aWindowRaised) {}
1728 NS_IMETHOD Run()
1730 nsFocusEvent event(PR_TRUE, mType);
1731 event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
1732 event.fromRaise = mWindowRaised;
1733 return nsEventDispatcher::Dispatch(mTarget, mContext, &event);
1736 nsCOMPtr<nsISupports> mTarget;
1737 PRUint32 mType;
1738 nsRefPtr<nsPresContext> mContext;
1739 PRBool mWindowRaised;
1742 void
1743 nsFocusManager::SendFocusOrBlurEvent(PRUint32 aType,
1744 nsIPresShell* aPresShell,
1745 nsIDocument* aDocument,
1746 nsISupports* aTarget,
1747 PRUint32 aFocusMethod,
1748 PRBool aWindowRaised)
1750 NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
1751 "Wrong event type for SendFocusOrBlurEvent");
1753 nsCOMPtr<nsPIDOMEventTarget> eventTarget = do_QueryInterface(aTarget);
1755 // for focus events, if this event was from a mouse or key and event
1756 // handling on the document is suppressed, queue the event and fire it
1757 // later. For blur events, a non-zero value would be set for aFocusMethod.
1758 if (aFocusMethod && aDocument && aDocument->EventHandlingSuppressed()) {
1759 // aFlags is always 0 when aWindowRaised is true so this won't be called
1760 // on a window raise.
1761 NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
1763 for (PRUint32 i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
1764 // if this event was already queued, remove it and append it to the end
1765 if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
1766 mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
1767 mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
1768 mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget) {
1769 mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
1773 mDelayedBlurFocusEvents.AppendElement(
1774 nsDelayedBlurOrFocusEvent(aType, aPresShell, aDocument, eventTarget));
1775 return;
1778 nsContentUtils::AddScriptRunner(
1779 new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
1780 aWindowRaised));
1783 void
1784 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
1785 nsIContent* aContent,
1786 PRUint32 aFlags)
1788 // if the noscroll flag isn't set, scroll the newly focused element into view
1789 if (!(aFlags & FLAG_NOSCROLL))
1790 aPresShell->ScrollContentIntoView(aContent,
1791 NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,
1792 NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE);
1796 void
1797 nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
1799 // don't raise windows that are already raised or are in the process of
1800 // being lowered
1801 if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
1802 return;
1804 #if defined(XP_WIN) || defined(XP_OS2)
1805 // Windows would rather we focus the child widget, otherwise, the toplevel
1806 // widget will always end up being focused. Fortunately, focusing the child
1807 // widget will also have the effect of raising the window this widget is in.
1808 // But on other platforms, we can just focus the toplevel widget to raise
1809 // the window.
1810 nsCOMPtr<nsPIDOMWindow> childWindow;
1811 GetFocusedDescendant(aWindow, PR_TRUE, getter_AddRefs(childWindow));
1812 if (!childWindow)
1813 childWindow = aWindow;
1815 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1816 if (!docShell)
1817 return;
1819 nsCOMPtr<nsIPresShell> presShell;
1820 docShell->GetPresShell(getter_AddRefs(presShell));
1821 if (!presShell)
1822 return;
1824 nsIViewManager* vm = presShell->GetViewManager();
1825 if (vm) {
1826 nsCOMPtr<nsIWidget> widget;
1827 vm->GetRootWidget(getter_AddRefs(widget));
1828 if (widget)
1829 widget->SetFocus(PR_TRUE);
1831 #else
1832 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
1833 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(webnav);
1834 if (treeOwnerAsWin) {
1835 nsCOMPtr<nsIWidget> widget;
1836 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
1837 if (widget)
1838 widget->SetFocus(PR_TRUE);
1840 #endif
1843 void
1844 nsFocusManager::UpdateCaret(PRBool aMoveCaretToFocus,
1845 PRBool aUpdateVisibility,
1846 nsIContent* aContent)
1848 #ifdef DEBUG_FOCUS
1849 printf("Update Caret: %d %d\n", aMoveCaretToFocus, aUpdateVisibility);
1850 #endif
1852 if (!mFocusedWindow)
1853 return;
1855 // this is called when a document is focused or when the caretbrowsing
1856 // preference is changed
1857 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
1858 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
1859 if (!dsti)
1860 return;
1862 PRInt32 itemType;
1863 dsti->GetItemType(&itemType);
1864 if (itemType == nsIDocShellTreeItem::typeChrome)
1865 return; // Never browse with caret in chrome
1867 PRPackedBool browseWithCaret =
1868 nsContentUtils::GetBoolPref("accessibility.browsewithcaret");
1870 nsCOMPtr<nsIPresShell> presShell;
1871 focusedDocShell->GetPresShell(getter_AddRefs(presShell));
1872 if (!presShell)
1873 return;
1875 // If this is an editable document which isn't contentEditable, or a
1876 // contentEditable document and the node to focus is contentEditable,
1877 // return, so that we don't mess with caret visibility.
1878 PRBool isEditable = PR_FALSE;
1879 nsCOMPtr<nsIEditorDocShell> editorDocShell(do_QueryInterface(dsti));
1880 if (editorDocShell) {
1881 editorDocShell->GetEditable(&isEditable);
1883 if (isEditable) {
1884 nsCOMPtr<nsIHTMLDocument> doc =
1885 do_QueryInterface(presShell->GetDocument());
1887 PRBool isContentEditableDoc =
1888 doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
1890 PRBool isFocusEditable =
1891 aContent && aContent->HasFlag(NODE_IS_EDITABLE);
1892 if (!isContentEditableDoc || isFocusEditable)
1893 return;
1897 if (!isEditable && aMoveCaretToFocus)
1898 MoveCaretToFocus(presShell, aContent);
1900 if (!aUpdateVisibility)
1901 return;
1903 // XXXndeakin this doesn't seem right. It should be checking for this only
1904 // on the nearest ancestor frame which is a chrome frame. But this is
1905 // what the existing code does, so just leave it for now.
1906 if (!browseWithCaret) {
1907 nsCOMPtr<nsIContent> docContent =
1908 do_QueryInterface(mFocusedWindow->GetFrameElementInternal());
1909 if (docContent)
1910 browseWithCaret = docContent->AttrValueIs(kNameSpaceID_None,
1911 nsGkAtoms::showcaret,
1912 NS_LITERAL_STRING("true"),
1913 eCaseMatters);
1916 SetCaretVisible(presShell, browseWithCaret, aContent);
1919 void
1920 nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
1922 // rangeDoc is a document interface we can create a range with
1923 nsCOMPtr<nsIDOMDocumentRange> rangeDoc(do_QueryInterface(aPresShell->GetDocument()));
1924 if (rangeDoc) {
1925 nsCOMPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
1926 nsCOMPtr<nsISelection> domSelection = frameSelection->
1927 GetSelection(nsISelectionController::SELECTION_NORMAL);
1928 if (domSelection) {
1929 nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
1930 // First clear the selection. This way, if there is no currently focused
1931 // content, the selection will just be cleared.
1932 domSelection->RemoveAllRanges();
1933 if (currentFocusNode) {
1934 nsCOMPtr<nsIDOMRange> newRange;
1935 nsresult rv = rangeDoc->CreateRange(getter_AddRefs(newRange));
1936 if (NS_SUCCEEDED(rv)) {
1937 // Set the range to the start of the currently focused node
1938 // Make sure it's collapsed
1939 newRange->SelectNodeContents(currentFocusNode);
1940 nsCOMPtr<nsIDOMNode> firstChild;
1941 currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
1942 if (!firstChild ||
1943 aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
1944 // If current focus node is a leaf, set range to before the
1945 // node by using the parent as a container.
1946 // This prevents it from appearing as selected.
1947 newRange->SetStartBefore(currentFocusNode);
1948 newRange->SetEndBefore(currentFocusNode);
1950 domSelection->AddRange(newRange);
1951 domSelection->CollapseToStart();
1958 nsresult
1959 nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
1960 PRBool aVisible,
1961 nsIContent* aContent)
1963 // When browsing with caret, make sure caret is visible after new focus
1964 // Return early if there is no caret. This can happen for the testcase
1965 // for bug 308025 where a window is closed in a blur handler.
1966 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
1967 if (!caret)
1968 return NS_OK;
1970 PRBool caretVisible = PR_FALSE;
1971 caret->GetCaretVisible(&caretVisible);
1972 if (!aVisible && !caretVisible)
1973 return NS_OK;
1975 nsCOMPtr<nsFrameSelection> frameSelection;
1976 if (aContent) {
1977 NS_ASSERTION(aContent->GetDocument() == aPresShell->GetDocument(),
1978 "Wrong document?");
1979 nsIFrame *focusFrame = aContent->GetPrimaryFrame();
1980 if (focusFrame)
1981 frameSelection = focusFrame->GetFrameSelection();
1984 nsCOMPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
1986 if (docFrameSelection && caret &&
1987 (frameSelection == docFrameSelection || !aContent)) {
1988 nsISelection* domSelection = docFrameSelection->
1989 GetSelection(nsISelectionController::SELECTION_NORMAL);
1990 if (domSelection) {
1991 // First, tell the caret which selection to use
1992 caret->SetCaretDOMSelection(domSelection);
1994 // In content, we need to set the caret. The only special case is edit
1995 // fields, which have a different frame selection from the document.
1996 // They will take care of making the caret visible themselves.
1998 nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
1999 if (!selCon)
2000 return NS_ERROR_FAILURE;
2002 selCon->SetCaretEnabled(aVisible);
2003 caret->SetCaretVisible(aVisible);
2007 return NS_OK;
2010 nsresult
2011 nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
2012 nsIPresShell* aPresShell,
2013 nsIContent **aStartContent,
2014 nsIContent **aEndContent)
2016 *aStartContent = *aEndContent = nsnull;
2017 nsresult rv = NS_ERROR_FAILURE;
2019 nsPresContext* presContext = aPresShell->GetPresContext();
2020 NS_ASSERTION(presContext, "mPresContent is null!!");
2022 nsCOMPtr<nsFrameSelection> frameSelection;
2023 frameSelection = aPresShell->FrameSelection();
2025 nsCOMPtr<nsISelection> domSelection;
2026 if (frameSelection) {
2027 domSelection = frameSelection->
2028 GetSelection(nsISelectionController::SELECTION_NORMAL);
2031 nsCOMPtr<nsIDOMNode> startNode, endNode;
2032 PRBool isCollapsed = PR_FALSE;
2033 nsCOMPtr<nsIContent> startContent, endContent;
2034 PRInt32 startOffset = 0;
2035 if (domSelection) {
2036 domSelection->GetIsCollapsed(&isCollapsed);
2037 nsCOMPtr<nsIDOMRange> domRange;
2038 rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
2039 if (domRange) {
2040 domRange->GetStartContainer(getter_AddRefs(startNode));
2041 domRange->GetEndContainer(getter_AddRefs(endNode));
2042 domRange->GetStartOffset(&startOffset);
2044 nsIContent *childContent = nsnull;
2046 startContent = do_QueryInterface(startNode);
2047 if (startContent && startContent->IsElement()) {
2048 NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
2049 childContent = startContent->GetChildAt(startOffset);
2050 if (childContent) {
2051 startContent = childContent;
2055 endContent = do_QueryInterface(endNode);
2056 if (endContent && endContent->IsElement()) {
2057 PRInt32 endOffset = 0;
2058 domRange->GetEndOffset(&endOffset);
2059 NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
2060 childContent = endContent->GetChildAt(endOffset);
2061 if (childContent) {
2062 endContent = childContent;
2067 else {
2068 rv = NS_ERROR_INVALID_ARG;
2071 nsIFrame *startFrame = nsnull;
2072 if (startContent) {
2073 startFrame = startContent->GetPrimaryFrame();
2074 if (isCollapsed) {
2075 // Next check to see if our caret is at the very end of a node
2076 // If so, the caret is actually sitting in front of the next
2077 // logical frame's primary node - so for this case we need to
2078 // change caretContent to that node.
2080 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(startContent));
2081 PRUint16 nodeType;
2082 domNode->GetNodeType(&nodeType);
2084 if (nodeType == nsIDOMNode::TEXT_NODE) {
2085 nsAutoString nodeValue;
2086 domNode->GetNodeValue(nodeValue);
2088 PRBool isFormControl =
2089 startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2091 if (nodeValue.Length() == (PRUint32)startOffset && !isFormControl &&
2092 startContent != aDocument->GetRootElement()) {
2093 // Yes, indeed we were at the end of the last node
2094 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2095 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2096 presContext, startFrame,
2097 eLeaf,
2098 PR_FALSE, // aVisual
2099 PR_FALSE, // aLockInScrollView
2100 PR_TRUE // aFollowOOFs
2102 NS_ENSURE_SUCCESS(rv, rv);
2104 nsIFrame *newCaretFrame = nsnull;
2105 nsCOMPtr<nsIContent> newCaretContent = startContent;
2106 PRBool endOfSelectionInStartNode(startContent == endContent);
2107 do {
2108 // Continue getting the next frame until the primary content for the frame
2109 // we are on changes - we don't want to be stuck in the same place
2110 frameTraversal->Next();
2111 newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2112 if (nsnull == newCaretFrame)
2113 break;
2114 newCaretContent = newCaretFrame->GetContent();
2115 } while (!newCaretContent || newCaretContent == startContent);
2117 if (newCaretFrame && newCaretContent) {
2118 // If the caret is exactly at the same position of the new frame,
2119 // then we can use the newCaretFrame and newCaretContent for our position
2120 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
2121 nsRect caretRect;
2122 nsIFrame *frame = caret->GetGeometry(domSelection, &caretRect);
2123 if (frame) {
2124 nsPoint caretWidgetOffset;
2125 nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
2126 caretRect.MoveBy(caretWidgetOffset);
2127 nsPoint newCaretOffset;
2128 nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
2129 if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2130 caretRect.x == newCaretOffset.x) {
2131 // The caret is at the start of the new element.
2132 startFrame = newCaretFrame;
2133 startContent = newCaretContent;
2134 if (endOfSelectionInStartNode) {
2135 endContent = newCaretContent; // Ensure end of selection is not before start
2145 *aStartContent = startContent;
2146 *aEndContent = endContent;
2147 NS_IF_ADDREF(*aStartContent);
2148 NS_IF_ADDREF(*aEndContent);
2150 return rv;
2153 nsresult
2154 nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
2155 nsIContent* aStartContent,
2156 PRInt32 aType,
2157 nsIContent** aNextContent)
2159 *aNextContent = nsnull;
2161 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2162 if (!docShell)
2163 return NS_OK;
2165 nsCOMPtr<nsIContent> startContent = aStartContent;
2166 if (!startContent && aType != MOVEFOCUS_CARET)
2167 startContent = aWindow->GetFocusedNode();
2169 nsCOMPtr<nsIDocument> doc;
2170 if (startContent)
2171 doc = startContent->GetCurrentDoc();
2172 else
2173 doc = do_QueryInterface(aWindow->GetExtantDocument());
2174 if (!doc)
2175 return NS_OK;
2177 nsCOMPtr<nsILookAndFeel> lookNFeel(do_GetService(kLookAndFeelCID));
2178 lookNFeel->GetMetric(nsILookAndFeel::eMetric_TabFocusModel,
2179 nsIContent::sTabFocusModel);
2181 if (aType == MOVEFOCUS_ROOT) {
2182 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, PR_FALSE, PR_FALSE));
2183 return NS_OK;
2185 if (aType == MOVEFOCUS_FORWARDDOC) {
2186 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(PR_TRUE));
2187 return NS_OK;
2189 if (aType == MOVEFOCUS_BACKWARDDOC) {
2190 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(PR_FALSE));
2191 return NS_OK;
2194 nsIContent* rootContent = doc->GetRootElement();
2195 NS_ENSURE_TRUE(rootContent, NS_OK);
2197 nsIPresShell *presShell = doc->GetShell();
2198 NS_ENSURE_TRUE(presShell, NS_OK);
2200 if (aType == MOVEFOCUS_FIRST) {
2201 if (!aStartContent)
2202 startContent = rootContent;
2203 return GetNextTabbableContent(presShell, startContent,
2204 nsnull, startContent,
2205 PR_TRUE, 1, PR_FALSE, aNextContent);
2207 if (aType == MOVEFOCUS_LAST) {
2208 if (!aStartContent)
2209 startContent = rootContent;
2210 return GetNextTabbableContent(presShell, startContent,
2211 nsnull, startContent,
2212 PR_FALSE, 0, PR_FALSE, aNextContent);
2215 PRBool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
2216 PRBool doNavigation = PR_TRUE;
2217 PRBool ignoreTabIndex = PR_FALSE;
2218 // when a popup is open, we want to ensure that tab navigation occurs only
2219 // within the most recently opened panel. If a popup is open, its frame will
2220 // be stored in popupFrame.
2221 nsIFrame* popupFrame = nsnull;
2223 PRInt32 tabIndex = forward ? 1 : 0;
2224 if (startContent) {
2225 nsIFrame* frame = startContent->GetPrimaryFrame();
2226 if (startContent->Tag() == nsGkAtoms::area &&
2227 startContent->IsHTML())
2228 startContent->IsFocusable(&tabIndex);
2229 else if (frame)
2230 frame->IsFocusable(&tabIndex, 0);
2231 else
2232 startContent->IsFocusable(&tabIndex);
2234 // if the current element isn't tabbable, ignore the tabindex and just
2235 // look for the next element. The root content won't have a tabindex
2236 // so just treat this as the beginning of the tab order.
2237 if (tabIndex < 0) {
2238 tabIndex = 1;
2239 if (startContent != rootContent)
2240 ignoreTabIndex = PR_TRUE;
2243 // check if the focus is currently inside a popup. Elements such as the
2244 // autocomplete widget use the noautofocus attribute to allow the focus to
2245 // remain outside the popup when it is opened.
2246 if (frame) {
2247 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2248 nsGkAtoms::menuPopupFrame);
2251 if (popupFrame) {
2252 // Don't navigate outside of a popup, so pretend that the
2253 // root content is the popup itself
2254 rootContent = popupFrame->GetContent();
2255 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2257 else if (!forward) {
2258 // If focus moves backward and when current focused node is root
2259 // content or <body> element which is editable by contenteditable
2260 // attribute, focus should move to its parent document.
2261 if (startContent == rootContent) {
2262 doNavigation = PR_FALSE;
2263 } else {
2264 nsIDocument* doc = startContent->GetCurrentDoc();
2265 if (startContent ==
2266 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2267 doNavigation = PR_FALSE;
2272 else {
2273 #ifdef MOZ_XUL
2274 // if there is no focus, yet a panel is open, focus the first item in
2275 // the panel
2276 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2277 if (pm)
2278 popupFrame = pm->GetTopPopup(ePopupTypePanel);
2279 #endif
2280 if (popupFrame) {
2281 rootContent = popupFrame->GetContent();
2282 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2283 startContent = rootContent;
2285 else {
2286 // Otherwise, for content shells, start from the location of the caret.
2287 PRInt32 itemType;
2288 nsCOMPtr<nsIDocShellTreeItem> shellItem = do_QueryInterface(docShell);
2289 shellItem->GetItemType(&itemType);
2290 if (itemType != nsIDocShellTreeItem::typeChrome) {
2291 nsCOMPtr<nsIContent> endSelectionContent;
2292 GetSelectionLocation(doc, presShell,
2293 getter_AddRefs(startContent),
2294 getter_AddRefs(endSelectionContent));
2295 // If the selection is on the rootContent, then there is no selection
2296 if (startContent == rootContent) {
2297 startContent = nsnull;
2299 else if (startContent && startContent->HasFlag(NODE_IS_EDITABLE)) {
2300 // Don't start from the selection if the selection is in a
2301 // contentEditable region.
2302 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
2303 if (htmlDoc &&
2304 htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable)
2305 startContent = nsnull;
2308 if (aType == MOVEFOCUS_CARET) {
2309 // GetFocusInSelection finds a focusable link near the caret.
2310 // If there is no start content though, don't do this to avoid
2311 // focusing something unexpected.
2312 if (startContent) {
2313 GetFocusInSelection(aWindow, startContent,
2314 endSelectionContent, aNextContent);
2316 return NS_OK;
2319 if (startContent) {
2320 // when starting from a selection, we always want to find the next or
2321 // previous element in the document. So the tabindex on elements
2322 // should be ignored.
2323 ignoreTabIndex = PR_TRUE;
2327 if (!startContent) {
2328 // otherwise, just use the root content as the starting point
2329 startContent = rootContent;
2330 NS_ENSURE_TRUE(startContent, NS_OK);
2335 NS_ASSERTION(startContent, "starting content not set");
2337 // keep a reference to the starting content. If we find that again, it means
2338 // we've iterated around completely and we don't want to adjust the focus.
2339 // The skipOriginalContentCheck will be set to true only for the first time
2340 // GetNextTabbableContent is called. This ensures that we don't break out
2341 // when nothing is focused to start with. Specifically,
2342 // GetNextTabbableContent first checks the root content -- which happens to
2343 // be the same as the start content -- when nothing is focused and tabbing
2344 // forward. Without skipOriginalContentCheck set to true, we'd end up
2345 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2346 // will never wrap around on its own, and can only return the original
2347 // content when it is called a second time or later.
2348 PRBool skipOriginalContentCheck = PR_TRUE;
2349 nsIContent* originalStartContent = startContent;
2351 #ifdef DEBUG_FOCUS_NAVIGATION
2352 PRINTTAGF("Focus Navigation Start Content %s\n", startContent);
2353 printf("[Tabindex: %d Ignore: %d]", tabIndex, ignoreTabIndex);
2354 #endif
2356 while (doc) {
2357 if (doNavigation) {
2358 nsCOMPtr<nsIContent> nextFocus;
2359 nsresult rv = GetNextTabbableContent(presShell, rootContent,
2360 skipOriginalContentCheck ? nsnull : originalStartContent,
2361 startContent, forward,
2362 tabIndex, ignoreTabIndex,
2363 getter_AddRefs(nextFocus));
2364 NS_ENSURE_SUCCESS(rv, rv);
2366 // found a content node to focus.
2367 if (nextFocus) {
2368 #ifdef DEBUG_FOCUS_NAVIGATION
2369 PRINTTAGF("Next Content: %s\n", nextFocus);
2370 #endif
2371 // as long as the found node was not the same as the starting node,
2372 // set it as the return value.
2373 if (nextFocus != originalStartContent)
2374 NS_ADDREF(*aNextContent = nextFocus);
2375 return NS_OK;
2378 if (popupFrame) {
2379 // in a popup, so start again from the beginning of the popup. However,
2380 // if we already started at the beginning, then there isn't anything to
2381 // focus, so just return
2382 if (startContent != rootContent) {
2383 startContent = rootContent;
2384 tabIndex = forward ? 1 : 0;
2385 continue;
2387 return NS_OK;
2391 doNavigation = PR_TRUE;
2392 skipOriginalContentCheck = PR_FALSE;
2393 ignoreTabIndex = PR_FALSE;
2395 // reached the beginning or end of the document. Traverse up to the parent
2396 // document and try again.
2397 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(docShell);
2399 nsCOMPtr<nsIDocShellTreeItem> docShellParent;
2400 dsti->GetParent(getter_AddRefs(docShellParent));
2401 if (docShellParent) {
2402 // move up to the parent shell and try again from there.
2404 // first, get the frame element this window is inside.
2405 nsCOMPtr<nsPIDOMWindow> piWindow = do_GetInterface(docShell);
2406 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2408 // Next, retrieve the parent docshell, document and presshell.
2409 docShell = do_QueryInterface(docShellParent);
2410 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2412 nsCOMPtr<nsPIDOMWindow> piParentWindow = do_GetInterface(docShellParent);
2413 NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
2414 doc = do_QueryInterface(piParentWindow->GetExtantDocument());
2415 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2417 presShell = doc->GetShell();
2419 rootContent = doc->GetRootElement();
2420 startContent = do_QueryInterface(piWindow->GetFrameElementInternal());
2421 if (startContent) {
2422 nsIFrame* frame = startContent->GetPrimaryFrame();
2423 if (!frame)
2424 return NS_OK;
2426 frame->IsFocusable(&tabIndex, 0);
2427 if (tabIndex < 0) {
2428 tabIndex = 1;
2429 ignoreTabIndex = PR_TRUE;
2432 // if the frame is inside a popup, make sure to scan only within the
2433 // popup. This handles the situation of tabbing amongst elements
2434 // inside an iframe which is itself inside a popup. Otherwise,
2435 // navigation would move outside the popup when tabbing outside the
2436 // iframe.
2437 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2438 nsGkAtoms::menuPopupFrame);
2439 if (popupFrame) {
2440 rootContent = popupFrame->GetContent();
2441 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2444 else {
2445 startContent = rootContent;
2446 tabIndex = forward ? 1 : 0;
2449 else {
2450 // no parent, so call the tree owner. This will tell the embedder that
2451 // it should take the focus.
2452 PRBool tookFocus;
2453 docShell->TabToTreeOwner(forward, &tookFocus);
2454 // if the tree owner, took the focus, blur the current content
2455 if (tookFocus) {
2456 nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(docShell);
2457 if (window->GetFocusedNode() == mFocusedContent)
2458 Blur(mFocusedWindow, nsnull, PR_TRUE);
2459 else
2460 window->SetFocusedNode(nsnull);
2461 return NS_OK;
2464 // reset the tab index and start again from the beginning or end
2465 startContent = rootContent;
2466 tabIndex = forward ? 1 : 0;
2469 // wrapped all the way around and didn't find anything to move the focus
2470 // to, so just break out
2471 if (startContent == originalStartContent)
2472 break;
2475 return NS_OK;
2478 nsresult
2479 nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
2480 nsIContent* aRootContent,
2481 nsIContent* aOriginalStartContent,
2482 nsIContent* aStartContent,
2483 PRBool aForward,
2484 PRInt32 aCurrentTabIndex,
2485 PRBool aIgnoreTabIndex,
2486 nsIContent** aResultContent)
2488 *aResultContent = nsnull;
2490 nsCOMPtr<nsIContent> startContent = aStartContent;
2491 if (!startContent)
2492 return NS_OK;
2494 #ifdef DEBUG_FOCUS_NAVIGATION
2495 PRINTTAGF("GetNextTabbable: %s", aStartContent);
2496 printf(" tabindex: %d\n", aCurrentTabIndex);
2497 #endif
2499 nsPresContext* presContext = aPresShell->GetPresContext();
2501 PRBool getNextFrame = PR_TRUE;
2502 nsCOMPtr<nsIContent> iterStartContent = aStartContent;
2503 while (1) {
2504 nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
2505 // if there is no frame, look for another content node that has a frame
2506 if (!startFrame) {
2507 // if the root content doesn't have a frame, just return
2508 if (iterStartContent == aRootContent)
2509 return NS_OK;
2511 // look for the next or previous content node in tree order
2512 nsTreeWalker walker(aRootContent, nsIDOMNodeFilter::SHOW_ALL, nsnull, PR_TRUE);
2513 nsCOMPtr<nsIDOMNode> nextNode = do_QueryInterface(iterStartContent);
2514 walker.SetCurrentNode(nextNode);
2515 if (NS_SUCCEEDED(aForward ? walker.NextNode(getter_AddRefs(nextNode)) :
2516 walker.PreviousNode(getter_AddRefs(nextNode)))) {
2517 iterStartContent = do_QueryInterface(nextNode);
2518 // we've already skipped over the initial focused content, so we
2519 // don't want to traverse frames.
2520 getNextFrame = PR_FALSE;
2521 if (iterStartContent)
2522 continue;
2525 // otherwise, as a last attempt, just look at the root content
2526 iterStartContent = aRootContent;
2527 continue;
2530 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2531 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2532 presContext, startFrame,
2533 ePreOrder,
2534 PR_FALSE, // aVisual
2535 PR_FALSE, // aLockInScrollView
2536 PR_TRUE // aFollowOOFs
2538 NS_ENSURE_SUCCESS(rv, rv);
2540 if (iterStartContent == aRootContent) {
2541 if (!aForward) {
2542 frameTraversal->Last();
2543 } else if (aRootContent->IsFocusable()) {
2544 frameTraversal->Next();
2547 else if (getNextFrame &&
2548 (!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
2549 !iterStartContent->IsHTML())) {
2550 // Need to do special check in case we're in an imagemap which has multiple
2551 // content nodes per frame, so don't skip over the starting frame.
2552 if (aForward)
2553 frameTraversal->Next();
2554 else
2555 frameTraversal->Prev();
2558 // Walk frames to find something tabbable matching mCurrentTabIndex
2559 nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2560 while (frame) {
2561 // TabIndex not set defaults to 0 for form elements, anchors and other
2562 // elements that are normally focusable. Tabindex defaults to -1
2563 // for elements that are not normally focusable.
2564 // The returned computed tabindex from IsFocusable() is as follows:
2565 // < 0 not tabbable at all
2566 // == 0 in normal tab order (last after positive tabindexed items)
2567 // > 0 can be tabbed to in the order specified by this value
2569 PRInt32 tabIndex;
2570 frame->IsFocusable(&tabIndex, 0);
2572 #ifdef DEBUG_FOCUS_NAVIGATION
2573 if (frame->GetContent()) {
2574 PRINTTAGF("Next Tabbable %s:", frame->GetContent());
2575 printf(" with tabindex: %d expected: %d\n", tabIndex, aCurrentTabIndex);
2577 #endif
2579 nsIContent* currentContent = frame->GetContent();
2580 if (tabIndex >= 0) {
2581 NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
2582 if (currentContent->Tag() == nsGkAtoms::img &&
2583 currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
2584 // This is an image with a map. Image map areas are not traversed by
2585 // nsIFrameTraversal so look for the next or previous area element.
2586 nsIContent *areaContent =
2587 GetNextTabbableMapArea(aForward, aCurrentTabIndex,
2588 currentContent, iterStartContent);
2589 if (areaContent) {
2590 NS_ADDREF(*aResultContent = areaContent);
2591 return NS_OK;
2594 else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
2595 // break out if we've wrapped around to the start again.
2596 if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2597 NS_ADDREF(*aResultContent = currentContent);
2598 return NS_OK;
2601 // found a node with a matching tab index. Check if it is a child
2602 // frame. If so, navigate into the child frame instead.
2603 nsIDocument* doc = currentContent->GetCurrentDoc();
2604 NS_ASSERTION(doc, "content not in document");
2605 nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
2606 if (subdoc) {
2607 if (!subdoc->EventHandlingSuppressed()) {
2608 if (aForward) {
2609 // when tabbing forward into a frame, return the root
2610 // frame so that the canvas becomes focused.
2611 nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
2612 if (subframe) {
2613 // If the subframe body is editable by contenteditable,
2614 // we should set the editor's root element rather than the
2615 // actual root element. Otherwise, we should set the focus
2616 // to the root content.
2617 *aResultContent =
2618 nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
2619 if (!*aResultContent ||
2620 !((*aResultContent)->GetPrimaryFrame())) {
2621 *aResultContent =
2622 GetRootForFocus(subframe, subdoc, PR_FALSE, PR_TRUE);
2624 if (*aResultContent) {
2625 NS_ADDREF(*aResultContent);
2626 return NS_OK;
2630 Element* rootElement = subdoc->GetRootElement();
2631 nsIPresShell* subShell = subdoc->GetShell();
2632 if (rootElement && subShell) {
2633 rv = GetNextTabbableContent(subShell, rootElement,
2634 aOriginalStartContent, rootElement,
2635 aForward, (aForward ? 1 : 0),
2636 PR_FALSE, aResultContent);
2637 NS_ENSURE_SUCCESS(rv, rv);
2638 if (*aResultContent)
2639 return NS_OK;
2643 // otherwise, use this as the next content node to tab to, unless
2644 // this was the element we started on. This would happen for
2645 // instance on an element with child frames, where frame navigation
2646 // could return the original element again. In that case, just skip
2647 // it. Also, if the next content node is the root content, then
2648 // return it. This latter case would happen only if someone made a
2649 // popup focusable.
2650 // Also, when going backwards, check to ensure that the focus
2651 // wouldn't be redirected. Otherwise, for example, when an input in
2652 // a textbox is focused, the enclosing textbox would be found and
2653 // the same inner input would be returned again.
2654 else if (currentContent == aRootContent ||
2655 (currentContent != startContent &&
2656 (aForward || !GetRedirectedFocus(currentContent)))) {
2657 NS_ADDREF(*aResultContent = currentContent);
2658 return NS_OK;
2662 else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2663 // not focusable, so return if we have wrapped around to the original
2664 // content. This is necessary in case the original starting content was
2665 // not focusable.
2666 NS_ADDREF(*aResultContent = currentContent);
2667 return NS_OK;
2670 // Move to the next or previous frame, but ignore continuation frames
2671 // since only the first frame should be involved in focusability.
2672 // Otherwise, a loop will occur in the following example:
2673 // <span tabindex="1">...<a/><a/>...</span>
2674 // where the text wraps onto multiple lines. Tabbing from the second
2675 // link can find one of the span's continuation frames between the link
2676 // and the end of the span, and the span would end up getting focused
2677 // again.
2678 do {
2679 if (aForward)
2680 frameTraversal->Next();
2681 else
2682 frameTraversal->Prev();
2683 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2684 } while (frame && frame->GetPrevContinuation());
2687 // If already at lowest priority tab (0), end search completely.
2688 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
2689 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
2690 // if going backwards, the canvas should be focused once the beginning
2691 // has been reached.
2692 if (!aForward) {
2693 nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
2694 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2695 NS_IF_ADDREF(*aResultContent =
2696 GetRootForFocus(window, aRootContent->GetCurrentDoc(), PR_FALSE, PR_TRUE));
2698 break;
2701 // continue looking for next highest priority tabindex
2702 aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
2703 startContent = iterStartContent = aRootContent;
2706 return NS_OK;
2709 nsIContent*
2710 nsFocusManager::GetNextTabbableMapArea(PRBool aForward,
2711 PRInt32 aCurrentTabIndex,
2712 nsIContent* aImageContent,
2713 nsIContent* aStartContent)
2715 nsAutoString useMap;
2716 aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
2718 nsCOMPtr<nsIDocument> doc = aImageContent->GetDocument();
2719 if (doc) {
2720 nsCOMPtr<nsIDOMHTMLMapElement> imageMap = nsImageMapUtils::FindImageMap(doc, useMap);
2721 if (!imageMap)
2722 return nsnull;
2723 nsCOMPtr<nsIContent> mapContent = do_QueryInterface(imageMap);
2724 PRUint32 count = mapContent->GetChildCount();
2725 // First see if the the start content is in this map
2727 PRInt32 index = mapContent->IndexOf(aStartContent);
2728 PRInt32 tabIndex;
2729 if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
2730 tabIndex != aCurrentTabIndex)) {
2731 // If aStartContent is in this map we must start iterating past it.
2732 // We skip the case where aStartContent has tabindex == aStartContent
2733 // since the next tab ordered element might be before it
2734 // (or after for backwards) in the child list.
2735 index = aForward ? -1 : (PRInt32)count;
2738 // GetChildAt will return nsnull if our index < 0 or index >= count
2739 nsCOMPtr<nsIContent> areaContent;
2740 while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nsnull) {
2741 if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
2742 return areaContent;
2747 return nsnull;
2750 PRInt32
2751 nsFocusManager::GetNextTabIndex(nsIContent* aParent,
2752 PRInt32 aCurrentTabIndex,
2753 PRBool aForward)
2755 PRInt32 tabIndex, childTabIndex;
2756 nsIContent *child;
2758 PRUint32 count = aParent->GetChildCount();
2760 if (aForward) {
2761 tabIndex = 0;
2762 for (PRUint32 index = 0; index < count; index++) {
2763 child = aParent->GetChildAt(index);
2764 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2765 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
2766 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
2769 nsAutoString tabIndexStr;
2770 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2771 PRInt32 ec, val = tabIndexStr.ToInteger(&ec);
2772 if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
2773 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
2777 else { /* !aForward */
2778 tabIndex = 1;
2779 for (PRUint32 index = 0; index < count; index++) {
2780 child = aParent->GetChildAt(index);
2781 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2782 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
2783 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
2784 tabIndex = childTabIndex;
2787 nsAutoString tabIndexStr;
2788 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2789 PRInt32 ec, val = tabIndexStr.ToInteger(&ec);
2790 if (NS_SUCCEEDED (ec)) {
2791 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
2792 (val < aCurrentTabIndex && val > tabIndex) ) {
2793 tabIndex = val;
2799 return tabIndex;
2802 nsIContent*
2803 nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
2804 nsIDocument* aDocument,
2805 PRBool aIsForDocNavigation,
2806 PRBool aCheckVisibility)
2808 // the root element's canvas may be focused as long as the document is in a
2809 // a non-chrome shell and does not contain a frameset.
2810 if (aIsForDocNavigation) {
2811 nsCOMPtr<nsIContent> docContent =
2812 do_QueryInterface(aWindow->GetFrameElementInternal());
2813 if (docContent && docContent->Tag() == nsGkAtoms::iframe)
2814 return nsnull;
2816 else {
2817 PRInt32 itemType;
2818 nsCOMPtr<nsIDocShellTreeItem> shellItem = do_QueryInterface(aWindow->GetDocShell());
2819 shellItem->GetItemType(&itemType);
2821 if (itemType == nsIDocShellTreeItem::typeChrome)
2822 return nsnull;
2825 if (aCheckVisibility && !IsWindowVisible(aWindow))
2826 return nsnull;
2828 Element *rootElement = aDocument->GetRootElement();
2829 if (rootElement) {
2830 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
2831 return nsnull;
2834 // Finally, check if this is a frameset
2835 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
2836 if (htmlDoc) {
2837 PRUint32 childCount = rootElement->GetChildCount();
2838 for (PRUint32 i = 0; i < childCount; ++i) {
2839 nsIContent *childContent = rootElement->GetChildAt(i);
2840 nsINodeInfo *ni = childContent->NodeInfo();
2841 if (childContent->IsHTML() &&
2842 ni->Equals(nsGkAtoms::frameset))
2843 return nsnull;
2848 return rootElement;
2851 void
2852 nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
2853 nsIDocShellTreeItem** aResult)
2855 *aResult = nsnull;
2857 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
2858 while (curItem) {
2859 PRInt32 childCount = 0;
2860 curItem->GetChildCount(&childCount);
2861 if (!childCount) {
2862 *aResult = curItem;
2863 NS_ADDREF(*aResult);
2864 return;
2868 curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
2872 void
2873 nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
2874 nsIDocShellTreeItem** aResult)
2876 *aResult = nsnull;
2878 PRInt32 childCount = 0;
2879 aItem->GetChildCount(&childCount);
2880 if (childCount) {
2881 aItem->GetChildAt(0, aResult);
2882 if (*aResult)
2883 return;
2886 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
2887 while (curItem) {
2888 nsCOMPtr<nsIDocShellTreeItem> parentItem;
2889 curItem->GetParent(getter_AddRefs(parentItem));
2890 if (!parentItem)
2891 return;
2893 // Note that we avoid using GetChildOffset() here because docshell
2894 // child offsets can't be trusted to be correct. bug 162283.
2895 nsCOMPtr<nsIDocShellTreeItem> iterItem;
2896 childCount = 0;
2897 parentItem->GetChildCount(&childCount);
2898 for (PRInt32 index = 0; index < childCount; ++index) {
2899 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
2900 if (iterItem == curItem) {
2901 ++index;
2902 if (index < childCount) {
2903 parentItem->GetChildAt(index, aResult);
2904 if (*aResult)
2905 return;
2907 break;
2911 curItem = parentItem;
2915 void
2916 nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
2917 nsIDocShellTreeItem** aResult)
2919 *aResult = nsnull;
2921 nsCOMPtr<nsIDocShellTreeItem> parentItem;
2922 aItem->GetParent(getter_AddRefs(parentItem));
2923 if (!parentItem)
2924 return;
2926 // Note that we avoid using GetChildOffset() here because docshell
2927 // child offsets can't be trusted to be correct. bug 162283.
2928 PRInt32 childCount = 0;
2929 parentItem->GetChildCount(&childCount);
2930 nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
2931 for (PRInt32 index = 0; index < childCount; ++index) {
2932 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
2933 if (iterItem == aItem)
2934 break;
2935 prevItem = iterItem;
2938 if (prevItem)
2939 GetLastDocShell(prevItem, aResult);
2940 else
2941 NS_ADDREF(*aResult = parentItem);
2944 nsIContent*
2945 nsFocusManager::GetNextTabbableDocument(PRBool aForward)
2947 nsCOMPtr<nsIDocShellTreeItem> startItem;
2948 if (mFocusedWindow) {
2949 startItem = do_QueryInterface(mFocusedWindow->GetDocShell());
2951 else {
2952 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(mActiveWindow);
2953 startItem = do_QueryInterface(webnav);
2955 if (!startItem)
2956 return nsnull;
2958 // perform a depth first search (preorder) of the docshell tree
2959 // looking for an HTML Frame or a chrome document
2960 nsIContent* content = nsnull;
2961 nsCOMPtr<nsIDocShellTreeItem> curItem = startItem;
2962 nsCOMPtr<nsIDocShellTreeItem> nextItem;
2963 do {
2964 if (aForward) {
2965 GetNextDocShell(curItem, getter_AddRefs(nextItem));
2966 if (!nextItem) {
2967 // wrap around to the beginning, which is the top of the tree
2968 startItem->GetRootTreeItem(getter_AddRefs(nextItem));
2971 else {
2972 GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
2973 if (!nextItem) {
2974 // wrap around to the end, which is the last item in the tree
2975 nsCOMPtr<nsIDocShellTreeItem> rootItem;
2976 startItem->GetRootTreeItem(getter_AddRefs(rootItem));
2977 GetLastDocShell(rootItem, getter_AddRefs(nextItem));
2981 curItem = nextItem;
2982 nsCOMPtr<nsPIDOMWindow> nextFrame = do_GetInterface(nextItem);
2983 if (!nextFrame)
2984 return nsnull;
2986 nsCOMPtr<nsIDocument> doc = do_QueryInterface(nextFrame->GetExtantDocument());
2987 if (doc && !doc->EventHandlingSuppressed()) {
2988 content = GetRootForFocus(nextFrame, doc, PR_TRUE, PR_TRUE);
2989 if (content && !GetRootForFocus(nextFrame, doc, PR_FALSE, PR_FALSE)) {
2990 // if the found content is in a chrome shell or a frameset, navigate
2991 // forward one tabbable item so that the first item is focused. Note
2992 // that we always go forward and not back here.
2993 nsCOMPtr<nsIContent> nextFocus;
2994 Element* rootElement = doc->GetRootElement();
2995 nsIPresShell* presShell = doc->GetShell();
2996 if (presShell) {
2997 nsresult rv = GetNextTabbableContent(presShell, rootElement,
2998 nsnull, rootElement,
2999 PR_TRUE, 1, PR_FALSE,
3000 getter_AddRefs(nextFocus));
3001 return NS_SUCCEEDED(rv) ? nextFocus.get() : nsnull;
3005 } while (!content);
3007 return content;
3010 void
3011 nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
3012 nsIContent* aStartSelection,
3013 nsIContent* aEndSelection,
3014 nsIContent** aFocusedContent)
3016 *aFocusedContent = nsnull;
3018 nsCOMPtr<nsIContent> testContent = aStartSelection;
3019 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
3021 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
3023 // We now have the correct start node in selectionContent!
3024 // Search for focusable elements, starting with selectionContent
3026 // Method #1: Keep going up while we look - an ancestor might be focusable
3027 // We could end the loop earlier, such as when we're no longer
3028 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
3029 // with a variable holding the starting selectionContent
3030 while (testContent) {
3031 // Keep testing while selectionContent is equal to something,
3032 // eventually we'll run out of ancestors
3034 nsCOMPtr<nsIURI> uri;
3035 if (testContent == currentFocus ||
3036 testContent->IsLink(getter_AddRefs(uri))) {
3037 NS_ADDREF(*aFocusedContent = testContent);
3038 return;
3041 // Get the parent
3042 testContent = testContent->GetParent();
3044 if (!testContent) {
3045 // We run this loop again, checking the ancestor chain of the selection's end point
3046 testContent = nextTestContent;
3047 nextTestContent = nsnull;
3051 // We couldn't find an anchor that was an ancestor of the selection start
3052 // Method #2: look for anchor in selection's primary range (depth first search)
3054 // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
3055 nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
3056 nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
3057 nsCOMPtr<nsIDOMNode> testNode;
3059 do {
3060 testContent = do_QueryInterface(selectionNode);
3062 // We're looking for any focusable link that could be part of the
3063 // main document's selection.
3064 nsCOMPtr<nsIURI> uri;
3065 if (testContent == currentFocus ||
3066 testContent->IsLink(getter_AddRefs(uri))) {
3067 NS_ADDREF(*aFocusedContent = testContent);
3068 return;
3071 selectionNode->GetFirstChild(getter_AddRefs(testNode));
3072 if (testNode) {
3073 selectionNode = testNode;
3074 continue;
3077 if (selectionNode == endSelectionNode)
3078 break;
3079 selectionNode->GetNextSibling(getter_AddRefs(testNode));
3080 if (testNode) {
3081 selectionNode = testNode;
3082 continue;
3085 do {
3086 selectionNode->GetParentNode(getter_AddRefs(testNode));
3087 if (!testNode || testNode == endSelectionNode) {
3088 selectionNode = nsnull;
3089 break;
3091 testNode->GetNextSibling(getter_AddRefs(selectionNode));
3092 if (selectionNode)
3093 break;
3094 selectionNode = testNode;
3095 } while (PR_TRUE);
3097 while (selectionNode && selectionNode != endSelectionNode);
3100 nsresult
3101 NS_NewFocusManager(nsIFocusManager** aResult)
3103 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
3104 return NS_OK;