Bumping manifests a=b2g-bump
[gecko.git] / dom / base / nsFocusManager.cpp
blob3307075ef93fbf6de3e7c36da14a14a017186131
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/TabParent.h"
8 #include "nsFocusManager.h"
10 #include "nsIInterfaceRequestorUtils.h"
11 #include "nsGkAtoms.h"
12 #include "nsContentUtils.h"
13 #include "nsIDocument.h"
14 #include "nsIDOMWindow.h"
15 #include "nsPIDOMWindow.h"
16 #include "nsIDOMElement.h"
17 #include "nsIDOMDocument.h"
18 #include "nsIDOMRange.h"
19 #include "nsIHTMLDocument.h"
20 #include "nsIDocShell.h"
21 #include "nsIDocShellTreeOwner.h"
22 #include "nsLayoutUtils.h"
23 #include "nsIPresShell.h"
24 #include "nsFrameTraversal.h"
25 #include "nsIWebNavigation.h"
26 #include "nsCaret.h"
27 #include "nsIBaseWindow.h"
28 #include "nsViewManager.h"
29 #include "nsFrameSelection.h"
30 #include "mozilla/dom/Selection.h"
31 #include "nsXULPopupManager.h"
32 #include "nsIScriptObjectPrincipal.h"
33 #include "nsIPrincipal.h"
34 #include "nsIObserverService.h"
35 #include "nsIObjectFrame.h"
36 #include "nsBindingManager.h"
37 #include "nsStyleCoord.h"
39 #include "mozilla/ContentEvents.h"
40 #include "mozilla/dom/Element.h"
41 #include "mozilla/EventDispatcher.h"
42 #include "mozilla/EventStateManager.h"
43 #include "mozilla/EventStates.h"
44 #include "mozilla/IMEStateManager.h"
45 #include "mozilla/LookAndFeel.h"
46 #include "mozilla/Preferences.h"
47 #include "mozilla/Services.h"
48 #include <algorithm>
50 #ifdef MOZ_XUL
51 #include "nsIDOMXULTextboxElement.h"
52 #include "nsIDOMXULMenuListElement.h"
53 #endif
55 #ifdef ACCESSIBILITY
56 #include "nsAccessibilityService.h"
57 #endif
59 #ifndef XP_MACOSX
60 #include "nsIScriptError.h"
61 #endif
63 using namespace mozilla;
64 using namespace mozilla::dom;
65 using namespace mozilla::widget;
67 #ifdef PR_LOGGING
69 // Two types of focus pr logging are available:
70 // 'Focus' for normal focus manager calls
71 // 'FocusNavigation' for tab and document navigation
72 PRLogModuleInfo* gFocusLog;
73 PRLogModuleInfo* gFocusNavigationLog;
75 #define LOGFOCUS(args) PR_LOG(gFocusLog, 4, args)
76 #define LOGFOCUSNAVIGATION(args) PR_LOG(gFocusNavigationLog, 4, args)
78 #define LOGTAG(log, format, content) \
79 { \
80 nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \
81 if (content) { \
82 content->Tag()->ToUTF8String(tag); \
83 } \
84 PR_LOG(log, 4, (format, tag.get())); \
87 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
88 #define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content)
90 #else
92 #define LOGFOCUS(args)
93 #define LOGFOCUSNAVIGATION(args)
94 #define LOGCONTENT(format, content)
95 #define LOGCONTENTNAVIGATION(format, content)
97 #endif
99 struct nsDelayedBlurOrFocusEvent
101 nsDelayedBlurOrFocusEvent(uint32_t aType,
102 nsIPresShell* aPresShell,
103 nsIDocument* aDocument,
104 EventTarget* aTarget)
105 : mType(aType),
106 mPresShell(aPresShell),
107 mDocument(aDocument),
108 mTarget(aTarget) { }
110 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
111 : mType(aOther.mType),
112 mPresShell(aOther.mPresShell),
113 mDocument(aOther.mDocument),
114 mTarget(aOther.mTarget) { }
116 uint32_t mType;
117 nsCOMPtr<nsIPresShell> mPresShell;
118 nsCOMPtr<nsIDocument> mDocument;
119 nsCOMPtr<EventTarget> mTarget;
122 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
123 NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
124 NS_INTERFACE_MAP_ENTRY(nsIObserver)
125 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
126 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
127 NS_INTERFACE_MAP_END
129 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
130 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
132 NS_IMPL_CYCLE_COLLECTION(nsFocusManager,
133 mActiveWindow,
134 mFocusedWindow,
135 mFocusedContent,
136 mFirstBlurEvent,
137 mFirstFocusEvent,
138 mWindowBeingLowered)
140 nsFocusManager* nsFocusManager::sInstance = nullptr;
141 bool nsFocusManager::sMouseFocusesFormControl = false;
142 bool nsFocusManager::sTestMode = false;
144 static const char* kObservedPrefs[] = {
145 "accessibility.browsewithcaret",
146 "accessibility.tabfocus_applies_to_xul",
147 "accessibility.mouse_focuses_formcontrol",
148 "focusmanager.testmode",
149 nullptr
152 nsFocusManager::nsFocusManager()
155 nsFocusManager::~nsFocusManager()
157 Preferences::RemoveObservers(this, kObservedPrefs);
159 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
160 if (obs) {
161 obs->RemoveObserver(this, "xpcom-shutdown");
165 // static
166 nsresult
167 nsFocusManager::Init()
169 nsFocusManager* fm = new nsFocusManager();
170 NS_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY);
171 NS_ADDREF(fm);
172 sInstance = fm;
174 #ifdef PR_LOGGING
175 gFocusLog = PR_NewLogModule("Focus");
176 gFocusNavigationLog = PR_NewLogModule("FocusNavigation");
177 #endif
179 nsIContent::sTabFocusModelAppliesToXUL =
180 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
181 nsIContent::sTabFocusModelAppliesToXUL);
183 sMouseFocusesFormControl =
184 Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
186 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
188 Preferences::AddWeakObservers(fm, kObservedPrefs);
190 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
191 if (obs) {
192 obs->AddObserver(fm, "xpcom-shutdown", true);
195 return NS_OK;
198 // static
199 void
200 nsFocusManager::Shutdown()
202 NS_IF_RELEASE(sInstance);
205 NS_IMETHODIMP
206 nsFocusManager::Observe(nsISupports *aSubject,
207 const char *aTopic,
208 const char16_t *aData)
210 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
211 nsDependentString data(aData);
212 if (data.EqualsLiteral("accessibility.browsewithcaret")) {
213 UpdateCaretForCaretBrowsingMode();
215 else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
216 nsIContent::sTabFocusModelAppliesToXUL =
217 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
218 nsIContent::sTabFocusModelAppliesToXUL);
220 else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
221 sMouseFocusesFormControl =
222 Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
223 false);
225 else if (data.EqualsLiteral("focusmanager.testmode")) {
226 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
228 } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
229 mActiveWindow = nullptr;
230 mFocusedWindow = nullptr;
231 mFocusedContent = nullptr;
232 mFirstBlurEvent = nullptr;
233 mFirstFocusEvent = nullptr;
234 mWindowBeingLowered = nullptr;
235 mDelayedBlurFocusEvents.Clear();
236 mMouseButtonEventHandlingDocument = nullptr;
239 return NS_OK;
242 // given a frame content node, retrieve the nsIDOMWindow displayed in it
243 static nsPIDOMWindow*
244 GetContentWindow(nsIContent* aContent)
246 nsIDocument* doc = aContent->GetComposedDoc();
247 if (doc) {
248 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
249 if (subdoc)
250 return subdoc->GetWindow();
253 return nullptr;
256 // get the current window for the given content node
257 static nsPIDOMWindow*
258 GetCurrentWindow(nsIContent* aContent)
260 nsIDocument* doc = aContent->GetComposedDoc();
261 return doc ? doc->GetWindow() : nullptr;
264 // static
265 nsIContent*
266 nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep,
267 nsPIDOMWindow** aFocusedWindow)
269 NS_ENSURE_TRUE(aWindow, nullptr);
271 *aFocusedWindow = nullptr;
273 nsIContent* currentContent = nullptr;
274 nsPIDOMWindow* window = aWindow->GetOuterWindow();
275 while (window) {
276 *aFocusedWindow = window;
277 currentContent = window->GetFocusedNode();
278 if (!currentContent || !aDeep)
279 break;
281 window = GetContentWindow(currentContent);
284 NS_IF_ADDREF(*aFocusedWindow);
286 return currentContent;
289 // static
290 nsIContent*
291 nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
293 #ifdef MOZ_XUL
294 if (aContent->IsXUL()) {
295 nsCOMPtr<nsIDOMNode> inputField;
297 nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
298 if (textbox) {
299 textbox->GetInputField(getter_AddRefs(inputField));
301 else {
302 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
303 if (menulist) {
304 menulist->GetInputField(getter_AddRefs(inputField));
306 else if (aContent->Tag() == nsGkAtoms::scale) {
307 nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
308 if (!doc)
309 return nullptr;
311 nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent);
312 if (children) {
313 nsIContent* child = children->Item(0);
314 if (child && child->Tag() == nsGkAtoms::slider)
315 return child;
320 if (inputField) {
321 nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
322 return retval;
325 #endif
327 return nullptr;
330 // static
331 InputContextAction::Cause
332 nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags)
334 if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
335 return InputContextAction::CAUSE_MOUSE;
336 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
337 return InputContextAction::CAUSE_KEY;
339 return InputContextAction::CAUSE_UNKNOWN;
342 NS_IMETHODIMP
343 nsFocusManager::GetActiveWindow(nsIDOMWindow** aWindow)
345 NS_IF_ADDREF(*aWindow = mActiveWindow);
346 return NS_OK;
349 NS_IMETHODIMP
350 nsFocusManager::SetActiveWindow(nsIDOMWindow* aWindow)
352 // only top-level windows can be made active
353 nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow);
354 if (piWindow)
355 piWindow = piWindow->GetOuterWindow();
357 NS_ENSURE_TRUE(piWindow && (piWindow == piWindow->GetPrivateRoot()),
358 NS_ERROR_INVALID_ARG);
360 RaiseWindow(piWindow);
361 return NS_OK;
364 NS_IMETHODIMP
365 nsFocusManager::GetFocusedWindow(nsIDOMWindow** aFocusedWindow)
367 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
368 return NS_OK;
371 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(nsIDOMWindow* aWindowToFocus)
373 LOGFOCUS(("<<SetFocusedWindow begin>>"));
375 nsCOMPtr<nsPIDOMWindow> windowToFocus(do_QueryInterface(aWindowToFocus));
376 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
378 windowToFocus = windowToFocus->GetOuterWindow();
380 nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
381 if (frameElement) {
382 // pass false for aFocusChanged so that the caret does not get updated
383 // and scrolling does not occur.
384 SetFocusInner(frameElement, 0, false, true);
386 else {
387 // this is a top-level window. If the window has a child frame focused,
388 // clear the focus. Otherwise, focus should already be in this frame, or
389 // already cleared. This ensures that focus will be in this frame and not
390 // in a child.
391 nsIContent* content = windowToFocus->GetFocusedNode();
392 if (content) {
393 nsCOMPtr<nsIDOMWindow> childWindow = GetContentWindow(content);
394 if (childWindow)
395 ClearFocus(windowToFocus);
399 nsCOMPtr<nsPIDOMWindow> rootWindow = windowToFocus->GetPrivateRoot();
400 if (rootWindow)
401 RaiseWindow(rootWindow);
403 LOGFOCUS(("<<SetFocusedWindow end>>"));
405 return NS_OK;
408 NS_IMETHODIMP
409 nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
411 if (mFocusedContent)
412 CallQueryInterface(mFocusedContent, aFocusedElement);
413 else
414 *aFocusedElement = nullptr;
415 return NS_OK;
418 NS_IMETHODIMP
419 nsFocusManager::GetLastFocusMethod(nsIDOMWindow* aWindow, uint32_t* aLastFocusMethod)
421 // the focus method is stored on the inner window
422 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
423 if (window && window->IsOuterWindow())
424 window = window->GetCurrentInnerWindow();
425 if (!window)
426 window = mFocusedWindow;
428 *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
430 NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
431 "invalid focus method");
432 return NS_OK;
435 NS_IMETHODIMP
436 nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags)
438 LOGFOCUS(("<<SetFocus begin>>"));
440 nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
441 NS_ENSURE_ARG(newFocus);
443 SetFocusInner(newFocus, aFlags, true, true);
445 LOGFOCUS(("<<SetFocus end>>"));
447 return NS_OK;
450 NS_IMETHODIMP
451 nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags,
452 bool* aIsFocusable)
454 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
456 nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement);
458 *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr;
460 return NS_OK;
463 NS_IMETHODIMP
464 nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
465 uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement)
467 *aElement = nullptr;
469 #ifdef PR_LOGGING
470 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
472 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG) && mFocusedWindow) {
473 nsIDocument* doc = mFocusedWindow->GetExtantDoc();
474 if (doc && doc->GetDocumentURI()) {
475 nsAutoCString spec;
476 doc->GetDocumentURI()->GetSpec(spec);
477 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), spec.get()));
481 LOGCONTENT(" Current Focus: %s", mFocusedContent.get());
482 #endif
484 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
485 // the other focus methods is already set, or we're just moving to the root
486 // or caret position.
487 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
488 (aFlags & FOCUSMETHOD_MASK) == 0) {
489 aFlags |= FLAG_BYMOVEFOCUS;
492 nsCOMPtr<nsPIDOMWindow> window;
493 nsCOMPtr<nsIContent> startContent;
494 if (aStartElement) {
495 startContent = do_QueryInterface(aStartElement);
496 NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
498 window = GetCurrentWindow(startContent);
500 else {
501 window = aWindow ? do_QueryInterface(aWindow) : mFocusedWindow;
502 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
503 window = window->GetOuterWindow();
506 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
508 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
509 nsCOMPtr<nsIContent> newFocus;
510 nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
511 getter_AddRefs(newFocus));
512 NS_ENSURE_SUCCESS(rv, rv);
514 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
516 if (newFocus) {
517 // for caret movement, pass false for the aFocusChanged argument,
518 // otherwise the caret will end up moving to the focus position. This
519 // would be a problem because the caret would move to the beginning of the
520 // focused link making it impossible to navigate the caret over a link.
521 SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET, true);
522 CallQueryInterface(newFocus, aElement);
524 else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
525 // no content was found, so clear the focus for these two types.
526 ClearFocus(window);
529 LOGFOCUS(("<<MoveFocus end>>"));
531 return NS_OK;
534 NS_IMETHODIMP
535 nsFocusManager::ClearFocus(nsIDOMWindow* aWindow)
537 LOGFOCUS(("<<ClearFocus begin>>"));
539 // if the window to clear is the focused window or an ancestor of the
540 // focused window, then blur the existing focused content. Otherwise, the
541 // focus is somewhere else so just update the current node.
542 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
543 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
545 window = window->GetOuterWindow();
546 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
548 if (IsSameOrAncestor(window, mFocusedWindow)) {
549 bool isAncestor = (window != mFocusedWindow);
550 if (Blur(window, nullptr, isAncestor, true)) {
551 // if we are clearing the focus on an ancestor of the focused window,
552 // the ancestor will become the new focused window, so focus it
553 if (isAncestor)
554 Focus(window, nullptr, 0, true, false, false, true);
557 else {
558 window->SetFocusedNode(nullptr);
561 LOGFOCUS(("<<ClearFocus end>>"));
563 return NS_OK;
566 NS_IMETHODIMP
567 nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow,
568 bool aDeep,
569 nsIDOMWindow** aFocusedWindow,
570 nsIDOMElement** aElement)
572 *aElement = nullptr;
573 if (aFocusedWindow)
574 *aFocusedWindow = nullptr;
576 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
577 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
579 window = window->GetOuterWindow();
580 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
582 nsCOMPtr<nsPIDOMWindow> focusedWindow;
583 nsCOMPtr<nsIContent> focusedContent =
584 GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
585 if (focusedContent)
586 CallQueryInterface(focusedContent, aElement);
588 if (aFocusedWindow)
589 NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
591 return NS_OK;
594 NS_IMETHODIMP
595 nsFocusManager::MoveCaretToFocus(nsIDOMWindow* aWindow)
597 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
598 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
599 if (dsti) {
600 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
601 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
602 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
604 // don't move the caret for editable documents
605 bool isEditable;
606 docShell->GetEditable(&isEditable);
607 if (isEditable)
608 return NS_OK;
610 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
611 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
613 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
614 nsCOMPtr<nsIContent> content = window->GetFocusedNode();
615 if (content)
616 MoveCaretToFocus(presShell, content);
620 return NS_OK;
623 NS_IMETHODIMP
624 nsFocusManager::WindowRaised(nsIDOMWindow* aWindow)
626 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
627 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
629 #ifdef PR_LOGGING
630 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
631 LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
632 nsAutoCString spec;
633 nsIDocument* doc = window->GetExtantDoc();
634 if (doc && doc->GetDocumentURI()) {
635 doc->GetDocumentURI()->GetSpec(spec);
636 LOGFOCUS((" Raised Window: %p %s", aWindow, spec.get()));
638 if (mActiveWindow) {
639 doc = mActiveWindow->GetExtantDoc();
640 if (doc && doc->GetDocumentURI()) {
641 doc->GetDocumentURI()->GetSpec(spec);
642 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), spec.get()));
646 #endif
648 if (mActiveWindow == window) {
649 // The window is already active, so there is no need to focus anything,
650 // but make sure that the right widget is focused. This is a special case
651 // for Windows because when restoring a minimized window, a second
652 // activation will occur and the top-level widget could be focused instead
653 // of the child we want. We solve this by calling SetFocus to ensure that
654 // what the focus manager thinks should be the current widget is actually
655 // focused.
656 EnsureCurrentWidgetFocused();
657 return NS_OK;
660 // lower the existing window, if any. This shouldn't happen usually.
661 if (mActiveWindow)
662 WindowLowered(mActiveWindow);
664 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
665 // If there's no docShellAsItem, this window must have been closed,
666 // in that case there is no tree owner.
667 NS_ENSURE_TRUE(docShellAsItem, NS_OK);
669 // set this as the active window
670 mActiveWindow = window;
672 // ensure that the window is enabled and visible
673 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
674 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
675 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
676 if (baseWindow) {
677 bool isEnabled = true;
678 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
679 return NS_ERROR_FAILURE;
682 if (!sTestMode) {
683 baseWindow->SetVisibility(true);
687 // inform the DOM window that it has activated, so that the active attribute
688 // is updated on the window
689 window->ActivateOrDeactivate(true);
691 // send activate event
692 nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(),
693 window,
694 NS_LITERAL_STRING("activate"),
695 true, true, nullptr);
697 // retrieve the last focused element within the window that was raised
698 nsCOMPtr<nsPIDOMWindow> currentWindow;
699 nsCOMPtr<nsIContent> currentFocus =
700 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
702 NS_ASSERTION(currentWindow, "window raised with no window current");
703 if (!currentWindow)
704 return NS_OK;
706 nsCOMPtr<nsIDocShell> currentDocShell = currentWindow->GetDocShell();
708 nsCOMPtr<nsIPresShell> presShell = currentDocShell->GetPresShell();
709 if (presShell) {
710 // disable selection mousedown state on activation
711 // XXXndeakin P3 not sure if this is necessary, but it doesn't hurt
712 nsRefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
713 frameSelection->SetDragState(false);
716 Focus(currentWindow, currentFocus, 0, true, false, true, true);
718 return NS_OK;
721 NS_IMETHODIMP
722 nsFocusManager::WindowLowered(nsIDOMWindow* aWindow)
724 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
725 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
727 #ifdef PR_LOGGING
728 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
729 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
730 nsAutoCString spec;
731 nsIDocument* doc = window->GetExtantDoc();
732 if (doc && doc->GetDocumentURI()) {
733 doc->GetDocumentURI()->GetSpec(spec);
734 LOGFOCUS((" Lowered Window: %s", spec.get()));
736 if (mActiveWindow) {
737 doc = mActiveWindow->GetExtantDoc();
738 if (doc && doc->GetDocumentURI()) {
739 doc->GetDocumentURI()->GetSpec(spec);
740 LOGFOCUS((" Active Window: %s", spec.get()));
744 #endif
746 if (mActiveWindow != window)
747 return NS_OK;
749 // clear the mouse capture as the active window has changed
750 nsIPresShell::SetCapturingContent(nullptr, 0);
752 // inform the DOM window that it has deactivated, so that the active
753 // attribute is updated on the window
754 window->ActivateOrDeactivate(false);
756 // send deactivate event
757 nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(),
758 window,
759 NS_LITERAL_STRING("deactivate"),
760 true, true, nullptr);
762 // keep track of the window being lowered, so that attempts to raise the
763 // window can be prevented until we return. Otherwise, focus can get into
764 // an unusual state.
765 mWindowBeingLowered = mActiveWindow;
766 mActiveWindow = nullptr;
768 if (mFocusedWindow)
769 Blur(nullptr, nullptr, true, true);
771 mWindowBeingLowered = nullptr;
773 return NS_OK;
776 nsresult
777 nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
779 NS_ENSURE_ARG(aDocument);
780 NS_ENSURE_ARG(aContent);
782 nsPIDOMWindow *window = aDocument->GetWindow();
783 if (!window)
784 return NS_OK;
786 // if the content is currently focused in the window, or is an ancestor
787 // of the currently focused element, reset the focus within that window.
788 nsIContent* content = window->GetFocusedNode();
789 if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
790 bool shouldShowFocusRing = window->ShouldShowFocusRing();
791 window->SetFocusedNode(nullptr);
793 // if this window is currently focused, clear the global focused
794 // element as well, but don't fire any events.
795 if (window == mFocusedWindow) {
796 mFocusedContent = nullptr;
798 else {
799 // Check if the node that was focused is an iframe or similar by looking
800 // if it has a subdocument. This would indicate that this focused iframe
801 // and its descendants will be going away. We will need to move the
802 // focus somewhere else, so just clear the focus in the toplevel window
803 // so that no element is focused.
804 nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
805 if (subdoc) {
806 nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
807 if (docShell) {
808 nsCOMPtr<nsPIDOMWindow> childWindow = docShell->GetWindow();
809 if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
810 ClearFocus(mActiveWindow);
816 NotifyFocusStateChange(content, shouldShowFocusRing, false);
819 return NS_OK;
822 NS_IMETHODIMP
823 nsFocusManager::WindowShown(nsIDOMWindow* aWindow, bool aNeedsFocus)
825 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
826 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
828 window = window->GetOuterWindow();
830 #ifdef PR_LOGGING
831 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
832 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
833 nsAutoCString spec;
834 nsIDocument* doc = window->GetExtantDoc();
835 if (doc && doc->GetDocumentURI()) {
836 doc->GetDocumentURI()->GetSpec(spec);
837 LOGFOCUS(("Shown Window: %s", spec.get()));
840 if (mFocusedWindow) {
841 doc = mFocusedWindow->GetExtantDoc();
842 if (doc && doc->GetDocumentURI()) {
843 doc->GetDocumentURI()->GetSpec(spec);
844 LOGFOCUS((" Focused Window: %s", spec.get()));
848 #endif
850 if (mFocusedWindow != window)
851 return NS_OK;
853 if (aNeedsFocus) {
854 nsCOMPtr<nsPIDOMWindow> currentWindow;
855 nsCOMPtr<nsIContent> currentFocus =
856 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
857 if (currentWindow)
858 Focus(currentWindow, currentFocus, 0, true, false, false, true);
860 else {
861 // Sometimes, an element in a window can be focused before the window is
862 // visible, which would mean that the widget may not be properly focused.
863 // When the window becomes visible, make sure the right widget is focused.
864 EnsureCurrentWidgetFocused();
867 return NS_OK;
870 NS_IMETHODIMP
871 nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
873 // if there is no window or it is not the same or an ancestor of the
874 // currently focused window, just return, as the current focus will not
875 // be affected.
877 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
878 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
880 window = window->GetOuterWindow();
882 #ifdef PR_LOGGING
883 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
884 LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
885 nsAutoCString spec;
886 nsIDocument* doc = window->GetExtantDoc();
887 if (doc && doc->GetDocumentURI()) {
888 doc->GetDocumentURI()->GetSpec(spec);
889 LOGFOCUS((" Hide Window: %s", spec.get()));
892 if (mFocusedWindow) {
893 doc = mFocusedWindow->GetExtantDoc();
894 if (doc && doc->GetDocumentURI()) {
895 doc->GetDocumentURI()->GetSpec(spec);
896 LOGFOCUS((" Focused Window: %s", spec.get()));
900 if (mActiveWindow) {
901 doc = mActiveWindow->GetExtantDoc();
902 if (doc && doc->GetDocumentURI()) {
903 doc->GetDocumentURI()->GetSpec(spec);
904 LOGFOCUS((" Active Window: %s", spec.get()));
908 #endif
910 if (!IsSameOrAncestor(window, mFocusedWindow))
911 return NS_OK;
913 // at this point, we know that the window being hidden is either the focused
914 // window, or an ancestor of the focused window. Either way, the focus is no
915 // longer valid, so it needs to be updated.
917 nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
919 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
920 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
922 if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) {
923 NotifyFocusStateChange(oldFocusedContent,
924 mFocusedWindow->ShouldShowFocusRing(),
925 false);
926 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
928 if (presShell) {
929 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
930 oldFocusedContent->GetComposedDoc(),
931 oldFocusedContent, 1, false);
935 nsPresContext* focusedPresContext =
936 presShell ? presShell->GetPresContext() : nullptr;
937 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
938 GetFocusMoveActionCause(0));
939 if (presShell) {
940 SetCaretVisible(presShell, false, nullptr);
943 // if the docshell being hidden is being destroyed, then we want to move
944 // focus somewhere else. Call ClearFocus on the toplevel window, which
945 // will have the effect of clearing the focus and moving the focused window
946 // to the toplevel window. But if the window isn't being destroyed, we are
947 // likely just loading a new document in it, so we want to maintain the
948 // focused window so that the new document gets properly focused.
949 bool beingDestroyed;
950 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
951 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
952 if (beingDestroyed) {
953 // There is usually no need to do anything if a toplevel window is going
954 // away, as we assume that WindowLowered will be called. However, this may
955 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
956 // a leak. So if the active window is being destroyed, call WindowLowered
957 // directly.
958 NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
959 if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
960 WindowLowered(mActiveWindow);
961 else
962 ClearFocus(mActiveWindow);
963 return NS_OK;
966 // if the window being hidden is an ancestor of the focused window, adjust
967 // the focused window so that it points to the one being hidden. This
968 // ensures that the focused window isn't in a chain of frames that doesn't
969 // exist any more.
970 if (window != mFocusedWindow) {
971 nsCOMPtr<nsIDocShellTreeItem> dsti =
972 mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
973 if (dsti) {
974 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
975 dsti->GetParent(getter_AddRefs(parentDsti));
976 if (parentDsti) {
977 nsCOMPtr<nsPIDOMWindow> parentWindow = parentDsti->GetWindow();
978 if (parentWindow)
979 parentWindow->SetFocusedNode(nullptr);
983 SetFocusedWindowInternal(window);
986 return NS_OK;
989 NS_IMETHODIMP
990 nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
992 NS_ENSURE_ARG(aDocument);
994 // fire any delayed focus and blur events in the same order that they were added
995 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
996 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
997 if (!aDocument->GetInnerWindow() ||
998 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
999 // If the document was navigated away from or is defunct, don't bother
1000 // firing events on it. Note the symmetry between this condition and
1001 // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
1002 mDelayedBlurFocusEvents.RemoveElementAt(i);
1003 --i;
1004 } else if (!aDocument->EventHandlingSuppressed()) {
1005 uint32_t type = mDelayedBlurFocusEvents[i].mType;
1006 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1007 nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1008 mDelayedBlurFocusEvents.RemoveElementAt(i);
1009 SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false);
1010 --i;
1015 return NS_OK;
1018 NS_IMETHODIMP
1019 nsFocusManager::FocusPlugin(nsIContent* aContent)
1021 NS_ENSURE_ARG(aContent);
1022 SetFocusInner(aContent, 0, true, false);
1023 return NS_OK;
1026 /* static */
1027 void
1028 nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
1029 bool aWindowShouldShowFocusRing,
1030 bool aGettingFocus)
1032 if (!aContent->IsElement()) {
1033 return;
1035 EventStates eventState = NS_EVENT_STATE_FOCUS;
1036 if (aWindowShouldShowFocusRing) {
1037 eventState |= NS_EVENT_STATE_FOCUSRING;
1039 if (aGettingFocus) {
1040 aContent->AsElement()->AddStates(eventState);
1041 } else {
1042 aContent->AsElement()->RemoveStates(eventState);
1046 // static
1047 void
1048 nsFocusManager::EnsureCurrentWidgetFocused()
1050 if (!mFocusedWindow || sTestMode)
1051 return;
1053 // get the main child widget for the focused window and ensure that the
1054 // platform knows that this widget is focused.
1055 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1056 if (docShell) {
1057 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1058 if (presShell) {
1059 nsViewManager* vm = presShell->GetViewManager();
1060 if (vm) {
1061 nsCOMPtr<nsIWidget> widget;
1062 vm->GetRootWidget(getter_AddRefs(widget));
1063 if (widget)
1064 widget->SetFocus(false);
1070 void
1071 nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
1072 bool aFocusChanged, bool aAdjustWidget)
1074 // if the element is not focusable, just return and leave the focus as is
1075 nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
1076 if (!contentToFocus)
1077 return;
1079 // check if the element to focus is a frame (iframe) containing a child
1080 // document. Frames are never directly focused; instead focusing a frame
1081 // means focus what is inside the frame. To do this, the descendant content
1082 // within the frame is retrieved and that will be focused instead.
1083 nsCOMPtr<nsPIDOMWindow> newWindow;
1084 nsCOMPtr<nsPIDOMWindow> subWindow = GetContentWindow(contentToFocus);
1085 if (subWindow) {
1086 contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow));
1087 // since a window is being refocused, clear aFocusChanged so that the
1088 // caret position isn't updated.
1089 aFocusChanged = false;
1092 // unless it was set above, retrieve the window for the element to focus
1093 if (!newWindow)
1094 newWindow = GetCurrentWindow(contentToFocus);
1096 // if the element is already focused, just return. Note that this happens
1097 // after the frame check above so that we compare the element that will be
1098 // focused rather than the frame it is in.
1099 if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
1100 return;
1102 // don't allow focus to be placed in docshells or descendants of docshells
1103 // that are being destroyed. Also, ensure that the page hasn't been
1104 // unloaded. The prevents content from being refocused during an unload event.
1105 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1106 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1107 while (docShell) {
1108 bool inUnload;
1109 docShell->GetIsInUnload(&inUnload);
1110 if (inUnload)
1111 return;
1113 bool beingDestroyed;
1114 docShell->IsBeingDestroyed(&beingDestroyed);
1115 if (beingDestroyed)
1116 return;
1118 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1119 docShell->GetParent(getter_AddRefs(parentDsti));
1120 docShell = do_QueryInterface(parentDsti);
1123 // if the new element is in the same window as the currently focused element
1124 bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1126 if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1127 nsContentUtils::IsHandlingKeyBoardEvent()) {
1128 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1129 do_QueryInterface(mFocusedWindow);
1130 nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1131 do_QueryInterface(newWindow);
1132 nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1133 nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1134 if (!focusedPrincipal || !newPrincipal) {
1135 return;
1137 bool subsumes = false;
1138 focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1139 if (!subsumes && !nsContentUtils::IsCallerChrome()) {
1140 NS_WARNING("Not allowed to focus the new window!");
1141 return;
1145 // to check if the new element is in the active window, compare the
1146 // new root docshell for the new element with the active window's docshell.
1147 bool isElementInActiveWindow = false;
1149 nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
1150 nsCOMPtr<nsPIDOMWindow> newRootWindow;
1151 if (dsti) {
1152 nsCOMPtr<nsIDocShellTreeItem> root;
1153 dsti->GetRootTreeItem(getter_AddRefs(root));
1154 newRootWindow = root ? root->GetWindow() : nullptr;
1156 isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1159 // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1160 // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1161 // so we can't display the "Press ESC to leave fullscreen mode" warning on
1162 // key input if a windowed plugin is focused, so just exit fullscreen
1163 // to guard against phishing.
1164 #ifndef XP_MACOSX
1165 nsIDocument* fullscreenAncestor;
1166 if (contentToFocus &&
1167 (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) &&
1168 nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
1169 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1170 NS_LITERAL_CSTRING("DOM"),
1171 contentToFocus->OwnerDoc(),
1172 nsContentUtils::eDOM_PROPERTIES,
1173 "FocusedWindowedPluginWhileFullScreen");
1174 nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true);
1176 #endif
1178 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1179 // shifted away from the current element if the new shell to focus is
1180 // the same or an ancestor shell of the currently focused shell.
1181 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1182 IsSameOrAncestor(newWindow, mFocusedWindow);
1184 // if the element is in the active window, frame switching is allowed and
1185 // the content is in a visible window, fire blur and focus events.
1186 bool sendFocusEvent =
1187 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1189 // When the following conditions are true:
1190 // * an element has focus
1191 // * isn't called by trusted event (i.e., called by untrusted event or by js)
1192 // * the focus is moved to another document's element
1193 // we need to check the permission.
1194 if (sendFocusEvent && mFocusedContent &&
1195 mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) {
1196 // If the caller cannot access the current focused node, the caller should
1197 // not be able to steal focus from it. E.g., When the current focused node
1198 // is in chrome, any web contents should not be able to steal the focus.
1199 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
1200 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1201 if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
1202 // However, while mouse button event is handling, the handling document's
1203 // script should be able to steal focus.
1204 domNode = do_QueryInterface(mMouseButtonEventHandlingDocument);
1205 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1209 LOGCONTENT("Shift Focus: %s", contentToFocus.get());
1210 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1211 aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()));
1212 LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
1213 isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
1215 if (sendFocusEvent) {
1216 // return if blurring fails or the focus changes during the blur
1217 if (mFocusedWindow) {
1218 // if the focus is being moved to another element in the same document,
1219 // or to a descendant, pass the existing window to Blur so that the
1220 // current node in the existing window is cleared. If moving to a
1221 // window elsewhere, we want to maintain the current node in the
1222 // window but still blur it.
1223 bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
1224 // find the common ancestor of the currently focused window and the new
1225 // window. The ancestor will need to have its currently focused node
1226 // cleared once the document has been blurred. Otherwise, we'll be in a
1227 // state where a document is blurred yet the chain of windows above it
1228 // still points to that document.
1229 // For instance, in the following frame tree:
1230 // A
1231 // B C
1232 // D
1233 // D is focused and we want to focus C. Once D has been blurred, we need
1234 // to clear out the focus in A, otherwise A would still maintain that B
1235 // was focused, and B that D was focused.
1236 nsCOMPtr<nsPIDOMWindow> commonAncestor;
1237 if (!isElementInFocusedWindow)
1238 commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1240 if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
1241 commonAncestor, !isElementInFocusedWindow, aAdjustWidget))
1242 return;
1245 Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
1246 aFocusChanged, false, aAdjustWidget);
1248 else {
1249 // otherwise, for inactive windows and when the caller cannot steal the
1250 // focus, update the node in the window, and raise the window if desired.
1251 if (allowFrameSwitch)
1252 AdjustWindowFocus(newWindow, true);
1254 // set the focus node and method as needed
1255 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1256 newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1257 newWindow->SetFocusedNode(contentToFocus, focusMethod);
1258 if (aFocusChanged) {
1259 nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1261 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1262 if (presShell)
1263 ScrollIntoView(presShell, contentToFocus, aFlags);
1266 // update the commands even when inactive so that the attributes for that
1267 // window are up to date.
1268 if (allowFrameSwitch)
1269 newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1271 if (aFlags & FLAG_RAISE)
1272 RaiseWindow(newRootWindow);
1276 bool
1277 nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor,
1278 nsPIDOMWindow* aWindow)
1280 if (!aWindow || !aPossibleAncestor) {
1281 return false;
1284 nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
1285 nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
1286 while (dsti) {
1287 if (dsti == ancestordsti)
1288 return true;
1289 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1290 dsti->GetParent(getter_AddRefs(parentDsti));
1291 dsti.swap(parentDsti);
1294 return false;
1297 already_AddRefed<nsPIDOMWindow>
1298 nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1,
1299 nsPIDOMWindow* aWindow2)
1301 NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
1303 nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
1304 NS_ENSURE_TRUE(dsti1, nullptr);
1306 nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
1307 NS_ENSURE_TRUE(dsti2, nullptr);
1309 nsAutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1310 do {
1311 parents1.AppendElement(dsti1);
1312 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1313 dsti1->GetParent(getter_AddRefs(parentDsti1));
1314 dsti1.swap(parentDsti1);
1315 } while (dsti1);
1316 do {
1317 parents2.AppendElement(dsti2);
1318 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1319 dsti2->GetParent(getter_AddRefs(parentDsti2));
1320 dsti2.swap(parentDsti2);
1321 } while (dsti2);
1323 uint32_t pos1 = parents1.Length();
1324 uint32_t pos2 = parents2.Length();
1325 nsIDocShellTreeItem* parent = nullptr;
1326 uint32_t len;
1327 for (len = std::min(pos1, pos2); len > 0; --len) {
1328 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1329 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1330 if (child1 != child2) {
1331 break;
1333 parent = child1;
1336 nsCOMPtr<nsPIDOMWindow> window = parent ? parent->GetWindow() : nullptr;
1337 return window.forget();
1340 void
1341 nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
1342 bool aCheckPermission)
1344 bool isVisible = IsWindowVisible(aWindow);
1346 nsCOMPtr<nsPIDOMWindow> window(aWindow);
1347 while (window) {
1348 // get the containing <iframe> or equivalent element so that it can be
1349 // focused below.
1350 nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
1352 nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
1353 if (!dsti)
1354 return;
1355 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1356 dsti->GetParent(getter_AddRefs(parentDsti));
1357 if (!parentDsti) {
1358 return;
1361 window = parentDsti->GetWindow();
1362 if (window) {
1363 // if the parent window is visible but aWindow was not, then we have
1364 // likely moved up and out from a hidden tab to the browser window, or a
1365 // similar such arrangement. Stop adjusting the current nodes.
1366 if (IsWindowVisible(window) != isVisible)
1367 break;
1369 // When aCheckPermission is true, we should check whether the caller can
1370 // access the window or not. If it cannot access, we should stop the
1371 // adjusting.
1372 if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
1373 break;
1375 window->SetFocusedNode(frameElement);
1380 bool
1381 nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
1383 if (!aWindow || aWindow->IsFrozen())
1384 return false;
1386 // Check if the inner window is frozen as well. This can happen when a focus change
1387 // occurs while restoring a previous page.
1388 nsPIDOMWindow* innerWindow = aWindow->GetCurrentInnerWindow();
1389 if (!innerWindow || innerWindow->IsFrozen())
1390 return false;
1392 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1393 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1394 if (!baseWin)
1395 return false;
1397 bool visible = false;
1398 baseWin->GetVisibility(&visible);
1399 return visible;
1402 bool
1403 nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
1405 NS_PRECONDITION(aContent, "aContent must not be NULL");
1406 NS_PRECONDITION(aContent->IsInComposedDoc(), "aContent must be in a document");
1408 // If aContent is in designMode, the root element is not focusable.
1409 // NOTE: in designMode, most elements are not focusable, just the document is
1410 // focusable.
1411 // Also, if aContent is not editable but it isn't in designMode, it's not
1412 // focusable.
1413 // And in userfocusignored context nothing is focusable.
1414 nsIDocument* doc = aContent->GetComposedDoc();
1415 NS_ASSERTION(doc, "aContent must have current document");
1416 return aContent == doc->GetRootElement() &&
1417 (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
1418 nsContentUtils::IsUserFocusIgnored(aContent));
1421 nsIContent*
1422 nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
1424 if (!aContent)
1425 return nullptr;
1427 // this is a special case for some XUL elements where an anonymous child is
1428 // actually focusable and not the element itself.
1429 nsIContent* redirectedFocus = GetRedirectedFocus(aContent);
1430 if (redirectedFocus)
1431 return CheckIfFocusable(redirectedFocus, aFlags);
1433 nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
1434 // can't focus elements that are not in documents
1435 if (!doc) {
1436 LOGCONTENT("Cannot focus %s because content not in document", aContent)
1437 return nullptr;
1440 // Make sure that our frames are up to date
1441 doc->FlushPendingNotifications(Flush_Layout);
1443 nsIPresShell *shell = doc->GetShell();
1444 if (!shell)
1445 return nullptr;
1447 // the root content can always be focused,
1448 // except in userfocusignored context.
1449 if (aContent == doc->GetRootElement())
1450 return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
1452 // cannot focus content in print preview mode. Only the root can be focused.
1453 nsPresContext* presContext = shell->GetPresContext();
1454 if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
1455 LOGCONTENT("Cannot focus %s while in print preview", aContent)
1456 return nullptr;
1459 nsIFrame* frame = aContent->GetPrimaryFrame();
1460 if (!frame) {
1461 LOGCONTENT("Cannot focus %s as it has no frame", aContent)
1462 return nullptr;
1465 if (aContent->Tag() == nsGkAtoms::area && aContent->IsHTML()) {
1466 // HTML areas do not have their own frame, and the img frame we get from
1467 // GetPrimaryFrame() is not relevant as to whether it is focusable or
1468 // not, so we have to do all the relevant checks manually for them.
1469 return frame->IsVisibleConsideringAncestors() &&
1470 aContent->IsFocusable() ? aContent : nullptr;
1473 // if this is a child frame content node, check if it is visible and
1474 // call the content node's IsFocusable method instead of the frame's
1475 // IsFocusable method. This skips checking the style system and ensures that
1476 // offscreen browsers can still be focused.
1477 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
1478 if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1479 const nsStyleUserInterface* ui = frame->StyleUserInterface();
1480 int32_t tabIndex = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE ||
1481 ui->mUserFocus == NS_STYLE_USER_FOCUS_NONE) ? -1 : 0;
1482 return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1485 return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1488 bool
1489 nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
1490 nsPIDOMWindow* aAncestorWindowToFocus,
1491 bool aIsLeavingDocument,
1492 bool aAdjustWidgets)
1494 LOGFOCUS(("<<Blur begin>>"));
1496 // hold a reference to the focused content, which may be null
1497 nsCOMPtr<nsIContent> content = mFocusedContent;
1498 if (content) {
1499 if (!content->IsInComposedDoc()) {
1500 mFocusedContent = nullptr;
1501 return true;
1503 if (content == mFirstBlurEvent)
1504 return true;
1507 // hold a reference to the focused window
1508 nsCOMPtr<nsPIDOMWindow> window = mFocusedWindow;
1509 if (!window) {
1510 mFocusedContent = nullptr;
1511 return true;
1514 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1515 if (!docShell) {
1516 mFocusedContent = nullptr;
1517 return true;
1520 // Keep a ref to presShell since dispatching the DOM event may cause
1521 // the document to be destroyed.
1522 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1523 if (!presShell) {
1524 mFocusedContent = nullptr;
1525 return true;
1528 bool clearFirstBlurEvent = false;
1529 if (!mFirstBlurEvent) {
1530 mFirstBlurEvent = content;
1531 clearFirstBlurEvent = true;
1534 nsPresContext* focusedPresContext =
1535 mActiveWindow ? presShell->GetPresContext() : nullptr;
1536 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1537 GetFocusMoveActionCause(0));
1539 // now adjust the actual focus, by clearing the fields in the focus manager
1540 // and in the window.
1541 mFocusedContent = nullptr;
1542 bool shouldShowFocusRing = window->ShouldShowFocusRing();
1543 if (aWindowToClear)
1544 aWindowToClear->SetFocusedNode(nullptr);
1546 LOGCONTENT("Element %s has been blurred", content.get());
1548 // Don't fire blur event on the root content which isn't editable.
1549 bool sendBlurEvent =
1550 content && content->IsInComposedDoc() && !IsNonFocusableRoot(content);
1551 if (content) {
1552 if (sendBlurEvent) {
1553 NotifyFocusStateChange(content, shouldShowFocusRing, false);
1556 // if an object/plug-in/remote browser is being blurred, move the system focus
1557 // to the parent window, otherwise events will still get fired at the plugin.
1558 // But don't do this if we are blurring due to the window being lowered,
1559 // otherwise, the parent window can get raised again.
1560 if (mActiveWindow) {
1561 nsIFrame* contentFrame = content->GetPrimaryFrame();
1562 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1563 if (aAdjustWidgets && objectFrame && !sTestMode) {
1564 // note that the presshell's widget is being retrieved here, not the one
1565 // for the object frame.
1566 nsViewManager* vm = presShell->GetViewManager();
1567 if (vm) {
1568 nsCOMPtr<nsIWidget> widget;
1569 vm->GetRootWidget(getter_AddRefs(widget));
1570 if (widget)
1571 widget->SetFocus(false);
1575 // if the object being blurred is a remote browser, deactivate remote content
1576 if (TabParent* remote = TabParent::GetFrom(content)) {
1577 remote->Deactivate();
1578 LOGFOCUS(("Remote browser deactivated"));
1583 bool result = true;
1584 if (sendBlurEvent) {
1585 // if there is an active window, update commands. If there isn't an active
1586 // window, then this was a blur caused by the active window being lowered,
1587 // so there is no need to update the commands
1588 if (mActiveWindow)
1589 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1591 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
1592 content->GetComposedDoc(), content, 1, false);
1595 // if we are leaving the document or the window was lowered, make the caret
1596 // invisible.
1597 if (aIsLeavingDocument || !mActiveWindow)
1598 SetCaretVisible(presShell, false, nullptr);
1600 // at this point, it is expected that this window will be still be
1601 // focused, but the focused content will be null, as it was cleared before
1602 // the event. If this isn't the case, then something else was focused during
1603 // the blur event above and we should just return. However, if
1604 // aIsLeavingDocument is set, a new document is desired, so make sure to
1605 // blur the document and window.
1606 if (mFocusedWindow != window ||
1607 (mFocusedContent != nullptr && !aIsLeavingDocument)) {
1608 result = false;
1610 else if (aIsLeavingDocument) {
1611 window->TakeFocus(false, 0);
1613 // clear the focus so that the ancestor frame hierarchy is in the correct
1614 // state. Pass true because aAncestorWindowToFocus is thought to be
1615 // focused at this point.
1616 if (aAncestorWindowToFocus)
1617 aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
1619 SetFocusedWindowInternal(nullptr);
1620 mFocusedContent = nullptr;
1622 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1623 // that the check is made for suppressed documents. Check to ensure that
1624 // the document isn't null in case someone closed it during the blur above
1625 nsIDocument* doc = window->GetExtantDoc();
1626 if (doc)
1627 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, doc, 1, false);
1628 if (mFocusedWindow == nullptr)
1629 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, window, 1, false);
1631 // check if a different window was focused
1632 result = (mFocusedWindow == nullptr && mActiveWindow);
1634 else if (mActiveWindow) {
1635 // Otherwise, the blur of the element without blurring the document
1636 // occurred normally. Call UpdateCaret to redisplay the caret at the right
1637 // location within the document. This is needed to ensure that the caret
1638 // used for caret browsing is made visible again when an input field is
1639 // blurred.
1640 UpdateCaret(false, true, nullptr);
1643 if (clearFirstBlurEvent)
1644 mFirstBlurEvent = nullptr;
1646 return result;
1649 void
1650 nsFocusManager::Focus(nsPIDOMWindow* aWindow,
1651 nsIContent* aContent,
1652 uint32_t aFlags,
1653 bool aIsNewDocument,
1654 bool aFocusChanged,
1655 bool aWindowRaised,
1656 bool aAdjustWidgets)
1658 LOGFOCUS(("<<Focus begin>>"));
1660 if (!aWindow)
1661 return;
1663 if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
1664 return;
1666 // Keep a reference to the presShell since dispatching the DOM event may
1667 // cause the document to be destroyed.
1668 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1669 if (!docShell)
1670 return;
1672 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1673 if (!presShell)
1674 return;
1676 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1677 // Otherwise, just get the current focus method and use that. This ensures
1678 // that the method is set during the document and window focus events.
1679 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1680 aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1682 if (!IsWindowVisible(aWindow)) {
1683 // if the window isn't visible, for instance because it is a hidden tab,
1684 // update the current focus and scroll it into view but don't do anything else
1685 if (CheckIfFocusable(aContent, aFlags)) {
1686 aWindow->SetFocusedNode(aContent, focusMethod);
1687 if (aFocusChanged)
1688 ScrollIntoView(presShell, aContent, aFlags);
1690 return;
1693 bool clearFirstFocusEvent = false;
1694 if (!mFirstFocusEvent) {
1695 mFirstFocusEvent = aContent;
1696 clearFirstFocusEvent = true;
1699 #ifdef PR_LOGGING
1700 LOGCONTENT("Element %s has been focused", aContent);
1702 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
1703 nsIDocument* docm = aWindow->GetExtantDoc();
1704 if (docm) {
1705 LOGCONTENT(" from %s", docm->GetRootElement());
1707 LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
1708 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
1710 #endif
1712 if (aIsNewDocument) {
1713 // if this is a new document, update the parent chain of frames so that
1714 // focus can be traversed from the top level down to the newly focused
1715 // window.
1716 AdjustWindowFocus(aWindow, false);
1718 // Update the window touch registration to reflect the state of
1719 // the new document that got focus
1720 aWindow->UpdateTouchState();
1723 // indicate that the window has taken focus.
1724 if (aWindow->TakeFocus(true, focusMethod))
1725 aIsNewDocument = true;
1727 SetFocusedWindowInternal(aWindow);
1729 // Update the system focus by focusing the root widget. But avoid this
1730 // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
1731 // own widget and is either already focused or is about to be focused.
1732 nsCOMPtr<nsIWidget> objectFrameWidget;
1733 if (aContent) {
1734 nsIFrame* contentFrame = aContent->GetPrimaryFrame();
1735 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1736 if (objectFrame)
1737 objectFrameWidget = objectFrame->GetWidget();
1739 if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
1740 nsViewManager* vm = presShell->GetViewManager();
1741 if (vm) {
1742 nsCOMPtr<nsIWidget> widget;
1743 vm->GetRootWidget(getter_AddRefs(widget));
1744 if (widget)
1745 widget->SetFocus(false);
1749 // if switching to a new document, first fire the focus event on the
1750 // document and then the window.
1751 if (aIsNewDocument) {
1752 nsIDocument* doc = aWindow->GetExtantDoc();
1753 // The focus change should be notified to IMEStateManager from here if
1754 // the focused content is a designMode editor since any content won't
1755 // receive focus event.
1756 if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
1757 IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
1758 GetFocusMoveActionCause(aFlags));
1760 if (doc)
1761 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1762 doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1763 if (mFocusedWindow == aWindow && mFocusedContent == nullptr)
1764 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1765 aWindow, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1768 // check to ensure that the element is still focusable, and that nothing
1769 // else was focused during the events above.
1770 if (CheckIfFocusable(aContent, aFlags) &&
1771 mFocusedWindow == aWindow && mFocusedContent == nullptr) {
1772 mFocusedContent = aContent;
1774 nsIContent* focusedNode = aWindow->GetFocusedNode();
1775 bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
1777 aWindow->SetFocusedNode(aContent, focusMethod);
1779 bool sendFocusEvent =
1780 aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
1781 nsPresContext* presContext = presShell->GetPresContext();
1782 if (sendFocusEvent) {
1783 // if the focused element changed, scroll it into view
1784 if (aFocusChanged)
1785 ScrollIntoView(presShell, aContent, aFlags);
1787 NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
1789 // if this is an object/plug-in/remote browser, focus its widget. Note that we might
1790 // no longer be in the same document, due to the events we fired above when
1791 // aIsNewDocument.
1792 if (presShell->GetDocument() == aContent->GetComposedDoc()) {
1793 if (aAdjustWidgets && objectFrameWidget && !sTestMode)
1794 objectFrameWidget->SetFocus(false);
1796 // if the object being focused is a remote browser, activate remote content
1797 if (TabParent* remote = TabParent::GetFrom(aContent)) {
1798 remote->Activate();
1799 LOGFOCUS(("Remote browser activated"));
1803 IMEStateManager::OnChangeFocus(presContext, aContent,
1804 GetFocusMoveActionCause(aFlags));
1806 // as long as this focus wasn't because a window was raised, update the
1807 // commands
1808 // XXXndeakin P2 someone could adjust the focus during the update
1809 if (!aWindowRaised)
1810 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1812 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell,
1813 aContent->GetComposedDoc(),
1814 aContent, aFlags & FOCUSMETHOD_MASK,
1815 aWindowRaised, isRefocus);
1816 } else {
1817 IMEStateManager::OnChangeFocus(presContext, nullptr,
1818 GetFocusMoveActionCause(aFlags));
1819 if (!aWindowRaised) {
1820 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1824 else {
1825 // If the window focus event (fired above when aIsNewDocument) caused
1826 // the plugin not to be focusable, update the system focus by focusing
1827 // the root widget.
1828 if (aAdjustWidgets && objectFrameWidget &&
1829 mFocusedWindow == aWindow && mFocusedContent == nullptr &&
1830 !sTestMode) {
1831 nsViewManager* vm = presShell->GetViewManager();
1832 if (vm) {
1833 nsCOMPtr<nsIWidget> widget;
1834 vm->GetRootWidget(getter_AddRefs(widget));
1835 if (widget)
1836 widget->SetFocus(false);
1840 nsPresContext* presContext = presShell->GetPresContext();
1841 IMEStateManager::OnChangeFocus(presContext, nullptr,
1842 GetFocusMoveActionCause(aFlags));
1844 if (!aWindowRaised)
1845 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1848 // update the caret visibility and position to match the newly focused
1849 // element. However, don't update the position if this was a focus due to a
1850 // mouse click as the selection code would already have moved the caret as
1851 // needed. If this is a different document than was focused before, also
1852 // update the caret's visibility. If this is the same document, the caret
1853 // visibility should be the same as before so there is no need to update it.
1854 if (mFocusedContent == aContent)
1855 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
1856 mFocusedContent);
1858 if (clearFirstFocusEvent)
1859 mFirstFocusEvent = nullptr;
1862 class FocusBlurEvent : public nsRunnable
1864 public:
1865 FocusBlurEvent(nsISupports* aTarget, uint32_t aType,
1866 nsPresContext* aContext, bool aWindowRaised,
1867 bool aIsRefocus)
1868 : mTarget(aTarget), mType(aType), mContext(aContext),
1869 mWindowRaised(aWindowRaised), mIsRefocus(aIsRefocus) {}
1871 NS_IMETHOD Run()
1873 InternalFocusEvent event(true, mType);
1874 event.mFlags.mBubbles = false;
1875 event.fromRaise = mWindowRaised;
1876 event.isRefocus = mIsRefocus;
1877 return EventDispatcher::Dispatch(mTarget, mContext, &event);
1880 nsCOMPtr<nsISupports> mTarget;
1881 uint32_t mType;
1882 nsRefPtr<nsPresContext> mContext;
1883 bool mWindowRaised;
1884 bool mIsRefocus;
1887 void
1888 nsFocusManager::SendFocusOrBlurEvent(uint32_t aType,
1889 nsIPresShell* aPresShell,
1890 nsIDocument* aDocument,
1891 nsISupports* aTarget,
1892 uint32_t aFocusMethod,
1893 bool aWindowRaised,
1894 bool aIsRefocus)
1896 NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
1897 "Wrong event type for SendFocusOrBlurEvent");
1899 nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
1901 nsCOMPtr<nsINode> n = do_QueryInterface(aTarget);
1902 if (!n) {
1903 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aTarget);
1904 n = win ? win->GetExtantDoc() : nullptr;
1906 bool dontDispatchEvent = n && nsContentUtils::IsUserFocusIgnored(n);
1908 // for focus events, if this event was from a mouse or key and event
1909 // handling on the document is suppressed, queue the event and fire it
1910 // later. For blur events, a non-zero value would be set for aFocusMethod.
1911 if (aFocusMethod && !dontDispatchEvent &&
1912 aDocument && aDocument->EventHandlingSuppressed()) {
1913 // aFlags is always 0 when aWindowRaised is true so this won't be called
1914 // on a window raise.
1915 NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
1917 for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
1918 // if this event was already queued, remove it and append it to the end
1919 if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
1920 mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
1921 mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
1922 mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget) {
1923 mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
1927 mDelayedBlurFocusEvents.AppendElement(
1928 nsDelayedBlurOrFocusEvent(aType, aPresShell, aDocument, eventTarget));
1929 return;
1932 #ifdef ACCESSIBILITY
1933 nsAccessibilityService* accService = GetAccService();
1934 if (accService) {
1935 if (aType == NS_FOCUS_CONTENT)
1936 accService->NotifyOfDOMFocus(aTarget);
1937 else
1938 accService->NotifyOfDOMBlur(aTarget);
1940 #endif
1942 if (!dontDispatchEvent) {
1943 nsContentUtils::AddScriptRunner(
1944 new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
1945 aWindowRaised, aIsRefocus));
1949 void
1950 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
1951 nsIContent* aContent,
1952 uint32_t aFlags)
1954 // if the noscroll flag isn't set, scroll the newly focused element into view
1955 if (!(aFlags & FLAG_NOSCROLL))
1956 aPresShell->ScrollContentIntoView(aContent,
1957 nsIPresShell::ScrollAxis(
1958 nsIPresShell::SCROLL_MINIMUM,
1959 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
1960 nsIPresShell::ScrollAxis(
1961 nsIPresShell::SCROLL_MINIMUM,
1962 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
1963 nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
1967 void
1968 nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
1970 // don't raise windows that are already raised or are in the process of
1971 // being lowered
1972 if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
1973 return;
1975 if (sTestMode) {
1976 // In test mode, emulate the existing window being lowered and the new
1977 // window being raised.
1978 if (mActiveWindow)
1979 WindowLowered(mActiveWindow);
1980 WindowRaised(aWindow);
1981 return;
1984 #if defined(XP_WIN)
1985 // Windows would rather we focus the child widget, otherwise, the toplevel
1986 // widget will always end up being focused. Fortunately, focusing the child
1987 // widget will also have the effect of raising the window this widget is in.
1988 // But on other platforms, we can just focus the toplevel widget to raise
1989 // the window.
1990 nsCOMPtr<nsPIDOMWindow> childWindow;
1991 GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
1992 if (!childWindow)
1993 childWindow = aWindow;
1995 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1996 if (!docShell)
1997 return;
1999 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
2000 if (!presShell)
2001 return;
2003 nsViewManager* vm = presShell->GetViewManager();
2004 if (vm) {
2005 nsCOMPtr<nsIWidget> widget;
2006 vm->GetRootWidget(getter_AddRefs(widget));
2007 if (widget)
2008 widget->SetFocus(true);
2010 #else
2011 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
2012 do_QueryInterface(aWindow->GetDocShell());
2013 if (treeOwnerAsWin) {
2014 nsCOMPtr<nsIWidget> widget;
2015 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
2016 if (widget)
2017 widget->SetFocus(true);
2019 #endif
2022 void
2023 nsFocusManager::UpdateCaretForCaretBrowsingMode()
2025 UpdateCaret(false, true, mFocusedContent);
2028 void
2029 nsFocusManager::UpdateCaret(bool aMoveCaretToFocus,
2030 bool aUpdateVisibility,
2031 nsIContent* aContent)
2033 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
2035 if (!mFocusedWindow)
2036 return;
2038 // this is called when a document is focused or when the caretbrowsing
2039 // preference is changed
2040 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
2041 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
2042 if (!dsti)
2043 return;
2045 if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
2046 return; // Never browse with caret in chrome
2049 bool browseWithCaret =
2050 Preferences::GetBool("accessibility.browsewithcaret");
2052 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
2053 if (!presShell)
2054 return;
2056 // If this is an editable document which isn't contentEditable, or a
2057 // contentEditable document and the node to focus is contentEditable,
2058 // return, so that we don't mess with caret visibility.
2059 bool isEditable = false;
2060 focusedDocShell->GetEditable(&isEditable);
2062 if (isEditable) {
2063 nsCOMPtr<nsIHTMLDocument> doc =
2064 do_QueryInterface(presShell->GetDocument());
2066 bool isContentEditableDoc =
2067 doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
2069 bool isFocusEditable =
2070 aContent && aContent->HasFlag(NODE_IS_EDITABLE);
2071 if (!isContentEditableDoc || isFocusEditable)
2072 return;
2075 if (!isEditable && aMoveCaretToFocus)
2076 MoveCaretToFocus(presShell, aContent);
2078 if (!aUpdateVisibility)
2079 return;
2081 // XXXndeakin this doesn't seem right. It should be checking for this only
2082 // on the nearest ancestor frame which is a chrome frame. But this is
2083 // what the existing code does, so just leave it for now.
2084 if (!browseWithCaret) {
2085 nsCOMPtr<Element> docElement =
2086 mFocusedWindow->GetFrameElementInternal();
2087 if (docElement)
2088 browseWithCaret = docElement->AttrValueIs(kNameSpaceID_None,
2089 nsGkAtoms::showcaret,
2090 NS_LITERAL_STRING("true"),
2091 eCaseMatters);
2094 SetCaretVisible(presShell, browseWithCaret, aContent);
2097 void
2098 nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
2100 // domDoc is a document interface we can create a range with
2101 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument());
2102 if (domDoc) {
2103 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2104 nsCOMPtr<nsISelection> domSelection = frameSelection->
2105 GetSelection(nsISelectionController::SELECTION_NORMAL);
2106 if (domSelection) {
2107 nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
2108 // First clear the selection. This way, if there is no currently focused
2109 // content, the selection will just be cleared.
2110 domSelection->RemoveAllRanges();
2111 if (currentFocusNode) {
2112 nsCOMPtr<nsIDOMRange> newRange;
2113 nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange));
2114 if (NS_SUCCEEDED(rv)) {
2115 // Set the range to the start of the currently focused node
2116 // Make sure it's collapsed
2117 newRange->SelectNodeContents(currentFocusNode);
2118 nsCOMPtr<nsIDOMNode> firstChild;
2119 currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
2120 if (!firstChild ||
2121 aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
2122 // If current focus node is a leaf, set range to before the
2123 // node by using the parent as a container.
2124 // This prevents it from appearing as selected.
2125 newRange->SetStartBefore(currentFocusNode);
2126 newRange->SetEndBefore(currentFocusNode);
2128 domSelection->AddRange(newRange);
2129 domSelection->CollapseToStart();
2136 nsresult
2137 nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
2138 bool aVisible,
2139 nsIContent* aContent)
2141 // When browsing with caret, make sure caret is visible after new focus
2142 // Return early if there is no caret. This can happen for the testcase
2143 // for bug 308025 where a window is closed in a blur handler.
2144 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
2145 if (!caret)
2146 return NS_OK;
2148 bool caretVisible = caret->IsVisible();
2149 if (!aVisible && !caretVisible)
2150 return NS_OK;
2152 nsRefPtr<nsFrameSelection> frameSelection;
2153 if (aContent) {
2154 NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
2155 "Wrong document?");
2156 nsIFrame *focusFrame = aContent->GetPrimaryFrame();
2157 if (focusFrame)
2158 frameSelection = focusFrame->GetFrameSelection();
2161 nsRefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
2163 if (docFrameSelection && caret &&
2164 (frameSelection == docFrameSelection || !aContent)) {
2165 nsISelection* domSelection = docFrameSelection->
2166 GetSelection(nsISelectionController::SELECTION_NORMAL);
2167 if (domSelection) {
2168 nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
2169 if (!selCon) {
2170 return NS_ERROR_FAILURE;
2172 // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
2173 selCon->SetCaretEnabled(false);
2175 // Caret must blink on non-editable elements
2176 caret->SetIgnoreUserModify(true);
2177 // Tell the caret which selection to use
2178 caret->SetSelection(domSelection);
2180 // In content, we need to set the caret. The only special case is edit
2181 // fields, which have a different frame selection from the document.
2182 // They will take care of making the caret visible themselves.
2184 selCon->SetCaretReadOnly(false);
2185 selCon->SetCaretEnabled(aVisible);
2189 return NS_OK;
2192 nsresult
2193 nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
2194 nsIPresShell* aPresShell,
2195 nsIContent **aStartContent,
2196 nsIContent **aEndContent)
2198 *aStartContent = *aEndContent = nullptr;
2199 nsresult rv = NS_ERROR_FAILURE;
2201 nsPresContext* presContext = aPresShell->GetPresContext();
2202 NS_ASSERTION(presContext, "mPresContent is null!!");
2204 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2206 nsCOMPtr<nsISelection> domSelection;
2207 if (frameSelection) {
2208 domSelection = frameSelection->
2209 GetSelection(nsISelectionController::SELECTION_NORMAL);
2212 nsCOMPtr<nsIDOMNode> startNode, endNode;
2213 bool isCollapsed = false;
2214 nsCOMPtr<nsIContent> startContent, endContent;
2215 int32_t startOffset = 0;
2216 if (domSelection) {
2217 domSelection->GetIsCollapsed(&isCollapsed);
2218 nsCOMPtr<nsIDOMRange> domRange;
2219 rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
2220 if (domRange) {
2221 domRange->GetStartContainer(getter_AddRefs(startNode));
2222 domRange->GetEndContainer(getter_AddRefs(endNode));
2223 domRange->GetStartOffset(&startOffset);
2225 nsIContent *childContent = nullptr;
2227 startContent = do_QueryInterface(startNode);
2228 if (startContent && startContent->IsElement()) {
2229 NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
2230 childContent = startContent->GetChildAt(startOffset);
2231 if (childContent) {
2232 startContent = childContent;
2236 endContent = do_QueryInterface(endNode);
2237 if (endContent && endContent->IsElement()) {
2238 int32_t endOffset = 0;
2239 domRange->GetEndOffset(&endOffset);
2240 NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
2241 childContent = endContent->GetChildAt(endOffset);
2242 if (childContent) {
2243 endContent = childContent;
2248 else {
2249 rv = NS_ERROR_INVALID_ARG;
2252 nsIFrame *startFrame = nullptr;
2253 if (startContent) {
2254 startFrame = startContent->GetPrimaryFrame();
2255 if (isCollapsed) {
2256 // Next check to see if our caret is at the very end of a node
2257 // If so, the caret is actually sitting in front of the next
2258 // logical frame's primary node - so for this case we need to
2259 // change caretContent to that node.
2261 if (startContent->NodeType() == nsIDOMNode::TEXT_NODE) {
2262 nsAutoString nodeValue;
2263 startContent->AppendTextTo(nodeValue);
2265 bool isFormControl =
2266 startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2268 if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
2269 startContent != aDocument->GetRootElement()) {
2270 // Yes, indeed we were at the end of the last node
2271 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2272 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2273 presContext, startFrame,
2274 eLeaf,
2275 false, // aVisual
2276 false, // aLockInScrollView
2277 true // aFollowOOFs
2279 NS_ENSURE_SUCCESS(rv, rv);
2281 nsIFrame *newCaretFrame = nullptr;
2282 nsCOMPtr<nsIContent> newCaretContent = startContent;
2283 bool endOfSelectionInStartNode(startContent == endContent);
2284 do {
2285 // Continue getting the next frame until the primary content for the frame
2286 // we are on changes - we don't want to be stuck in the same place
2287 frameTraversal->Next();
2288 newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2289 if (nullptr == newCaretFrame)
2290 break;
2291 newCaretContent = newCaretFrame->GetContent();
2292 } while (!newCaretContent || newCaretContent == startContent);
2294 if (newCaretFrame && newCaretContent) {
2295 // If the caret is exactly at the same position of the new frame,
2296 // then we can use the newCaretFrame and newCaretContent for our position
2297 nsRect caretRect;
2298 nsIFrame *frame = nsCaret::GetGeometry(domSelection, &caretRect);
2299 if (frame) {
2300 nsPoint caretWidgetOffset;
2301 nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
2302 caretRect.MoveBy(caretWidgetOffset);
2303 nsPoint newCaretOffset;
2304 nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
2305 if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2306 caretRect.x == newCaretOffset.x) {
2307 // The caret is at the start of the new element.
2308 startFrame = newCaretFrame;
2309 startContent = newCaretContent;
2310 if (endOfSelectionInStartNode) {
2311 endContent = newCaretContent; // Ensure end of selection is not before start
2321 *aStartContent = startContent;
2322 *aEndContent = endContent;
2323 NS_IF_ADDREF(*aStartContent);
2324 NS_IF_ADDREF(*aEndContent);
2326 return rv;
2329 nsresult
2330 nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
2331 nsIContent* aStartContent,
2332 int32_t aType, bool aNoParentTraversal,
2333 nsIContent** aNextContent)
2335 *aNextContent = nullptr;
2337 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2338 if (!docShell)
2339 return NS_OK;
2341 nsCOMPtr<nsIContent> startContent = aStartContent;
2342 if (!startContent && aType != MOVEFOCUS_CARET) {
2343 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
2344 // When moving between documents, make sure to get the right
2345 // starting content in a descendant.
2346 nsCOMPtr<nsPIDOMWindow> focusedWindow;
2347 startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
2349 else {
2350 startContent = aWindow->GetFocusedNode();
2354 nsCOMPtr<nsIDocument> doc;
2355 if (startContent)
2356 doc = startContent->GetComposedDoc();
2357 else
2358 doc = aWindow->GetExtantDoc();
2359 if (!doc)
2360 return NS_OK;
2362 LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
2363 &nsIContent::sTabFocusModel);
2365 if (aType == MOVEFOCUS_ROOT) {
2366 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
2367 return NS_OK;
2369 if (aType == MOVEFOCUS_FORWARDDOC) {
2370 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true));
2371 return NS_OK;
2373 if (aType == MOVEFOCUS_BACKWARDDOC) {
2374 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false));
2375 return NS_OK;
2378 nsIContent* rootContent = doc->GetRootElement();
2379 NS_ENSURE_TRUE(rootContent, NS_OK);
2381 nsIPresShell *presShell = doc->GetShell();
2382 NS_ENSURE_TRUE(presShell, NS_OK);
2384 if (aType == MOVEFOCUS_FIRST) {
2385 if (!aStartContent)
2386 startContent = rootContent;
2387 return GetNextTabbableContent(presShell, startContent,
2388 nullptr, startContent,
2389 true, 1, false, aNextContent);
2391 if (aType == MOVEFOCUS_LAST) {
2392 if (!aStartContent)
2393 startContent = rootContent;
2394 return GetNextTabbableContent(presShell, startContent,
2395 nullptr, startContent,
2396 false, 0, false, aNextContent);
2399 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
2400 bool doNavigation = true;
2401 bool ignoreTabIndex = false;
2402 // when a popup is open, we want to ensure that tab navigation occurs only
2403 // within the most recently opened panel. If a popup is open, its frame will
2404 // be stored in popupFrame.
2405 nsIFrame* popupFrame = nullptr;
2407 int32_t tabIndex = forward ? 1 : 0;
2408 if (startContent) {
2409 nsIFrame* frame = startContent->GetPrimaryFrame();
2410 if (startContent->Tag() == nsGkAtoms::area &&
2411 startContent->IsHTML())
2412 startContent->IsFocusable(&tabIndex);
2413 else if (frame)
2414 frame->IsFocusable(&tabIndex, 0);
2415 else
2416 startContent->IsFocusable(&tabIndex);
2418 // if the current element isn't tabbable, ignore the tabindex and just
2419 // look for the next element. The root content won't have a tabindex
2420 // so just treat this as the beginning of the tab order.
2421 if (tabIndex < 0) {
2422 tabIndex = 1;
2423 if (startContent != rootContent)
2424 ignoreTabIndex = true;
2427 // check if the focus is currently inside a popup. Elements such as the
2428 // autocomplete widget use the noautofocus attribute to allow the focus to
2429 // remain outside the popup when it is opened.
2430 if (frame) {
2431 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2432 nsGkAtoms::menuPopupFrame);
2435 if (popupFrame) {
2436 // Don't navigate outside of a popup, so pretend that the
2437 // root content is the popup itself
2438 rootContent = popupFrame->GetContent();
2439 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2441 else if (!forward) {
2442 // If focus moves backward and when current focused node is root
2443 // content or <body> element which is editable by contenteditable
2444 // attribute, focus should move to its parent document.
2445 if (startContent == rootContent) {
2446 doNavigation = false;
2447 } else {
2448 nsIDocument* doc = startContent->GetComposedDoc();
2449 if (startContent ==
2450 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2451 doNavigation = false;
2456 else {
2457 #ifdef MOZ_XUL
2458 if (aType != MOVEFOCUS_CARET) {
2459 // if there is no focus, yet a panel is open, focus the first item in
2460 // the panel
2461 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2462 if (pm)
2463 popupFrame = pm->GetTopPopup(ePopupTypePanel);
2465 #endif
2466 if (popupFrame) {
2467 rootContent = popupFrame->GetContent();
2468 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2469 startContent = rootContent;
2471 else {
2472 // Otherwise, for content shells, start from the location of the caret.
2473 if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
2474 nsCOMPtr<nsIContent> endSelectionContent;
2475 GetSelectionLocation(doc, presShell,
2476 getter_AddRefs(startContent),
2477 getter_AddRefs(endSelectionContent));
2478 // If the selection is on the rootContent, then there is no selection
2479 if (startContent == rootContent) {
2480 startContent = nullptr;
2483 if (aType == MOVEFOCUS_CARET) {
2484 // GetFocusInSelection finds a focusable link near the caret.
2485 // If there is no start content though, don't do this to avoid
2486 // focusing something unexpected.
2487 if (startContent) {
2488 GetFocusInSelection(aWindow, startContent,
2489 endSelectionContent, aNextContent);
2491 return NS_OK;
2494 if (startContent) {
2495 // when starting from a selection, we always want to find the next or
2496 // previous element in the document. So the tabindex on elements
2497 // should be ignored.
2498 ignoreTabIndex = true;
2502 if (!startContent) {
2503 // otherwise, just use the root content as the starting point
2504 startContent = rootContent;
2505 NS_ENSURE_TRUE(startContent, NS_OK);
2510 NS_ASSERTION(startContent, "starting content not set");
2512 // keep a reference to the starting content. If we find that again, it means
2513 // we've iterated around completely and we don't want to adjust the focus.
2514 // The skipOriginalContentCheck will be set to true only for the first time
2515 // GetNextTabbableContent is called. This ensures that we don't break out
2516 // when nothing is focused to start with. Specifically,
2517 // GetNextTabbableContent first checks the root content -- which happens to
2518 // be the same as the start content -- when nothing is focused and tabbing
2519 // forward. Without skipOriginalContentCheck set to true, we'd end up
2520 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2521 // will never wrap around on its own, and can only return the original
2522 // content when it is called a second time or later.
2523 bool skipOriginalContentCheck = true;
2524 nsIContent* originalStartContent = startContent;
2526 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
2527 LOGFOCUSNAVIGATION((" Tabindex: %d Ignore: %d", tabIndex, ignoreTabIndex));
2529 while (doc) {
2530 if (doNavigation) {
2531 nsCOMPtr<nsIContent> nextFocus;
2532 nsresult rv = GetNextTabbableContent(presShell, rootContent,
2533 skipOriginalContentCheck ? nullptr : originalStartContent,
2534 startContent, forward,
2535 tabIndex, ignoreTabIndex,
2536 getter_AddRefs(nextFocus));
2537 NS_ENSURE_SUCCESS(rv, rv);
2539 // found a content node to focus.
2540 if (nextFocus) {
2541 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
2543 // as long as the found node was not the same as the starting node,
2544 // set it as the return value.
2545 if (nextFocus != originalStartContent)
2546 NS_ADDREF(*aNextContent = nextFocus);
2547 return NS_OK;
2550 if (popupFrame) {
2551 // in a popup, so start again from the beginning of the popup. However,
2552 // if we already started at the beginning, then there isn't anything to
2553 // focus, so just return
2554 if (startContent != rootContent) {
2555 startContent = rootContent;
2556 tabIndex = forward ? 1 : 0;
2557 continue;
2559 return NS_OK;
2563 doNavigation = true;
2564 skipOriginalContentCheck = false;
2565 ignoreTabIndex = false;
2567 if (aNoParentTraversal) {
2568 if (startContent == rootContent)
2569 return NS_OK;
2571 startContent = rootContent;
2572 tabIndex = forward ? 1 : 0;
2573 continue;
2576 // reached the beginning or end of the document. Traverse up to the parent
2577 // document and try again.
2578 nsCOMPtr<nsIDocShellTreeItem> docShellParent;
2579 docShell->GetParent(getter_AddRefs(docShellParent));
2580 if (docShellParent) {
2581 // move up to the parent shell and try again from there.
2583 // first, get the frame element this window is inside.
2584 nsCOMPtr<nsPIDOMWindow> piWindow = docShell->GetWindow();
2585 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2587 // Next, retrieve the parent docshell, document and presshell.
2588 docShell = do_QueryInterface(docShellParent);
2589 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2591 nsCOMPtr<nsPIDOMWindow> piParentWindow = docShellParent->GetWindow();
2592 NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
2593 doc = piParentWindow->GetExtantDoc();
2594 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2596 presShell = doc->GetShell();
2598 rootContent = doc->GetRootElement();
2599 startContent = piWindow->GetFrameElementInternal();
2600 if (startContent) {
2601 nsIFrame* frame = startContent->GetPrimaryFrame();
2602 if (!frame)
2603 return NS_OK;
2605 frame->IsFocusable(&tabIndex, 0);
2606 if (tabIndex < 0) {
2607 tabIndex = 1;
2608 ignoreTabIndex = true;
2611 // if the frame is inside a popup, make sure to scan only within the
2612 // popup. This handles the situation of tabbing amongst elements
2613 // inside an iframe which is itself inside a popup. Otherwise,
2614 // navigation would move outside the popup when tabbing outside the
2615 // iframe.
2616 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2617 nsGkAtoms::menuPopupFrame);
2618 if (popupFrame) {
2619 rootContent = popupFrame->GetContent();
2620 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2623 else {
2624 startContent = rootContent;
2625 tabIndex = forward ? 1 : 0;
2628 else {
2629 // no parent, so call the tree owner. This will tell the embedder that
2630 // it should take the focus.
2631 bool tookFocus;
2632 docShell->TabToTreeOwner(forward, &tookFocus);
2633 // if the tree owner, took the focus, blur the current content
2634 if (tookFocus) {
2635 nsCOMPtr<nsPIDOMWindow> window = docShell->GetWindow();
2636 if (window->GetFocusedNode() == mFocusedContent)
2637 Blur(mFocusedWindow, nullptr, true, true);
2638 else
2639 window->SetFocusedNode(nullptr);
2640 return NS_OK;
2643 // reset the tab index and start again from the beginning or end
2644 startContent = rootContent;
2645 tabIndex = forward ? 1 : 0;
2648 // wrapped all the way around and didn't find anything to move the focus
2649 // to, so just break out
2650 if (startContent == originalStartContent)
2651 break;
2654 return NS_OK;
2657 nsresult
2658 nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
2659 nsIContent* aRootContent,
2660 nsIContent* aOriginalStartContent,
2661 nsIContent* aStartContent,
2662 bool aForward,
2663 int32_t aCurrentTabIndex,
2664 bool aIgnoreTabIndex,
2665 nsIContent** aResultContent)
2667 *aResultContent = nullptr;
2669 nsCOMPtr<nsIContent> startContent = aStartContent;
2670 if (!startContent)
2671 return NS_OK;
2673 LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
2674 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
2676 nsPresContext* presContext = aPresShell->GetPresContext();
2678 bool getNextFrame = true;
2679 nsCOMPtr<nsIContent> iterStartContent = aStartContent;
2680 while (1) {
2681 nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
2682 // if there is no frame, look for another content node that has a frame
2683 if (!startFrame) {
2684 // if the root content doesn't have a frame, just return
2685 if (iterStartContent == aRootContent)
2686 return NS_OK;
2688 // look for the next or previous content node in tree order
2689 iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
2690 // we've already skipped over the initial focused content, so we
2691 // don't want to traverse frames.
2692 getNextFrame = false;
2693 if (iterStartContent)
2694 continue;
2696 // otherwise, as a last attempt, just look at the root content
2697 iterStartContent = aRootContent;
2698 continue;
2701 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2702 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2703 presContext, startFrame,
2704 ePreOrder,
2705 false, // aVisual
2706 false, // aLockInScrollView
2707 true // aFollowOOFs
2709 NS_ENSURE_SUCCESS(rv, rv);
2711 if (iterStartContent == aRootContent) {
2712 if (!aForward) {
2713 frameTraversal->Last();
2714 } else if (aRootContent->IsFocusable()) {
2715 frameTraversal->Next();
2718 else if (getNextFrame &&
2719 (!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
2720 !iterStartContent->IsHTML())) {
2721 // Need to do special check in case we're in an imagemap which has multiple
2722 // content nodes per frame, so don't skip over the starting frame.
2723 if (aForward)
2724 frameTraversal->Next();
2725 else
2726 frameTraversal->Prev();
2729 // Walk frames to find something tabbable matching mCurrentTabIndex
2730 nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2731 while (frame) {
2732 // TabIndex not set defaults to 0 for form elements, anchors and other
2733 // elements that are normally focusable. Tabindex defaults to -1
2734 // for elements that are not normally focusable.
2735 // The returned computed tabindex from IsFocusable() is as follows:
2736 // < 0 not tabbable at all
2737 // == 0 in normal tab order (last after positive tabindexed items)
2738 // > 0 can be tabbed to in the order specified by this value
2740 int32_t tabIndex;
2741 frame->IsFocusable(&tabIndex, 0);
2743 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
2744 LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
2746 nsIContent* currentContent = frame->GetContent();
2747 if (tabIndex >= 0) {
2748 NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
2749 if (currentContent->Tag() == nsGkAtoms::img &&
2750 currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
2751 // This is an image with a map. Image map areas are not traversed by
2752 // nsIFrameTraversal so look for the next or previous area element.
2753 nsIContent *areaContent =
2754 GetNextTabbableMapArea(aForward, aCurrentTabIndex,
2755 currentContent, iterStartContent);
2756 if (areaContent) {
2757 NS_ADDREF(*aResultContent = areaContent);
2758 return NS_OK;
2761 else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
2762 // break out if we've wrapped around to the start again.
2763 if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2764 NS_ADDREF(*aResultContent = currentContent);
2765 return NS_OK;
2768 // found a node with a matching tab index. Check if it is a child
2769 // frame. If so, navigate into the child frame instead.
2770 nsIDocument* doc = currentContent->GetComposedDoc();
2771 NS_ASSERTION(doc, "content not in document");
2772 nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
2773 if (subdoc) {
2774 if (!subdoc->EventHandlingSuppressed()) {
2775 if (aForward) {
2776 // when tabbing forward into a frame, return the root
2777 // frame so that the canvas becomes focused.
2778 nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
2779 if (subframe) {
2780 // If the subframe body is editable by contenteditable,
2781 // we should set the editor's root element rather than the
2782 // actual root element. Otherwise, we should set the focus
2783 // to the root content.
2784 *aResultContent =
2785 nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
2786 if (!*aResultContent ||
2787 !((*aResultContent)->GetPrimaryFrame())) {
2788 *aResultContent =
2789 GetRootForFocus(subframe, subdoc, false, true);
2791 if (*aResultContent) {
2792 NS_ADDREF(*aResultContent);
2793 return NS_OK;
2797 Element* rootElement = subdoc->GetRootElement();
2798 nsIPresShell* subShell = subdoc->GetShell();
2799 if (rootElement && subShell) {
2800 rv = GetNextTabbableContent(subShell, rootElement,
2801 aOriginalStartContent, rootElement,
2802 aForward, (aForward ? 1 : 0),
2803 false, aResultContent);
2804 NS_ENSURE_SUCCESS(rv, rv);
2805 if (*aResultContent)
2806 return NS_OK;
2810 // otherwise, use this as the next content node to tab to, unless
2811 // this was the element we started on. This would happen for
2812 // instance on an element with child frames, where frame navigation
2813 // could return the original element again. In that case, just skip
2814 // it. Also, if the next content node is the root content, then
2815 // return it. This latter case would happen only if someone made a
2816 // popup focusable.
2817 // Also, when going backwards, check to ensure that the focus
2818 // wouldn't be redirected. Otherwise, for example, when an input in
2819 // a textbox is focused, the enclosing textbox would be found and
2820 // the same inner input would be returned again.
2821 else if (currentContent == aRootContent ||
2822 (currentContent != startContent &&
2823 (aForward || !GetRedirectedFocus(currentContent)))) {
2824 NS_ADDREF(*aResultContent = currentContent);
2825 return NS_OK;
2829 else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2830 // not focusable, so return if we have wrapped around to the original
2831 // content. This is necessary in case the original starting content was
2832 // not focusable.
2833 NS_ADDREF(*aResultContent = currentContent);
2834 return NS_OK;
2837 // Move to the next or previous frame, but ignore continuation frames
2838 // since only the first frame should be involved in focusability.
2839 // Otherwise, a loop will occur in the following example:
2840 // <span tabindex="1">...<a/><a/>...</span>
2841 // where the text wraps onto multiple lines. Tabbing from the second
2842 // link can find one of the span's continuation frames between the link
2843 // and the end of the span, and the span would end up getting focused
2844 // again.
2845 do {
2846 if (aForward)
2847 frameTraversal->Next();
2848 else
2849 frameTraversal->Prev();
2850 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2851 } while (frame && frame->GetPrevContinuation());
2854 // If already at lowest priority tab (0), end search completely.
2855 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
2856 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
2857 // if going backwards, the canvas should be focused once the beginning
2858 // has been reached.
2859 if (!aForward) {
2860 nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
2861 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2862 NS_IF_ADDREF(*aResultContent =
2863 GetRootForFocus(window, aRootContent->GetComposedDoc(),
2864 false, true));
2866 break;
2869 // continue looking for next highest priority tabindex
2870 aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
2871 startContent = iterStartContent = aRootContent;
2874 return NS_OK;
2877 nsIContent*
2878 nsFocusManager::GetNextTabbableMapArea(bool aForward,
2879 int32_t aCurrentTabIndex,
2880 nsIContent* aImageContent,
2881 nsIContent* aStartContent)
2883 nsAutoString useMap;
2884 aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
2886 nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc();
2887 if (doc) {
2888 nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
2889 if (!mapContent)
2890 return nullptr;
2891 uint32_t count = mapContent->GetChildCount();
2892 // First see if the the start content is in this map
2894 int32_t index = mapContent->IndexOf(aStartContent);
2895 int32_t tabIndex;
2896 if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
2897 tabIndex != aCurrentTabIndex)) {
2898 // If aStartContent is in this map we must start iterating past it.
2899 // We skip the case where aStartContent has tabindex == aStartContent
2900 // since the next tab ordered element might be before it
2901 // (or after for backwards) in the child list.
2902 index = aForward ? -1 : (int32_t)count;
2905 // GetChildAt will return nullptr if our index < 0 or index >= count
2906 nsCOMPtr<nsIContent> areaContent;
2907 while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) {
2908 if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
2909 return areaContent;
2914 return nullptr;
2917 int32_t
2918 nsFocusManager::GetNextTabIndex(nsIContent* aParent,
2919 int32_t aCurrentTabIndex,
2920 bool aForward)
2922 int32_t tabIndex, childTabIndex;
2924 if (aForward) {
2925 tabIndex = 0;
2926 for (nsIContent* child = aParent->GetFirstChild();
2927 child;
2928 child = child->GetNextSibling()) {
2929 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2930 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
2931 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
2934 nsAutoString tabIndexStr;
2935 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2936 nsresult ec;
2937 int32_t val = tabIndexStr.ToInteger(&ec);
2938 if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
2939 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
2943 else { /* !aForward */
2944 tabIndex = 1;
2945 for (nsIContent* child = aParent->GetFirstChild();
2946 child;
2947 child = child->GetNextSibling()) {
2948 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2949 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
2950 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
2951 tabIndex = childTabIndex;
2954 nsAutoString tabIndexStr;
2955 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2956 nsresult ec;
2957 int32_t val = tabIndexStr.ToInteger(&ec);
2958 if (NS_SUCCEEDED (ec)) {
2959 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
2960 (val < aCurrentTabIndex && val > tabIndex) ) {
2961 tabIndex = val;
2967 return tabIndex;
2970 nsIContent*
2971 nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
2972 nsIDocument* aDocument,
2973 bool aIsForDocNavigation,
2974 bool aCheckVisibility)
2976 // the root element's canvas may be focused as long as the document is in a
2977 // a non-chrome shell and does not contain a frameset.
2978 if (aIsForDocNavigation) {
2979 nsCOMPtr<Element> docElement = aWindow->GetFrameElementInternal();
2980 // document navigation skips iframes and frames that are specifically non-focusable
2981 if (docElement) {
2982 if (docElement->Tag() == nsGkAtoms::iframe)
2983 return nullptr;
2985 nsIFrame* frame = docElement->GetPrimaryFrame();
2986 if (!frame || !frame->IsFocusable(nullptr, 0))
2987 return nullptr;
2989 } else {
2990 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2991 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
2992 return nullptr;
2996 if (aCheckVisibility && !IsWindowVisible(aWindow))
2997 return nullptr;
2999 Element *rootElement = aDocument->GetRootElement();
3000 if (!rootElement) {
3001 return nullptr;
3004 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
3005 return nullptr;
3008 // Finally, check if this is a frameset
3009 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
3010 if (htmlDoc && aDocument->GetHtmlChildElement(nsGkAtoms::frameset)) {
3011 return nullptr;
3014 return rootElement;
3017 void
3018 nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
3019 nsIDocShellTreeItem** aResult)
3021 *aResult = nullptr;
3023 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3024 while (curItem) {
3025 int32_t childCount = 0;
3026 curItem->GetChildCount(&childCount);
3027 if (!childCount) {
3028 *aResult = curItem;
3029 NS_ADDREF(*aResult);
3030 return;
3034 curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
3038 void
3039 nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
3040 nsIDocShellTreeItem** aResult)
3042 *aResult = nullptr;
3044 int32_t childCount = 0;
3045 aItem->GetChildCount(&childCount);
3046 if (childCount) {
3047 aItem->GetChildAt(0, aResult);
3048 if (*aResult)
3049 return;
3052 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3053 while (curItem) {
3054 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3055 curItem->GetParent(getter_AddRefs(parentItem));
3056 if (!parentItem)
3057 return;
3059 // Note that we avoid using GetChildOffset() here because docshell
3060 // child offsets can't be trusted to be correct. bug 162283.
3061 nsCOMPtr<nsIDocShellTreeItem> iterItem;
3062 childCount = 0;
3063 parentItem->GetChildCount(&childCount);
3064 for (int32_t index = 0; index < childCount; ++index) {
3065 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3066 if (iterItem == curItem) {
3067 ++index;
3068 if (index < childCount) {
3069 parentItem->GetChildAt(index, aResult);
3070 if (*aResult)
3071 return;
3073 break;
3077 curItem = parentItem;
3081 void
3082 nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
3083 nsIDocShellTreeItem** aResult)
3085 *aResult = nullptr;
3087 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3088 aItem->GetParent(getter_AddRefs(parentItem));
3089 if (!parentItem)
3090 return;
3092 // Note that we avoid using GetChildOffset() here because docshell
3093 // child offsets can't be trusted to be correct. bug 162283.
3094 int32_t childCount = 0;
3095 parentItem->GetChildCount(&childCount);
3096 nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
3097 for (int32_t index = 0; index < childCount; ++index) {
3098 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3099 if (iterItem == aItem)
3100 break;
3101 prevItem = iterItem;
3104 if (prevItem)
3105 GetLastDocShell(prevItem, aResult);
3106 else
3107 NS_ADDREF(*aResult = parentItem);
3110 nsIContent*
3111 nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward)
3113 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
3114 if (!pm)
3115 return nullptr;
3117 // Iterate through the array backwards if aForward is false.
3118 nsTArray<nsIFrame *> popups;
3119 pm->GetVisiblePopups(popups);
3120 int32_t i = aForward ? 0 : popups.Length() - 1;
3121 int32_t end = aForward ? popups.Length() : -1;
3123 for (; i != end; aForward ? i++ : i--) {
3124 nsIFrame* popupFrame = popups[i];
3125 if (aCurrentPopup) {
3126 // If the current popup is set, then we need to skip over this popup and
3127 // wait until the currently focused popup is found. Once found, the
3128 // current popup will be cleared so that the next popup is used.
3129 if (aCurrentPopup == popupFrame)
3130 aCurrentPopup = nullptr;
3131 continue;
3134 // Skip over non-panels
3135 if (popupFrame->GetContent()->Tag() != nsGkAtoms::panel ||
3136 (aDocument && popupFrame->GetContent()->GetComposedDoc() != aDocument)) {
3137 continue;
3140 // Find the first focusable content within the popup. If there isn't any
3141 // focusable content in the popup, skip to the next popup.
3142 nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell();
3143 if (presShell) {
3144 nsCOMPtr<nsIContent> nextFocus;
3145 nsIContent* popup = popupFrame->GetContent();
3146 nsresult rv = GetNextTabbableContent(presShell, popup,
3147 nullptr, popup,
3148 true, 1, false,
3149 getter_AddRefs(nextFocus));
3150 if (NS_SUCCEEDED(rv) && nextFocus) {
3151 return nextFocus.get();
3156 return nullptr;
3159 nsIContent*
3160 nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward)
3162 // If currentPopup is set, then the starting content is in a panel.
3163 nsIFrame* currentPopup = nullptr;
3164 nsCOMPtr<nsIDocument> doc;
3165 nsCOMPtr<nsIDocShell> startDocShell;
3167 if (aStartContent) {
3168 doc = aStartContent->GetComposedDoc();
3169 if (doc) {
3170 startDocShell = doc->GetWindow()->GetDocShell();
3173 // Check if the starting content is inside a panel. Document navigation
3174 // must start from this panel instead of the document root.
3175 nsIContent* content = aStartContent;
3176 while (content) {
3177 if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) {
3178 currentPopup = content->GetPrimaryFrame();
3179 break;
3181 content = content->GetParent();
3184 else if (mFocusedWindow) {
3185 startDocShell = mFocusedWindow->GetDocShell();
3186 doc = mFocusedWindow->GetExtantDoc();
3187 } else if (mActiveWindow) {
3188 startDocShell = mActiveWindow->GetDocShell();
3189 doc = mActiveWindow->GetExtantDoc();
3192 if (!startDocShell)
3193 return nullptr;
3195 // perform a depth first search (preorder) of the docshell tree
3196 // looking for an HTML Frame or a chrome document
3197 nsIContent* content = aStartContent;
3198 nsCOMPtr<nsIDocShellTreeItem> curItem = startDocShell.get();
3199 nsCOMPtr<nsIDocShellTreeItem> nextItem;
3200 do {
3201 // If moving forward, check for a panel in the starting document. If one
3202 // exists with focusable content, return that content instead of the next
3203 // document. If currentPopup is set, then, another panel may exist. If no
3204 // such panel exists, then continue on to check the next document.
3205 // When moving backwards, and the starting content is in a panel, then
3206 // check for additional panels in the starting document. If the starting
3207 // content is not in a panel, move back to the previous document and check
3208 // for panels there.
3210 bool checkPopups = false;
3211 nsCOMPtr<nsPIDOMWindow> nextFrame = nullptr;
3213 if (doc && (aForward || currentPopup)) {
3214 nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward);
3215 if (popupContent)
3216 return popupContent;
3218 if (!aForward && currentPopup) {
3219 // The starting content was in a popup, yet no other popups were
3220 // found. Move onto the starting content's document.
3221 nextFrame = doc->GetWindow();
3225 // Look for the next or previous document.
3226 if (!nextFrame) {
3227 if (aForward) {
3228 GetNextDocShell(curItem, getter_AddRefs(nextItem));
3229 if (!nextItem) {
3230 // wrap around to the beginning, which is the top of the tree
3231 startDocShell->GetRootTreeItem(getter_AddRefs(nextItem));
3234 else {
3235 GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
3236 if (!nextItem) {
3237 // wrap around to the end, which is the last item in the tree
3238 nsCOMPtr<nsIDocShellTreeItem> rootItem;
3239 startDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
3240 GetLastDocShell(rootItem, getter_AddRefs(nextItem));
3243 // When going back to the previous document, check for any focusable
3244 // popups in that previous document first.
3245 checkPopups = true;
3248 curItem = nextItem;
3249 nextFrame = nextItem ? nextItem->GetWindow() : nullptr;
3252 if (!nextFrame)
3253 return nullptr;
3255 // Clear currentPopup for the next iteration
3256 currentPopup = nullptr;
3258 // If event handling is suppressed, move on to the next document. Set
3259 // content to null so that the popup check will be skipped on the next
3260 // loop iteration.
3261 doc = nextFrame->GetExtantDoc();
3262 if (!doc || doc->EventHandlingSuppressed()) {
3263 content = nullptr;
3264 continue;
3267 if (checkPopups) {
3268 // When iterating backwards, check the panels of the previous document
3269 // first. If a panel exists that has focusable content, focus that.
3270 // Otherwise, continue on to focus the document.
3271 nsIContent* popupContent = GetNextTabbablePanel(doc, nullptr, false);
3272 if (popupContent)
3273 return popupContent;
3276 content = GetRootForFocus(nextFrame, doc, true, true);
3277 if (content && !GetRootForFocus(nextFrame, doc, false, false)) {
3278 // if the found content is in a chrome shell or a frameset, navigate
3279 // forward one tabbable item so that the first item is focused. Note
3280 // that we always go forward and not back here.
3281 nsCOMPtr<nsIContent> nextFocus;
3282 Element* rootElement = doc->GetRootElement();
3283 nsIPresShell* presShell = doc->GetShell();
3284 if (presShell) {
3285 nsresult rv = GetNextTabbableContent(presShell, rootElement,
3286 nullptr, rootElement,
3287 true, 1, false,
3288 getter_AddRefs(nextFocus));
3289 return NS_SUCCEEDED(rv) ? nextFocus.get() : nullptr;
3293 } while (!content);
3295 return content;
3298 void
3299 nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
3300 nsIContent* aStartSelection,
3301 nsIContent* aEndSelection,
3302 nsIContent** aFocusedContent)
3304 *aFocusedContent = nullptr;
3306 nsCOMPtr<nsIContent> testContent = aStartSelection;
3307 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
3309 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
3311 // We now have the correct start node in selectionContent!
3312 // Search for focusable elements, starting with selectionContent
3314 // Method #1: Keep going up while we look - an ancestor might be focusable
3315 // We could end the loop earlier, such as when we're no longer
3316 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
3317 // with a variable holding the starting selectionContent
3318 while (testContent) {
3319 // Keep testing while selectionContent is equal to something,
3320 // eventually we'll run out of ancestors
3322 nsCOMPtr<nsIURI> uri;
3323 if (testContent == currentFocus ||
3324 testContent->IsLink(getter_AddRefs(uri))) {
3325 NS_ADDREF(*aFocusedContent = testContent);
3326 return;
3329 // Get the parent
3330 testContent = testContent->GetParent();
3332 if (!testContent) {
3333 // We run this loop again, checking the ancestor chain of the selection's end point
3334 testContent = nextTestContent;
3335 nextTestContent = nullptr;
3339 // We couldn't find an anchor that was an ancestor of the selection start
3340 // Method #2: look for anchor in selection's primary range (depth first search)
3342 // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
3343 nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
3344 nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
3345 nsCOMPtr<nsIDOMNode> testNode;
3347 do {
3348 testContent = do_QueryInterface(selectionNode);
3350 // We're looking for any focusable link that could be part of the
3351 // main document's selection.
3352 nsCOMPtr<nsIURI> uri;
3353 if (testContent == currentFocus ||
3354 testContent->IsLink(getter_AddRefs(uri))) {
3355 NS_ADDREF(*aFocusedContent = testContent);
3356 return;
3359 selectionNode->GetFirstChild(getter_AddRefs(testNode));
3360 if (testNode) {
3361 selectionNode = testNode;
3362 continue;
3365 if (selectionNode == endSelectionNode)
3366 break;
3367 selectionNode->GetNextSibling(getter_AddRefs(testNode));
3368 if (testNode) {
3369 selectionNode = testNode;
3370 continue;
3373 do {
3374 selectionNode->GetParentNode(getter_AddRefs(testNode));
3375 if (!testNode || testNode == endSelectionNode) {
3376 selectionNode = nullptr;
3377 break;
3379 testNode->GetNextSibling(getter_AddRefs(selectionNode));
3380 if (selectionNode)
3381 break;
3382 selectionNode = testNode;
3383 } while (true);
3385 while (selectionNode && selectionNode != endSelectionNode);
3388 class PointerUnlocker : public nsRunnable
3390 public:
3391 PointerUnlocker()
3393 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
3394 PointerUnlocker::sActiveUnlocker = this;
3397 ~PointerUnlocker()
3399 if (PointerUnlocker::sActiveUnlocker == this) {
3400 PointerUnlocker::sActiveUnlocker = nullptr;
3404 NS_IMETHOD Run()
3406 if (PointerUnlocker::sActiveUnlocker == this) {
3407 PointerUnlocker::sActiveUnlocker = nullptr;
3409 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
3410 nsPIDOMWindow* focused =
3411 nsFocusManager::GetFocusManager()->GetFocusedWindow();
3412 nsCOMPtr<nsIDocument> pointerLockedDoc =
3413 do_QueryReferent(EventStateManager::sPointerLockedDoc);
3414 if (pointerLockedDoc &&
3415 !nsContentUtils::IsInPointerLockContext(focused)) {
3416 nsIDocument::UnlockPointer();
3418 return NS_OK;
3421 static PointerUnlocker* sActiveUnlocker;
3424 PointerUnlocker*
3425 PointerUnlocker::sActiveUnlocker = nullptr;
3427 void
3428 nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindow* aWindow)
3430 if (!PointerUnlocker::sActiveUnlocker &&
3431 nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
3432 !nsContentUtils::IsInPointerLockContext(aWindow)) {
3433 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
3434 NS_DispatchToCurrentThread(runnable);
3436 mFocusedWindow = aWindow;
3439 void
3440 nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
3442 if (!sInstance) {
3443 return;
3446 if (sInstance->mActiveWindow) {
3447 sInstance->mActiveWindow->
3448 MarkUncollectableForCCGeneration(aGeneration);
3450 if (sInstance->mFocusedWindow) {
3451 sInstance->mFocusedWindow->
3452 MarkUncollectableForCCGeneration(aGeneration);
3454 if (sInstance->mWindowBeingLowered) {
3455 sInstance->mWindowBeingLowered->
3456 MarkUncollectableForCCGeneration(aGeneration);
3458 if (sInstance->mFocusedContent) {
3459 sInstance->mFocusedContent->OwnerDoc()->
3460 MarkUncollectableForCCGeneration(aGeneration);
3462 if (sInstance->mFirstBlurEvent) {
3463 sInstance->mFirstBlurEvent->OwnerDoc()->
3464 MarkUncollectableForCCGeneration(aGeneration);
3466 if (sInstance->mFirstFocusEvent) {
3467 sInstance->mFirstFocusEvent->OwnerDoc()->
3468 MarkUncollectableForCCGeneration(aGeneration);
3470 if (sInstance->mMouseButtonEventHandlingDocument) {
3471 sInstance->mMouseButtonEventHandlingDocument->
3472 MarkUncollectableForCCGeneration(aGeneration);
3476 nsresult
3477 NS_NewFocusManager(nsIFocusManager** aResult)
3479 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
3480 return NS_OK;