Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / base / nsFocusManager.cpp
blob5e001d9f73cce081b5e7b690d4b1c985e75a7516
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 "nsIEditor.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsIDOMElement.h"
18 #include "nsIDOMDocument.h"
19 #include "nsIDOMRange.h"
20 #include "nsIHTMLDocument.h"
21 #include "nsIDocShell.h"
22 #include "nsIDocShellTreeOwner.h"
23 #include "nsLayoutUtils.h"
24 #include "nsIPresShell.h"
25 #include "nsFrameTraversal.h"
26 #include "nsIWebNavigation.h"
27 #include "nsCaret.h"
28 #include "nsIBaseWindow.h"
29 #include "nsIXULWindow.h"
30 #include "nsViewManager.h"
31 #include "nsFrameSelection.h"
32 #include "mozilla/dom/Selection.h"
33 #include "nsXULPopupManager.h"
34 #include "nsIScriptObjectPrincipal.h"
35 #include "nsIPrincipal.h"
36 #include "nsIObserverService.h"
37 #include "nsIObjectFrame.h"
38 #include "nsBindingManager.h"
39 #include "nsStyleCoord.h"
40 #include "SelectionCarets.h"
42 #include "mozilla/ContentEvents.h"
43 #include "mozilla/dom/Element.h"
44 #include "mozilla/EventDispatcher.h"
45 #include "mozilla/EventStateManager.h"
46 #include "mozilla/EventStates.h"
47 #include "mozilla/IMEStateManager.h"
48 #include "mozilla/LookAndFeel.h"
49 #include "mozilla/Preferences.h"
50 #include "mozilla/Services.h"
51 #include "mozilla/unused.h"
52 #include <algorithm>
54 #ifdef MOZ_XUL
55 #include "nsIDOMXULTextboxElement.h"
56 #include "nsIDOMXULMenuListElement.h"
57 #endif
59 #ifdef ACCESSIBILITY
60 #include "nsAccessibilityService.h"
61 #endif
63 #ifndef XP_MACOSX
64 #include "nsIScriptError.h"
65 #endif
67 using namespace mozilla;
68 using namespace mozilla::dom;
69 using namespace mozilla::widget;
71 #ifdef PR_LOGGING
73 // Two types of focus pr logging are available:
74 // 'Focus' for normal focus manager calls
75 // 'FocusNavigation' for tab and document navigation
76 PRLogModuleInfo* gFocusLog;
77 PRLogModuleInfo* gFocusNavigationLog;
79 #define LOGFOCUS(args) PR_LOG(gFocusLog, 4, args)
80 #define LOGFOCUSNAVIGATION(args) PR_LOG(gFocusNavigationLog, 4, args)
82 #define LOGTAG(log, format, content) \
83 { \
84 nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \
85 if (content) { \
86 content->Tag()->ToUTF8String(tag); \
87 } \
88 PR_LOG(log, 4, (format, tag.get())); \
91 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
92 #define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content)
94 #else
96 #define LOGFOCUS(args)
97 #define LOGFOCUSNAVIGATION(args)
98 #define LOGCONTENT(format, content)
99 #define LOGCONTENTNAVIGATION(format, content)
101 #endif
103 struct nsDelayedBlurOrFocusEvent
105 nsDelayedBlurOrFocusEvent(uint32_t aType,
106 nsIPresShell* aPresShell,
107 nsIDocument* aDocument,
108 EventTarget* aTarget)
109 : mType(aType),
110 mPresShell(aPresShell),
111 mDocument(aDocument),
112 mTarget(aTarget) { }
114 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
115 : mType(aOther.mType),
116 mPresShell(aOther.mPresShell),
117 mDocument(aOther.mDocument),
118 mTarget(aOther.mTarget) { }
120 uint32_t mType;
121 nsCOMPtr<nsIPresShell> mPresShell;
122 nsCOMPtr<nsIDocument> mDocument;
123 nsCOMPtr<EventTarget> mTarget;
126 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField)
128 aField.mPresShell = nullptr;
129 aField.mDocument = nullptr;
130 aField.mTarget = nullptr;
133 inline void
134 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
135 nsDelayedBlurOrFocusEvent& aField,
136 const char* aName,
137 uint32_t aFlags = 0)
139 CycleCollectionNoteChild(aCallback, aField.mPresShell.get(), aName, aFlags);
140 CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
141 CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
144 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
145 NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
146 NS_INTERFACE_MAP_ENTRY(nsIObserver)
147 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
148 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
149 NS_INTERFACE_MAP_END
151 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
152 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
154 NS_IMPL_CYCLE_COLLECTION(nsFocusManager,
155 mActiveWindow,
156 mFocusedWindow,
157 mFocusedContent,
158 mFirstBlurEvent,
159 mFirstFocusEvent,
160 mWindowBeingLowered,
161 mDelayedBlurFocusEvents,
162 mMouseButtonEventHandlingDocument)
164 nsFocusManager* nsFocusManager::sInstance = nullptr;
165 bool nsFocusManager::sMouseFocusesFormControl = false;
166 bool nsFocusManager::sTestMode = false;
168 static const char* kObservedPrefs[] = {
169 "accessibility.browsewithcaret",
170 "accessibility.tabfocus_applies_to_xul",
171 "accessibility.mouse_focuses_formcontrol",
172 "focusmanager.testmode",
173 nullptr
176 nsFocusManager::nsFocusManager()
177 : mParentFocusType(ParentFocusType_Ignore)
180 nsFocusManager::~nsFocusManager()
182 Preferences::RemoveObservers(this, kObservedPrefs);
184 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
185 if (obs) {
186 obs->RemoveObserver(this, "xpcom-shutdown");
190 // static
191 nsresult
192 nsFocusManager::Init()
194 nsFocusManager* fm = new nsFocusManager();
195 NS_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY);
196 NS_ADDREF(fm);
197 sInstance = fm;
199 #ifdef PR_LOGGING
200 gFocusLog = PR_NewLogModule("Focus");
201 gFocusNavigationLog = PR_NewLogModule("FocusNavigation");
202 #endif
204 nsIContent::sTabFocusModelAppliesToXUL =
205 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
206 nsIContent::sTabFocusModelAppliesToXUL);
208 sMouseFocusesFormControl =
209 Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
211 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
213 Preferences::AddWeakObservers(fm, kObservedPrefs);
215 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
216 if (obs) {
217 obs->AddObserver(fm, "xpcom-shutdown", true);
220 return NS_OK;
223 // static
224 void
225 nsFocusManager::Shutdown()
227 NS_IF_RELEASE(sInstance);
230 NS_IMETHODIMP
231 nsFocusManager::Observe(nsISupports *aSubject,
232 const char *aTopic,
233 const char16_t *aData)
235 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
236 nsDependentString data(aData);
237 if (data.EqualsLiteral("accessibility.browsewithcaret")) {
238 UpdateCaretForCaretBrowsingMode();
240 else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
241 nsIContent::sTabFocusModelAppliesToXUL =
242 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
243 nsIContent::sTabFocusModelAppliesToXUL);
245 else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
246 sMouseFocusesFormControl =
247 Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
248 false);
250 else if (data.EqualsLiteral("focusmanager.testmode")) {
251 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
253 } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
254 mActiveWindow = nullptr;
255 mFocusedWindow = nullptr;
256 mFocusedContent = nullptr;
257 mFirstBlurEvent = nullptr;
258 mFirstFocusEvent = nullptr;
259 mWindowBeingLowered = nullptr;
260 mDelayedBlurFocusEvents.Clear();
261 mMouseButtonEventHandlingDocument = nullptr;
264 return NS_OK;
267 // given a frame content node, retrieve the nsIDOMWindow displayed in it
268 static nsPIDOMWindow*
269 GetContentWindow(nsIContent* aContent)
271 nsIDocument* doc = aContent->GetComposedDoc();
272 if (doc) {
273 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
274 if (subdoc)
275 return subdoc->GetWindow();
278 return nullptr;
281 // get the current window for the given content node
282 static nsPIDOMWindow*
283 GetCurrentWindow(nsIContent* aContent)
285 nsIDocument* doc = aContent->GetComposedDoc();
286 return doc ? doc->GetWindow() : nullptr;
289 // static
290 nsIContent*
291 nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep,
292 nsPIDOMWindow** aFocusedWindow)
294 NS_ENSURE_TRUE(aWindow, nullptr);
296 *aFocusedWindow = nullptr;
298 nsIContent* currentContent = nullptr;
299 nsPIDOMWindow* window = aWindow->GetOuterWindow();
300 while (window) {
301 *aFocusedWindow = window;
302 currentContent = window->GetFocusedNode();
303 if (!currentContent || !aDeep)
304 break;
306 window = GetContentWindow(currentContent);
309 NS_IF_ADDREF(*aFocusedWindow);
311 return currentContent;
314 // static
315 nsIContent*
316 nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
318 #ifdef MOZ_XUL
319 if (aContent->IsXUL()) {
320 nsCOMPtr<nsIDOMNode> inputField;
322 nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
323 if (textbox) {
324 textbox->GetInputField(getter_AddRefs(inputField));
326 else {
327 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
328 if (menulist) {
329 menulist->GetInputField(getter_AddRefs(inputField));
331 else if (aContent->Tag() == nsGkAtoms::scale) {
332 nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
333 if (!doc)
334 return nullptr;
336 nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent);
337 if (children) {
338 nsIContent* child = children->Item(0);
339 if (child && child->Tag() == nsGkAtoms::slider)
340 return child;
345 if (inputField) {
346 nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
347 return retval;
350 #endif
352 return nullptr;
355 // static
356 InputContextAction::Cause
357 nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags)
359 if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
360 return InputContextAction::CAUSE_MOUSE;
361 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
362 return InputContextAction::CAUSE_KEY;
364 return InputContextAction::CAUSE_UNKNOWN;
367 NS_IMETHODIMP
368 nsFocusManager::GetActiveWindow(nsIDOMWindow** aWindow)
370 NS_IF_ADDREF(*aWindow = mActiveWindow);
371 return NS_OK;
374 NS_IMETHODIMP
375 nsFocusManager::SetActiveWindow(nsIDOMWindow* aWindow)
377 // only top-level windows can be made active
378 nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow);
379 if (piWindow)
380 piWindow = piWindow->GetOuterWindow();
382 NS_ENSURE_TRUE(piWindow && (piWindow == piWindow->GetPrivateRoot()),
383 NS_ERROR_INVALID_ARG);
385 RaiseWindow(piWindow);
386 return NS_OK;
389 NS_IMETHODIMP
390 nsFocusManager::GetFocusedWindow(nsIDOMWindow** aFocusedWindow)
392 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
393 return NS_OK;
396 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(nsIDOMWindow* aWindowToFocus)
398 LOGFOCUS(("<<SetFocusedWindow begin>>"));
400 nsCOMPtr<nsPIDOMWindow> windowToFocus(do_QueryInterface(aWindowToFocus));
401 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
403 windowToFocus = windowToFocus->GetOuterWindow();
405 nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
406 if (frameElement) {
407 // pass false for aFocusChanged so that the caret does not get updated
408 // and scrolling does not occur.
409 SetFocusInner(frameElement, 0, false, true);
411 else {
412 // this is a top-level window. If the window has a child frame focused,
413 // clear the focus. Otherwise, focus should already be in this frame, or
414 // already cleared. This ensures that focus will be in this frame and not
415 // in a child.
416 nsIContent* content = windowToFocus->GetFocusedNode();
417 if (content) {
418 nsCOMPtr<nsIDOMWindow> childWindow = GetContentWindow(content);
419 if (childWindow)
420 ClearFocus(windowToFocus);
424 nsCOMPtr<nsPIDOMWindow> rootWindow = windowToFocus->GetPrivateRoot();
425 if (rootWindow)
426 RaiseWindow(rootWindow);
428 LOGFOCUS(("<<SetFocusedWindow end>>"));
430 return NS_OK;
433 NS_IMETHODIMP
434 nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
436 if (mFocusedContent)
437 CallQueryInterface(mFocusedContent, aFocusedElement);
438 else
439 *aFocusedElement = nullptr;
440 return NS_OK;
443 NS_IMETHODIMP
444 nsFocusManager::GetLastFocusMethod(nsIDOMWindow* aWindow, uint32_t* aLastFocusMethod)
446 // the focus method is stored on the inner window
447 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
448 if (window && window->IsOuterWindow())
449 window = window->GetCurrentInnerWindow();
450 if (!window)
451 window = mFocusedWindow;
453 *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
455 NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
456 "invalid focus method");
457 return NS_OK;
460 NS_IMETHODIMP
461 nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags)
463 LOGFOCUS(("<<SetFocus begin>>"));
465 nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
466 NS_ENSURE_ARG(newFocus);
468 SetFocusInner(newFocus, aFlags, true, true);
470 LOGFOCUS(("<<SetFocus end>>"));
472 return NS_OK;
475 NS_IMETHODIMP
476 nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags,
477 bool* aIsFocusable)
479 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
481 nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement);
483 *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr;
485 return NS_OK;
488 NS_IMETHODIMP
489 nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
490 uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement)
492 *aElement = nullptr;
494 #ifdef PR_LOGGING
495 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
497 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG) && mFocusedWindow) {
498 nsIDocument* doc = mFocusedWindow->GetExtantDoc();
499 if (doc && doc->GetDocumentURI()) {
500 nsAutoCString spec;
501 doc->GetDocumentURI()->GetSpec(spec);
502 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), spec.get()));
506 LOGCONTENT(" Current Focus: %s", mFocusedContent.get());
507 #endif
509 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
510 // the other focus methods is already set, or we're just moving to the root
511 // or caret position.
512 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
513 (aFlags & FOCUSMETHOD_MASK) == 0) {
514 aFlags |= FLAG_BYMOVEFOCUS;
517 nsCOMPtr<nsPIDOMWindow> window;
518 nsCOMPtr<nsIContent> startContent;
519 if (aStartElement) {
520 startContent = do_QueryInterface(aStartElement);
521 NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
523 window = GetCurrentWindow(startContent);
525 else {
526 window = aWindow ? do_QueryInterface(aWindow) : mFocusedWindow;
527 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
528 window = window->GetOuterWindow();
531 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
533 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
534 nsCOMPtr<nsIContent> newFocus;
535 nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
536 getter_AddRefs(newFocus));
537 NS_ENSURE_SUCCESS(rv, rv);
539 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
541 if (newFocus) {
542 // for caret movement, pass false for the aFocusChanged argument,
543 // otherwise the caret will end up moving to the focus position. This
544 // would be a problem because the caret would move to the beginning of the
545 // focused link making it impossible to navigate the caret over a link.
546 SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET, true);
547 CallQueryInterface(newFocus, aElement);
549 else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
550 // no content was found, so clear the focus for these two types.
551 ClearFocus(window);
554 LOGFOCUS(("<<MoveFocus end>>"));
556 return NS_OK;
559 NS_IMETHODIMP
560 nsFocusManager::ClearFocus(nsIDOMWindow* aWindow)
562 LOGFOCUS(("<<ClearFocus begin>>"));
564 // if the window to clear is the focused window or an ancestor of the
565 // focused window, then blur the existing focused content. Otherwise, the
566 // focus is somewhere else so just update the current node.
567 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
568 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
570 window = window->GetOuterWindow();
571 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
573 if (IsSameOrAncestor(window, mFocusedWindow)) {
574 bool isAncestor = (window != mFocusedWindow);
575 if (Blur(window, nullptr, isAncestor, true)) {
576 // if we are clearing the focus on an ancestor of the focused window,
577 // the ancestor will become the new focused window, so focus it
578 if (isAncestor)
579 Focus(window, nullptr, 0, true, false, false, true);
582 else {
583 window->SetFocusedNode(nullptr);
586 LOGFOCUS(("<<ClearFocus end>>"));
588 return NS_OK;
591 NS_IMETHODIMP
592 nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow,
593 bool aDeep,
594 nsIDOMWindow** aFocusedWindow,
595 nsIDOMElement** aElement)
597 *aElement = nullptr;
598 if (aFocusedWindow)
599 *aFocusedWindow = nullptr;
601 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
602 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
604 window = window->GetOuterWindow();
605 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
607 nsCOMPtr<nsPIDOMWindow> focusedWindow;
608 nsCOMPtr<nsIContent> focusedContent =
609 GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
610 if (focusedContent)
611 CallQueryInterface(focusedContent, aElement);
613 if (aFocusedWindow)
614 NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
616 return NS_OK;
619 NS_IMETHODIMP
620 nsFocusManager::MoveCaretToFocus(nsIDOMWindow* aWindow)
622 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
623 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
624 if (dsti) {
625 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
626 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
627 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
629 // don't move the caret for editable documents
630 bool isEditable;
631 docShell->GetEditable(&isEditable);
632 if (isEditable)
633 return NS_OK;
635 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
636 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
638 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
639 nsCOMPtr<nsIContent> content = window->GetFocusedNode();
640 if (content)
641 MoveCaretToFocus(presShell, content);
645 return NS_OK;
648 NS_IMETHODIMP
649 nsFocusManager::WindowRaised(nsIDOMWindow* aWindow)
651 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
652 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
654 #ifdef PR_LOGGING
655 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
656 LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
657 nsAutoCString spec;
658 nsIDocument* doc = window->GetExtantDoc();
659 if (doc && doc->GetDocumentURI()) {
660 doc->GetDocumentURI()->GetSpec(spec);
661 LOGFOCUS((" Raised Window: %p %s", aWindow, spec.get()));
663 if (mActiveWindow) {
664 doc = mActiveWindow->GetExtantDoc();
665 if (doc && doc->GetDocumentURI()) {
666 doc->GetDocumentURI()->GetSpec(spec);
667 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), spec.get()));
671 #endif
673 if (mActiveWindow == window) {
674 // The window is already active, so there is no need to focus anything,
675 // but make sure that the right widget is focused. This is a special case
676 // for Windows because when restoring a minimized window, a second
677 // activation will occur and the top-level widget could be focused instead
678 // of the child we want. We solve this by calling SetFocus to ensure that
679 // what the focus manager thinks should be the current widget is actually
680 // focused.
681 EnsureCurrentWidgetFocused();
682 return NS_OK;
685 // lower the existing window, if any. This shouldn't happen usually.
686 if (mActiveWindow)
687 WindowLowered(mActiveWindow);
689 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
690 // If there's no docShellAsItem, this window must have been closed,
691 // in that case there is no tree owner.
692 NS_ENSURE_TRUE(docShellAsItem, NS_OK);
694 // set this as the active window
695 mActiveWindow = window;
697 // ensure that the window is enabled and visible
698 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
699 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
700 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
701 if (baseWindow) {
702 bool isEnabled = true;
703 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
704 return NS_ERROR_FAILURE;
707 if (!sTestMode) {
708 baseWindow->SetVisibility(true);
712 // If this is a parent or single process window, send the activate event.
713 // Events for child process windows will be sent when ParentActivated
714 // is called.
715 if (mParentFocusType == ParentFocusType_Ignore) {
716 ActivateOrDeactivate(window, true);
719 // retrieve the last focused element within the window that was raised
720 nsCOMPtr<nsPIDOMWindow> currentWindow;
721 nsCOMPtr<nsIContent> currentFocus =
722 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
724 NS_ASSERTION(currentWindow, "window raised with no window current");
725 if (!currentWindow)
726 return NS_OK;
728 nsCOMPtr<nsIDocShell> currentDocShell = currentWindow->GetDocShell();
730 nsCOMPtr<nsIPresShell> presShell = currentDocShell->GetPresShell();
731 if (presShell) {
732 // disable selection mousedown state on activation
733 // XXXndeakin P3 not sure if this is necessary, but it doesn't hurt
734 nsRefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
735 frameSelection->SetDragState(false);
738 // If there is no nsIXULWindow, then this is an embedded or child process window.
739 // Pass false for aWindowRaised so that commands get updated.
740 nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow));
741 Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true);
743 return NS_OK;
746 NS_IMETHODIMP
747 nsFocusManager::WindowLowered(nsIDOMWindow* aWindow)
749 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
750 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
752 #ifdef PR_LOGGING
753 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
754 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
755 nsAutoCString spec;
756 nsIDocument* doc = window->GetExtantDoc();
757 if (doc && doc->GetDocumentURI()) {
758 doc->GetDocumentURI()->GetSpec(spec);
759 LOGFOCUS((" Lowered Window: %s", spec.get()));
761 if (mActiveWindow) {
762 doc = mActiveWindow->GetExtantDoc();
763 if (doc && doc->GetDocumentURI()) {
764 doc->GetDocumentURI()->GetSpec(spec);
765 LOGFOCUS((" Active Window: %s", spec.get()));
769 #endif
771 if (mActiveWindow != window)
772 return NS_OK;
774 // clear the mouse capture as the active window has changed
775 nsIPresShell::SetCapturingContent(nullptr, 0);
777 // If this is a parent or single process window, send the deactivate event.
778 // Events for child process windows will be sent when ParentActivated
779 // is called.
780 if (mParentFocusType == ParentFocusType_Ignore) {
781 ActivateOrDeactivate(window, false);
784 // keep track of the window being lowered, so that attempts to raise the
785 // window can be prevented until we return. Otherwise, focus can get into
786 // an unusual state.
787 mWindowBeingLowered = mActiveWindow;
788 mActiveWindow = nullptr;
790 if (mFocusedWindow)
791 Blur(nullptr, nullptr, true, true);
793 mWindowBeingLowered = nullptr;
795 return NS_OK;
798 nsresult
799 nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
801 NS_ENSURE_ARG(aDocument);
802 NS_ENSURE_ARG(aContent);
804 nsPIDOMWindow *window = aDocument->GetWindow();
805 if (!window)
806 return NS_OK;
808 // if the content is currently focused in the window, or is an ancestor
809 // of the currently focused element, reset the focus within that window.
810 nsIContent* content = window->GetFocusedNode();
811 if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
812 bool shouldShowFocusRing = window->ShouldShowFocusRing();
813 window->SetFocusedNode(nullptr);
815 // if this window is currently focused, clear the global focused
816 // element as well, but don't fire any events.
817 if (window == mFocusedWindow) {
818 mFocusedContent = nullptr;
819 } else {
820 // Check if the node that was focused is an iframe or similar by looking
821 // if it has a subdocument. This would indicate that this focused iframe
822 // and its descendants will be going away. We will need to move the
823 // focus somewhere else, so just clear the focus in the toplevel window
824 // so that no element is focused.
825 nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
826 if (subdoc) {
827 nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
828 if (docShell) {
829 nsCOMPtr<nsPIDOMWindow> childWindow = docShell->GetWindow();
830 if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
831 ClearFocus(mActiveWindow);
837 // Notify the editor in case we removed its ancestor limiter.
838 if (content->IsEditable()) {
839 nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell();
840 if (docShell) {
841 nsCOMPtr<nsIEditor> editor;
842 docShell->GetEditor(getter_AddRefs(editor));
843 if (editor) {
844 nsCOMPtr<nsISelection> s;
845 editor->GetSelection(getter_AddRefs(s));
846 nsCOMPtr<nsISelectionPrivate> selection = do_QueryInterface(s);
847 if (selection) {
848 nsCOMPtr<nsIContent> limiter;
849 selection->GetAncestorLimiter(getter_AddRefs(limiter));
850 if (limiter == content) {
851 editor->FinalizeSelection();
858 NotifyFocusStateChange(content, shouldShowFocusRing, false);
861 return NS_OK;
864 NS_IMETHODIMP
865 nsFocusManager::WindowShown(nsIDOMWindow* aWindow, bool aNeedsFocus)
867 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
868 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
870 window = window->GetOuterWindow();
872 #ifdef PR_LOGGING
873 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
874 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
875 nsAutoCString spec;
876 nsIDocument* doc = window->GetExtantDoc();
877 if (doc && doc->GetDocumentURI()) {
878 doc->GetDocumentURI()->GetSpec(spec);
879 LOGFOCUS(("Shown Window: %s", spec.get()));
882 if (mFocusedWindow) {
883 doc = mFocusedWindow->GetExtantDoc();
884 if (doc && doc->GetDocumentURI()) {
885 doc->GetDocumentURI()->GetSpec(spec);
886 LOGFOCUS((" Focused Window: %s", spec.get()));
890 #endif
892 if (mFocusedWindow != window)
893 return NS_OK;
895 if (aNeedsFocus) {
896 nsCOMPtr<nsPIDOMWindow> currentWindow;
897 nsCOMPtr<nsIContent> currentFocus =
898 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
899 if (currentWindow)
900 Focus(currentWindow, currentFocus, 0, true, false, false, true);
902 else {
903 // Sometimes, an element in a window can be focused before the window is
904 // visible, which would mean that the widget may not be properly focused.
905 // When the window becomes visible, make sure the right widget is focused.
906 EnsureCurrentWidgetFocused();
909 if (mParentFocusType == ParentFocusType_Active) {
910 ActivateOrDeactivate(window, true);
913 return NS_OK;
916 NS_IMETHODIMP
917 nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
919 // if there is no window or it is not the same or an ancestor of the
920 // currently focused window, just return, as the current focus will not
921 // be affected.
923 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
924 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
926 window = window->GetOuterWindow();
928 #ifdef PR_LOGGING
929 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
930 LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
931 nsAutoCString spec;
932 nsIDocument* doc = window->GetExtantDoc();
933 if (doc && doc->GetDocumentURI()) {
934 doc->GetDocumentURI()->GetSpec(spec);
935 LOGFOCUS((" Hide Window: %s", spec.get()));
938 if (mFocusedWindow) {
939 doc = mFocusedWindow->GetExtantDoc();
940 if (doc && doc->GetDocumentURI()) {
941 doc->GetDocumentURI()->GetSpec(spec);
942 LOGFOCUS((" Focused Window: %s", spec.get()));
946 if (mActiveWindow) {
947 doc = mActiveWindow->GetExtantDoc();
948 if (doc && doc->GetDocumentURI()) {
949 doc->GetDocumentURI()->GetSpec(spec);
950 LOGFOCUS((" Active Window: %s", spec.get()));
954 #endif
956 if (!IsSameOrAncestor(window, mFocusedWindow))
957 return NS_OK;
959 // at this point, we know that the window being hidden is either the focused
960 // window, or an ancestor of the focused window. Either way, the focus is no
961 // longer valid, so it needs to be updated.
963 nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
965 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
966 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
968 if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) {
969 NotifyFocusStateChange(oldFocusedContent,
970 mFocusedWindow->ShouldShowFocusRing(),
971 false);
972 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
974 if (presShell) {
975 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
976 oldFocusedContent->GetComposedDoc(),
977 oldFocusedContent, 1, false);
981 nsPresContext* focusedPresContext =
982 presShell ? presShell->GetPresContext() : nullptr;
983 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
984 GetFocusMoveActionCause(0));
985 if (presShell) {
986 SetCaretVisible(presShell, false, nullptr);
989 // if the docshell being hidden is being destroyed, then we want to move
990 // focus somewhere else. Call ClearFocus on the toplevel window, which
991 // will have the effect of clearing the focus and moving the focused window
992 // to the toplevel window. But if the window isn't being destroyed, we are
993 // likely just loading a new document in it, so we want to maintain the
994 // focused window so that the new document gets properly focused.
995 bool beingDestroyed;
996 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
997 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
998 if (beingDestroyed) {
999 // There is usually no need to do anything if a toplevel window is going
1000 // away, as we assume that WindowLowered will be called. However, this may
1001 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1002 // a leak. So if the active window is being destroyed, call WindowLowered
1003 // directly.
1004 NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
1005 if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
1006 WindowLowered(mActiveWindow);
1007 else
1008 ClearFocus(mActiveWindow);
1009 return NS_OK;
1012 // if the window being hidden is an ancestor of the focused window, adjust
1013 // the focused window so that it points to the one being hidden. This
1014 // ensures that the focused window isn't in a chain of frames that doesn't
1015 // exist any more.
1016 if (window != mFocusedWindow) {
1017 nsCOMPtr<nsIDocShellTreeItem> dsti =
1018 mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
1019 if (dsti) {
1020 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1021 dsti->GetParent(getter_AddRefs(parentDsti));
1022 if (parentDsti) {
1023 nsCOMPtr<nsPIDOMWindow> parentWindow = parentDsti->GetWindow();
1024 if (parentWindow)
1025 parentWindow->SetFocusedNode(nullptr);
1029 SetFocusedWindowInternal(window);
1032 return NS_OK;
1035 NS_IMETHODIMP
1036 nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
1038 NS_ENSURE_ARG(aDocument);
1040 // fire any delayed focus and blur events in the same order that they were added
1041 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
1042 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
1043 if (!aDocument->GetInnerWindow() ||
1044 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
1045 // If the document was navigated away from or is defunct, don't bother
1046 // firing events on it. Note the symmetry between this condition and
1047 // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
1048 mDelayedBlurFocusEvents.RemoveElementAt(i);
1049 --i;
1050 } else if (!aDocument->EventHandlingSuppressed()) {
1051 uint32_t type = mDelayedBlurFocusEvents[i].mType;
1052 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1053 nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1054 mDelayedBlurFocusEvents.RemoveElementAt(i);
1055 SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false);
1056 --i;
1061 return NS_OK;
1064 NS_IMETHODIMP
1065 nsFocusManager::FocusPlugin(nsIContent* aContent)
1067 NS_ENSURE_ARG(aContent);
1068 SetFocusInner(aContent, 0, true, false);
1069 return NS_OK;
1072 NS_IMETHODIMP
1073 nsFocusManager::ParentActivated(nsIDOMWindow* aWindow, bool aActive)
1075 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
1076 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
1078 window = window->GetOuterWindow();
1080 mParentFocusType = aActive ? ParentFocusType_Active : ParentFocusType_Inactive;
1081 ActivateOrDeactivate(window, aActive);
1082 return NS_OK;
1085 /* static */
1086 void
1087 nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
1088 bool aWindowShouldShowFocusRing,
1089 bool aGettingFocus)
1091 if (!aContent->IsElement()) {
1092 return;
1094 EventStates eventState = NS_EVENT_STATE_FOCUS;
1095 if (aWindowShouldShowFocusRing) {
1096 eventState |= NS_EVENT_STATE_FOCUSRING;
1098 if (aGettingFocus) {
1099 aContent->AsElement()->AddStates(eventState);
1100 } else {
1101 aContent->AsElement()->RemoveStates(eventState);
1105 // static
1106 void
1107 nsFocusManager::EnsureCurrentWidgetFocused()
1109 if (!mFocusedWindow || sTestMode)
1110 return;
1112 // get the main child widget for the focused window and ensure that the
1113 // platform knows that this widget is focused.
1114 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1115 if (docShell) {
1116 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1117 if (presShell) {
1118 nsViewManager* vm = presShell->GetViewManager();
1119 if (vm) {
1120 nsCOMPtr<nsIWidget> widget;
1121 vm->GetRootWidget(getter_AddRefs(widget));
1122 if (widget)
1123 widget->SetFocus(false);
1129 void
1130 ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
1132 bool active = static_cast<bool>(aArg);
1133 unused << aParent->SendParentActivated(active);
1136 void
1137 nsFocusManager::ActivateOrDeactivate(nsPIDOMWindow* aWindow, bool aActive)
1139 if (!aWindow) {
1140 return;
1143 // Inform the DOM window that it has activated or deactivated, so that
1144 // the active attribute is updated on the window.
1145 aWindow->ActivateOrDeactivate(aActive);
1147 // Send the activate event.
1148 nsContentUtils::DispatchTrustedEvent(aWindow->GetExtantDoc(),
1149 aWindow,
1150 aActive ? NS_LITERAL_STRING("activate") :
1151 NS_LITERAL_STRING("deactivate"),
1152 true, true, nullptr);
1154 // Look for any remote child frames, iterate over them and send the activation notification.
1155 nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild,
1156 (void *)aActive);
1159 void
1160 nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
1161 bool aFocusChanged, bool aAdjustWidget)
1163 // if the element is not focusable, just return and leave the focus as is
1164 nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
1165 if (!contentToFocus)
1166 return;
1168 // check if the element to focus is a frame (iframe) containing a child
1169 // document. Frames are never directly focused; instead focusing a frame
1170 // means focus what is inside the frame. To do this, the descendant content
1171 // within the frame is retrieved and that will be focused instead.
1172 nsCOMPtr<nsPIDOMWindow> newWindow;
1173 nsCOMPtr<nsPIDOMWindow> subWindow = GetContentWindow(contentToFocus);
1174 if (subWindow) {
1175 contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow));
1176 // since a window is being refocused, clear aFocusChanged so that the
1177 // caret position isn't updated.
1178 aFocusChanged = false;
1181 // unless it was set above, retrieve the window for the element to focus
1182 if (!newWindow)
1183 newWindow = GetCurrentWindow(contentToFocus);
1185 // if the element is already focused, just return. Note that this happens
1186 // after the frame check above so that we compare the element that will be
1187 // focused rather than the frame it is in.
1188 if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
1189 return;
1191 // don't allow focus to be placed in docshells or descendants of docshells
1192 // that are being destroyed. Also, ensure that the page hasn't been
1193 // unloaded. The prevents content from being refocused during an unload event.
1194 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1195 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1196 while (docShell) {
1197 bool inUnload;
1198 docShell->GetIsInUnload(&inUnload);
1199 if (inUnload)
1200 return;
1202 bool beingDestroyed;
1203 docShell->IsBeingDestroyed(&beingDestroyed);
1204 if (beingDestroyed)
1205 return;
1207 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1208 docShell->GetParent(getter_AddRefs(parentDsti));
1209 docShell = do_QueryInterface(parentDsti);
1212 // if the new element is in the same window as the currently focused element
1213 bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1215 if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1216 nsContentUtils::IsHandlingKeyBoardEvent()) {
1217 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1218 do_QueryInterface(mFocusedWindow);
1219 nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1220 do_QueryInterface(newWindow);
1221 nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1222 nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1223 if (!focusedPrincipal || !newPrincipal) {
1224 return;
1226 bool subsumes = false;
1227 focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1228 if (!subsumes && !nsContentUtils::IsCallerChrome()) {
1229 NS_WARNING("Not allowed to focus the new window!");
1230 return;
1234 // to check if the new element is in the active window, compare the
1235 // new root docshell for the new element with the active window's docshell.
1236 bool isElementInActiveWindow = false;
1238 nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
1239 nsCOMPtr<nsPIDOMWindow> newRootWindow;
1240 if (dsti) {
1241 nsCOMPtr<nsIDocShellTreeItem> root;
1242 dsti->GetRootTreeItem(getter_AddRefs(root));
1243 newRootWindow = root ? root->GetWindow() : nullptr;
1245 isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1248 // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1249 // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1250 // so we can't display the "Press ESC to leave fullscreen mode" warning on
1251 // key input if a windowed plugin is focused, so just exit fullscreen
1252 // to guard against phishing.
1253 #ifndef XP_MACOSX
1254 nsIDocument* fullscreenAncestor;
1255 if (contentToFocus &&
1256 (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) &&
1257 nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
1258 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1259 NS_LITERAL_CSTRING("DOM"),
1260 contentToFocus->OwnerDoc(),
1261 nsContentUtils::eDOM_PROPERTIES,
1262 "FocusedWindowedPluginWhileFullScreen");
1263 nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true);
1265 #endif
1267 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1268 // shifted away from the current element if the new shell to focus is
1269 // the same or an ancestor shell of the currently focused shell.
1270 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1271 IsSameOrAncestor(newWindow, mFocusedWindow);
1273 // if the element is in the active window, frame switching is allowed and
1274 // the content is in a visible window, fire blur and focus events.
1275 bool sendFocusEvent =
1276 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1278 // When the following conditions are true:
1279 // * an element has focus
1280 // * isn't called by trusted event (i.e., called by untrusted event or by js)
1281 // * the focus is moved to another document's element
1282 // we need to check the permission.
1283 if (sendFocusEvent && mFocusedContent &&
1284 mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) {
1285 // If the caller cannot access the current focused node, the caller should
1286 // not be able to steal focus from it. E.g., When the current focused node
1287 // is in chrome, any web contents should not be able to steal the focus.
1288 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
1289 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1290 if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
1291 // However, while mouse button event is handling, the handling document's
1292 // script should be able to steal focus.
1293 domNode = do_QueryInterface(mMouseButtonEventHandlingDocument);
1294 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1298 LOGCONTENT("Shift Focus: %s", contentToFocus.get());
1299 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1300 aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()));
1301 LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
1302 isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
1304 if (sendFocusEvent) {
1305 // return if blurring fails or the focus changes during the blur
1306 if (mFocusedWindow) {
1307 // if the focus is being moved to another element in the same document,
1308 // or to a descendant, pass the existing window to Blur so that the
1309 // current node in the existing window is cleared. If moving to a
1310 // window elsewhere, we want to maintain the current node in the
1311 // window but still blur it.
1312 bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
1313 // find the common ancestor of the currently focused window and the new
1314 // window. The ancestor will need to have its currently focused node
1315 // cleared once the document has been blurred. Otherwise, we'll be in a
1316 // state where a document is blurred yet the chain of windows above it
1317 // still points to that document.
1318 // For instance, in the following frame tree:
1319 // A
1320 // B C
1321 // D
1322 // D is focused and we want to focus C. Once D has been blurred, we need
1323 // to clear out the focus in A, otherwise A would still maintain that B
1324 // was focused, and B that D was focused.
1325 nsCOMPtr<nsPIDOMWindow> commonAncestor;
1326 if (!isElementInFocusedWindow)
1327 commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1329 if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
1330 commonAncestor, !isElementInFocusedWindow, aAdjustWidget))
1331 return;
1334 Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
1335 aFocusChanged, false, aAdjustWidget);
1337 else {
1338 // otherwise, for inactive windows and when the caller cannot steal the
1339 // focus, update the node in the window, and raise the window if desired.
1340 if (allowFrameSwitch)
1341 AdjustWindowFocus(newWindow, true);
1343 // set the focus node and method as needed
1344 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1345 newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1346 newWindow->SetFocusedNode(contentToFocus, focusMethod);
1347 if (aFocusChanged) {
1348 nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1350 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1351 if (presShell)
1352 ScrollIntoView(presShell, contentToFocus, aFlags);
1355 // update the commands even when inactive so that the attributes for that
1356 // window are up to date.
1357 if (allowFrameSwitch)
1358 newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1360 if (aFlags & FLAG_RAISE)
1361 RaiseWindow(newRootWindow);
1365 bool
1366 nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor,
1367 nsPIDOMWindow* aWindow)
1369 if (!aWindow || !aPossibleAncestor) {
1370 return false;
1373 nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
1374 nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
1375 while (dsti) {
1376 if (dsti == ancestordsti)
1377 return true;
1378 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1379 dsti->GetParent(getter_AddRefs(parentDsti));
1380 dsti.swap(parentDsti);
1383 return false;
1386 already_AddRefed<nsPIDOMWindow>
1387 nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1,
1388 nsPIDOMWindow* aWindow2)
1390 NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
1392 nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
1393 NS_ENSURE_TRUE(dsti1, nullptr);
1395 nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
1396 NS_ENSURE_TRUE(dsti2, nullptr);
1398 nsAutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1399 do {
1400 parents1.AppendElement(dsti1);
1401 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1402 dsti1->GetParent(getter_AddRefs(parentDsti1));
1403 dsti1.swap(parentDsti1);
1404 } while (dsti1);
1405 do {
1406 parents2.AppendElement(dsti2);
1407 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1408 dsti2->GetParent(getter_AddRefs(parentDsti2));
1409 dsti2.swap(parentDsti2);
1410 } while (dsti2);
1412 uint32_t pos1 = parents1.Length();
1413 uint32_t pos2 = parents2.Length();
1414 nsIDocShellTreeItem* parent = nullptr;
1415 uint32_t len;
1416 for (len = std::min(pos1, pos2); len > 0; --len) {
1417 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1418 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1419 if (child1 != child2) {
1420 break;
1422 parent = child1;
1425 nsCOMPtr<nsPIDOMWindow> window = parent ? parent->GetWindow() : nullptr;
1426 return window.forget();
1429 void
1430 nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
1431 bool aCheckPermission)
1433 bool isVisible = IsWindowVisible(aWindow);
1435 nsCOMPtr<nsPIDOMWindow> window(aWindow);
1436 while (window) {
1437 // get the containing <iframe> or equivalent element so that it can be
1438 // focused below.
1439 nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
1441 nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
1442 if (!dsti)
1443 return;
1444 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1445 dsti->GetParent(getter_AddRefs(parentDsti));
1446 if (!parentDsti) {
1447 return;
1450 window = parentDsti->GetWindow();
1451 if (window) {
1452 // if the parent window is visible but aWindow was not, then we have
1453 // likely moved up and out from a hidden tab to the browser window, or a
1454 // similar such arrangement. Stop adjusting the current nodes.
1455 if (IsWindowVisible(window) != isVisible)
1456 break;
1458 // When aCheckPermission is true, we should check whether the caller can
1459 // access the window or not. If it cannot access, we should stop the
1460 // adjusting.
1461 if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
1462 break;
1464 window->SetFocusedNode(frameElement);
1469 bool
1470 nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
1472 if (!aWindow || aWindow->IsFrozen())
1473 return false;
1475 // Check if the inner window is frozen as well. This can happen when a focus change
1476 // occurs while restoring a previous page.
1477 nsPIDOMWindow* innerWindow = aWindow->GetCurrentInnerWindow();
1478 if (!innerWindow || innerWindow->IsFrozen())
1479 return false;
1481 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1482 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1483 if (!baseWin)
1484 return false;
1486 bool visible = false;
1487 baseWin->GetVisibility(&visible);
1488 return visible;
1491 bool
1492 nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
1494 NS_PRECONDITION(aContent, "aContent must not be NULL");
1495 NS_PRECONDITION(aContent->IsInComposedDoc(), "aContent must be in a document");
1497 // If aContent is in designMode, the root element is not focusable.
1498 // NOTE: in designMode, most elements are not focusable, just the document is
1499 // focusable.
1500 // Also, if aContent is not editable but it isn't in designMode, it's not
1501 // focusable.
1502 // And in userfocusignored context nothing is focusable.
1503 nsIDocument* doc = aContent->GetComposedDoc();
1504 NS_ASSERTION(doc, "aContent must have current document");
1505 return aContent == doc->GetRootElement() &&
1506 (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
1507 nsContentUtils::IsUserFocusIgnored(aContent));
1510 nsIContent*
1511 nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
1513 if (!aContent)
1514 return nullptr;
1516 // this is a special case for some XUL elements where an anonymous child is
1517 // actually focusable and not the element itself.
1518 nsIContent* redirectedFocus = GetRedirectedFocus(aContent);
1519 if (redirectedFocus)
1520 return CheckIfFocusable(redirectedFocus, aFlags);
1522 nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
1523 // can't focus elements that are not in documents
1524 if (!doc) {
1525 LOGCONTENT("Cannot focus %s because content not in document", aContent)
1526 return nullptr;
1529 // Make sure that our frames are up to date
1530 doc->FlushPendingNotifications(Flush_Layout);
1532 nsIPresShell *shell = doc->GetShell();
1533 if (!shell)
1534 return nullptr;
1536 // the root content can always be focused,
1537 // except in userfocusignored context.
1538 if (aContent == doc->GetRootElement())
1539 return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
1541 // cannot focus content in print preview mode. Only the root can be focused.
1542 nsPresContext* presContext = shell->GetPresContext();
1543 if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
1544 LOGCONTENT("Cannot focus %s while in print preview", aContent)
1545 return nullptr;
1548 nsIFrame* frame = aContent->GetPrimaryFrame();
1549 if (!frame) {
1550 LOGCONTENT("Cannot focus %s as it has no frame", aContent)
1551 return nullptr;
1554 if (aContent->Tag() == nsGkAtoms::area && aContent->IsHTML()) {
1555 // HTML areas do not have their own frame, and the img frame we get from
1556 // GetPrimaryFrame() is not relevant as to whether it is focusable or
1557 // not, so we have to do all the relevant checks manually for them.
1558 return frame->IsVisibleConsideringAncestors() &&
1559 aContent->IsFocusable() ? aContent : nullptr;
1562 // if this is a child frame content node, check if it is visible and
1563 // call the content node's IsFocusable method instead of the frame's
1564 // IsFocusable method. This skips checking the style system and ensures that
1565 // offscreen browsers can still be focused.
1566 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
1567 if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1568 const nsStyleUserInterface* ui = frame->StyleUserInterface();
1569 int32_t tabIndex = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE ||
1570 ui->mUserFocus == NS_STYLE_USER_FOCUS_NONE) ? -1 : 0;
1571 return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1574 return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1577 bool
1578 nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
1579 nsPIDOMWindow* aAncestorWindowToFocus,
1580 bool aIsLeavingDocument,
1581 bool aAdjustWidgets)
1583 LOGFOCUS(("<<Blur begin>>"));
1585 // hold a reference to the focused content, which may be null
1586 nsCOMPtr<nsIContent> content = mFocusedContent;
1587 if (content) {
1588 if (!content->IsInComposedDoc()) {
1589 mFocusedContent = nullptr;
1590 return true;
1592 if (content == mFirstBlurEvent)
1593 return true;
1596 // hold a reference to the focused window
1597 nsCOMPtr<nsPIDOMWindow> window = mFocusedWindow;
1598 if (!window) {
1599 mFocusedContent = nullptr;
1600 return true;
1603 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1604 if (!docShell) {
1605 mFocusedContent = nullptr;
1606 return true;
1609 // Keep a ref to presShell since dispatching the DOM event may cause
1610 // the document to be destroyed.
1611 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1612 if (!presShell) {
1613 mFocusedContent = nullptr;
1614 return true;
1617 bool clearFirstBlurEvent = false;
1618 if (!mFirstBlurEvent) {
1619 mFirstBlurEvent = content;
1620 clearFirstBlurEvent = true;
1623 nsPresContext* focusedPresContext =
1624 mActiveWindow ? presShell->GetPresContext() : nullptr;
1625 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1626 GetFocusMoveActionCause(0));
1628 // now adjust the actual focus, by clearing the fields in the focus manager
1629 // and in the window.
1630 mFocusedContent = nullptr;
1631 bool shouldShowFocusRing = window->ShouldShowFocusRing();
1632 if (aWindowToClear)
1633 aWindowToClear->SetFocusedNode(nullptr);
1635 LOGCONTENT("Element %s has been blurred", content.get());
1637 // Don't fire blur event on the root content which isn't editable.
1638 bool sendBlurEvent =
1639 content && content->IsInComposedDoc() && !IsNonFocusableRoot(content);
1640 if (content) {
1641 if (sendBlurEvent) {
1642 NotifyFocusStateChange(content, shouldShowFocusRing, false);
1645 // if an object/plug-in/remote browser is being blurred, move the system focus
1646 // to the parent window, otherwise events will still get fired at the plugin.
1647 // But don't do this if we are blurring due to the window being lowered,
1648 // otherwise, the parent window can get raised again.
1649 if (mActiveWindow) {
1650 nsIFrame* contentFrame = content->GetPrimaryFrame();
1651 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1652 if (aAdjustWidgets && objectFrame && !sTestMode) {
1653 // note that the presshell's widget is being retrieved here, not the one
1654 // for the object frame.
1655 nsViewManager* vm = presShell->GetViewManager();
1656 if (vm) {
1657 nsCOMPtr<nsIWidget> widget;
1658 vm->GetRootWidget(getter_AddRefs(widget));
1659 if (widget)
1660 widget->SetFocus(false);
1665 // if the object being blurred is a remote browser, deactivate remote content
1666 if (TabParent* remote = TabParent::GetFrom(content)) {
1667 remote->Deactivate();
1668 LOGFOCUS(("Remote browser deactivated"));
1672 bool result = true;
1673 if (sendBlurEvent) {
1674 // if there is an active window, update commands. If there isn't an active
1675 // window, then this was a blur caused by the active window being lowered,
1676 // so there is no need to update the commands
1677 if (mActiveWindow)
1678 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1680 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
1681 content->GetComposedDoc(), content, 1, false);
1684 // if we are leaving the document or the window was lowered, make the caret
1685 // invisible.
1686 if (aIsLeavingDocument || !mActiveWindow) {
1687 SetCaretVisible(presShell, false, nullptr);
1690 nsRefPtr<SelectionCarets> selectionCarets = presShell->GetSelectionCarets();
1691 if (selectionCarets) {
1692 selectionCarets->NotifyBlur(aIsLeavingDocument || !mActiveWindow);
1695 // at this point, it is expected that this window will be still be
1696 // focused, but the focused content will be null, as it was cleared before
1697 // the event. If this isn't the case, then something else was focused during
1698 // the blur event above and we should just return. However, if
1699 // aIsLeavingDocument is set, a new document is desired, so make sure to
1700 // blur the document and window.
1701 if (mFocusedWindow != window ||
1702 (mFocusedContent != nullptr && !aIsLeavingDocument)) {
1703 result = false;
1705 else if (aIsLeavingDocument) {
1706 window->TakeFocus(false, 0);
1708 // clear the focus so that the ancestor frame hierarchy is in the correct
1709 // state. Pass true because aAncestorWindowToFocus is thought to be
1710 // focused at this point.
1711 if (aAncestorWindowToFocus)
1712 aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
1714 SetFocusedWindowInternal(nullptr);
1715 mFocusedContent = nullptr;
1717 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1718 // that the check is made for suppressed documents. Check to ensure that
1719 // the document isn't null in case someone closed it during the blur above
1720 nsIDocument* doc = window->GetExtantDoc();
1721 if (doc)
1722 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, doc, 1, false);
1723 if (mFocusedWindow == nullptr)
1724 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, window, 1, false);
1726 // check if a different window was focused
1727 result = (mFocusedWindow == nullptr && mActiveWindow);
1729 else if (mActiveWindow) {
1730 // Otherwise, the blur of the element without blurring the document
1731 // occurred normally. Call UpdateCaret to redisplay the caret at the right
1732 // location within the document. This is needed to ensure that the caret
1733 // used for caret browsing is made visible again when an input field is
1734 // blurred.
1735 UpdateCaret(false, true, nullptr);
1738 if (clearFirstBlurEvent)
1739 mFirstBlurEvent = nullptr;
1741 return result;
1744 void
1745 nsFocusManager::Focus(nsPIDOMWindow* aWindow,
1746 nsIContent* aContent,
1747 uint32_t aFlags,
1748 bool aIsNewDocument,
1749 bool aFocusChanged,
1750 bool aWindowRaised,
1751 bool aAdjustWidgets)
1753 LOGFOCUS(("<<Focus begin>>"));
1755 if (!aWindow)
1756 return;
1758 if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
1759 return;
1761 // Keep a reference to the presShell since dispatching the DOM event may
1762 // cause the document to be destroyed.
1763 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1764 if (!docShell)
1765 return;
1767 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1768 if (!presShell)
1769 return;
1771 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1772 // Otherwise, just get the current focus method and use that. This ensures
1773 // that the method is set during the document and window focus events.
1774 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1775 aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1777 if (!IsWindowVisible(aWindow)) {
1778 // if the window isn't visible, for instance because it is a hidden tab,
1779 // update the current focus and scroll it into view but don't do anything else
1780 if (CheckIfFocusable(aContent, aFlags)) {
1781 aWindow->SetFocusedNode(aContent, focusMethod);
1782 if (aFocusChanged)
1783 ScrollIntoView(presShell, aContent, aFlags);
1785 return;
1788 bool clearFirstFocusEvent = false;
1789 if (!mFirstFocusEvent) {
1790 mFirstFocusEvent = aContent;
1791 clearFirstFocusEvent = true;
1794 #ifdef PR_LOGGING
1795 LOGCONTENT("Element %s has been focused", aContent);
1797 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
1798 nsIDocument* docm = aWindow->GetExtantDoc();
1799 if (docm) {
1800 LOGCONTENT(" from %s", docm->GetRootElement());
1802 LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
1803 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
1805 #endif
1807 if (aIsNewDocument) {
1808 // if this is a new document, update the parent chain of frames so that
1809 // focus can be traversed from the top level down to the newly focused
1810 // window.
1811 AdjustWindowFocus(aWindow, false);
1813 // Update the window touch registration to reflect the state of
1814 // the new document that got focus
1815 aWindow->UpdateTouchState();
1818 // indicate that the window has taken focus.
1819 if (aWindow->TakeFocus(true, focusMethod))
1820 aIsNewDocument = true;
1822 SetFocusedWindowInternal(aWindow);
1824 // Update the system focus by focusing the root widget. But avoid this
1825 // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
1826 // own widget and is either already focused or is about to be focused.
1827 nsCOMPtr<nsIWidget> objectFrameWidget;
1828 if (aContent) {
1829 nsIFrame* contentFrame = aContent->GetPrimaryFrame();
1830 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1831 if (objectFrame)
1832 objectFrameWidget = objectFrame->GetWidget();
1834 if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
1835 nsViewManager* vm = presShell->GetViewManager();
1836 if (vm) {
1837 nsCOMPtr<nsIWidget> widget;
1838 vm->GetRootWidget(getter_AddRefs(widget));
1839 if (widget)
1840 widget->SetFocus(false);
1844 // if switching to a new document, first fire the focus event on the
1845 // document and then the window.
1846 if (aIsNewDocument) {
1847 nsIDocument* doc = aWindow->GetExtantDoc();
1848 // The focus change should be notified to IMEStateManager from here if
1849 // the focused content is a designMode editor since any content won't
1850 // receive focus event.
1851 if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
1852 IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
1853 GetFocusMoveActionCause(aFlags));
1855 if (doc)
1856 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1857 doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1858 if (mFocusedWindow == aWindow && mFocusedContent == nullptr)
1859 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1860 aWindow, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1863 // check to ensure that the element is still focusable, and that nothing
1864 // else was focused during the events above.
1865 if (CheckIfFocusable(aContent, aFlags) &&
1866 mFocusedWindow == aWindow && mFocusedContent == nullptr) {
1867 mFocusedContent = aContent;
1869 nsIContent* focusedNode = aWindow->GetFocusedNode();
1870 bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
1872 aWindow->SetFocusedNode(aContent, focusMethod);
1874 bool sendFocusEvent =
1875 aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
1876 nsPresContext* presContext = presShell->GetPresContext();
1877 if (sendFocusEvent) {
1878 // if the focused element changed, scroll it into view
1879 if (aFocusChanged)
1880 ScrollIntoView(presShell, aContent, aFlags);
1882 NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
1884 // if this is an object/plug-in/remote browser, focus its widget. Note that we might
1885 // no longer be in the same document, due to the events we fired above when
1886 // aIsNewDocument.
1887 if (presShell->GetDocument() == aContent->GetComposedDoc()) {
1888 if (aAdjustWidgets && objectFrameWidget && !sTestMode)
1889 objectFrameWidget->SetFocus(false);
1891 // if the object being focused is a remote browser, activate remote content
1892 if (TabParent* remote = TabParent::GetFrom(aContent)) {
1893 remote->Activate();
1894 LOGFOCUS(("Remote browser activated"));
1898 IMEStateManager::OnChangeFocus(presContext, aContent,
1899 GetFocusMoveActionCause(aFlags));
1901 // as long as this focus wasn't because a window was raised, update the
1902 // commands
1903 // XXXndeakin P2 someone could adjust the focus during the update
1904 if (!aWindowRaised)
1905 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1907 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell,
1908 aContent->GetComposedDoc(),
1909 aContent, aFlags & FOCUSMETHOD_MASK,
1910 aWindowRaised, isRefocus);
1911 } else {
1912 IMEStateManager::OnChangeFocus(presContext, nullptr,
1913 GetFocusMoveActionCause(aFlags));
1914 if (!aWindowRaised) {
1915 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1919 else {
1920 // If the window focus event (fired above when aIsNewDocument) caused
1921 // the plugin not to be focusable, update the system focus by focusing
1922 // the root widget.
1923 if (aAdjustWidgets && objectFrameWidget &&
1924 mFocusedWindow == aWindow && mFocusedContent == nullptr &&
1925 !sTestMode) {
1926 nsViewManager* vm = presShell->GetViewManager();
1927 if (vm) {
1928 nsCOMPtr<nsIWidget> widget;
1929 vm->GetRootWidget(getter_AddRefs(widget));
1930 if (widget)
1931 widget->SetFocus(false);
1935 nsPresContext* presContext = presShell->GetPresContext();
1936 IMEStateManager::OnChangeFocus(presContext, nullptr,
1937 GetFocusMoveActionCause(aFlags));
1939 if (!aWindowRaised)
1940 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1943 // update the caret visibility and position to match the newly focused
1944 // element. However, don't update the position if this was a focus due to a
1945 // mouse click as the selection code would already have moved the caret as
1946 // needed. If this is a different document than was focused before, also
1947 // update the caret's visibility. If this is the same document, the caret
1948 // visibility should be the same as before so there is no need to update it.
1949 if (mFocusedContent == aContent)
1950 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
1951 mFocusedContent);
1953 if (clearFirstFocusEvent)
1954 mFirstFocusEvent = nullptr;
1957 class FocusBlurEvent : public nsRunnable
1959 public:
1960 FocusBlurEvent(nsISupports* aTarget, uint32_t aType,
1961 nsPresContext* aContext, bool aWindowRaised,
1962 bool aIsRefocus)
1963 : mTarget(aTarget), mType(aType), mContext(aContext),
1964 mWindowRaised(aWindowRaised), mIsRefocus(aIsRefocus) {}
1966 NS_IMETHOD Run()
1968 InternalFocusEvent event(true, mType);
1969 event.mFlags.mBubbles = false;
1970 event.fromRaise = mWindowRaised;
1971 event.isRefocus = mIsRefocus;
1972 return EventDispatcher::Dispatch(mTarget, mContext, &event);
1975 nsCOMPtr<nsISupports> mTarget;
1976 uint32_t mType;
1977 nsRefPtr<nsPresContext> mContext;
1978 bool mWindowRaised;
1979 bool mIsRefocus;
1982 void
1983 nsFocusManager::SendFocusOrBlurEvent(uint32_t aType,
1984 nsIPresShell* aPresShell,
1985 nsIDocument* aDocument,
1986 nsISupports* aTarget,
1987 uint32_t aFocusMethod,
1988 bool aWindowRaised,
1989 bool aIsRefocus)
1991 NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
1992 "Wrong event type for SendFocusOrBlurEvent");
1994 nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
1996 nsCOMPtr<nsINode> n = do_QueryInterface(aTarget);
1997 if (!n) {
1998 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aTarget);
1999 n = win ? win->GetExtantDoc() : nullptr;
2001 bool dontDispatchEvent = n && nsContentUtils::IsUserFocusIgnored(n);
2003 // for focus events, if this event was from a mouse or key and event
2004 // handling on the document is suppressed, queue the event and fire it
2005 // later. For blur events, a non-zero value would be set for aFocusMethod.
2006 if (aFocusMethod && !dontDispatchEvent &&
2007 aDocument && aDocument->EventHandlingSuppressed()) {
2008 // aFlags is always 0 when aWindowRaised is true so this won't be called
2009 // on a window raise.
2010 NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
2012 for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
2013 // if this event was already queued, remove it and append it to the end
2014 if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
2015 mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
2016 mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
2017 mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget) {
2018 mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
2022 mDelayedBlurFocusEvents.AppendElement(
2023 nsDelayedBlurOrFocusEvent(aType, aPresShell, aDocument, eventTarget));
2024 return;
2027 #ifdef ACCESSIBILITY
2028 nsAccessibilityService* accService = GetAccService();
2029 if (accService) {
2030 if (aType == NS_FOCUS_CONTENT)
2031 accService->NotifyOfDOMFocus(aTarget);
2032 else
2033 accService->NotifyOfDOMBlur(aTarget);
2035 #endif
2037 if (!dontDispatchEvent) {
2038 nsContentUtils::AddScriptRunner(
2039 new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
2040 aWindowRaised, aIsRefocus));
2044 void
2045 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
2046 nsIContent* aContent,
2047 uint32_t aFlags)
2049 // if the noscroll flag isn't set, scroll the newly focused element into view
2050 if (!(aFlags & FLAG_NOSCROLL))
2051 aPresShell->ScrollContentIntoView(aContent,
2052 nsIPresShell::ScrollAxis(
2053 nsIPresShell::SCROLL_MINIMUM,
2054 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
2055 nsIPresShell::ScrollAxis(
2056 nsIPresShell::SCROLL_MINIMUM,
2057 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
2058 nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
2062 void
2063 nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
2065 // don't raise windows that are already raised or are in the process of
2066 // being lowered
2067 if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
2068 return;
2070 if (sTestMode) {
2071 // In test mode, emulate the existing window being lowered and the new
2072 // window being raised.
2073 if (mActiveWindow)
2074 WindowLowered(mActiveWindow);
2075 WindowRaised(aWindow);
2076 return;
2079 #if defined(XP_WIN)
2080 // Windows would rather we focus the child widget, otherwise, the toplevel
2081 // widget will always end up being focused. Fortunately, focusing the child
2082 // widget will also have the effect of raising the window this widget is in.
2083 // But on other platforms, we can just focus the toplevel widget to raise
2084 // the window.
2085 nsCOMPtr<nsPIDOMWindow> childWindow;
2086 GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
2087 if (!childWindow)
2088 childWindow = aWindow;
2090 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2091 if (!docShell)
2092 return;
2094 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
2095 if (!presShell)
2096 return;
2098 nsViewManager* vm = presShell->GetViewManager();
2099 if (vm) {
2100 nsCOMPtr<nsIWidget> widget;
2101 vm->GetRootWidget(getter_AddRefs(widget));
2102 if (widget)
2103 widget->SetFocus(true);
2105 #else
2106 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
2107 do_QueryInterface(aWindow->GetDocShell());
2108 if (treeOwnerAsWin) {
2109 nsCOMPtr<nsIWidget> widget;
2110 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
2111 if (widget)
2112 widget->SetFocus(true);
2114 #endif
2117 void
2118 nsFocusManager::UpdateCaretForCaretBrowsingMode()
2120 UpdateCaret(false, true, mFocusedContent);
2123 void
2124 nsFocusManager::UpdateCaret(bool aMoveCaretToFocus,
2125 bool aUpdateVisibility,
2126 nsIContent* aContent)
2128 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
2130 if (!mFocusedWindow)
2131 return;
2133 // this is called when a document is focused or when the caretbrowsing
2134 // preference is changed
2135 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
2136 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
2137 if (!dsti)
2138 return;
2140 if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
2141 return; // Never browse with caret in chrome
2144 bool browseWithCaret =
2145 Preferences::GetBool("accessibility.browsewithcaret");
2147 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
2148 if (!presShell)
2149 return;
2151 // If this is an editable document which isn't contentEditable, or a
2152 // contentEditable document and the node to focus is contentEditable,
2153 // return, so that we don't mess with caret visibility.
2154 bool isEditable = false;
2155 focusedDocShell->GetEditable(&isEditable);
2157 if (isEditable) {
2158 nsCOMPtr<nsIHTMLDocument> doc =
2159 do_QueryInterface(presShell->GetDocument());
2161 bool isContentEditableDoc =
2162 doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
2164 bool isFocusEditable =
2165 aContent && aContent->HasFlag(NODE_IS_EDITABLE);
2166 if (!isContentEditableDoc || isFocusEditable)
2167 return;
2170 if (!isEditable && aMoveCaretToFocus)
2171 MoveCaretToFocus(presShell, aContent);
2173 if (!aUpdateVisibility)
2174 return;
2176 // XXXndeakin this doesn't seem right. It should be checking for this only
2177 // on the nearest ancestor frame which is a chrome frame. But this is
2178 // what the existing code does, so just leave it for now.
2179 if (!browseWithCaret) {
2180 nsCOMPtr<Element> docElement =
2181 mFocusedWindow->GetFrameElementInternal();
2182 if (docElement)
2183 browseWithCaret = docElement->AttrValueIs(kNameSpaceID_None,
2184 nsGkAtoms::showcaret,
2185 NS_LITERAL_STRING("true"),
2186 eCaseMatters);
2189 SetCaretVisible(presShell, browseWithCaret, aContent);
2192 void
2193 nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
2195 // domDoc is a document interface we can create a range with
2196 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument());
2197 if (domDoc) {
2198 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2199 nsCOMPtr<nsISelection> domSelection = frameSelection->
2200 GetSelection(nsISelectionController::SELECTION_NORMAL);
2201 if (domSelection) {
2202 nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
2203 // First clear the selection. This way, if there is no currently focused
2204 // content, the selection will just be cleared.
2205 domSelection->RemoveAllRanges();
2206 if (currentFocusNode) {
2207 nsCOMPtr<nsIDOMRange> newRange;
2208 nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange));
2209 if (NS_SUCCEEDED(rv)) {
2210 // Set the range to the start of the currently focused node
2211 // Make sure it's collapsed
2212 newRange->SelectNodeContents(currentFocusNode);
2213 nsCOMPtr<nsIDOMNode> firstChild;
2214 currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
2215 if (!firstChild ||
2216 aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
2217 // If current focus node is a leaf, set range to before the
2218 // node by using the parent as a container.
2219 // This prevents it from appearing as selected.
2220 newRange->SetStartBefore(currentFocusNode);
2221 newRange->SetEndBefore(currentFocusNode);
2223 domSelection->AddRange(newRange);
2224 domSelection->CollapseToStart();
2231 nsresult
2232 nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
2233 bool aVisible,
2234 nsIContent* aContent)
2236 // When browsing with caret, make sure caret is visible after new focus
2237 // Return early if there is no caret. This can happen for the testcase
2238 // for bug 308025 where a window is closed in a blur handler.
2239 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
2240 if (!caret)
2241 return NS_OK;
2243 bool caretVisible = caret->IsVisible();
2244 if (!aVisible && !caretVisible)
2245 return NS_OK;
2247 nsRefPtr<nsFrameSelection> frameSelection;
2248 if (aContent) {
2249 NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
2250 "Wrong document?");
2251 nsIFrame *focusFrame = aContent->GetPrimaryFrame();
2252 if (focusFrame)
2253 frameSelection = focusFrame->GetFrameSelection();
2256 nsRefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
2258 if (docFrameSelection && caret &&
2259 (frameSelection == docFrameSelection || !aContent)) {
2260 nsISelection* domSelection = docFrameSelection->
2261 GetSelection(nsISelectionController::SELECTION_NORMAL);
2262 if (domSelection) {
2263 nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
2264 if (!selCon) {
2265 return NS_ERROR_FAILURE;
2267 // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
2268 selCon->SetCaretEnabled(false);
2270 // Caret must blink on non-editable elements
2271 caret->SetIgnoreUserModify(true);
2272 // Tell the caret which selection to use
2273 caret->SetSelection(domSelection);
2275 // In content, we need to set the caret. The only special case is edit
2276 // fields, which have a different frame selection from the document.
2277 // They will take care of making the caret visible themselves.
2279 selCon->SetCaretReadOnly(false);
2280 selCon->SetCaretEnabled(aVisible);
2284 return NS_OK;
2287 nsresult
2288 nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
2289 nsIPresShell* aPresShell,
2290 nsIContent **aStartContent,
2291 nsIContent **aEndContent)
2293 *aStartContent = *aEndContent = nullptr;
2294 nsresult rv = NS_ERROR_FAILURE;
2296 nsPresContext* presContext = aPresShell->GetPresContext();
2297 NS_ASSERTION(presContext, "mPresContent is null!!");
2299 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2301 nsCOMPtr<nsISelection> domSelection;
2302 if (frameSelection) {
2303 domSelection = frameSelection->
2304 GetSelection(nsISelectionController::SELECTION_NORMAL);
2307 nsCOMPtr<nsIDOMNode> startNode, endNode;
2308 bool isCollapsed = false;
2309 nsCOMPtr<nsIContent> startContent, endContent;
2310 int32_t startOffset = 0;
2311 if (domSelection) {
2312 domSelection->GetIsCollapsed(&isCollapsed);
2313 nsCOMPtr<nsIDOMRange> domRange;
2314 rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
2315 if (domRange) {
2316 domRange->GetStartContainer(getter_AddRefs(startNode));
2317 domRange->GetEndContainer(getter_AddRefs(endNode));
2318 domRange->GetStartOffset(&startOffset);
2320 nsIContent *childContent = nullptr;
2322 startContent = do_QueryInterface(startNode);
2323 if (startContent && startContent->IsElement()) {
2324 NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
2325 childContent = startContent->GetChildAt(startOffset);
2326 if (childContent) {
2327 startContent = childContent;
2331 endContent = do_QueryInterface(endNode);
2332 if (endContent && endContent->IsElement()) {
2333 int32_t endOffset = 0;
2334 domRange->GetEndOffset(&endOffset);
2335 NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
2336 childContent = endContent->GetChildAt(endOffset);
2337 if (childContent) {
2338 endContent = childContent;
2343 else {
2344 rv = NS_ERROR_INVALID_ARG;
2347 nsIFrame *startFrame = nullptr;
2348 if (startContent) {
2349 startFrame = startContent->GetPrimaryFrame();
2350 if (isCollapsed) {
2351 // Next check to see if our caret is at the very end of a node
2352 // If so, the caret is actually sitting in front of the next
2353 // logical frame's primary node - so for this case we need to
2354 // change caretContent to that node.
2356 if (startContent->NodeType() == nsIDOMNode::TEXT_NODE) {
2357 nsAutoString nodeValue;
2358 startContent->AppendTextTo(nodeValue);
2360 bool isFormControl =
2361 startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2363 if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
2364 startContent != aDocument->GetRootElement()) {
2365 // Yes, indeed we were at the end of the last node
2366 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2367 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2368 presContext, startFrame,
2369 eLeaf,
2370 false, // aVisual
2371 false, // aLockInScrollView
2372 true // aFollowOOFs
2374 NS_ENSURE_SUCCESS(rv, rv);
2376 nsIFrame *newCaretFrame = nullptr;
2377 nsCOMPtr<nsIContent> newCaretContent = startContent;
2378 bool endOfSelectionInStartNode(startContent == endContent);
2379 do {
2380 // Continue getting the next frame until the primary content for the frame
2381 // we are on changes - we don't want to be stuck in the same place
2382 frameTraversal->Next();
2383 newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2384 if (nullptr == newCaretFrame)
2385 break;
2386 newCaretContent = newCaretFrame->GetContent();
2387 } while (!newCaretContent || newCaretContent == startContent);
2389 if (newCaretFrame && newCaretContent) {
2390 // If the caret is exactly at the same position of the new frame,
2391 // then we can use the newCaretFrame and newCaretContent for our position
2392 nsRect caretRect;
2393 nsIFrame *frame = nsCaret::GetGeometry(domSelection, &caretRect);
2394 if (frame) {
2395 nsPoint caretWidgetOffset;
2396 nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
2397 caretRect.MoveBy(caretWidgetOffset);
2398 nsPoint newCaretOffset;
2399 nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
2400 if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2401 caretRect.x == newCaretOffset.x) {
2402 // The caret is at the start of the new element.
2403 startFrame = newCaretFrame;
2404 startContent = newCaretContent;
2405 if (endOfSelectionInStartNode) {
2406 endContent = newCaretContent; // Ensure end of selection is not before start
2416 *aStartContent = startContent;
2417 *aEndContent = endContent;
2418 NS_IF_ADDREF(*aStartContent);
2419 NS_IF_ADDREF(*aEndContent);
2421 return rv;
2424 nsresult
2425 nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
2426 nsIContent* aStartContent,
2427 int32_t aType, bool aNoParentTraversal,
2428 nsIContent** aNextContent)
2430 *aNextContent = nullptr;
2432 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2433 if (!docShell)
2434 return NS_OK;
2436 nsCOMPtr<nsIContent> startContent = aStartContent;
2437 if (!startContent && aType != MOVEFOCUS_CARET) {
2438 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
2439 // When moving between documents, make sure to get the right
2440 // starting content in a descendant.
2441 nsCOMPtr<nsPIDOMWindow> focusedWindow;
2442 startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
2444 else {
2445 startContent = aWindow->GetFocusedNode();
2449 nsCOMPtr<nsIDocument> doc;
2450 if (startContent)
2451 doc = startContent->GetComposedDoc();
2452 else
2453 doc = aWindow->GetExtantDoc();
2454 if (!doc)
2455 return NS_OK;
2457 LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
2458 &nsIContent::sTabFocusModel);
2460 if (aType == MOVEFOCUS_ROOT) {
2461 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
2462 return NS_OK;
2464 if (aType == MOVEFOCUS_FORWARDDOC) {
2465 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true));
2466 return NS_OK;
2468 if (aType == MOVEFOCUS_BACKWARDDOC) {
2469 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false));
2470 return NS_OK;
2473 nsIContent* rootContent = doc->GetRootElement();
2474 NS_ENSURE_TRUE(rootContent, NS_OK);
2476 nsIPresShell *presShell = doc->GetShell();
2477 NS_ENSURE_TRUE(presShell, NS_OK);
2479 if (aType == MOVEFOCUS_FIRST) {
2480 if (!aStartContent)
2481 startContent = rootContent;
2482 return GetNextTabbableContent(presShell, startContent,
2483 nullptr, startContent,
2484 true, 1, false, aNextContent);
2486 if (aType == MOVEFOCUS_LAST) {
2487 if (!aStartContent)
2488 startContent = rootContent;
2489 return GetNextTabbableContent(presShell, startContent,
2490 nullptr, startContent,
2491 false, 0, false, aNextContent);
2494 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
2495 bool doNavigation = true;
2496 bool ignoreTabIndex = false;
2497 // when a popup is open, we want to ensure that tab navigation occurs only
2498 // within the most recently opened panel. If a popup is open, its frame will
2499 // be stored in popupFrame.
2500 nsIFrame* popupFrame = nullptr;
2502 int32_t tabIndex = forward ? 1 : 0;
2503 if (startContent) {
2504 nsIFrame* frame = startContent->GetPrimaryFrame();
2505 if (startContent->Tag() == nsGkAtoms::area &&
2506 startContent->IsHTML())
2507 startContent->IsFocusable(&tabIndex);
2508 else if (frame)
2509 frame->IsFocusable(&tabIndex, 0);
2510 else
2511 startContent->IsFocusable(&tabIndex);
2513 // if the current element isn't tabbable, ignore the tabindex and just
2514 // look for the next element. The root content won't have a tabindex
2515 // so just treat this as the beginning of the tab order.
2516 if (tabIndex < 0) {
2517 tabIndex = 1;
2518 if (startContent != rootContent)
2519 ignoreTabIndex = true;
2522 // check if the focus is currently inside a popup. Elements such as the
2523 // autocomplete widget use the noautofocus attribute to allow the focus to
2524 // remain outside the popup when it is opened.
2525 if (frame) {
2526 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2527 nsGkAtoms::menuPopupFrame);
2530 if (popupFrame) {
2531 // Don't navigate outside of a popup, so pretend that the
2532 // root content is the popup itself
2533 rootContent = popupFrame->GetContent();
2534 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2536 else if (!forward) {
2537 // If focus moves backward and when current focused node is root
2538 // content or <body> element which is editable by contenteditable
2539 // attribute, focus should move to its parent document.
2540 if (startContent == rootContent) {
2541 doNavigation = false;
2542 } else {
2543 nsIDocument* doc = startContent->GetComposedDoc();
2544 if (startContent ==
2545 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2546 doNavigation = false;
2551 else {
2552 #ifdef MOZ_XUL
2553 if (aType != MOVEFOCUS_CARET) {
2554 // if there is no focus, yet a panel is open, focus the first item in
2555 // the panel
2556 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2557 if (pm)
2558 popupFrame = pm->GetTopPopup(ePopupTypePanel);
2560 #endif
2561 if (popupFrame) {
2562 rootContent = popupFrame->GetContent();
2563 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2564 startContent = rootContent;
2566 else {
2567 // Otherwise, for content shells, start from the location of the caret.
2568 if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
2569 nsCOMPtr<nsIContent> endSelectionContent;
2570 GetSelectionLocation(doc, presShell,
2571 getter_AddRefs(startContent),
2572 getter_AddRefs(endSelectionContent));
2573 // If the selection is on the rootContent, then there is no selection
2574 if (startContent == rootContent) {
2575 startContent = nullptr;
2578 if (aType == MOVEFOCUS_CARET) {
2579 // GetFocusInSelection finds a focusable link near the caret.
2580 // If there is no start content though, don't do this to avoid
2581 // focusing something unexpected.
2582 if (startContent) {
2583 GetFocusInSelection(aWindow, startContent,
2584 endSelectionContent, aNextContent);
2586 return NS_OK;
2589 if (startContent) {
2590 // when starting from a selection, we always want to find the next or
2591 // previous element in the document. So the tabindex on elements
2592 // should be ignored.
2593 ignoreTabIndex = true;
2597 if (!startContent) {
2598 // otherwise, just use the root content as the starting point
2599 startContent = rootContent;
2600 NS_ENSURE_TRUE(startContent, NS_OK);
2605 NS_ASSERTION(startContent, "starting content not set");
2607 // keep a reference to the starting content. If we find that again, it means
2608 // we've iterated around completely and we don't want to adjust the focus.
2609 // The skipOriginalContentCheck will be set to true only for the first time
2610 // GetNextTabbableContent is called. This ensures that we don't break out
2611 // when nothing is focused to start with. Specifically,
2612 // GetNextTabbableContent first checks the root content -- which happens to
2613 // be the same as the start content -- when nothing is focused and tabbing
2614 // forward. Without skipOriginalContentCheck set to true, we'd end up
2615 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2616 // will never wrap around on its own, and can only return the original
2617 // content when it is called a second time or later.
2618 bool skipOriginalContentCheck = true;
2619 nsIContent* originalStartContent = startContent;
2621 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
2622 LOGFOCUSNAVIGATION((" Tabindex: %d Ignore: %d", tabIndex, ignoreTabIndex));
2624 while (doc) {
2625 if (doNavigation) {
2626 nsCOMPtr<nsIContent> nextFocus;
2627 nsresult rv = GetNextTabbableContent(presShell, rootContent,
2628 skipOriginalContentCheck ? nullptr : originalStartContent,
2629 startContent, forward,
2630 tabIndex, ignoreTabIndex,
2631 getter_AddRefs(nextFocus));
2632 NS_ENSURE_SUCCESS(rv, rv);
2634 // found a content node to focus.
2635 if (nextFocus) {
2636 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
2638 // as long as the found node was not the same as the starting node,
2639 // set it as the return value.
2640 if (nextFocus != originalStartContent)
2641 NS_ADDREF(*aNextContent = nextFocus);
2642 return NS_OK;
2645 if (popupFrame) {
2646 // in a popup, so start again from the beginning of the popup. However,
2647 // if we already started at the beginning, then there isn't anything to
2648 // focus, so just return
2649 if (startContent != rootContent) {
2650 startContent = rootContent;
2651 tabIndex = forward ? 1 : 0;
2652 continue;
2654 return NS_OK;
2658 doNavigation = true;
2659 skipOriginalContentCheck = false;
2660 ignoreTabIndex = false;
2662 if (aNoParentTraversal) {
2663 if (startContent == rootContent)
2664 return NS_OK;
2666 startContent = rootContent;
2667 tabIndex = forward ? 1 : 0;
2668 continue;
2671 // reached the beginning or end of the document. Traverse up to the parent
2672 // document and try again.
2673 nsCOMPtr<nsIDocShellTreeItem> docShellParent;
2674 docShell->GetParent(getter_AddRefs(docShellParent));
2675 if (docShellParent) {
2676 // move up to the parent shell and try again from there.
2678 // first, get the frame element this window is inside.
2679 nsCOMPtr<nsPIDOMWindow> piWindow = docShell->GetWindow();
2680 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2682 // Next, retrieve the parent docshell, document and presshell.
2683 docShell = do_QueryInterface(docShellParent);
2684 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2686 nsCOMPtr<nsPIDOMWindow> piParentWindow = docShellParent->GetWindow();
2687 NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
2688 doc = piParentWindow->GetExtantDoc();
2689 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2691 presShell = doc->GetShell();
2693 rootContent = doc->GetRootElement();
2694 startContent = piWindow->GetFrameElementInternal();
2695 if (startContent) {
2696 nsIFrame* frame = startContent->GetPrimaryFrame();
2697 if (!frame)
2698 return NS_OK;
2700 frame->IsFocusable(&tabIndex, 0);
2701 if (tabIndex < 0) {
2702 tabIndex = 1;
2703 ignoreTabIndex = true;
2706 // if the frame is inside a popup, make sure to scan only within the
2707 // popup. This handles the situation of tabbing amongst elements
2708 // inside an iframe which is itself inside a popup. Otherwise,
2709 // navigation would move outside the popup when tabbing outside the
2710 // iframe.
2711 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2712 nsGkAtoms::menuPopupFrame);
2713 if (popupFrame) {
2714 rootContent = popupFrame->GetContent();
2715 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2718 else {
2719 startContent = rootContent;
2720 tabIndex = forward ? 1 : 0;
2723 else {
2724 // no parent, so call the tree owner. This will tell the embedder that
2725 // it should take the focus.
2726 bool tookFocus;
2727 docShell->TabToTreeOwner(forward, &tookFocus);
2728 // if the tree owner, took the focus, blur the current content
2729 if (tookFocus) {
2730 nsCOMPtr<nsPIDOMWindow> window = docShell->GetWindow();
2731 if (window->GetFocusedNode() == mFocusedContent)
2732 Blur(mFocusedWindow, nullptr, true, true);
2733 else
2734 window->SetFocusedNode(nullptr);
2735 return NS_OK;
2738 // reset the tab index and start again from the beginning or end
2739 startContent = rootContent;
2740 tabIndex = forward ? 1 : 0;
2743 // wrapped all the way around and didn't find anything to move the focus
2744 // to, so just break out
2745 if (startContent == originalStartContent)
2746 break;
2749 return NS_OK;
2752 nsresult
2753 nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
2754 nsIContent* aRootContent,
2755 nsIContent* aOriginalStartContent,
2756 nsIContent* aStartContent,
2757 bool aForward,
2758 int32_t aCurrentTabIndex,
2759 bool aIgnoreTabIndex,
2760 nsIContent** aResultContent)
2762 *aResultContent = nullptr;
2764 nsCOMPtr<nsIContent> startContent = aStartContent;
2765 if (!startContent)
2766 return NS_OK;
2768 LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
2769 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
2771 nsPresContext* presContext = aPresShell->GetPresContext();
2773 bool getNextFrame = true;
2774 nsCOMPtr<nsIContent> iterStartContent = aStartContent;
2775 while (1) {
2776 nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
2777 // if there is no frame, look for another content node that has a frame
2778 if (!startFrame) {
2779 // if the root content doesn't have a frame, just return
2780 if (iterStartContent == aRootContent)
2781 return NS_OK;
2783 // look for the next or previous content node in tree order
2784 iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
2785 // we've already skipped over the initial focused content, so we
2786 // don't want to traverse frames.
2787 getNextFrame = false;
2788 if (iterStartContent)
2789 continue;
2791 // otherwise, as a last attempt, just look at the root content
2792 iterStartContent = aRootContent;
2793 continue;
2796 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2797 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2798 presContext, startFrame,
2799 ePreOrder,
2800 false, // aVisual
2801 false, // aLockInScrollView
2802 true // aFollowOOFs
2804 NS_ENSURE_SUCCESS(rv, rv);
2806 if (iterStartContent == aRootContent) {
2807 if (!aForward) {
2808 frameTraversal->Last();
2809 } else if (aRootContent->IsFocusable()) {
2810 frameTraversal->Next();
2813 else if (getNextFrame &&
2814 (!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
2815 !iterStartContent->IsHTML())) {
2816 // Need to do special check in case we're in an imagemap which has multiple
2817 // content nodes per frame, so don't skip over the starting frame.
2818 if (aForward)
2819 frameTraversal->Next();
2820 else
2821 frameTraversal->Prev();
2824 // Walk frames to find something tabbable matching mCurrentTabIndex
2825 nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2826 while (frame) {
2827 // TabIndex not set defaults to 0 for form elements, anchors and other
2828 // elements that are normally focusable. Tabindex defaults to -1
2829 // for elements that are not normally focusable.
2830 // The returned computed tabindex from IsFocusable() is as follows:
2831 // < 0 not tabbable at all
2832 // == 0 in normal tab order (last after positive tabindexed items)
2833 // > 0 can be tabbed to in the order specified by this value
2835 int32_t tabIndex;
2836 frame->IsFocusable(&tabIndex, 0);
2838 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
2839 LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
2841 nsIContent* currentContent = frame->GetContent();
2842 if (tabIndex >= 0) {
2843 NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
2844 if (currentContent->Tag() == nsGkAtoms::img &&
2845 currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
2846 // This is an image with a map. Image map areas are not traversed by
2847 // nsIFrameTraversal so look for the next or previous area element.
2848 nsIContent *areaContent =
2849 GetNextTabbableMapArea(aForward, aCurrentTabIndex,
2850 currentContent, iterStartContent);
2851 if (areaContent) {
2852 NS_ADDREF(*aResultContent = areaContent);
2853 return NS_OK;
2856 else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
2857 // break out if we've wrapped around to the start again.
2858 if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2859 NS_ADDREF(*aResultContent = currentContent);
2860 return NS_OK;
2863 // found a node with a matching tab index. Check if it is a child
2864 // frame. If so, navigate into the child frame instead.
2865 nsIDocument* doc = currentContent->GetComposedDoc();
2866 NS_ASSERTION(doc, "content not in document");
2867 nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
2868 if (subdoc) {
2869 if (!subdoc->EventHandlingSuppressed()) {
2870 if (aForward) {
2871 // when tabbing forward into a frame, return the root
2872 // frame so that the canvas becomes focused.
2873 nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
2874 if (subframe) {
2875 // If the subframe body is editable by contenteditable,
2876 // we should set the editor's root element rather than the
2877 // actual root element. Otherwise, we should set the focus
2878 // to the root content.
2879 *aResultContent =
2880 nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
2881 if (!*aResultContent ||
2882 !((*aResultContent)->GetPrimaryFrame())) {
2883 *aResultContent =
2884 GetRootForFocus(subframe, subdoc, false, true);
2886 if (*aResultContent) {
2887 NS_ADDREF(*aResultContent);
2888 return NS_OK;
2892 Element* rootElement = subdoc->GetRootElement();
2893 nsIPresShell* subShell = subdoc->GetShell();
2894 if (rootElement && subShell) {
2895 rv = GetNextTabbableContent(subShell, rootElement,
2896 aOriginalStartContent, rootElement,
2897 aForward, (aForward ? 1 : 0),
2898 false, aResultContent);
2899 NS_ENSURE_SUCCESS(rv, rv);
2900 if (*aResultContent)
2901 return NS_OK;
2905 // otherwise, use this as the next content node to tab to, unless
2906 // this was the element we started on. This would happen for
2907 // instance on an element with child frames, where frame navigation
2908 // could return the original element again. In that case, just skip
2909 // it. Also, if the next content node is the root content, then
2910 // return it. This latter case would happen only if someone made a
2911 // popup focusable.
2912 // Also, when going backwards, check to ensure that the focus
2913 // wouldn't be redirected. Otherwise, for example, when an input in
2914 // a textbox is focused, the enclosing textbox would be found and
2915 // the same inner input would be returned again.
2916 else if (currentContent == aRootContent ||
2917 (currentContent != startContent &&
2918 (aForward || !GetRedirectedFocus(currentContent)))) {
2919 NS_ADDREF(*aResultContent = currentContent);
2920 return NS_OK;
2924 else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2925 // not focusable, so return if we have wrapped around to the original
2926 // content. This is necessary in case the original starting content was
2927 // not focusable.
2928 NS_ADDREF(*aResultContent = currentContent);
2929 return NS_OK;
2932 // Move to the next or previous frame, but ignore continuation frames
2933 // since only the first frame should be involved in focusability.
2934 // Otherwise, a loop will occur in the following example:
2935 // <span tabindex="1">...<a/><a/>...</span>
2936 // where the text wraps onto multiple lines. Tabbing from the second
2937 // link can find one of the span's continuation frames between the link
2938 // and the end of the span, and the span would end up getting focused
2939 // again.
2940 do {
2941 if (aForward)
2942 frameTraversal->Next();
2943 else
2944 frameTraversal->Prev();
2945 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2946 } while (frame && frame->GetPrevContinuation());
2949 // If already at lowest priority tab (0), end search completely.
2950 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
2951 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
2952 // if going backwards, the canvas should be focused once the beginning
2953 // has been reached.
2954 if (!aForward) {
2955 nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
2956 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2957 NS_IF_ADDREF(*aResultContent =
2958 GetRootForFocus(window, aRootContent->GetComposedDoc(),
2959 false, true));
2961 break;
2964 // continue looking for next highest priority tabindex
2965 aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
2966 startContent = iterStartContent = aRootContent;
2969 return NS_OK;
2972 nsIContent*
2973 nsFocusManager::GetNextTabbableMapArea(bool aForward,
2974 int32_t aCurrentTabIndex,
2975 nsIContent* aImageContent,
2976 nsIContent* aStartContent)
2978 nsAutoString useMap;
2979 aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
2981 nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc();
2982 if (doc) {
2983 nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
2984 if (!mapContent)
2985 return nullptr;
2986 uint32_t count = mapContent->GetChildCount();
2987 // First see if the the start content is in this map
2989 int32_t index = mapContent->IndexOf(aStartContent);
2990 int32_t tabIndex;
2991 if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
2992 tabIndex != aCurrentTabIndex)) {
2993 // If aStartContent is in this map we must start iterating past it.
2994 // We skip the case where aStartContent has tabindex == aStartContent
2995 // since the next tab ordered element might be before it
2996 // (or after for backwards) in the child list.
2997 index = aForward ? -1 : (int32_t)count;
3000 // GetChildAt will return nullptr if our index < 0 or index >= count
3001 nsCOMPtr<nsIContent> areaContent;
3002 while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) {
3003 if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
3004 return areaContent;
3009 return nullptr;
3012 int32_t
3013 nsFocusManager::GetNextTabIndex(nsIContent* aParent,
3014 int32_t aCurrentTabIndex,
3015 bool aForward)
3017 int32_t tabIndex, childTabIndex;
3019 if (aForward) {
3020 tabIndex = 0;
3021 for (nsIContent* child = aParent->GetFirstChild();
3022 child;
3023 child = child->GetNextSibling()) {
3024 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
3025 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
3026 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
3029 nsAutoString tabIndexStr;
3030 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
3031 nsresult ec;
3032 int32_t val = tabIndexStr.ToInteger(&ec);
3033 if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
3034 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
3038 else { /* !aForward */
3039 tabIndex = 1;
3040 for (nsIContent* child = aParent->GetFirstChild();
3041 child;
3042 child = child->GetNextSibling()) {
3043 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
3044 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
3045 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
3046 tabIndex = childTabIndex;
3049 nsAutoString tabIndexStr;
3050 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
3051 nsresult ec;
3052 int32_t val = tabIndexStr.ToInteger(&ec);
3053 if (NS_SUCCEEDED (ec)) {
3054 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
3055 (val < aCurrentTabIndex && val > tabIndex) ) {
3056 tabIndex = val;
3062 return tabIndex;
3065 nsIContent*
3066 nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
3067 nsIDocument* aDocument,
3068 bool aIsForDocNavigation,
3069 bool aCheckVisibility)
3071 // the root element's canvas may be focused as long as the document is in a
3072 // a non-chrome shell and does not contain a frameset.
3073 if (aIsForDocNavigation) {
3074 nsCOMPtr<Element> docElement = aWindow->GetFrameElementInternal();
3075 // document navigation skips iframes and frames that are specifically non-focusable
3076 if (docElement) {
3077 if (docElement->Tag() == nsGkAtoms::iframe)
3078 return nullptr;
3080 nsIFrame* frame = docElement->GetPrimaryFrame();
3081 if (!frame || !frame->IsFocusable(nullptr, 0))
3082 return nullptr;
3084 } else {
3085 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
3086 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
3087 return nullptr;
3091 if (aCheckVisibility && !IsWindowVisible(aWindow))
3092 return nullptr;
3094 Element *rootElement = aDocument->GetRootElement();
3095 if (!rootElement) {
3096 return nullptr;
3099 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
3100 return nullptr;
3103 // Finally, check if this is a frameset
3104 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
3105 if (htmlDoc && aDocument->GetHtmlChildElement(nsGkAtoms::frameset)) {
3106 return nullptr;
3109 return rootElement;
3112 void
3113 nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
3114 nsIDocShellTreeItem** aResult)
3116 *aResult = nullptr;
3118 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3119 while (curItem) {
3120 int32_t childCount = 0;
3121 curItem->GetChildCount(&childCount);
3122 if (!childCount) {
3123 *aResult = curItem;
3124 NS_ADDREF(*aResult);
3125 return;
3129 curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
3133 void
3134 nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
3135 nsIDocShellTreeItem** aResult)
3137 *aResult = nullptr;
3139 int32_t childCount = 0;
3140 aItem->GetChildCount(&childCount);
3141 if (childCount) {
3142 aItem->GetChildAt(0, aResult);
3143 if (*aResult)
3144 return;
3147 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3148 while (curItem) {
3149 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3150 curItem->GetParent(getter_AddRefs(parentItem));
3151 if (!parentItem)
3152 return;
3154 // Note that we avoid using GetChildOffset() here because docshell
3155 // child offsets can't be trusted to be correct. bug 162283.
3156 nsCOMPtr<nsIDocShellTreeItem> iterItem;
3157 childCount = 0;
3158 parentItem->GetChildCount(&childCount);
3159 for (int32_t index = 0; index < childCount; ++index) {
3160 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3161 if (iterItem == curItem) {
3162 ++index;
3163 if (index < childCount) {
3164 parentItem->GetChildAt(index, aResult);
3165 if (*aResult)
3166 return;
3168 break;
3172 curItem = parentItem;
3176 void
3177 nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
3178 nsIDocShellTreeItem** aResult)
3180 *aResult = nullptr;
3182 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3183 aItem->GetParent(getter_AddRefs(parentItem));
3184 if (!parentItem)
3185 return;
3187 // Note that we avoid using GetChildOffset() here because docshell
3188 // child offsets can't be trusted to be correct. bug 162283.
3189 int32_t childCount = 0;
3190 parentItem->GetChildCount(&childCount);
3191 nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
3192 for (int32_t index = 0; index < childCount; ++index) {
3193 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3194 if (iterItem == aItem)
3195 break;
3196 prevItem = iterItem;
3199 if (prevItem)
3200 GetLastDocShell(prevItem, aResult);
3201 else
3202 NS_ADDREF(*aResult = parentItem);
3205 nsIContent*
3206 nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward)
3208 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
3209 if (!pm)
3210 return nullptr;
3212 // Iterate through the array backwards if aForward is false.
3213 nsTArray<nsIFrame *> popups;
3214 pm->GetVisiblePopups(popups);
3215 int32_t i = aForward ? 0 : popups.Length() - 1;
3216 int32_t end = aForward ? popups.Length() : -1;
3218 for (; i != end; aForward ? i++ : i--) {
3219 nsIFrame* popupFrame = popups[i];
3220 if (aCurrentPopup) {
3221 // If the current popup is set, then we need to skip over this popup and
3222 // wait until the currently focused popup is found. Once found, the
3223 // current popup will be cleared so that the next popup is used.
3224 if (aCurrentPopup == popupFrame)
3225 aCurrentPopup = nullptr;
3226 continue;
3229 // Skip over non-panels
3230 if (popupFrame->GetContent()->Tag() != nsGkAtoms::panel ||
3231 (aDocument && popupFrame->GetContent()->GetComposedDoc() != aDocument)) {
3232 continue;
3235 // Find the first focusable content within the popup. If there isn't any
3236 // focusable content in the popup, skip to the next popup.
3237 nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell();
3238 if (presShell) {
3239 nsCOMPtr<nsIContent> nextFocus;
3240 nsIContent* popup = popupFrame->GetContent();
3241 nsresult rv = GetNextTabbableContent(presShell, popup,
3242 nullptr, popup,
3243 true, 1, false,
3244 getter_AddRefs(nextFocus));
3245 if (NS_SUCCEEDED(rv) && nextFocus) {
3246 return nextFocus.get();
3251 return nullptr;
3254 nsIContent*
3255 nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward)
3257 // If currentPopup is set, then the starting content is in a panel.
3258 nsIFrame* currentPopup = nullptr;
3259 nsCOMPtr<nsIDocument> doc;
3260 nsCOMPtr<nsIDocShell> startDocShell;
3262 if (aStartContent) {
3263 doc = aStartContent->GetComposedDoc();
3264 if (doc) {
3265 startDocShell = doc->GetWindow()->GetDocShell();
3268 // Check if the starting content is inside a panel. Document navigation
3269 // must start from this panel instead of the document root.
3270 nsIContent* content = aStartContent;
3271 while (content) {
3272 if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) {
3273 currentPopup = content->GetPrimaryFrame();
3274 break;
3276 content = content->GetParent();
3279 else if (mFocusedWindow) {
3280 startDocShell = mFocusedWindow->GetDocShell();
3281 doc = mFocusedWindow->GetExtantDoc();
3282 } else if (mActiveWindow) {
3283 startDocShell = mActiveWindow->GetDocShell();
3284 doc = mActiveWindow->GetExtantDoc();
3287 if (!startDocShell)
3288 return nullptr;
3290 // perform a depth first search (preorder) of the docshell tree
3291 // looking for an HTML Frame or a chrome document
3292 nsIContent* content = aStartContent;
3293 nsCOMPtr<nsIDocShellTreeItem> curItem = startDocShell.get();
3294 nsCOMPtr<nsIDocShellTreeItem> nextItem;
3295 do {
3296 // If moving forward, check for a panel in the starting document. If one
3297 // exists with focusable content, return that content instead of the next
3298 // document. If currentPopup is set, then, another panel may exist. If no
3299 // such panel exists, then continue on to check the next document.
3300 // When moving backwards, and the starting content is in a panel, then
3301 // check for additional panels in the starting document. If the starting
3302 // content is not in a panel, move back to the previous document and check
3303 // for panels there.
3305 bool checkPopups = false;
3306 nsCOMPtr<nsPIDOMWindow> nextFrame = nullptr;
3308 if (doc && (aForward || currentPopup)) {
3309 nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward);
3310 if (popupContent)
3311 return popupContent;
3313 if (!aForward && currentPopup) {
3314 // The starting content was in a popup, yet no other popups were
3315 // found. Move onto the starting content's document.
3316 nextFrame = doc->GetWindow();
3320 // Look for the next or previous document.
3321 if (!nextFrame) {
3322 if (aForward) {
3323 GetNextDocShell(curItem, getter_AddRefs(nextItem));
3324 if (!nextItem) {
3325 // wrap around to the beginning, which is the top of the tree
3326 startDocShell->GetRootTreeItem(getter_AddRefs(nextItem));
3329 else {
3330 GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
3331 if (!nextItem) {
3332 // wrap around to the end, which is the last item in the tree
3333 nsCOMPtr<nsIDocShellTreeItem> rootItem;
3334 startDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
3335 GetLastDocShell(rootItem, getter_AddRefs(nextItem));
3338 // When going back to the previous document, check for any focusable
3339 // popups in that previous document first.
3340 checkPopups = true;
3343 curItem = nextItem;
3344 nextFrame = nextItem ? nextItem->GetWindow() : nullptr;
3347 if (!nextFrame)
3348 return nullptr;
3350 // Clear currentPopup for the next iteration
3351 currentPopup = nullptr;
3353 // If event handling is suppressed, move on to the next document. Set
3354 // content to null so that the popup check will be skipped on the next
3355 // loop iteration.
3356 doc = nextFrame->GetExtantDoc();
3357 if (!doc || doc->EventHandlingSuppressed()) {
3358 content = nullptr;
3359 continue;
3362 if (checkPopups) {
3363 // When iterating backwards, check the panels of the previous document
3364 // first. If a panel exists that has focusable content, focus that.
3365 // Otherwise, continue on to focus the document.
3366 nsIContent* popupContent = GetNextTabbablePanel(doc, nullptr, false);
3367 if (popupContent)
3368 return popupContent;
3371 content = GetRootForFocus(nextFrame, doc, true, true);
3372 if (content && !GetRootForFocus(nextFrame, doc, false, false)) {
3373 // if the found content is in a chrome shell or a frameset, navigate
3374 // forward one tabbable item so that the first item is focused. Note
3375 // that we always go forward and not back here.
3376 nsCOMPtr<nsIContent> nextFocus;
3377 Element* rootElement = doc->GetRootElement();
3378 nsIPresShell* presShell = doc->GetShell();
3379 if (presShell) {
3380 nsresult rv = GetNextTabbableContent(presShell, rootElement,
3381 nullptr, rootElement,
3382 true, 1, false,
3383 getter_AddRefs(nextFocus));
3384 return NS_SUCCEEDED(rv) ? nextFocus.get() : nullptr;
3388 } while (!content);
3390 return content;
3393 void
3394 nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
3395 nsIContent* aStartSelection,
3396 nsIContent* aEndSelection,
3397 nsIContent** aFocusedContent)
3399 *aFocusedContent = nullptr;
3401 nsCOMPtr<nsIContent> testContent = aStartSelection;
3402 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
3404 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
3406 // We now have the correct start node in selectionContent!
3407 // Search for focusable elements, starting with selectionContent
3409 // Method #1: Keep going up while we look - an ancestor might be focusable
3410 // We could end the loop earlier, such as when we're no longer
3411 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
3412 // with a variable holding the starting selectionContent
3413 while (testContent) {
3414 // Keep testing while selectionContent is equal to something,
3415 // eventually we'll run out of ancestors
3417 nsCOMPtr<nsIURI> uri;
3418 if (testContent == currentFocus ||
3419 testContent->IsLink(getter_AddRefs(uri))) {
3420 NS_ADDREF(*aFocusedContent = testContent);
3421 return;
3424 // Get the parent
3425 testContent = testContent->GetParent();
3427 if (!testContent) {
3428 // We run this loop again, checking the ancestor chain of the selection's end point
3429 testContent = nextTestContent;
3430 nextTestContent = nullptr;
3434 // We couldn't find an anchor that was an ancestor of the selection start
3435 // Method #2: look for anchor in selection's primary range (depth first search)
3437 // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
3438 nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
3439 nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
3440 nsCOMPtr<nsIDOMNode> testNode;
3442 do {
3443 testContent = do_QueryInterface(selectionNode);
3445 // We're looking for any focusable link that could be part of the
3446 // main document's selection.
3447 nsCOMPtr<nsIURI> uri;
3448 if (testContent == currentFocus ||
3449 testContent->IsLink(getter_AddRefs(uri))) {
3450 NS_ADDREF(*aFocusedContent = testContent);
3451 return;
3454 selectionNode->GetFirstChild(getter_AddRefs(testNode));
3455 if (testNode) {
3456 selectionNode = testNode;
3457 continue;
3460 if (selectionNode == endSelectionNode)
3461 break;
3462 selectionNode->GetNextSibling(getter_AddRefs(testNode));
3463 if (testNode) {
3464 selectionNode = testNode;
3465 continue;
3468 do {
3469 selectionNode->GetParentNode(getter_AddRefs(testNode));
3470 if (!testNode || testNode == endSelectionNode) {
3471 selectionNode = nullptr;
3472 break;
3474 testNode->GetNextSibling(getter_AddRefs(selectionNode));
3475 if (selectionNode)
3476 break;
3477 selectionNode = testNode;
3478 } while (true);
3480 while (selectionNode && selectionNode != endSelectionNode);
3483 class PointerUnlocker : public nsRunnable
3485 public:
3486 PointerUnlocker()
3488 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
3489 PointerUnlocker::sActiveUnlocker = this;
3492 ~PointerUnlocker()
3494 if (PointerUnlocker::sActiveUnlocker == this) {
3495 PointerUnlocker::sActiveUnlocker = nullptr;
3499 NS_IMETHOD Run()
3501 if (PointerUnlocker::sActiveUnlocker == this) {
3502 PointerUnlocker::sActiveUnlocker = nullptr;
3504 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
3505 nsPIDOMWindow* focused =
3506 nsFocusManager::GetFocusManager()->GetFocusedWindow();
3507 nsCOMPtr<nsIDocument> pointerLockedDoc =
3508 do_QueryReferent(EventStateManager::sPointerLockedDoc);
3509 if (pointerLockedDoc &&
3510 !nsContentUtils::IsInPointerLockContext(focused)) {
3511 nsIDocument::UnlockPointer();
3513 return NS_OK;
3516 static PointerUnlocker* sActiveUnlocker;
3519 PointerUnlocker*
3520 PointerUnlocker::sActiveUnlocker = nullptr;
3522 void
3523 nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindow* aWindow)
3525 if (!PointerUnlocker::sActiveUnlocker &&
3526 nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
3527 !nsContentUtils::IsInPointerLockContext(aWindow)) {
3528 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
3529 NS_DispatchToCurrentThread(runnable);
3531 mFocusedWindow = aWindow;
3534 void
3535 nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
3537 if (!sInstance) {
3538 return;
3541 if (sInstance->mActiveWindow) {
3542 sInstance->mActiveWindow->
3543 MarkUncollectableForCCGeneration(aGeneration);
3545 if (sInstance->mFocusedWindow) {
3546 sInstance->mFocusedWindow->
3547 MarkUncollectableForCCGeneration(aGeneration);
3549 if (sInstance->mWindowBeingLowered) {
3550 sInstance->mWindowBeingLowered->
3551 MarkUncollectableForCCGeneration(aGeneration);
3553 if (sInstance->mFocusedContent) {
3554 sInstance->mFocusedContent->OwnerDoc()->
3555 MarkUncollectableForCCGeneration(aGeneration);
3557 if (sInstance->mFirstBlurEvent) {
3558 sInstance->mFirstBlurEvent->OwnerDoc()->
3559 MarkUncollectableForCCGeneration(aGeneration);
3561 if (sInstance->mFirstFocusEvent) {
3562 sInstance->mFirstFocusEvent->OwnerDoc()->
3563 MarkUncollectableForCCGeneration(aGeneration);
3565 if (sInstance->mMouseButtonEventHandlingDocument) {
3566 sInstance->mMouseButtonEventHandlingDocument->
3567 MarkUncollectableForCCGeneration(aGeneration);
3571 nsresult
3572 NS_NewFocusManager(nsIFocusManager** aResult)
3574 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
3575 return NS_OK;