1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/BrowserParent.h"
9 #include "nsFocusManager.h"
11 #include "LayoutConstants.h"
12 #include "ChildIterator.h"
13 #include "nsIInterfaceRequestorUtils.h"
14 #include "nsGkAtoms.h"
15 #include "nsContentUtils.h"
16 #include "ContentParent.h"
17 #include "nsPIDOMWindow.h"
18 #include "nsIContentInlines.h"
19 #include "nsIDocShell.h"
20 #include "nsIDocShellTreeOwner.h"
21 #include "nsIFormControl.h"
22 #include "nsLayoutUtils.h"
23 #include "nsFrameTraversal.h"
24 #include "nsIWebNavigation.h"
26 #include "nsIBaseWindow.h"
27 #include "nsIAppWindow.h"
28 #include "nsTextControlFrame.h"
29 #include "nsViewManager.h"
30 #include "nsFrameSelection.h"
31 #include "mozilla/dom/Selection.h"
32 #include "nsXULPopupManager.h"
33 #include "nsMenuPopupFrame.h"
34 #include "nsIScriptError.h"
35 #include "nsIScriptObjectPrincipal.h"
36 #include "nsIPrincipal.h"
37 #include "nsIObserverService.h"
38 #include "BrowserChild.h"
39 #include "nsFrameLoader.h"
40 #include "nsHTMLDocument.h"
41 #include "nsNetUtil.h"
43 #include "nsFrameLoaderOwner.h"
44 #include "nsQueryObject.h"
46 #include "mozilla/AccessibleCaretEventHub.h"
47 #include "mozilla/ContentEvents.h"
48 #include "mozilla/dom/ContentChild.h"
49 #include "mozilla/dom/Document.h"
50 #include "mozilla/dom/DocumentInlines.h"
51 #include "mozilla/dom/Element.h"
52 #include "mozilla/dom/ElementBinding.h"
53 #include "mozilla/dom/HTMLImageElement.h"
54 #include "mozilla/dom/HTMLInputElement.h"
55 #include "mozilla/dom/HTMLSlotElement.h"
56 #include "mozilla/dom/BrowserBridgeChild.h"
57 #include "mozilla/dom/Text.h"
58 #include "mozilla/dom/XULPopupElement.h"
59 #include "mozilla/dom/WindowGlobalParent.h"
60 #include "mozilla/dom/WindowGlobalChild.h"
61 #include "mozilla/EventDispatcher.h"
62 #include "mozilla/EventStateManager.h"
63 #include "mozilla/HTMLEditor.h"
64 #include "mozilla/IMEStateManager.h"
65 #include "mozilla/LookAndFeel.h"
66 #include "mozilla/Maybe.h"
67 #include "mozilla/PointerLockManager.h"
68 #include "mozilla/Preferences.h"
69 #include "mozilla/PresShell.h"
70 #include "mozilla/Services.h"
71 #include "mozilla/Unused.h"
72 #include "mozilla/StaticPrefs_full_screen_api.h"
73 #include "mozilla/Try.h"
74 #include "mozilla/widget/IMEData.h"
77 #include "nsIDOMXULMenuListElement.h"
80 # include "nsAccessibilityService.h"
83 using namespace mozilla
;
84 using namespace mozilla::dom
;
85 using namespace mozilla::widget
;
87 // Two types of focus pr logging are available:
88 // 'Focus' for normal focus manager calls
89 // 'FocusNavigation' for tab and document navigation
90 LazyLogModule
gFocusLog("Focus");
91 LazyLogModule
gFocusNavigationLog("FocusNavigation");
93 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
94 #define LOGFOCUSNAVIGATION(args) \
95 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
97 #define LOGTAG(log, format, content) \
98 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
99 nsAutoCString tag("(none)"_ns); \
101 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
103 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
106 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
107 #define LOGCONTENTNAVIGATION(format, content) \
108 LOGTAG(gFocusNavigationLog, format, content)
110 struct nsDelayedBlurOrFocusEvent
{
111 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage
, PresShell
* aPresShell
,
112 Document
* aDocument
, EventTarget
* aTarget
,
113 EventTarget
* aRelatedTarget
)
114 : mPresShell(aPresShell
),
115 mDocument(aDocument
),
117 mEventMessage(aEventMessage
),
118 mRelatedTarget(aRelatedTarget
) {}
120 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent
& aOther
)
121 : mPresShell(aOther
.mPresShell
),
122 mDocument(aOther
.mDocument
),
123 mTarget(aOther
.mTarget
),
124 mEventMessage(aOther
.mEventMessage
) {}
126 RefPtr
<PresShell
> mPresShell
;
127 nsCOMPtr
<Document
> mDocument
;
128 nsCOMPtr
<EventTarget
> mTarget
;
129 EventMessage mEventMessage
;
130 nsCOMPtr
<EventTarget
> mRelatedTarget
;
133 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent
& aField
) {
134 aField
.mPresShell
= nullptr;
135 aField
.mDocument
= nullptr;
136 aField
.mTarget
= nullptr;
137 aField
.mRelatedTarget
= nullptr;
140 inline void ImplCycleCollectionTraverse(
141 nsCycleCollectionTraversalCallback
& aCallback
,
142 nsDelayedBlurOrFocusEvent
& aField
, const char* aName
, uint32_t aFlags
= 0) {
143 CycleCollectionNoteChild(
144 aCallback
, static_cast<nsIDocumentObserver
*>(aField
.mPresShell
.get()),
146 CycleCollectionNoteChild(aCallback
, aField
.mDocument
.get(), aName
, aFlags
);
147 CycleCollectionNoteChild(aCallback
, aField
.mTarget
.get(), aName
, aFlags
);
148 CycleCollectionNoteChild(aCallback
, aField
.mRelatedTarget
.get(), aName
,
152 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager
)
153 NS_INTERFACE_MAP_ENTRY(nsIFocusManager
)
154 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
155 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
156 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIFocusManager
)
159 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager
)
160 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager
)
162 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager
, mActiveWindow
,
163 mActiveBrowsingContextInContent
,
164 mActiveBrowsingContextInChrome
, mFocusedWindow
,
165 mFocusedBrowsingContextInContent
,
166 mFocusedBrowsingContextInChrome
, mFocusedElement
,
167 mFirstBlurEvent
, mFirstFocusEvent
,
168 mWindowBeingLowered
, mDelayedBlurFocusEvents
)
170 StaticRefPtr
<nsFocusManager
> nsFocusManager::sInstance
;
171 bool nsFocusManager::sTestMode
= false;
172 uint64_t nsFocusManager::sFocusActionCounter
= 0;
174 static const char* kObservedPrefs
[] = {"accessibility.browsewithcaret",
175 "accessibility.tabfocus_applies_to_xul",
176 "focusmanager.testmode", nullptr};
178 nsFocusManager::nsFocusManager()
179 : mActionIdForActiveBrowsingContextInContent(0),
180 mActionIdForActiveBrowsingContextInChrome(0),
181 mActionIdForFocusedBrowsingContextInContent(0),
182 mActionIdForFocusedBrowsingContextInChrome(0),
183 mActiveBrowsingContextInContentSetFromOtherProcess(false),
184 mEventHandlingNeedsFlush(false) {}
186 nsFocusManager::~nsFocusManager() {
187 Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
190 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
192 obs
->RemoveObserver(this, "xpcom-shutdown");
197 nsresult
nsFocusManager::Init() {
198 sInstance
= new nsFocusManager();
200 nsIContent::sTabFocusModelAppliesToXUL
=
201 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
202 nsIContent::sTabFocusModelAppliesToXUL
);
204 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
206 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
209 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
211 obs
->AddObserver(sInstance
, "xpcom-shutdown", true);
218 void nsFocusManager::Shutdown() { sInstance
= nullptr; }
221 void nsFocusManager::PrefChanged(const char* aPref
, void* aSelf
) {
222 if (RefPtr
<nsFocusManager
> fm
= static_cast<nsFocusManager
*>(aSelf
)) {
223 fm
->PrefChanged(aPref
);
227 void nsFocusManager::PrefChanged(const char* aPref
) {
228 nsDependentCString
pref(aPref
);
229 if (pref
.EqualsLiteral("accessibility.browsewithcaret")) {
230 UpdateCaretForCaretBrowsingMode();
231 } else if (pref
.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
232 nsIContent::sTabFocusModelAppliesToXUL
=
233 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
234 nsIContent::sTabFocusModelAppliesToXUL
);
235 } else if (pref
.EqualsLiteral("focusmanager.testmode")) {
236 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
241 nsFocusManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
242 const char16_t
* aData
) {
243 if (!nsCRT::strcmp(aTopic
, "xpcom-shutdown")) {
244 mActiveWindow
= nullptr;
245 mActiveBrowsingContextInContent
= nullptr;
246 mActionIdForActiveBrowsingContextInContent
= 0;
247 mActionIdForFocusedBrowsingContextInContent
= 0;
248 mActiveBrowsingContextInChrome
= nullptr;
249 mActionIdForActiveBrowsingContextInChrome
= 0;
250 mActionIdForFocusedBrowsingContextInChrome
= 0;
251 mFocusedWindow
= nullptr;
252 mFocusedBrowsingContextInContent
= nullptr;
253 mFocusedBrowsingContextInChrome
= nullptr;
254 mFocusedElement
= nullptr;
255 mFirstBlurEvent
= nullptr;
256 mFirstFocusEvent
= nullptr;
257 mWindowBeingLowered
= nullptr;
258 mDelayedBlurFocusEvents
.Clear();
264 static bool ActionIdComparableAndLower(uint64_t aActionId
,
265 uint64_t aReference
) {
266 MOZ_ASSERT(aActionId
, "Uninitialized action id");
267 auto [actionProc
, actionId
] =
268 nsContentUtils::SplitProcessSpecificId(aActionId
);
269 auto [refProc
, refId
] = nsContentUtils::SplitProcessSpecificId(aReference
);
270 return actionProc
== refProc
&& actionId
< refId
;
273 // given a frame content node, retrieve the nsIDOMWindow displayed in it
274 static nsPIDOMWindowOuter
* GetContentWindow(nsIContent
* aContent
) {
275 Document
* doc
= aContent
->GetComposedDoc();
277 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
279 return subdoc
->GetWindow();
286 bool nsFocusManager::IsFocused(nsIContent
* aContent
) {
287 if (!aContent
|| !mFocusedElement
) {
290 return aContent
== mFocusedElement
;
293 bool nsFocusManager::IsTestMode() { return sTestMode
; }
295 bool nsFocusManager::IsInActiveWindow(BrowsingContext
* aBC
) const {
296 RefPtr
<BrowsingContext
> top
= aBC
->Top();
297 if (XRE_IsParentProcess()) {
298 top
= top
->Canonical()->TopCrossChromeBoundary();
300 return IsSameOrAncestor(top
, GetActiveBrowsingContext());
303 // get the current window for the given content node
304 static nsPIDOMWindowOuter
* GetCurrentWindow(nsIContent
* aContent
) {
305 Document
* doc
= aContent
->GetComposedDoc();
306 return doc
? doc
->GetWindow() : nullptr;
310 Element
* nsFocusManager::GetFocusedDescendant(
311 nsPIDOMWindowOuter
* aWindow
, SearchRange aSearchRange
,
312 nsPIDOMWindowOuter
** aFocusedWindow
) {
313 NS_ENSURE_TRUE(aWindow
, nullptr);
315 *aFocusedWindow
= nullptr;
317 Element
* currentElement
= nullptr;
318 nsPIDOMWindowOuter
* window
= aWindow
;
320 *aFocusedWindow
= window
;
321 currentElement
= window
->GetFocusedElement();
322 if (!currentElement
|| aSearchRange
== eOnlyCurrentWindow
) {
326 window
= GetContentWindow(currentElement
);
331 if (aSearchRange
== eIncludeAllDescendants
) {
335 MOZ_ASSERT(aSearchRange
== eIncludeVisibleDescendants
);
337 // If the child window doesn't have PresShell, it means the window is
339 nsIDocShell
* docShell
= window
->GetDocShell();
343 if (!docShell
->GetPresShell()) {
348 NS_IF_ADDREF(*aFocusedWindow
);
350 return currentElement
;
354 InputContextAction::Cause
nsFocusManager::GetFocusMoveActionCause(
356 if (aFlags
& nsIFocusManager::FLAG_BYTOUCH
) {
357 return InputContextAction::CAUSE_TOUCH
;
358 } else if (aFlags
& nsIFocusManager::FLAG_BYMOUSE
) {
359 return InputContextAction::CAUSE_MOUSE
;
360 } else if (aFlags
& nsIFocusManager::FLAG_BYKEY
) {
361 return InputContextAction::CAUSE_KEY
;
362 } else if (aFlags
& nsIFocusManager::FLAG_BYLONGPRESS
) {
363 return InputContextAction::CAUSE_LONGPRESS
;
365 return InputContextAction::CAUSE_UNKNOWN
;
369 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy
** aWindow
) {
370 MOZ_ASSERT(XRE_IsParentProcess(),
371 "Must not be called outside the parent process.");
372 NS_IF_ADDREF(*aWindow
= mActiveWindow
);
377 nsFocusManager::GetActiveBrowsingContext(BrowsingContext
** aBrowsingContext
) {
378 NS_IF_ADDREF(*aBrowsingContext
= GetActiveBrowsingContext());
382 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter
* aWindow
,
383 CallerType aCallerType
) {
384 if (RefPtr
<nsFocusManager
> fm
= sInstance
) {
385 fm
->SetFocusedWindowWithCallerType(aWindow
, aCallerType
);
390 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy
** aFocusedWindow
) {
391 NS_IF_ADDREF(*aFocusedWindow
= mFocusedWindow
);
396 nsFocusManager::GetFocusedContentBrowsingContext(
397 BrowsingContext
** aBrowsingContext
) {
398 MOZ_DIAGNOSTIC_ASSERT(
399 XRE_IsParentProcess(),
400 "We only have use cases for this in the parent process");
401 NS_IF_ADDREF(*aBrowsingContext
= GetFocusedBrowsingContextInChrome());
405 nsresult
nsFocusManager::SetFocusedWindowWithCallerType(
406 mozIDOMWindowProxy
* aWindowToFocus
, CallerType aCallerType
) {
407 LOGFOCUS(("<<SetFocusedWindow begin>>"));
409 nsCOMPtr
<nsPIDOMWindowOuter
> windowToFocus
=
410 nsPIDOMWindowOuter::From(aWindowToFocus
);
411 NS_ENSURE_TRUE(windowToFocus
, NS_ERROR_FAILURE
);
413 nsCOMPtr
<Element
> frameElement
= windowToFocus
->GetFrameElementInternal();
414 Maybe
<uint64_t> actionIdFromSetFocusInner
;
416 // pass false for aFocusChanged so that the caret does not get updated
417 // and scrolling does not occur.
418 actionIdFromSetFocusInner
= SetFocusInner(frameElement
, 0, false, true);
420 // this is a top-level window. If the window has a child frame focused,
421 // clear the focus. Otherwise, focus should already be in this frame, or
422 // already cleared. This ensures that focus will be in this frame and not
424 nsIContent
* content
= windowToFocus
->GetFocusedElement();
426 if (nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= GetContentWindow(content
))
427 ClearFocus(windowToFocus
);
431 nsCOMPtr
<nsPIDOMWindowOuter
> rootWindow
= windowToFocus
->GetPrivateRoot();
432 const uint64_t actionId
= actionIdFromSetFocusInner
.isSome()
433 ? actionIdFromSetFocusInner
.value()
434 : sInstance
->GenerateFocusActionId();
436 RaiseWindow(rootWindow
, aCallerType
, actionId
);
439 LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64
">>", actionId
));
444 NS_IMETHODIMP
nsFocusManager::SetFocusedWindow(
445 mozIDOMWindowProxy
* aWindowToFocus
) {
446 return SetFocusedWindowWithCallerType(aWindowToFocus
, CallerType::System
);
450 nsFocusManager::GetFocusedElement(Element
** aFocusedElement
) {
451 RefPtr
<Element
> focusedElement
= mFocusedElement
;
452 focusedElement
.forget(aFocusedElement
);
456 uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter
* aWindow
) const {
457 nsPIDOMWindowOuter
* window
= aWindow
? aWindow
: mFocusedWindow
.get();
458 uint32_t method
= window
? window
->GetFocusMethod() : 0;
459 NS_ASSERTION((method
& METHOD_MASK
) == method
, "invalid focus method");
464 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy
* aWindow
,
465 uint32_t* aLastFocusMethod
) {
466 *aLastFocusMethod
= GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow
));
471 nsFocusManager::SetFocus(Element
* aElement
, uint32_t aFlags
) {
472 LOGFOCUS(("<<SetFocus begin>>"));
474 NS_ENSURE_ARG(aElement
);
476 SetFocusInner(aElement
, aFlags
, true, true);
478 LOGFOCUS(("<<SetFocus end>>"));
484 nsFocusManager::ElementIsFocusable(Element
* aElement
, uint32_t aFlags
,
485 bool* aIsFocusable
) {
486 NS_ENSURE_TRUE(aElement
, NS_ERROR_INVALID_ARG
);
487 *aIsFocusable
= !!FlushAndCheckIfFocusable(aElement
, aFlags
);
491 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
492 nsFocusManager::MoveFocus(mozIDOMWindowProxy
* aWindow
, Element
* aStartElement
,
493 uint32_t aType
, uint32_t aFlags
, Element
** aElement
) {
496 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType
, aFlags
));
498 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
) && mFocusedWindow
) {
499 Document
* doc
= mFocusedWindow
->GetExtantDoc();
500 if (doc
&& doc
->GetDocumentURI()) {
501 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow
.get(),
502 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
506 LOGCONTENT(" Current Focus: %s", mFocusedElement
.get());
508 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
509 // the other focus methods is already set, or we're just moving to the root
510 // or caret position.
511 if (aType
!= MOVEFOCUS_ROOT
&& aType
!= MOVEFOCUS_CARET
&&
512 (aFlags
& METHOD_MASK
) == 0) {
513 aFlags
|= FLAG_BYMOVEFOCUS
;
516 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
518 window
= GetCurrentWindow(aStartElement
);
520 window
= aWindow
? nsPIDOMWindowOuter::From(aWindow
) : mFocusedWindow
.get();
523 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
525 // Flush to ensure that focusability of descendants is computed correctly.
526 if (RefPtr
<Document
> doc
= window
->GetExtantDoc()) {
527 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
530 bool noParentTraversal
= aFlags
& FLAG_NOPARENTFRAME
;
531 nsCOMPtr
<nsIContent
> newFocus
;
532 nsresult rv
= DetermineElementToMoveFocus(window
, aStartElement
, aType
,
533 noParentTraversal
, true,
534 getter_AddRefs(newFocus
));
535 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
539 NS_ENSURE_SUCCESS(rv
, rv
);
541 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus
.get());
543 if (newFocus
&& newFocus
->IsElement()) {
544 // for caret movement, pass false for the aFocusChanged argument,
545 // otherwise the caret will end up moving to the focus position. This
546 // would be a problem because the caret would move to the beginning of the
547 // focused link making it impossible to navigate the caret over a link.
548 SetFocusInner(MOZ_KnownLive(newFocus
->AsElement()), aFlags
,
549 aType
!= MOVEFOCUS_CARET
, true);
550 *aElement
= do_AddRef(newFocus
->AsElement()).take();
551 } else if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_CARET
) {
552 // no content was found, so clear the focus for these two types.
556 LOGFOCUS(("<<MoveFocus end>>"));
562 nsFocusManager::ClearFocus(mozIDOMWindowProxy
* aWindow
) {
563 LOGFOCUS(("<<ClearFocus begin>>"));
565 // if the window to clear is the focused window or an ancestor of the
566 // focused window, then blur the existing focused content. Otherwise, the
567 // focus is somewhere else so just update the current node.
568 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
569 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
571 if (IsSameOrAncestor(window
, GetFocusedBrowsingContext())) {
572 RefPtr
<BrowsingContext
> bc
= window
->GetBrowsingContext();
573 bool isAncestor
= (GetFocusedBrowsingContext() != bc
);
574 uint64_t actionId
= GenerateFocusActionId();
575 if (Blur(bc
, nullptr, isAncestor
, true, false, actionId
)) {
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
579 Focus(window
, nullptr, 0, true, false, false, true, actionId
);
583 window
->SetFocusedElement(nullptr);
586 LOGFOCUS(("<<ClearFocus end>>"));
592 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy
* aWindow
,
594 mozIDOMWindowProxy
** aFocusedWindow
,
595 Element
** aElement
) {
597 if (aFocusedWindow
) {
598 *aFocusedWindow
= nullptr;
601 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
602 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
604 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
605 RefPtr
<Element
> focusedElement
=
606 GetFocusedDescendant(window
,
607 aDeep
? nsFocusManager::eIncludeAllDescendants
608 : nsFocusManager::eOnlyCurrentWindow
,
609 getter_AddRefs(focusedWindow
));
611 focusedElement
.forget(aElement
);
613 if (aFocusedWindow
) {
614 NS_IF_ADDREF(*aFocusedWindow
= focusedWindow
);
621 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy
* aWindow
) {
622 nsCOMPtr
<nsIWebNavigation
> webnav
= do_GetInterface(aWindow
);
623 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(webnav
);
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
631 docShell
->GetEditable(&isEditable
);
636 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
637 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
639 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
640 if (RefPtr
<Element
> focusedElement
= window
->GetFocusedElement()) {
641 MoveCaretToFocus(presShell
, focusedElement
);
649 void nsFocusManager::WindowRaised(mozIDOMWindowProxy
* aWindow
,
650 uint64_t aActionId
) {
655 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
656 BrowsingContext
* bc
= window
->GetBrowsingContext();
658 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
659 LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64
, aWindow
,
660 mActiveWindow
.get(), mFocusedWindow
.get(), aActionId
));
661 Document
* doc
= window
->GetExtantDoc();
662 if (doc
&& doc
->GetDocumentURI()) {
663 LOGFOCUS((" Raised Window: %p %s", aWindow
,
664 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
667 doc
= mActiveWindow
->GetExtantDoc();
668 if (doc
&& doc
->GetDocumentURI()) {
669 LOGFOCUS((" Active Window: %p %s", mActiveWindow
.get(),
670 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
675 if (XRE_IsParentProcess()) {
676 if (mActiveWindow
== window
) {
677 // The window is already active, so there is no need to focus anything,
678 // but make sure that the right widget is focused. This is a special case
679 // for Windows because when restoring a minimized window, a second
680 // activation will occur and the top-level widget could be focused instead
681 // of the child we want. We solve this by calling SetFocus to ensure that
682 // what the focus manager thinks should be the current widget is actually
684 EnsureCurrentWidgetFocused(CallerType::System
);
688 // lower the existing window, if any. This shouldn't happen usually.
689 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
) {
690 WindowLowered(activeWindow
, aActionId
);
692 } else if (bc
->IsTop()) {
693 BrowsingContext
* active
= GetActiveBrowsingContext();
694 if (active
== bc
&& !mActiveBrowsingContextInContentSetFromOtherProcess
) {
695 // EnsureCurrentWidgetFocused() should not be necessary with
700 if (active
&& active
!= bc
) {
701 if (active
->IsInProcess()) {
702 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= active
->GetDOMWindow();
703 WindowLowered(activeWindow
, aActionId
);
705 // No else, because trying to lower other-process windows
706 // from here can result in the BrowsingContext no longer
707 // existing in the parent process by the time it deserializes
712 nsCOMPtr
<nsIDocShellTreeItem
> docShellAsItem
= window
->GetDocShell();
713 // If there's no docShellAsItem, this window must have been closed,
714 // in that case there is no tree owner.
715 if (!docShellAsItem
) {
719 // set this as the active window
720 if (XRE_IsParentProcess()) {
721 mActiveWindow
= window
;
722 } else if (bc
->IsTop()) {
723 SetActiveBrowsingContextInContent(bc
, aActionId
);
726 // ensure that the window is enabled and visible
727 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
728 docShellAsItem
->GetTreeOwner(getter_AddRefs(treeOwner
));
729 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(treeOwner
);
731 bool isEnabled
= true;
732 if (NS_SUCCEEDED(baseWindow
->GetEnabled(&isEnabled
)) && !isEnabled
) {
736 baseWindow
->SetVisibility(true);
739 if (XRE_IsParentProcess()) {
740 // Unsetting top-level focus upon lowering was inhibited to accommodate
741 // ATOK, so we need to do it here.
742 BrowserParent::UnsetTopLevelWebFocusAll();
743 ActivateOrDeactivate(window
, true);
746 // retrieve the last focused element within the window that was raised
747 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
748 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
749 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
751 NS_ASSERTION(currentWindow
, "window raised with no window current");
752 if (!currentWindow
) {
756 nsCOMPtr
<nsIAppWindow
> appWin(do_GetInterface(baseWindow
));
757 // We use mFocusedWindow here is basically for the case that iframe navigate
758 // from a.com to b.com for example, so it ends up being loaded in a different
759 // process after Fission, but
760 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
761 // still be true because focused browsing context is synced, and we won't
762 // fire a focus event while focusing if we use it as condition.
763 Focus(currentWindow
, currentFocus
, 0, currentWindow
!= mFocusedWindow
, false,
764 appWin
!= nullptr, true, aActionId
);
767 void nsFocusManager::WindowLowered(mozIDOMWindowProxy
* aWindow
,
768 uint64_t aActionId
) {
773 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
775 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
776 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow
,
777 mActiveWindow
.get(), mFocusedWindow
.get()));
778 Document
* doc
= window
->GetExtantDoc();
779 if (doc
&& doc
->GetDocumentURI()) {
780 LOGFOCUS((" Lowered Window: %s",
781 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
784 doc
= mActiveWindow
->GetExtantDoc();
785 if (doc
&& doc
->GetDocumentURI()) {
786 LOGFOCUS((" Active Window: %s",
787 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
792 if (XRE_IsParentProcess()) {
793 if (mActiveWindow
!= window
) {
797 BrowsingContext
* bc
= window
->GetBrowsingContext();
798 BrowsingContext
* active
= GetActiveBrowsingContext();
799 if (active
!= bc
->Top()) {
804 // clear the mouse capture as the active window has changed
805 PresShell::ReleaseCapturingContent();
807 // In addition, reset the drag state to ensure that we are no longer in
809 if (mFocusedWindow
) {
810 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
812 if (PresShell
* presShell
= docShell
->GetPresShell()) {
813 RefPtr
<nsFrameSelection
> frameSelection
= presShell
->FrameSelection();
814 frameSelection
->SetDragState(false);
819 if (XRE_IsParentProcess()) {
820 ActivateOrDeactivate(window
, false);
823 // keep track of the window being lowered, so that attempts to raise the
824 // window can be prevented until we return. Otherwise, focus can get into
826 mWindowBeingLowered
= window
;
827 if (XRE_IsParentProcess()) {
828 mActiveWindow
= nullptr;
830 BrowsingContext
* bc
= window
->GetBrowsingContext();
831 if (bc
== bc
->Top()) {
832 SetActiveBrowsingContextInContent(nullptr, aActionId
);
836 if (mFocusedWindow
) {
837 Blur(nullptr, nullptr, true, true, false, aActionId
);
840 mWindowBeingLowered
= nullptr;
843 nsresult
nsFocusManager::ContentRemoved(Document
* aDocument
,
844 nsIContent
* aContent
) {
845 NS_ENSURE_ARG(aDocument
);
846 NS_ENSURE_ARG(aContent
);
848 RefPtr
<nsPIDOMWindowOuter
> window
= aDocument
->GetWindow();
853 // if the content is currently focused in the window, or is an
854 // shadow-including inclusive ancestor of the currently focused element,
855 // reset the focus within that window.
856 RefPtr
<Element
> previousFocusedElement
= window
->GetFocusedElement();
857 if (!previousFocusedElement
) {
861 if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
862 previousFocusedElement
, aContent
)) {
866 RefPtr
<Element
> newFocusedElement
= [&]() -> Element
* {
867 if (auto* sr
= ShadowRoot::FromNode(aContent
)) {
868 if (sr
->IsUAWidget() && sr
->Host()->IsHTMLElement(nsGkAtoms::input
)) {
875 window
->SetFocusedElement(newFocusedElement
);
877 // if this window is currently focused, clear the global focused
878 // element as well, but don't fire any events.
879 if (window
->GetBrowsingContext() == GetFocusedBrowsingContext()) {
880 mFocusedElement
= newFocusedElement
;
881 } else if (Document
* subdoc
=
882 aDocument
->GetSubDocumentFor(previousFocusedElement
)) {
883 // Check if the node that was focused is an iframe or similar by looking if
884 // it has a subdocument. This would indicate that this focused iframe
885 // and its descendants will be going away. We will need to move the focus
886 // somewhere else, so just clear the focus in the toplevel window so that no
887 // element is focused.
889 // The Fission case is handled in FlushAndCheckIfFocusable().
890 if (nsCOMPtr
<nsIDocShell
> docShell
= subdoc
->GetDocShell()) {
891 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= docShell
->GetWindow();
893 IsSameOrAncestor(childWindow
, GetFocusedBrowsingContext())) {
894 if (XRE_IsParentProcess()) {
895 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
896 ClearFocus(activeWindow
);
898 BrowsingContext
* active
= GetActiveBrowsingContext();
900 if (active
->IsInProcess()) {
901 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
902 active
->GetDOMWindow();
903 ClearFocus(activeWindow
);
905 mozilla::dom::ContentChild
* contentChild
=
906 mozilla::dom::ContentChild::GetSingleton();
907 MOZ_ASSERT(contentChild
);
908 contentChild
->SendClearFocus(active
);
910 } // no else, because ClearFocus does nothing with nullptr
916 // Notify the editor in case we removed its ancestor limiter.
917 if (previousFocusedElement
->IsEditable()) {
918 if (nsCOMPtr
<nsIDocShell
> docShell
= aDocument
->GetDocShell()) {
919 if (RefPtr
<HTMLEditor
> htmlEditor
= docShell
->GetHTMLEditor()) {
920 RefPtr
<Selection
> selection
= htmlEditor
->GetSelection();
921 if (selection
&& selection
->GetFrameSelection() &&
922 previousFocusedElement
==
923 selection
->GetFrameSelection()->GetAncestorLimiter()) {
924 htmlEditor
->FinalizeSelection();
930 if (!newFocusedElement
) {
931 NotifyFocusStateChange(previousFocusedElement
, newFocusedElement
, 0,
932 /* aGettingFocus = */ false, false);
934 // We should already have the right state, which is managed by the <input>
936 MOZ_ASSERT(newFocusedElement
->State().HasState(ElementState::FOCUS
));
939 // If we changed focused element and the element still has focus, let's
940 // notify IME of focus. Note that if new focus move has already occurred
941 // by running script, we should not let IMEStateManager of outdated focus
943 if (mFocusedElement
== newFocusedElement
&& mFocusedWindow
== window
) {
944 RefPtr
<nsPresContext
> presContext(aDocument
->GetPresContext());
945 IMEStateManager::OnChangeFocus(presContext
, newFocusedElement
,
946 InputContextAction::Cause::CAUSE_UNKNOWN
);
952 void nsFocusManager::WindowShown(mozIDOMWindowProxy
* aWindow
,
958 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
960 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
961 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window
.get(),
962 mActiveWindow
.get(), mFocusedWindow
.get()));
963 Document
* doc
= window
->GetExtantDoc();
964 if (doc
&& doc
->GetDocumentURI()) {
965 LOGFOCUS(("Shown Window: %s",
966 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
969 if (mFocusedWindow
) {
970 doc
= mFocusedWindow
->GetExtantDoc();
971 if (doc
&& doc
->GetDocumentURI()) {
972 LOGFOCUS((" Focused Window: %s",
973 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
978 if (XRE_IsParentProcess()) {
979 if (BrowsingContext
* bc
= window
->GetBrowsingContext()) {
981 bc
->SetIsActiveBrowserWindow(bc
->GetIsActiveBrowserWindow());
986 if (XRE_IsParentProcess()) {
987 if (mFocusedWindow
!= window
) {
991 BrowsingContext
* bc
= window
->GetBrowsingContext();
992 if (!bc
|| mFocusedBrowsingContextInContent
!= bc
) {
995 // Sync the window for a newly-created OOP iframe
996 // Set actionId to zero to signify that it should be ignored.
997 SetFocusedWindowInternal(window
, 0, false);
1001 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
1002 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
1003 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
1005 if (currentWindow
) {
1006 Focus(currentWindow
, currentFocus
, 0, true, false, false, true,
1007 GenerateFocusActionId());
1010 // Sometimes, an element in a window can be focused before the window is
1011 // visible, which would mean that the widget may not be properly focused.
1012 // When the window becomes visible, make sure the right widget is focused.
1013 EnsureCurrentWidgetFocused(CallerType::System
);
1017 void nsFocusManager::WindowHidden(mozIDOMWindowProxy
* aWindow
,
1018 uint64_t aActionId
) {
1019 // if there is no window or it is not the same or an ancestor of the
1020 // currently focused window, just return, as the current focus will not
1027 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
1029 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
1030 LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64
,
1031 window
.get(), mActiveWindow
.get(), mFocusedWindow
.get(),
1034 Document
* doc
= window
->GetExtantDoc();
1035 if (doc
&& doc
->GetDocumentURI()) {
1036 LOGFOCUS((" Hide Window: %s",
1037 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1040 if (mFocusedWindow
) {
1041 doc
= mFocusedWindow
->GetExtantDoc();
1042 if (doc
&& doc
->GetDocumentURI()) {
1043 LOGFOCUS((" Focused Window: %s",
1044 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1048 if (mActiveWindow
) {
1049 doc
= mActiveWindow
->GetExtantDoc();
1050 if (doc
&& doc
->GetDocumentURI()) {
1051 LOGFOCUS((" Active Window: %s",
1052 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1057 if (!IsSameOrAncestor(window
, mFocusedWindow
)) {
1061 // at this point, we know that the window being hidden is either the focused
1062 // window, or an ancestor of the focused window. Either way, the focus is no
1063 // longer valid, so it needs to be updated.
1065 const RefPtr
<Element
> oldFocusedElement
= std::move(mFocusedElement
);
1067 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
1068 if (!focusedDocShell
) {
1072 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
1074 if (oldFocusedElement
&& oldFocusedElement
->IsInComposedDoc()) {
1075 NotifyFocusStateChange(oldFocusedElement
, nullptr, 0, false, false);
1076 window
->UpdateCommands(u
"focus"_ns
);
1079 RefPtr
<Document
> composedDoc
= oldFocusedElement
->GetComposedDoc();
1080 SendFocusOrBlurEvent(eBlur
, presShell
, composedDoc
, oldFocusedElement
,
1085 const RefPtr
<nsPresContext
> focusedPresContext
=
1086 presShell
? presShell
->GetPresContext() : nullptr;
1087 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
1088 GetFocusMoveActionCause(0));
1090 SetCaretVisible(presShell
, false, nullptr);
1093 // If a window is being "hidden" because its BrowsingContext is changing
1094 // remoteness, we don't want to handle docshell destruction by moving focus.
1095 // Instead, the focused browsing context should stay the way it is (so that
1096 // the newly "shown" window in the other process knows to take focus) and
1097 // we should just null out the process-local field.
1098 nsCOMPtr
<nsIDocShell
> docShellBeingHidden
= window
->GetDocShell();
1099 // Check if we're currently hiding a non-remote nsDocShell due to its
1100 // BrowsingContext navigating to become remote. Normally, when a focused
1101 // subframe is hidden, focus is moved to the frame element, but focus should
1102 // stay with the BrowsingContext when performing a process switch. We don't
1103 // need to consider process switches where the hiding docshell is already
1104 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
1105 // frame element is handled elsewhere.
1106 if (docShellBeingHidden
&&
1107 nsDocShell::Cast(docShellBeingHidden
)->WillChangeProcess() &&
1108 docShellBeingHidden
->GetBrowsingContext()->GetEmbedderElement()) {
1109 if (mFocusedWindow
!= window
) {
1110 // The window being hidden is an ancestor of the focused window.
1112 BrowsingContext
* ancestor
= window
->GetBrowsingContext();
1113 BrowsingContext
* bc
= mFocusedWindow
->GetBrowsingContext();
1116 MOZ_ASSERT(false, "Should have found ancestor");
1118 bc
= bc
->GetParent();
1119 if (ancestor
== bc
) {
1124 // This call adjusts the focused browsing context and window.
1125 // The latter gets nulled out immediately below.
1126 SetFocusedWindowInternal(window
, aActionId
);
1128 mFocusedWindow
= nullptr;
1129 window
->SetFocusedElement(nullptr);
1133 // if the docshell being hidden is being destroyed, then we want to move
1134 // focus somewhere else. Call ClearFocus on the toplevel window, which
1135 // will have the effect of clearing the focus and moving the focused window
1136 // to the toplevel window. But if the window isn't being destroyed, we are
1137 // likely just loading a new document in it, so we want to maintain the
1138 // focused window so that the new document gets properly focused.
1139 bool beingDestroyed
= !docShellBeingHidden
;
1140 if (docShellBeingHidden
) {
1141 docShellBeingHidden
->IsBeingDestroyed(&beingDestroyed
);
1143 if (beingDestroyed
) {
1144 // There is usually no need to do anything if a toplevel window is going
1145 // away, as we assume that WindowLowered will be called. However, this may
1146 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1147 // a leak. So if the active window is being destroyed, call WindowLowered
1150 if (XRE_IsParentProcess()) {
1151 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
1152 if (activeWindow
== mFocusedWindow
|| activeWindow
== window
) {
1153 WindowLowered(activeWindow
, aActionId
);
1155 ClearFocus(activeWindow
);
1158 BrowsingContext
* active
= GetActiveBrowsingContext();
1160 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
1161 active
->GetDOMWindow()) {
1162 if ((mFocusedWindow
&&
1163 mFocusedWindow
->GetBrowsingContext() == active
) ||
1164 (window
->GetBrowsingContext() == active
)) {
1165 WindowLowered(activeWindow
, aActionId
);
1167 ClearFocus(activeWindow
);
1169 } // else do nothing when an out-of-process iframe is torn down
1175 if (!XRE_IsParentProcess() &&
1176 mActiveBrowsingContextInContent
==
1177 docShellBeingHidden
->GetBrowsingContext() &&
1178 mActiveBrowsingContextInContent
->GetIsInBFCache()) {
1179 SetActiveBrowsingContextInContent(nullptr, aActionId
);
1182 // if the window being hidden is an ancestor of the focused window, adjust
1183 // the focused window so that it points to the one being hidden. This
1184 // ensures that the focused window isn't in a chain of frames that doesn't
1186 if (window
!= mFocusedWindow
) {
1187 nsCOMPtr
<nsIDocShellTreeItem
> dsti
=
1188 mFocusedWindow
? mFocusedWindow
->GetDocShell() : nullptr;
1190 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1191 dsti
->GetInProcessParent(getter_AddRefs(parentDsti
));
1193 if (nsCOMPtr
<nsPIDOMWindowOuter
> parentWindow
=
1194 parentDsti
->GetWindow()) {
1195 parentWindow
->SetFocusedElement(nullptr);
1200 SetFocusedWindowInternal(window
, aActionId
);
1204 void nsFocusManager::FireDelayedEvents(Document
* aDocument
) {
1205 MOZ_ASSERT(aDocument
);
1207 // fire any delayed focus and blur events in the same order that they were
1209 for (uint32_t i
= 0; i
< mDelayedBlurFocusEvents
.Length(); i
++) {
1210 if (mDelayedBlurFocusEvents
[i
].mDocument
== aDocument
) {
1211 if (!aDocument
->GetInnerWindow() ||
1212 !aDocument
->GetInnerWindow()->IsCurrentInnerWindow()) {
1213 // If the document was navigated away from or is defunct, don't bother
1214 // firing events on it. Note the symmetry between this condition and
1215 // the similar one in Document.cpp:FireOrClearDelayedEvents.
1216 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1218 } else if (!aDocument
->EventHandlingSuppressed()) {
1219 EventMessage message
= mDelayedBlurFocusEvents
[i
].mEventMessage
;
1220 nsCOMPtr
<EventTarget
> target
= mDelayedBlurFocusEvents
[i
].mTarget
;
1221 RefPtr
<PresShell
> presShell
= mDelayedBlurFocusEvents
[i
].mPresShell
;
1222 nsCOMPtr
<EventTarget
> relatedTarget
=
1223 mDelayedBlurFocusEvents
[i
].mRelatedTarget
;
1224 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1226 FireFocusOrBlurEvent(message
, presShell
, target
, false, false,
1234 void nsFocusManager::WasNuked(nsPIDOMWindowOuter
* aWindow
) {
1235 MOZ_ASSERT(aWindow
, "Expected non-null window.");
1236 MOZ_ASSERT(aWindow
!= mActiveWindow
,
1237 "How come we're nuking a window that's still active?");
1238 if (aWindow
== mFocusedWindow
) {
1239 mFocusedWindow
= nullptr;
1240 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
1241 mFocusedElement
= nullptr;
1245 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element
& aElement
)
1246 : mElement(aElement
) {}
1248 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
1250 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
1251 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter
* aWindow
,
1252 const Element
& aElement
,
1253 int32_t aFocusFlags
) {
1254 // If we were explicitly requested to show the ring, do it.
1255 if (aFocusFlags
& nsIFocusManager::FLAG_SHOWRING
) {
1259 if (aFocusFlags
& nsIFocusManager::FLAG_NOSHOWRING
) {
1263 if (aWindow
->ShouldShowFocusRing()) {
1264 // The window decision also trumps any other heuristic.
1268 // Any element which supports keyboard input (such as an input element, or any
1269 // other element which may trigger a virtual keyboard to be shown on focus if
1270 // a physical keyboard is not present) should always match :focus-visible when
1273 if (aElement
.IsHTMLElement(nsGkAtoms::textarea
) || aElement
.IsEditable()) {
1277 if (auto* input
= HTMLInputElement::FromNode(aElement
)) {
1278 if (input
->IsSingleLineTextControl()) {
1284 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags
)) {
1285 case InputContextAction::CAUSE_KEY
:
1286 // If the user interacts with the page via the keyboard, the currently
1287 // focused element should match :focus-visible (i.e. keyboard usage may
1288 // change whether this pseudo-class matches even if it doesn't affect
1291 case InputContextAction::CAUSE_UNKNOWN
:
1292 // We render outlines if the last "known" focus method was by key or there
1293 // was no previous known focus method, otherwise we don't.
1294 return aWindow
->UnknownFocusMethodShouldShowOutline();
1295 case InputContextAction::CAUSE_MOUSE
:
1296 case InputContextAction::CAUSE_TOUCH
:
1297 case InputContextAction::CAUSE_LONGPRESS
:
1298 // If the user interacts with the page via a pointing device, such that
1299 // the focus is moved to a new element which does not support user input,
1300 // the newly focused element should not match :focus-visible.
1302 case InputContextAction::CAUSE_UNKNOWN_CHROME
:
1303 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT
:
1304 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT
:
1305 // TODO(emilio): We could return some of these though, looking at
1306 // UserActivation. We may want to suppress focus rings for unknown /
1307 // programatic focus if the user is interacting with the page but not
1308 // during keyboard input, or such.
1309 MOZ_ASSERT_UNREACHABLE(
1310 "These don't get returned by GetFocusMoveActionCause");
1317 void nsFocusManager::NotifyFocusStateChange(Element
* aElement
,
1318 Element
* aElementToFocus
,
1319 int32_t aFlags
, bool aGettingFocus
,
1320 bool aShouldShowFocusRing
) {
1321 MOZ_ASSERT_IF(aElementToFocus
, !aGettingFocus
);
1322 nsIContent
* commonAncestor
= nullptr;
1323 if (aElementToFocus
) {
1324 commonAncestor
= nsContentUtils::GetCommonFlattenedTreeAncestor(
1325 aElement
, aElementToFocus
);
1328 if (aGettingFocus
) {
1329 ElementState stateToAdd
= ElementState::FOCUS
;
1330 if (aShouldShowFocusRing
) {
1331 stateToAdd
|= ElementState::FOCUSRING
;
1333 aElement
->AddStates(stateToAdd
);
1335 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1336 host
= host
->GetContainingShadowHost()) {
1337 host
->AsElement()->AddStates(ElementState::FOCUS
);
1340 constexpr auto kStatesToRemove
=
1341 ElementState::FOCUS
| ElementState::FOCUSRING
;
1342 aElement
->RemoveStates(kStatesToRemove
);
1343 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1344 host
= host
->GetContainingShadowHost()) {
1345 host
->AsElement()->RemoveStates(kStatesToRemove
);
1349 // Special case for <input type="checkbox"> and <input type="radio">.
1350 // The other browsers cancel active state when they gets lost focus, but
1351 // does not do it for the other elements such as <button> and <a href="...">.
1352 // Additionally, they may be activated with <label>, but they will get focus
1353 // at `click`, but activated at `mousedown`. Therefore, we need to cancel
1354 // active state at moving focus.
1355 if (RefPtr
<nsPresContext
> presContext
=
1356 aElement
->GetPresContext(Element::PresContextFor::eForComposedDoc
)) {
1357 RefPtr
<EventStateManager
> esm
= presContext
->EventStateManager();
1358 auto* activeInputElement
=
1359 HTMLInputElement::FromNodeOrNull(esm
->GetActiveContent());
1360 if (activeInputElement
&&
1361 (activeInputElement
->ControlType() == FormControlType::InputCheckbox
||
1362 activeInputElement
->ControlType() == FormControlType::InputRadio
) &&
1363 !activeInputElement
->State().HasState(ElementState::FOCUS
)) {
1364 esm
->SetContentState(nullptr, ElementState::ACTIVE
);
1368 for (nsIContent
* content
= aElement
; content
&& content
!= commonAncestor
;
1369 content
= content
->GetFlattenedTreeParent()) {
1370 Element
* element
= Element::FromNode(content
);
1375 if (aGettingFocus
) {
1376 if (element
->State().HasState(ElementState::FOCUS_WITHIN
)) {
1379 element
->AddStates(ElementState::FOCUS_WITHIN
);
1381 element
->RemoveStates(ElementState::FOCUS_WITHIN
);
1387 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType
) {
1388 if (!mFocusedWindow
|| sTestMode
) return;
1390 // get the main child widget for the focused window and ensure that the
1391 // platform knows that this widget is focused.
1392 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
1396 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1400 nsViewManager
* vm
= presShell
->GetViewManager();
1404 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
1408 widget
->SetFocus(nsIWidget::Raise::No
, aCallerType
);
1411 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter
* aWindow
,
1413 MOZ_ASSERT(XRE_IsParentProcess());
1418 if (BrowsingContext
* bc
= aWindow
->GetBrowsingContext()) {
1419 MOZ_ASSERT(bc
->IsTop());
1421 RefPtr
<CanonicalBrowsingContext
> chromeTop
=
1422 bc
->Canonical()->TopCrossChromeBoundary();
1423 MOZ_ASSERT(bc
== chromeTop
);
1425 chromeTop
->SetIsActiveBrowserWindow(aActive
);
1426 chromeTop
->CallOnAllTopDescendants(
1427 [aActive
](CanonicalBrowsingContext
* aBrowsingContext
) -> CallState
{
1428 aBrowsingContext
->SetIsActiveBrowserWindow(aActive
);
1429 return CallState::Continue
;
1433 if (aWindow
->GetExtantDoc()) {
1434 nsContentUtils::DispatchEventOnlyToChrome(
1435 aWindow
->GetExtantDoc(),
1436 nsGlobalWindowInner::Cast(aWindow
->GetCurrentInnerWindow()),
1437 aActive
? u
"activate"_ns
: u
"deactivate"_ns
, CanBubble::eYes
,
1438 Cancelable::eYes
, nullptr);
1442 // Retrieves innerWindowId of the window of the last focused element to
1443 // log a warning to the website console.
1444 void LogWarningFullscreenWindowRaise(Element
* aElement
) {
1445 nsCOMPtr
<nsFrameLoaderOwner
> frameLoaderOwner(do_QueryInterface(aElement
));
1446 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1448 RefPtr
<nsFrameLoader
> frameLoader
= frameLoaderOwner
->GetFrameLoader();
1449 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1451 RefPtr
<BrowsingContext
> browsingContext
= frameLoader
->GetBrowsingContext();
1452 NS_ENSURE_TRUE_VOID(browsingContext
);
1454 WindowGlobalParent
* windowGlobalParent
=
1455 browsingContext
->Canonical()->GetCurrentWindowGlobal();
1456 NS_ENSURE_TRUE_VOID(windowGlobalParent
);
1459 nsAutoString localizedMsg
;
1460 nsTArray
<nsString
> params
;
1461 nsresult rv
= nsContentUtils::FormatLocalizedString(
1462 nsContentUtils::eDOM_PROPERTIES
, "FullscreenExitWindowFocus", params
,
1465 NS_ENSURE_SUCCESS_VOID(rv
);
1467 Unused
<< nsContentUtils::ReportToConsoleByWindowID(
1468 localizedMsg
, nsIScriptError::warningFlag
, "DOM"_ns
,
1469 windowGlobalParent
->InnerWindowId(),
1470 windowGlobalParent
->GetDocumentURI());
1473 // Ensure that when an embedded popup with a noautofocus attribute
1474 // like a date picker is opened and focused, the parent page does not blur
1475 static bool IsEmeddededInNoautofocusPopup(BrowsingContext
& aBc
) {
1476 auto* embedder
= aBc
.GetEmbedderElement();
1480 nsIFrame
* f
= embedder
->GetPrimaryFrame();
1481 if (!f
|| !f
->HasAnyStateBits(NS_FRAME_IN_POPUP
)) {
1485 nsIFrame
* menuPopup
=
1486 nsLayoutUtils::GetClosestFrameOfType(f
, LayoutFrameType::MenuPopup
);
1487 MOZ_ASSERT(menuPopup
, "NS_FRAME_IN_POPUP lied?");
1488 return static_cast<nsMenuPopupFrame
*>(menuPopup
)
1490 .GetXULBoolAttr(nsGkAtoms::noautofocus
);
1493 Maybe
<uint64_t> nsFocusManager::SetFocusInner(Element
* aNewContent
,
1496 bool aAdjustWidget
) {
1497 // if the element is not focusable, just return and leave the focus as is
1498 RefPtr
<Element
> elementToFocus
=
1499 FlushAndCheckIfFocusable(aNewContent
, aFlags
);
1500 if (!elementToFocus
) {
1504 const RefPtr
<BrowsingContext
> focusedBrowsingContext
=
1505 GetFocusedBrowsingContext();
1507 // check if the element to focus is a frame (iframe) containing a child
1508 // document. Frames are never directly focused; instead focusing a frame
1509 // means focus what is inside the frame. To do this, the descendant content
1510 // within the frame is retrieved and that will be focused instead.
1511 nsCOMPtr
<nsPIDOMWindowOuter
> newWindow
;
1512 nsCOMPtr
<nsPIDOMWindowOuter
> subWindow
= GetContentWindow(elementToFocus
);
1514 elementToFocus
= GetFocusedDescendant(subWindow
, eIncludeAllDescendants
,
1515 getter_AddRefs(newWindow
));
1517 // since a window is being refocused, clear aFocusChanged so that the
1518 // caret position isn't updated.
1519 aFocusChanged
= false;
1522 // unless it was set above, retrieve the window for the element to focus
1524 newWindow
= GetCurrentWindow(elementToFocus
);
1527 RefPtr
<BrowsingContext
> newBrowsingContext
;
1529 newBrowsingContext
= newWindow
->GetBrowsingContext();
1532 // if the element is already focused, just return. Note that this happens
1533 // after the frame check above so that we compare the element that will be
1534 // focused rather than the frame it is in.
1535 if (!newWindow
|| (newBrowsingContext
== GetFocusedBrowsingContext() &&
1536 elementToFocus
== mFocusedElement
)) {
1540 MOZ_ASSERT(newBrowsingContext
);
1542 BrowsingContext
* browsingContextToFocus
= newBrowsingContext
;
1543 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(elementToFocus
)) {
1544 // Only look at pre-existing browsing contexts. If this function is
1545 // called during reflow, calling GetBrowsingContext() could cause frame
1546 // loader initialization at a time when it isn't safe.
1547 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
1548 // If focus is already in the subtree rooted at bc, return early
1549 // to match the single-process focus semantics. Otherwise, we'd
1550 // blur and immediately refocus whatever is focused.
1551 BrowsingContext
* walk
= focusedBrowsingContext
;
1556 walk
= walk
->GetParent();
1558 browsingContextToFocus
= bc
;
1562 // don't allow focus to be placed in docshells or descendants of docshells
1563 // that are being destroyed. Also, ensure that the page hasn't been
1564 // unloaded. The prevents content from being refocused during an unload event.
1565 nsCOMPtr
<nsIDocShell
> newDocShell
= newWindow
->GetDocShell();
1566 nsCOMPtr
<nsIDocShell
> docShell
= newDocShell
;
1569 docShell
->GetIsInUnload(&inUnload
);
1574 bool beingDestroyed
;
1575 docShell
->IsBeingDestroyed(&beingDestroyed
);
1576 if (beingDestroyed
) {
1580 BrowsingContext
* bc
= docShell
->GetBrowsingContext();
1582 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1583 docShell
->GetInProcessParent(getter_AddRefs(parentDsti
));
1584 docShell
= do_QueryInterface(parentDsti
);
1585 if (!docShell
&& !XRE_IsParentProcess()) {
1586 // We don't have an in-process parent, but let's see if we have
1587 // an in-process ancestor or if an out-of-process ancestor
1590 bc
= bc
->GetParent();
1591 if (bc
&& bc
->IsDiscarded()) {
1594 } while (bc
&& !bc
->IsInProcess());
1596 docShell
= bc
->GetDocShell();
1603 bool focusMovesToDifferentBC
=
1604 (focusedBrowsingContext
!= browsingContextToFocus
);
1606 if (focusedBrowsingContext
&& focusMovesToDifferentBC
&&
1607 nsContentUtils::IsHandlingKeyBoardEvent() &&
1608 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1609 MOZ_ASSERT(browsingContextToFocus
,
1610 "BrowsingContext to focus should be non-null.");
1612 nsIPrincipal
* focusedPrincipal
= nullptr;
1613 nsIPrincipal
* newPrincipal
= nullptr;
1615 if (XRE_IsParentProcess()) {
1616 if (WindowGlobalParent
* focusedWindowGlobalParent
=
1617 focusedBrowsingContext
->Canonical()->GetCurrentWindowGlobal()) {
1618 focusedPrincipal
= focusedWindowGlobalParent
->DocumentPrincipal();
1621 if (WindowGlobalParent
* newWindowGlobalParent
=
1622 browsingContextToFocus
->Canonical()->GetCurrentWindowGlobal()) {
1623 newPrincipal
= newWindowGlobalParent
->DocumentPrincipal();
1625 } else if (focusedBrowsingContext
->IsInProcess() &&
1626 browsingContextToFocus
->IsInProcess()) {
1627 nsCOMPtr
<nsIScriptObjectPrincipal
> focused
=
1628 do_QueryInterface(focusedBrowsingContext
->GetDOMWindow());
1629 nsCOMPtr
<nsIScriptObjectPrincipal
> newFocus
=
1630 do_QueryInterface(browsingContextToFocus
->GetDOMWindow());
1631 MOZ_ASSERT(focused
&& newFocus
,
1632 "BrowsingContext should always have a window here.");
1633 focusedPrincipal
= focused
->GetPrincipal();
1634 newPrincipal
= newFocus
->GetPrincipal();
1637 if (!focusedPrincipal
|| !newPrincipal
) {
1641 if (!focusedPrincipal
->Subsumes(newPrincipal
)) {
1642 NS_WARNING("Not allowed to focus the new window!");
1647 // to check if the new element is in the active window, compare the
1648 // new root docshell for the new element with the active window's docshell.
1649 RefPtr
<BrowsingContext
> newRootBrowsingContext
= nullptr;
1650 bool isElementInActiveWindow
= false;
1651 if (XRE_IsParentProcess()) {
1652 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
= nullptr;
1653 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= newWindow
->GetDocShell();
1655 nsCOMPtr
<nsIDocShellTreeItem
> root
;
1656 dsti
->GetInProcessRootTreeItem(getter_AddRefs(root
));
1657 newRootWindow
= root
? root
->GetWindow() : nullptr;
1659 isElementInActiveWindow
=
1660 (mActiveWindow
&& newRootWindow
== mActiveWindow
);
1662 if (newRootWindow
) {
1663 newRootBrowsingContext
= newRootWindow
->GetBrowsingContext();
1666 // XXX This is wrong for `<iframe mozbrowser>` and for XUL
1667 // `<browser remote="true">`. See:
1668 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
1669 newRootBrowsingContext
= newBrowsingContext
->Top();
1670 // to check if the new element is in the active window, compare the
1671 // new root docshell for the new element with the active window's docshell.
1672 isElementInActiveWindow
=
1673 (GetActiveBrowsingContext() == newRootBrowsingContext
);
1676 // Exit fullscreen if a website focuses another window
1677 if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
1678 !isElementInActiveWindow
&& (aFlags
& FLAG_RAISE
)) {
1679 if (XRE_IsParentProcess()) {
1680 if (Document
* doc
= mActiveWindow
? mActiveWindow
->GetDoc() : nullptr) {
1681 Document::ClearPendingFullscreenRequests(doc
);
1682 if (doc
->GetFullscreenElement()) {
1683 LogWarningFullscreenWindowRaise(mFocusedElement
);
1684 Document::AsyncExitFullscreen(doc
);
1688 BrowsingContext
* activeBrowsingContext
= GetActiveBrowsingContext();
1689 if (activeBrowsingContext
) {
1690 nsIDocShell
* shell
= activeBrowsingContext
->GetDocShell();
1692 if (Document
* doc
= shell
->GetDocument()) {
1693 Document::ClearPendingFullscreenRequests(doc
);
1694 if (doc
->GetFullscreenElement()) {
1695 Document::AsyncExitFullscreen(doc
);
1699 mozilla::dom::ContentChild
* contentChild
=
1700 mozilla::dom::ContentChild::GetSingleton();
1701 MOZ_ASSERT(contentChild
);
1702 contentChild
->SendMaybeExitFullscreen(activeBrowsingContext
);
1708 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1709 // shifted away from the current element if the new shell to focus is
1710 // the same or an ancestor shell of the currently focused shell.
1711 bool allowFrameSwitch
= !(aFlags
& FLAG_NOSWITCHFRAME
) ||
1712 IsSameOrAncestor(newWindow
, focusedBrowsingContext
);
1714 // if the element is in the active window, frame switching is allowed and
1715 // the content is in a visible window, fire blur and focus events.
1716 bool sendFocusEvent
=
1717 isElementInActiveWindow
&& allowFrameSwitch
&& IsWindowVisible(newWindow
);
1719 // Don't allow to steal the focus from chrome nodes if the caller cannot
1721 if (sendFocusEvent
&& mFocusedElement
&&
1722 mFocusedElement
->OwnerDoc() != aNewContent
->OwnerDoc() &&
1723 mFocusedElement
->NodePrincipal()->IsSystemPrincipal() &&
1724 !nsContentUtils::LegacyIsCallerNativeCode() &&
1725 !nsContentUtils::CanCallerAccess(mFocusedElement
)) {
1726 sendFocusEvent
= false;
1729 LOGCONTENT("Shift Focus: %s", elementToFocus
.get());
1730 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1731 aFlags
, mFocusedWindow
.get(), newWindow
.get(),
1732 mFocusedElement
.get()));
1733 const uint64_t actionId
= GenerateFocusActionId();
1735 (" In Active Window: %d Moves to different BrowsingContext: %d "
1736 "SendFocus: %d actionid: %" PRIu64
,
1737 isElementInActiveWindow
, focusMovesToDifferentBC
, sendFocusEvent
,
1740 if (sendFocusEvent
) {
1741 Maybe
<BlurredElementInfo
> blurredInfo
;
1742 if (mFocusedElement
) {
1743 blurredInfo
.emplace(*mFocusedElement
);
1745 // return if blurring fails or the focus changes during the blur
1746 if (focusedBrowsingContext
) {
1747 // find the common ancestor of the currently focused window and the new
1748 // window. The ancestor will need to have its currently focused node
1749 // cleared once the document has been blurred. Otherwise, we'll be in a
1750 // state where a document is blurred yet the chain of windows above it
1751 // still points to that document.
1752 // For instance, in the following frame tree:
1756 // D is focused and we want to focus C. Once D has been blurred, we need
1757 // to clear out the focus in A, otherwise A would still maintain that B
1758 // was focused, and B that D was focused.
1759 RefPtr
<BrowsingContext
> commonAncestor
=
1760 focusMovesToDifferentBC
1761 ? GetCommonAncestor(newWindow
, focusedBrowsingContext
)
1764 const bool needToClearFocusedElement
= [&] {
1765 if (focusedBrowsingContext
->IsChrome()) {
1766 // Always reset focused element if focus is currently in chrome
1767 // window, unless we're moving focus to a popup.
1768 return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1770 if (focusedBrowsingContext
->Top() != browsingContextToFocus
->Top()) {
1771 // Only reset focused element if focus moves within the same top-level
1775 // XXX for the case that we try to focus an
1776 // already-focused-remote-frame, we would still send blur and focus
1777 // IPC to it, but they will not generate blur or focus event, we don't
1778 // want to reset activeElement on the remote frame.
1779 return focusMovesToDifferentBC
|| focusedBrowsingContext
->IsInProcess();
1782 const bool remainActive
=
1783 focusMovesToDifferentBC
&&
1784 IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1786 // TODO: MOZ_KnownLive is required due to bug 1770680
1787 if (!Blur(MOZ_KnownLive(needToClearFocusedElement
1788 ? focusedBrowsingContext
.get()
1790 commonAncestor
, focusMovesToDifferentBC
, aAdjustWidget
,
1791 remainActive
, actionId
, elementToFocus
)) {
1792 return Some(actionId
);
1796 Focus(newWindow
, elementToFocus
, aFlags
, focusMovesToDifferentBC
,
1797 aFocusChanged
, false, aAdjustWidget
, actionId
, blurredInfo
);
1799 // otherwise, for inactive windows and when the caller cannot steal the
1800 // focus, update the node in the window, and raise the window if desired.
1801 if (allowFrameSwitch
) {
1802 AdjustWindowFocus(newBrowsingContext
, true, IsWindowVisible(newWindow
),
1806 // set the focus node and method as needed
1807 uint32_t focusMethod
=
1808 aFocusChanged
? aFlags
& METHODANDRING_MASK
1809 : newWindow
->GetFocusMethod() |
1810 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
1811 newWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
1812 if (aFocusChanged
) {
1813 if (nsCOMPtr
<nsIDocShell
> docShell
= newWindow
->GetDocShell()) {
1814 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1815 if (presShell
&& presShell
->DidInitialize()) {
1816 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
1821 // update the commands even when inactive so that the attributes for that
1822 // window are up to date.
1823 if (allowFrameSwitch
) {
1824 newWindow
->UpdateCommands(u
"focus"_ns
);
1827 if (aFlags
& FLAG_RAISE
) {
1828 if (newRootBrowsingContext
) {
1829 if (XRE_IsParentProcess() || newRootBrowsingContext
->IsInProcess()) {
1830 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
=
1831 newRootBrowsingContext
->GetDOMWindow();
1832 RaiseWindow(outerWindow
,
1833 aFlags
& FLAG_NONSYSTEMCALLER
? CallerType::NonSystem
1834 : CallerType::System
,
1837 mozilla::dom::ContentChild
* contentChild
=
1838 mozilla::dom::ContentChild::GetSingleton();
1839 MOZ_ASSERT(contentChild
);
1840 contentChild
->SendRaiseWindow(newRootBrowsingContext
,
1841 aFlags
& FLAG_NONSYSTEMCALLER
1842 ? CallerType::NonSystem
1843 : CallerType::System
,
1849 return Some(actionId
);
1852 static already_AddRefed
<BrowsingContext
> GetParentIgnoreChromeBoundary(
1853 BrowsingContext
* aBC
) {
1854 // Chrome BrowsingContexts are only available in the parent process, so if
1855 // we're in a content process, we only worry about the context tree.
1856 if (XRE_IsParentProcess()) {
1857 return aBC
->Canonical()->GetParentCrossChromeBoundary();
1859 return do_AddRef(aBC
->GetParent());
1862 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1863 BrowsingContext
* aContext
) const {
1864 if (!aPossibleAncestor
) {
1868 for (RefPtr
<BrowsingContext
> bc
= aContext
; bc
;
1869 bc
= GetParentIgnoreChromeBoundary(bc
)) {
1870 if (bc
== aPossibleAncestor
) {
1878 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1879 nsPIDOMWindowOuter
* aWindow
) const {
1880 if (aWindow
&& aPossibleAncestor
) {
1881 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(),
1882 aWindow
->GetBrowsingContext());
1887 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1888 BrowsingContext
* aContext
) const {
1889 if (aPossibleAncestor
) {
1890 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(), aContext
);
1895 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1896 nsPIDOMWindowOuter
* aWindow
) const {
1898 return IsSameOrAncestor(aPossibleAncestor
, aWindow
->GetBrowsingContext());
1903 mozilla::dom::BrowsingContext
* nsFocusManager::GetCommonAncestor(
1904 nsPIDOMWindowOuter
* aWindow
, mozilla::dom::BrowsingContext
* aContext
) {
1905 NS_ENSURE_TRUE(aWindow
&& aContext
, nullptr);
1907 if (XRE_IsParentProcess()) {
1908 nsCOMPtr
<nsIDocShellTreeItem
> dsti1
= aWindow
->GetDocShell();
1909 NS_ENSURE_TRUE(dsti1
, nullptr);
1911 nsCOMPtr
<nsIDocShellTreeItem
> dsti2
= aContext
->GetDocShell();
1912 NS_ENSURE_TRUE(dsti2
, nullptr);
1914 AutoTArray
<nsIDocShellTreeItem
*, 30> parents1
, parents2
;
1916 parents1
.AppendElement(dsti1
);
1917 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti1
;
1918 dsti1
->GetInProcessParent(getter_AddRefs(parentDsti1
));
1919 dsti1
.swap(parentDsti1
);
1922 parents2
.AppendElement(dsti2
);
1923 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti2
;
1924 dsti2
->GetInProcessParent(getter_AddRefs(parentDsti2
));
1925 dsti2
.swap(parentDsti2
);
1928 uint32_t pos1
= parents1
.Length();
1929 uint32_t pos2
= parents2
.Length();
1930 nsIDocShellTreeItem
* parent
= nullptr;
1932 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1933 nsIDocShellTreeItem
* child1
= parents1
.ElementAt(--pos1
);
1934 nsIDocShellTreeItem
* child2
= parents2
.ElementAt(--pos2
);
1935 if (child1
!= child2
) {
1941 return parent
? parent
->GetBrowsingContext() : nullptr;
1944 BrowsingContext
* bc1
= aWindow
->GetBrowsingContext();
1945 NS_ENSURE_TRUE(bc1
, nullptr);
1947 BrowsingContext
* bc2
= aContext
;
1949 AutoTArray
<BrowsingContext
*, 30> parents1
, parents2
;
1951 parents1
.AppendElement(bc1
);
1952 bc1
= bc1
->GetParent();
1955 parents2
.AppendElement(bc2
);
1956 bc2
= bc2
->GetParent();
1959 uint32_t pos1
= parents1
.Length();
1960 uint32_t pos2
= parents2
.Length();
1961 BrowsingContext
* parent
= nullptr;
1963 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1964 BrowsingContext
* child1
= parents1
.ElementAt(--pos1
);
1965 BrowsingContext
* child2
= parents2
.ElementAt(--pos2
);
1966 if (child1
!= child2
) {
1975 bool nsFocusManager::AdjustInProcessWindowFocus(
1976 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
1977 uint64_t aActionId
) {
1978 if (ActionIdComparableAndLower(aActionId
,
1979 mActionIdForFocusedBrowsingContextInContent
)) {
1981 ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as "
1982 "focused from another process due to stale action id %" PRIu64
".",
1983 aBrowsingContext
, aActionId
));
1987 BrowsingContext
* bc
= aBrowsingContext
;
1988 bool needToNotifyOtherProcess
= false;
1990 // get the containing <iframe> or equivalent element so that it can be
1992 nsCOMPtr
<Element
> frameElement
= bc
->GetEmbedderElement();
1993 BrowsingContext
* parent
= bc
->GetParent();
1994 if (!parent
&& XRE_IsParentProcess()) {
1995 CanonicalBrowsingContext
* canonical
= bc
->Canonical();
1996 RefPtr
<WindowGlobalParent
> embedder
=
1997 canonical
->GetEmbedderWindowGlobal();
1999 parent
= embedder
->BrowsingContext();
2006 if (!frameElement
&& XRE_IsContentProcess()) {
2007 needToNotifyOtherProcess
= true;
2011 nsCOMPtr
<nsPIDOMWindowOuter
> window
= bc
->GetDOMWindow();
2013 // if the parent window is visible but the original window was not, then we
2014 // have likely moved up and out from a hidden tab to the browser window, or
2015 // a similar such arrangement. Stop adjusting the current nodes.
2016 if (IsWindowVisible(window
) != aIsVisible
) {
2020 // When aCheckPermission is true, we should check whether the caller can
2021 // access the window or not. If it cannot access, we should stop the
2023 if (aCheckPermission
&& !nsContentUtils::LegacyIsCallerNativeCode() &&
2024 !nsContentUtils::CanCallerAccess(window
->GetCurrentInnerWindow())) {
2028 if (frameElement
!= window
->GetFocusedElement()) {
2029 window
->SetFocusedElement(frameElement
);
2031 RefPtr
<nsFrameLoaderOwner
> loaderOwner
= do_QueryObject(frameElement
);
2032 MOZ_ASSERT(loaderOwner
);
2033 RefPtr
<nsFrameLoader
> loader
= loaderOwner
->GetFrameLoader();
2034 if (loader
&& loader
->IsRemoteFrame() &&
2035 GetFocusedBrowsingContext() == bc
) {
2036 Blur(nullptr, nullptr, true, true, false, aActionId
);
2040 return needToNotifyOtherProcess
;
2043 void nsFocusManager::AdjustWindowFocus(BrowsingContext
* aBrowsingContext
,
2044 bool aCheckPermission
, bool aIsVisible
,
2045 uint64_t aActionId
) {
2046 if (AdjustInProcessWindowFocus(aBrowsingContext
, aCheckPermission
, aIsVisible
,
2048 // Some ancestors of aBrowsingContext isn't in this process, so notify other
2049 // processes to adjust their focused element.
2050 mozilla::dom::ContentChild
* contentChild
=
2051 mozilla::dom::ContentChild::GetSingleton();
2052 MOZ_ASSERT(contentChild
);
2053 contentChild
->SendAdjustWindowFocus(aBrowsingContext
, aIsVisible
,
2058 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter
* aWindow
) {
2059 if (!aWindow
|| aWindow
->IsFrozen()) {
2063 // Check if the inner window is frozen as well. This can happen when a focus
2064 // change occurs while restoring a previous page.
2065 nsPIDOMWindowInner
* innerWindow
= aWindow
->GetCurrentInnerWindow();
2066 if (!innerWindow
|| innerWindow
->IsFrozen()) {
2070 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2071 nsCOMPtr
<nsIBaseWindow
> baseWin(do_QueryInterface(docShell
));
2076 bool visible
= false;
2077 baseWin
->GetVisibility(&visible
);
2081 bool nsFocusManager::IsNonFocusableRoot(nsIContent
* aContent
) {
2082 MOZ_ASSERT(aContent
, "aContent must not be NULL");
2083 MOZ_ASSERT(aContent
->IsInComposedDoc(), "aContent must be in a document");
2085 // If the uncomposed document of aContent is in designMode, the root element
2086 // is not focusable.
2087 // NOTE: Most elements whose uncomposed document is in design mode are not
2088 // focusable, just the document is focusable. However, if it's in a
2089 // shadow tree, it may be focus able even if the shadow host is in
2091 // Also, if aContent is not editable and it's not in designMode, it's not
2093 // And in userfocusignored context nothing is focusable.
2094 Document
* doc
= aContent
->GetComposedDoc();
2095 NS_ASSERTION(doc
, "aContent must have current document");
2096 return aContent
== doc
->GetRootElement() &&
2097 (aContent
->IsInDesignMode() || !aContent
->IsEditable());
2100 Element
* nsFocusManager::FlushAndCheckIfFocusable(Element
* aElement
,
2106 nsCOMPtr
<Document
> doc
= aElement
->GetComposedDoc();
2107 // can't focus elements that are not in documents
2109 LOGCONTENT("Cannot focus %s because content not in document", aElement
)
2113 // Make sure that our frames are up to date while ensuring the presshell is
2114 // also initialized in case we come from a script calling focus() early.
2115 mEventHandlingNeedsFlush
= false;
2116 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2118 PresShell
* presShell
= doc
->GetPresShell();
2123 // If this is an iframe that doesn't have an in-process subdocument, it is
2124 // either an OOP iframe or an in-process iframe without lazy about:blank
2125 // creation having taken place. In the OOP case, iframe is always focusable.
2126 // In the in-process case, create the initial about:blank for in-process
2127 // BrowsingContexts in order to have the `GetSubDocumentFor` call after this
2128 // block return something.
2130 // TODO(emilio): This block can probably go after bug 543435 lands.
2131 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2132 if (!aElement
->IsXULElement()) {
2133 // Only look at pre-existing browsing contexts. If this function is
2134 // called during reflow, calling GetBrowsingContext() could cause frame
2135 // loader initialization at a time when it isn't safe.
2136 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2137 // This call may create a contentViewer-created about:blank.
2138 // That's intentional, so we can move focus there.
2139 Unused
<< bc
->GetDocument();
2144 return GetTheFocusableArea(aElement
, aFlags
);
2147 bool nsFocusManager::Blur(BrowsingContext
* aBrowsingContextToClear
,
2148 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2149 bool aIsLeavingDocument
, bool aAdjustWidget
,
2150 bool aRemainActive
, uint64_t aActionId
,
2151 Element
* aElementToFocus
) {
2152 if (XRE_IsParentProcess()) {
2153 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2154 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2155 aElementToFocus
, aActionId
);
2157 mozilla::dom::ContentChild
* contentChild
=
2158 mozilla::dom::ContentChild::GetSingleton();
2159 MOZ_ASSERT(contentChild
);
2160 bool windowToClearHandled
= false;
2161 bool ancestorWindowToFocusHandled
= false;
2163 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2164 if (focusedBrowsingContext
&& focusedBrowsingContext
->IsDiscarded()) {
2165 focusedBrowsingContext
= nullptr;
2167 if (!focusedBrowsingContext
) {
2168 mFocusedElement
= nullptr;
2171 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsDiscarded()) {
2172 aBrowsingContextToClear
= nullptr;
2174 if (aAncestorBrowsingContextToFocus
&&
2175 aAncestorBrowsingContextToFocus
->IsDiscarded()) {
2176 aAncestorBrowsingContextToFocus
= nullptr;
2178 // XXX should more early returns from BlurImpl be hoisted here to avoid
2179 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
2180 // other processes when BlurImpl returns early in this process? Or should the
2181 // IPC messages for those be sent by BlurImpl itself, in which case they could
2183 if (focusedBrowsingContext
->IsInProcess()) {
2184 if (aBrowsingContextToClear
&& !aBrowsingContextToClear
->IsInProcess()) {
2185 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus
&&
2186 !aAncestorBrowsingContextToFocus
->IsInProcess()),
2187 "Both aBrowsingContextToClear and "
2188 "aAncestorBrowsingContextToFocus are "
2190 contentChild
->SendSetFocusedElement(aBrowsingContextToClear
, false);
2192 if (aAncestorBrowsingContextToFocus
&&
2193 !aAncestorBrowsingContextToFocus
->IsInProcess()) {
2194 contentChild
->SendSetFocusedElement(aAncestorBrowsingContextToFocus
,
2197 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2198 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2199 aElementToFocus
, aActionId
);
2201 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsInProcess()) {
2202 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2203 MOZ_ASSERT(windowToClear
);
2204 windowToClear
->SetFocusedElement(nullptr);
2205 windowToClearHandled
= true;
2207 if (aAncestorBrowsingContextToFocus
&&
2208 aAncestorBrowsingContextToFocus
->IsInProcess()) {
2209 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2210 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2211 MOZ_ASSERT(ancestorWindowToFocus
);
2212 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2213 ancestorWindowToFocusHandled
= true;
2215 // The expectation is that the blurring would eventually result in an IPC
2216 // message doing this anyway, but this doesn't happen if the focus is in OOP
2217 // iframe which won't try to bounce an IPC message to its parent frame.
2218 SetFocusedWindowInternal(nullptr, aActionId
);
2219 contentChild
->SendBlurToParent(
2220 focusedBrowsingContext
, aBrowsingContextToClear
,
2221 aAncestorBrowsingContextToFocus
, aIsLeavingDocument
, aAdjustWidget
,
2222 windowToClearHandled
, ancestorWindowToFocusHandled
, aActionId
);
2226 void nsFocusManager::BlurFromOtherProcess(
2227 mozilla::dom::BrowsingContext
* aFocusedBrowsingContext
,
2228 mozilla::dom::BrowsingContext
* aBrowsingContextToClear
,
2229 mozilla::dom::BrowsingContext
* aAncestorBrowsingContextToFocus
,
2230 bool aIsLeavingDocument
, bool aAdjustWidget
, uint64_t aActionId
) {
2231 if (aFocusedBrowsingContext
!= GetFocusedBrowsingContext()) {
2234 BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2235 aIsLeavingDocument
, aAdjustWidget
, /* aRemainActive = */ false,
2236 nullptr, aActionId
);
2239 bool nsFocusManager::BlurImpl(BrowsingContext
* aBrowsingContextToClear
,
2240 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2241 bool aIsLeavingDocument
, bool aAdjustWidget
,
2242 bool aRemainActive
, Element
* aElementToFocus
,
2243 uint64_t aActionId
) {
2244 LOGFOCUS(("<<Blur begin actionid: %" PRIu64
">>", aActionId
));
2246 // hold a reference to the focused content, which may be null
2247 RefPtr
<Element
> element
= mFocusedElement
;
2249 if (!element
->IsInComposedDoc()) {
2250 mFocusedElement
= nullptr;
2253 if (element
== mFirstBlurEvent
) {
2258 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2259 // hold a reference to the focused window
2260 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
2261 if (focusedBrowsingContext
) {
2262 window
= focusedBrowsingContext
->GetDOMWindow();
2265 mFocusedElement
= nullptr;
2269 nsCOMPtr
<nsIDocShell
> docShell
= window
->GetDocShell();
2271 if (XRE_IsContentProcess() &&
2272 ActionIdComparableAndLower(
2273 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2274 // Unclear if this ever happens.
2276 ("Ignored an attempt to null out focused BrowsingContext when "
2277 "docShell is null due to a stale action id %" PRIu64
".",
2282 mFocusedWindow
= nullptr;
2283 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2285 SetFocusedBrowsingContext(nullptr, aActionId
);
2286 mFocusedElement
= nullptr;
2290 // Keep a ref to presShell since dispatching the DOM event may cause
2291 // the document to be destroyed.
2292 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2294 if (XRE_IsContentProcess() &&
2295 ActionIdComparableAndLower(
2296 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2297 // Unclear if this ever happens.
2299 ("Ignored an attempt to null out focused BrowsingContext when "
2300 "presShell is null due to a stale action id %" PRIu64
".",
2304 mFocusedElement
= nullptr;
2305 mFocusedWindow
= nullptr;
2306 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2308 SetFocusedBrowsingContext(nullptr, aActionId
);
2312 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2313 if (!mFirstBlurEvent
) {
2314 ar
.emplace(mFirstBlurEvent
);
2315 mFirstBlurEvent
= element
;
2318 const RefPtr
<nsPresContext
> focusedPresContext
=
2319 GetActiveBrowsingContext() ? presShell
->GetPresContext() : nullptr;
2320 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
2321 GetFocusMoveActionCause(0));
2323 // now adjust the actual focus, by clearing the fields in the focus manager
2324 // and in the window.
2325 mFocusedElement
= nullptr;
2326 if (aBrowsingContextToClear
) {
2327 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2328 if (windowToClear
) {
2329 windowToClear
->SetFocusedElement(nullptr);
2333 LOGCONTENT("Element %s has been blurred", element
.get());
2335 // Don't fire blur event on the root content which isn't editable.
2336 bool sendBlurEvent
=
2337 element
&& element
->IsInComposedDoc() && !IsNonFocusableRoot(element
);
2339 if (sendBlurEvent
) {
2340 NotifyFocusStateChange(element
, aElementToFocus
, 0, false, false);
2343 if (!aRemainActive
) {
2344 bool windowBeingLowered
= !aBrowsingContextToClear
&&
2345 !aAncestorBrowsingContextToFocus
&&
2346 aIsLeavingDocument
&& aAdjustWidget
;
2347 // If the object being blurred is a remote browser, deactivate remote
2349 if (BrowserParent
* remote
= BrowserParent::GetFrom(element
)) {
2350 MOZ_ASSERT(XRE_IsParentProcess());
2351 // Let's deactivate all remote browsers.
2352 BrowsingContext
* topLevelBrowsingContext
= remote
->GetBrowsingContext();
2353 topLevelBrowsingContext
->PreOrderWalk([&](BrowsingContext
* aContext
) {
2354 if (WindowGlobalParent
* windowGlobalParent
=
2355 aContext
->Canonical()->GetCurrentWindowGlobal()) {
2356 if (RefPtr
<BrowserParent
> browserParent
=
2357 windowGlobalParent
->GetBrowserParent()) {
2358 browserParent
->Deactivate(windowBeingLowered
, aActionId
);
2360 ("%s remote browser deactivated %p, %d, actionid: %" PRIu64
,
2361 aContext
== topLevelBrowsingContext
? "Top-level"
2363 browserParent
.get(), windowBeingLowered
, aActionId
));
2369 // Same as above but for out-of-process iframes
2370 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(element
)) {
2371 bbc
->Deactivate(windowBeingLowered
, aActionId
);
2373 ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64
,
2374 bbc
, windowBeingLowered
, aActionId
));
2380 if (sendBlurEvent
) {
2381 // if there is an active window, update commands. If there isn't an active
2382 // window, then this was a blur caused by the active window being lowered,
2383 // so there is no need to update the commands
2384 if (GetActiveBrowsingContext()) {
2385 window
->UpdateCommands(u
"focus"_ns
);
2388 SendFocusOrBlurEvent(eBlur
, presShell
, element
->GetComposedDoc(), element
,
2389 false, false, aElementToFocus
);
2392 // if we are leaving the document or the window was lowered, make the caret
2394 if (aIsLeavingDocument
|| !GetActiveBrowsingContext()) {
2395 SetCaretVisible(presShell
, false, nullptr);
2398 RefPtr
<AccessibleCaretEventHub
> eventHub
=
2399 presShell
->GetAccessibleCaretEventHub();
2401 eventHub
->NotifyBlur(aIsLeavingDocument
|| !GetActiveBrowsingContext());
2404 // at this point, it is expected that this window will be still be
2405 // focused, but the focused element will be null, as it was cleared before
2406 // the event. If this isn't the case, then something else was focused during
2407 // the blur event above and we should just return. However, if
2408 // aIsLeavingDocument is set, a new document is desired, so make sure to
2409 // blur the document and window.
2410 if (GetFocusedBrowsingContext() != window
->GetBrowsingContext() ||
2411 (mFocusedElement
!= nullptr && !aIsLeavingDocument
)) {
2413 } else if (aIsLeavingDocument
) {
2414 window
->TakeFocus(false, 0);
2416 // clear the focus so that the ancestor frame hierarchy is in the correct
2417 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
2418 // focused at this point.
2419 if (aAncestorBrowsingContextToFocus
) {
2420 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2421 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2422 if (ancestorWindowToFocus
) {
2423 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2427 SetFocusedWindowInternal(nullptr, aActionId
);
2428 mFocusedElement
= nullptr;
2430 RefPtr
<Document
> doc
= window
->GetExtantDoc();
2432 SendFocusOrBlurEvent(eBlur
, presShell
, doc
, doc
, false);
2434 if (!GetFocusedBrowsingContext()) {
2435 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2436 window
->GetCurrentInnerWindow();
2437 // MOZ_KnownLive due to bug 1506441
2438 SendFocusOrBlurEvent(
2439 eBlur
, presShell
, doc
,
2440 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), false);
2443 // check if a different window was focused
2444 result
= (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
2445 } else if (GetActiveBrowsingContext()) {
2446 // Otherwise, the blur of the element without blurring the document
2447 // occurred normally. Call UpdateCaret to redisplay the caret at the right
2448 // location within the document. This is needed to ensure that the caret
2449 // used for caret browsing is made visible again when an input field is
2451 UpdateCaret(false, true, nullptr);
2457 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element
& aElement
,
2458 uint64_t aActionId
) {
2459 if (BrowserParent
* remote
= BrowserParent::GetFrom(&aElement
)) {
2460 remote
->Activate(aActionId
);
2462 ("Remote browser activated %p, actionid: %" PRIu64
, remote
, aActionId
));
2465 // Same as above but for out-of-process iframes
2466 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(&aElement
)) {
2467 bbc
->Activate(aActionId
);
2468 LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64
, bbc
,
2473 void nsFocusManager::Focus(
2474 nsPIDOMWindowOuter
* aWindow
, Element
* aElement
, uint32_t aFlags
,
2475 bool aIsNewDocument
, bool aFocusChanged
, bool aWindowRaised
,
2476 bool aAdjustWidget
, uint64_t aActionId
,
2477 const Maybe
<BlurredElementInfo
>& aBlurredElementInfo
) {
2478 LOGFOCUS(("<<Focus begin actionid: %" PRIu64
">>", aActionId
));
2485 (aElement
== mFirstFocusEvent
|| aElement
== mFirstBlurEvent
)) {
2489 // Keep a reference to the presShell since dispatching the DOM event may
2490 // cause the document to be destroyed.
2491 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2496 const RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2501 bool focusInOtherContentProcess
= false;
2502 // Keep mochitest-browser-chrome harness happy by ignoring
2503 // focusInOtherContentProcess in the chrome process, because the harness
2505 if (!XRE_IsParentProcess()) {
2506 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2507 // Only look at pre-existing browsing contexts. If this function is
2508 // called during reflow, calling GetBrowsingContext() could cause frame
2509 // loader initialization at a time when it isn't safe.
2510 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2511 focusInOtherContentProcess
= !bc
->IsInProcess();
2515 if (ActionIdComparableAndLower(
2516 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2517 // Unclear if this ever happens.
2519 ("Ignored an attempt to focus an element due to stale action id "
2526 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
2527 // Otherwise, just get the current focus method and use that. This ensures
2528 // that the method is set during the document and window focus events.
2529 uint32_t focusMethod
= aFocusChanged
2530 ? aFlags
& METHODANDRING_MASK
2531 : aWindow
->GetFocusMethod() |
2532 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
2534 if (!IsWindowVisible(aWindow
)) {
2535 // if the window isn't visible, for instance because it is a hidden tab,
2536 // update the current focus and scroll it into view but don't do anything
2538 if (RefPtr elementToFocus
= FlushAndCheckIfFocusable(aElement
, aFlags
)) {
2539 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
2540 if (aFocusChanged
) {
2541 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2547 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2548 if (!mFirstFocusEvent
) {
2549 ar
.emplace(mFirstFocusEvent
);
2550 mFirstFocusEvent
= aElement
;
2553 LOGCONTENT("Element %s has been focused", aElement
);
2555 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
2556 Document
* docm
= aWindow
->GetExtantDoc();
2558 LOGCONTENT(" from %s", docm
->GetRootElement());
2561 (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64
2563 aIsNewDocument
, aFocusChanged
, aWindowRaised
, aFlags
, aActionId
));
2566 if (aIsNewDocument
) {
2567 // if this is a new document, update the parent chain of frames so that
2568 // focus can be traversed from the top level down to the newly focused
2570 RefPtr
<BrowsingContext
> bc
= aWindow
->GetBrowsingContext();
2571 AdjustWindowFocus(bc
, false, IsWindowVisible(aWindow
), aActionId
);
2574 // indicate that the window has taken focus.
2575 if (aWindow
->TakeFocus(true, focusMethod
)) {
2576 aIsNewDocument
= true;
2579 SetFocusedWindowInternal(aWindow
, aActionId
);
2581 if (aAdjustWidget
&& !sTestMode
) {
2582 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
2583 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
2585 widget
->SetFocus(nsIWidget::Raise::No
, aFlags
& FLAG_NONSYSTEMCALLER
2586 ? CallerType::NonSystem
2587 : CallerType::System
);
2591 // if switching to a new document, first fire the focus event on the
2592 // document and then the window.
2593 if (aIsNewDocument
) {
2594 RefPtr
<Document
> doc
= aWindow
->GetExtantDoc();
2595 // The focus change should be notified to IMEStateManager from here if:
2596 // * the focused element is in design mode or
2597 // * nobody gets focus and the document is in design mode
2598 // since any element whose uncomposed document is in design mode won't
2599 // receive focus event.
2600 if (doc
&& ((aElement
&& aElement
->IsInDesignMode()) ||
2601 (!aElement
&& doc
->IsInDesignMode()))) {
2602 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2603 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2604 GetFocusMoveActionCause(aFlags
));
2606 if (doc
&& !focusInOtherContentProcess
) {
2607 SendFocusOrBlurEvent(eFocus
, presShell
, doc
, doc
, aWindowRaised
);
2609 if (GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext() &&
2610 !mFocusedElement
&& !focusInOtherContentProcess
) {
2611 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2612 aWindow
->GetCurrentInnerWindow();
2613 // MOZ_KnownLive due to bug 1506441
2614 SendFocusOrBlurEvent(
2615 eFocus
, presShell
, doc
,
2616 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), aWindowRaised
);
2620 // check to ensure that the element is still focusable, and that nothing
2621 // else was focused during the events above.
2622 // Note that the focusing element may have already been moved to another
2623 // document/window. In that case, we should stop setting focus to it
2624 // because setting focus to the new window would cause redirecting focus
2626 RefPtr elementToFocus
=
2627 aElement
&& aElement
->IsInComposedDoc() &&
2628 aElement
->GetComposedDoc() == aWindow
->GetExtantDoc()
2629 ? FlushAndCheckIfFocusable(aElement
, aFlags
)
2631 if (elementToFocus
&& !mFocusedElement
&&
2632 GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext()) {
2633 mFocusedElement
= elementToFocus
;
2635 nsIContent
* focusedNode
= aWindow
->GetFocusedElement();
2636 const bool sendFocusEvent
= elementToFocus
->IsInComposedDoc() &&
2637 !IsNonFocusableRoot(elementToFocus
);
2638 const bool isRefocus
= focusedNode
&& focusedNode
== elementToFocus
;
2639 const bool shouldShowFocusRing
=
2641 ShouldMatchFocusVisible(aWindow
, *elementToFocus
, aFlags
);
2643 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
, false);
2645 const RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2646 if (sendFocusEvent
) {
2647 NotifyFocusStateChange(elementToFocus
, nullptr, aFlags
,
2648 /* aGettingFocus = */ true, shouldShowFocusRing
);
2650 // If this is a remote browser, focus its widget and activate remote
2651 // content. Note that we might no longer be in the same document,
2652 // due to the events we fired above when aIsNewDocument.
2653 if (presShell
->GetDocument() == elementToFocus
->GetComposedDoc()) {
2654 ActivateRemoteFrameIfNeeded(*elementToFocus
, aActionId
);
2657 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2658 GetFocusMoveActionCause(aFlags
));
2660 // as long as this focus wasn't because a window was raised, update the
2662 // XXXndeakin P2 someone could adjust the focus during the update
2663 if (!aWindowRaised
) {
2664 aWindow
->UpdateCommands(u
"focus"_ns
);
2667 // If the focused element changed, scroll it into view
2668 if (aFocusChanged
) {
2669 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2672 if (!focusInOtherContentProcess
) {
2673 RefPtr
<Document
> composedDocument
= elementToFocus
->GetComposedDoc();
2674 RefPtr
<Element
> relatedTargetElement
=
2675 aBlurredElementInfo
? aBlurredElementInfo
->mElement
.get() : nullptr;
2676 SendFocusOrBlurEvent(eFocus
, presShell
, composedDocument
,
2677 elementToFocus
, aWindowRaised
, isRefocus
,
2678 relatedTargetElement
);
2681 // We should notify IMEStateManager of actual focused element even if it
2682 // won't get focus event because the other IMEStateManager users do not
2683 // want to depend on this check, but IMEStateManager wants to verify
2684 // passed focused element for avoidng to overrride nested calls.
2685 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2686 GetFocusMoveActionCause(aFlags
));
2687 if (!aWindowRaised
) {
2688 aWindow
->UpdateCommands(u
"focus"_ns
);
2690 if (aFocusChanged
) {
2691 // If the focused element changed, scroll it into view
2692 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2696 if (!mFocusedElement
&& mFocusedWindow
== aWindow
) {
2697 // When there is no focused element, IMEStateManager needs to adjust IME
2698 // enabled state with the document.
2699 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2700 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2701 GetFocusMoveActionCause(aFlags
));
2704 if (!aWindowRaised
) {
2705 aWindow
->UpdateCommands(u
"focus"_ns
);
2709 // update the caret visibility and position to match the newly focused
2710 // element. However, don't update the position if this was a focus due to a
2711 // mouse click as the selection code would already have moved the caret as
2712 // needed. If this is a different document than was focused before, also
2713 // update the caret's visibility. If this is the same document, the caret
2714 // visibility should be the same as before so there is no need to update it.
2715 if (mFocusedElement
== elementToFocus
) {
2716 RefPtr
<Element
> focusedElement
= mFocusedElement
;
2717 UpdateCaret(aFocusChanged
&& !(aFlags
& FLAG_BYMOUSE
), aIsNewDocument
,
2722 class FocusBlurEvent
: public Runnable
{
2724 FocusBlurEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2725 nsPresContext
* aContext
, bool aWindowRaised
, bool aIsRefocus
,
2726 EventTarget
* aRelatedTarget
)
2727 : mozilla::Runnable("FocusBlurEvent"),
2730 mEventMessage(aEventMessage
),
2731 mWindowRaised(aWindowRaised
),
2732 mIsRefocus(aIsRefocus
),
2733 mRelatedTarget(aRelatedTarget
) {}
2735 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2736 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2737 InternalFocusEvent
event(true, mEventMessage
);
2738 event
.mFlags
.mBubbles
= false;
2739 event
.mFlags
.mCancelable
= false;
2740 event
.mFromRaise
= mWindowRaised
;
2741 event
.mIsRefocus
= mIsRefocus
;
2742 event
.mRelatedTarget
= mRelatedTarget
;
2743 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2746 const nsCOMPtr
<EventTarget
> mTarget
;
2747 const RefPtr
<nsPresContext
> mContext
;
2748 EventMessage mEventMessage
;
2751 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2754 class FocusInOutEvent
: public Runnable
{
2756 FocusInOutEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2757 nsPresContext
* aContext
,
2758 nsPIDOMWindowOuter
* aOriginalFocusedWindow
,
2759 nsIContent
* aOriginalFocusedContent
,
2760 EventTarget
* aRelatedTarget
)
2761 : mozilla::Runnable("FocusInOutEvent"),
2764 mEventMessage(aEventMessage
),
2765 mOriginalFocusedWindow(aOriginalFocusedWindow
),
2766 mOriginalFocusedContent(aOriginalFocusedContent
),
2767 mRelatedTarget(aRelatedTarget
) {}
2769 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2770 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2771 nsCOMPtr
<nsIContent
> originalWindowFocus
=
2772 mOriginalFocusedWindow
? mOriginalFocusedWindow
->GetFocusedElement()
2774 // Blink does not check that focus is the same after blur, but WebKit does.
2775 // Opt to follow Blink's behavior (see bug 687787).
2776 if (mEventMessage
== eFocusOut
||
2777 originalWindowFocus
== mOriginalFocusedContent
) {
2778 InternalFocusEvent
event(true, mEventMessage
);
2779 event
.mFlags
.mBubbles
= true;
2780 event
.mFlags
.mCancelable
= false;
2781 event
.mRelatedTarget
= mRelatedTarget
;
2782 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2787 const nsCOMPtr
<EventTarget
> mTarget
;
2788 const RefPtr
<nsPresContext
> mContext
;
2789 EventMessage mEventMessage
;
2790 nsCOMPtr
<nsPIDOMWindowOuter
> mOriginalFocusedWindow
;
2791 nsCOMPtr
<nsIContent
> mOriginalFocusedContent
;
2792 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2795 static Document
* GetDocumentHelper(EventTarget
* aTarget
) {
2799 if (const nsINode
* node
= nsINode::FromEventTarget(aTarget
)) {
2800 return node
->OwnerDoc();
2802 nsPIDOMWindowInner
* win
= nsPIDOMWindowInner::FromEventTarget(aTarget
);
2803 return win
? win
->GetExtantDoc() : nullptr;
2806 void nsFocusManager::FireFocusInOrOutEvent(
2807 EventMessage aEventMessage
, PresShell
* aPresShell
, EventTarget
* aTarget
,
2808 nsPIDOMWindowOuter
* aCurrentFocusedWindow
,
2809 nsIContent
* aCurrentFocusedContent
, EventTarget
* aRelatedTarget
) {
2810 NS_ASSERTION(aEventMessage
== eFocusIn
|| aEventMessage
== eFocusOut
,
2811 "Wrong event type for FireFocusInOrOutEvent");
2813 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2814 aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2815 aCurrentFocusedWindow
, aCurrentFocusedContent
, aRelatedTarget
));
2818 void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage
,
2819 PresShell
* aPresShell
,
2820 Document
* aDocument
,
2821 EventTarget
* aTarget
,
2822 bool aWindowRaised
, bool aIsRefocus
,
2823 EventTarget
* aRelatedTarget
) {
2824 NS_ASSERTION(aEventMessage
== eFocus
|| aEventMessage
== eBlur
,
2825 "Wrong event type for SendFocusOrBlurEvent");
2827 nsCOMPtr
<Document
> eventTargetDoc
= GetDocumentHelper(aTarget
);
2828 nsCOMPtr
<Document
> relatedTargetDoc
= GetDocumentHelper(aRelatedTarget
);
2830 // set aRelatedTarget to null if it's not in the same document as aTarget
2831 if (eventTargetDoc
!= relatedTargetDoc
) {
2832 aRelatedTarget
= nullptr;
2835 if (aDocument
&& aDocument
->EventHandlingSuppressed()) {
2836 // if this event was already queued, remove it and append it to the end
2837 mDelayedBlurFocusEvents
.RemoveElementsBy([&](const auto& event
) {
2838 return event
.mEventMessage
== aEventMessage
&&
2839 event
.mPresShell
== aPresShell
&& event
.mDocument
== aDocument
&&
2840 event
.mTarget
== aTarget
&& event
.mRelatedTarget
== aRelatedTarget
;
2843 mDelayedBlurFocusEvents
.EmplaceBack(aEventMessage
, aPresShell
, aDocument
,
2844 aTarget
, aRelatedTarget
);
2848 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2849 // that belongs to this doc, if yes, fire them first.
2850 if (aDocument
&& !aDocument
->EventHandlingSuppressed() &&
2851 mDelayedBlurFocusEvents
.Length()) {
2852 FireDelayedEvents(aDocument
);
2855 FireFocusOrBlurEvent(aEventMessage
, aPresShell
, aTarget
, aWindowRaised
,
2856 aIsRefocus
, aRelatedTarget
);
2859 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage
,
2860 PresShell
* aPresShell
,
2861 EventTarget
* aTarget
,
2862 bool aWindowRaised
, bool aIsRefocus
,
2863 EventTarget
* aRelatedTarget
) {
2864 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
= mFocusedWindow
;
2865 nsCOMPtr
<nsPIDOMWindowInner
> targetWindow
= do_QueryInterface(aTarget
);
2866 nsCOMPtr
<Document
> targetDocument
= do_QueryInterface(aTarget
);
2867 nsCOMPtr
<nsIContent
> currentFocusedContent
=
2868 currentWindow
? currentWindow
->GetFocusedElement() : nullptr;
2870 #ifdef ACCESSIBILITY
2871 nsAccessibilityService
* accService
= GetAccService();
2873 if (aEventMessage
== eFocus
) {
2874 accService
->NotifyOfDOMFocus(aTarget
);
2876 accService
->NotifyOfDOMBlur(aTarget
);
2881 aPresShell
->ScheduleContentRelevancyUpdate(
2882 ContentRelevancyReason::FocusInSubtree
);
2884 nsContentUtils::AddScriptRunner(
2885 new FocusBlurEvent(aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2886 aWindowRaised
, aIsRefocus
, aRelatedTarget
));
2888 // Check that the target is not a window or document before firing
2889 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2890 // despite being required in the spec, so follow their behavior.
2892 // As for document, we should not even fire focus/blur, but until then, we
2893 // need this check. targetDocument should be removed once bug 1228802 is
2895 if (!targetWindow
&& !targetDocument
) {
2896 EventMessage focusInOrOutMessage
=
2897 aEventMessage
== eFocus
? eFocusIn
: eFocusOut
;
2898 FireFocusInOrOutEvent(focusInOrOutMessage
, aPresShell
, aTarget
,
2899 currentWindow
, currentFocusedContent
, aRelatedTarget
);
2903 void nsFocusManager::ScrollIntoView(PresShell
* aPresShell
, nsIContent
* aContent
,
2905 if (aFlags
& FLAG_NOSCROLL
) {
2909 // If the noscroll flag isn't set, scroll the newly focused element into view.
2910 const ScrollAxis
axis(WhereToScroll::Center
, WhenToScroll::IfNotVisible
);
2911 aPresShell
->ScrollContentIntoView(aContent
, axis
, axis
,
2912 ScrollFlags::ScrollOverflowHidden
);
2913 // Scroll the input / textarea selection into view, unless focused with the
2914 // mouse, see bug 572649.
2915 if (aFlags
& FLAG_BYMOUSE
) {
2918 // ScrollContentIntoView flushes layout, so no need to flush again here.
2919 if (nsTextControlFrame
* tf
= do_QueryFrame(aContent
->GetPrimaryFrame())) {
2920 tf
->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes
);
2924 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter
* aWindow
,
2925 CallerType aCallerType
, uint64_t aActionId
) {
2926 // don't raise windows that are already raised or are in the process of
2929 if (!aWindow
|| aWindow
== mWindowBeingLowered
) {
2933 if (XRE_IsParentProcess()) {
2934 if (aWindow
== mActiveWindow
) {
2938 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2939 // TODO: Deeper OOP frame hierarchies are
2940 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
2941 if (bc
== GetActiveBrowsingContext()) {
2944 if (bc
== GetFocusedBrowsingContext()) {
2950 // In test mode, emulate raising the window. WindowRaised takes
2951 // care of lowering the present active window. This happens in
2952 // a separate runnable to avoid touching multiple windows in
2953 // the current runnable.
2955 nsCOMPtr
<nsPIDOMWindowOuter
> window(aWindow
);
2956 RefPtr
<nsFocusManager
> self(this);
2957 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2958 "nsFocusManager::RaiseWindow",
2959 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093)
2960 [self
, window
]() MOZ_CAN_RUN_SCRIPT_BOUNDARY
-> void {
2961 self
->WindowRaised(window
, GenerateFocusActionId());
2966 if (XRE_IsContentProcess()) {
2967 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2969 // Assume the raise below will succeed and run the raising synchronously
2970 // in this process to make the focus event that is observable in this
2971 // process fire in the right order relative to mouseup when we are here
2972 // thanks to a mousedown.
2973 WindowRaised(aWindow
, aActionId
);
2978 // Windows would rather we focus the child widget, otherwise, the toplevel
2979 // widget will always end up being focused. Fortunately, focusing the child
2980 // widget will also have the effect of raising the window this widget is in.
2981 // But on other platforms, we can just focus the toplevel widget to raise
2983 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
;
2984 GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
2985 getter_AddRefs(childWindow
));
2987 childWindow
= aWindow
;
2990 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2995 PresShell
* presShell
= docShell
->GetPresShell();
3000 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
3001 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
3003 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3007 nsCOMPtr
<nsIBaseWindow
> treeOwnerAsWin
=
3008 do_QueryInterface(aWindow
->GetDocShell());
3009 if (treeOwnerAsWin
) {
3010 nsCOMPtr
<nsIWidget
> widget
;
3011 treeOwnerAsWin
->GetMainWidget(getter_AddRefs(widget
));
3013 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3019 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
3020 RefPtr
<Element
> focusedElement
= mFocusedElement
;
3021 UpdateCaret(false, true, focusedElement
);
3024 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus
, bool aUpdateVisibility
,
3025 nsIContent
* aContent
) {
3026 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus
, aUpdateVisibility
));
3028 if (!mFocusedWindow
) {
3032 // this is called when a document is focused or when the caretbrowsing
3033 // preference is changed
3034 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
3035 if (!focusedDocShell
) {
3039 if (focusedDocShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
3040 return; // Never browse with caret in chrome
3043 bool browseWithCaret
= Preferences::GetBool("accessibility.browsewithcaret");
3045 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
3050 // If this is an editable document which isn't contentEditable, or a
3051 // contentEditable document and the node to focus is contentEditable,
3052 // return, so that we don't mess with caret visibility.
3053 bool isEditable
= false;
3054 focusedDocShell
->GetEditable(&isEditable
);
3057 Document
* doc
= presShell
->GetDocument();
3059 bool isContentEditableDoc
=
3061 doc
->GetEditingState() == Document::EditingState::eContentEditable
;
3063 bool isFocusEditable
= aContent
&& aContent
->HasFlag(NODE_IS_EDITABLE
);
3064 if (!isContentEditableDoc
|| isFocusEditable
) {
3069 if (!isEditable
&& aMoveCaretToFocus
) {
3070 MoveCaretToFocus(presShell
, aContent
);
3073 // The above MoveCaretToFocus call may run scripts which
3074 // may clear mFocusWindow
3075 if (!mFocusedWindow
) {
3079 if (!aUpdateVisibility
) {
3083 // XXXndeakin this doesn't seem right. It should be checking for this only
3084 // on the nearest ancestor frame which is a chrome frame. But this is
3085 // what the existing code does, so just leave it for now.
3086 if (!browseWithCaret
) {
3087 nsCOMPtr
<Element
> docElement
= mFocusedWindow
->GetFrameElementInternal();
3089 browseWithCaret
= docElement
->AttrValueIs(
3090 kNameSpaceID_None
, nsGkAtoms::showcaret
, u
"true"_ns
, eCaseMatters
);
3093 SetCaretVisible(presShell
, browseWithCaret
, aContent
);
3096 void nsFocusManager::MoveCaretToFocus(PresShell
* aPresShell
,
3097 nsIContent
* aContent
) {
3098 nsCOMPtr
<Document
> doc
= aPresShell
->GetDocument();
3100 RefPtr
<nsFrameSelection
> frameSelection
= aPresShell
->FrameSelection();
3101 RefPtr
<Selection
> domSelection
=
3102 frameSelection
->GetSelection(SelectionType::eNormal
);
3104 // First clear the selection. This way, if there is no currently focused
3105 // content, the selection will just be cleared.
3106 domSelection
->RemoveAllRanges(IgnoreErrors());
3109 RefPtr
<nsRange
> newRange
= doc
->CreateRange(rv
);
3110 if (NS_WARN_IF(rv
.Failed())) {
3111 rv
.SuppressException();
3115 // Set the range to the start of the currently focused node
3116 // Make sure it's collapsed
3117 newRange
->SelectNodeContents(*aContent
, IgnoreErrors());
3119 if (!aContent
->GetFirstChild() ||
3120 aContent
->IsHTMLFormControlElement()) {
3121 // If current focus node is a leaf, set range to before the
3122 // node by using the parent as a container.
3123 // This prevents it from appearing as selected.
3124 newRange
->SetStartBefore(*aContent
, IgnoreErrors());
3125 newRange
->SetEndBefore(*aContent
, IgnoreErrors());
3127 domSelection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
3129 domSelection
->CollapseToStart(IgnoreErrors());
3135 nsresult
nsFocusManager::SetCaretVisible(PresShell
* aPresShell
, bool aVisible
,
3136 nsIContent
* aContent
) {
3137 // When browsing with caret, make sure caret is visible after new focus
3138 // Return early if there is no caret. This can happen for the testcase
3139 // for bug 308025 where a window is closed in a blur handler.
3140 RefPtr
<nsCaret
> caret
= aPresShell
->GetCaret();
3145 bool caretVisible
= caret
->IsVisible();
3146 if (!aVisible
&& !caretVisible
) {
3150 RefPtr
<nsFrameSelection
> frameSelection
;
3152 NS_ASSERTION(aContent
->GetComposedDoc() == aPresShell
->GetDocument(),
3154 nsIFrame
* focusFrame
= aContent
->GetPrimaryFrame();
3156 frameSelection
= focusFrame
->GetFrameSelection();
3160 RefPtr
<nsFrameSelection
> docFrameSelection
= aPresShell
->FrameSelection();
3162 if (docFrameSelection
&& caret
&&
3163 (frameSelection
== docFrameSelection
|| !aContent
)) {
3164 Selection
* domSelection
=
3165 docFrameSelection
->GetSelection(SelectionType::eNormal
);
3167 // First, hide the caret to prevent attempting to show it in
3168 // SetCaretDOMSelection
3169 aPresShell
->SetCaretEnabled(false);
3171 // Caret must blink on non-editable elements
3172 caret
->SetIgnoreUserModify(true);
3173 // Tell the caret which selection to use
3174 caret
->SetSelection(domSelection
);
3176 // In content, we need to set the caret. The only special case is edit
3177 // fields, which have a different frame selection from the document.
3178 // They will take care of making the caret visible themselves.
3180 aPresShell
->SetCaretReadOnly(false);
3181 aPresShell
->SetCaretEnabled(aVisible
);
3188 nsresult
nsFocusManager::GetSelectionLocation(Document
* aDocument
,
3189 PresShell
* aPresShell
,
3190 nsIContent
** aStartContent
,
3191 nsIContent
** aEndContent
) {
3192 *aStartContent
= *aEndContent
= nullptr;
3194 nsPresContext
* presContext
= aPresShell
->GetPresContext();
3195 NS_ASSERTION(presContext
, "mPresContent is null!!");
3197 RefPtr
<Selection
> domSelection
=
3198 aPresShell
->ConstFrameSelection()->GetSelection(SelectionType::eNormal
);
3199 if (!domSelection
) {
3203 const nsRange
* domRange
= domSelection
->GetRangeAt(0);
3204 if (!domRange
|| !domRange
->IsPositioned()) {
3207 nsIContent
* start
= nsIContent::FromNode(domRange
->GetStartContainer());
3208 nsIContent
* end
= nsIContent::FromNode(domRange
->GetEndContainer());
3209 if (nsIContent
* child
= domRange
->StartRef().GetChildAtOffset()) {
3212 if (nsIContent
* child
= domRange
->EndRef().GetChildAtOffset()) {
3216 // Next check to see if our caret is at the very end of a text node. If so,
3217 // the caret is actually sitting in front of the next logical frame's primary
3218 // node - so for this case we need to change the content to that node.
3219 if (auto* text
= Text::FromNodeOrNull(start
);
3220 text
&& text
->TextDataLength() == domRange
->StartOffset() &&
3221 domSelection
->IsCollapsed()) {
3222 nsIFrame
* startFrame
= start
->GetPrimaryFrame();
3223 // Yes, indeed we were at the end of the last node
3224 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
3226 domSelection
&& domSelection
->GetAncestorLimiter()
3227 ? domSelection
->GetAncestorLimiter()->GetPrimaryFrame()
3229 MOZ_TRY(NS_NewFrameTraversal(getter_AddRefs(frameTraversal
), presContext
,
3232 false, // aLockInScrollView
3233 true, // aFollowOOFs
3234 false, // aSkipPopupChecks
3237 nsIFrame
* newCaretFrame
= nullptr;
3238 nsIContent
* newCaretContent
= start
;
3239 const bool endOfSelectionInStartNode
= start
== end
;
3241 // Continue getting the next frame until the primary content for the
3242 // frame we are on changes - we don't want to be stuck in the same
3244 frameTraversal
->Next();
3245 newCaretFrame
= static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
3246 if (!newCaretFrame
) {
3249 newCaretContent
= newCaretFrame
->GetContent();
3250 } while (!newCaretContent
|| newCaretContent
== start
);
3252 if (newCaretFrame
&& newCaretContent
) {
3253 // If the caret is exactly at the same position of the new frame,
3254 // then we can use the newCaretFrame and newCaretContent for our
3257 if (nsIFrame
* frame
= nsCaret::GetGeometry(domSelection
, &caretRect
)) {
3258 nsPoint caretWidgetOffset
;
3259 nsIWidget
* widget
= frame
->GetNearestWidget(caretWidgetOffset
);
3260 caretRect
.MoveBy(caretWidgetOffset
);
3261 nsPoint newCaretOffset
;
3262 nsIWidget
* newCaretWidget
=
3263 newCaretFrame
->GetNearestWidget(newCaretOffset
);
3264 if (widget
== newCaretWidget
&& caretRect
.TopLeft() == newCaretOffset
) {
3265 // The caret is at the start of the new element.
3266 startFrame
= newCaretFrame
;
3267 start
= newCaretContent
;
3268 if (endOfSelectionInStartNode
) {
3269 end
= newCaretContent
; // Ensure end of selection is
3277 NS_IF_ADDREF(*aStartContent
= start
);
3278 NS_IF_ADDREF(*aEndContent
= end
);
3283 nsresult
nsFocusManager::DetermineElementToMoveFocus(
3284 nsPIDOMWindowOuter
* aWindow
, nsIContent
* aStartContent
, int32_t aType
,
3285 bool aNoParentTraversal
, bool aNavigateByKey
, nsIContent
** aNextContent
) {
3286 *aNextContent
= nullptr;
3288 // This is used for document navigation only. It will be set to true if we
3289 // start navigating from a starting point. If this starting point is near the
3290 // end of the document (for example, an element on a statusbar), and there
3291 // are no child documents or panels before the end of the document, then we
3292 // will need to ensure that we don't consider the root chrome window when we
3293 // loop around and instead find the next child document/panel, as focus is
3294 // already in that window. This flag will be cleared once we navigate into
3295 // another document.
3296 bool mayFocusRoot
= (aStartContent
!= nullptr);
3298 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
3299 if (!startContent
&& aType
!= MOVEFOCUS_CARET
) {
3300 if (aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
) {
3301 // When moving between documents, make sure to get the right
3302 // starting content in a descendant.
3303 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
3304 startContent
= GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3305 getter_AddRefs(focusedWindow
));
3306 } else if (aType
!= MOVEFOCUS_LASTDOC
) {
3307 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
3308 // then we are document-navigating backwards from chrome to the content
3309 // process, and we don't want to use this so that we start from the end
3311 startContent
= aWindow
->GetFocusedElement();
3315 nsCOMPtr
<Document
> doc
;
3317 doc
= startContent
->GetComposedDoc();
3319 doc
= aWindow
->GetExtantDoc();
3320 if (!doc
) return NS_OK
;
3322 LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel
,
3323 &nsIContent::sTabFocusModel
);
3325 // True if we are navigating by document (F6/Shift+F6) or false if we are
3326 // navigating by element (Tab/Shift+Tab).
3327 const bool forDocumentNavigation
=
3328 aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
||
3329 aType
== MOVEFOCUS_FIRSTDOC
|| aType
== MOVEFOCUS_LASTDOC
;
3331 // If moving to the root or first document, find the root element and return.
3332 if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_FIRSTDOC
) {
3333 NS_IF_ADDREF(*aNextContent
= GetRootForFocus(aWindow
, doc
, false, false));
3334 if (!*aNextContent
&& aType
== MOVEFOCUS_FIRSTDOC
) {
3335 // When looking for the first document, if the root wasn't focusable,
3336 // find the next focusable document.
3337 aType
= MOVEFOCUS_FORWARDDOC
;
3343 // rootElement and presShell may be set to sub-document's ones so that they
3344 // cannot be `const`.
3345 RefPtr
<Element
> rootElement
= doc
->GetRootElement();
3346 NS_ENSURE_TRUE(rootElement
, NS_OK
);
3348 RefPtr
<PresShell
> presShell
= doc
->GetPresShell();
3349 NS_ENSURE_TRUE(presShell
, NS_OK
);
3351 if (aType
== MOVEFOCUS_FIRST
) {
3352 if (!aStartContent
) {
3353 startContent
= rootElement
;
3355 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3356 startContent
, true, 1, false, false,
3357 aNavigateByKey
, aNextContent
);
3359 if (aType
== MOVEFOCUS_LAST
) {
3360 if (!aStartContent
) {
3361 startContent
= rootElement
;
3363 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3364 startContent
, false, 0, false, false,
3365 aNavigateByKey
, aNextContent
);
3368 bool forward
= (aType
== MOVEFOCUS_FORWARD
|| aType
== MOVEFOCUS_FORWARDDOC
||
3369 aType
== MOVEFOCUS_CARET
);
3370 bool doNavigation
= true;
3371 bool ignoreTabIndex
= false;
3372 // when a popup is open, we want to ensure that tab navigation occurs only
3373 // within the most recently opened panel. If a popup is open, its frame will
3374 // be stored in popupFrame.
3375 nsIFrame
* popupFrame
= nullptr;
3377 int32_t tabIndex
= forward
? 1 : 0;
3379 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3380 if (startContent
->IsHTMLElement(nsGkAtoms::area
)) {
3381 startContent
->IsFocusable(&tabIndex
);
3383 tabIndex
= frame
->IsFocusable().mTabIndex
;
3385 startContent
->IsFocusable(&tabIndex
);
3388 // if the current element isn't tabbable, ignore the tabindex and just
3389 // look for the next element. The root content won't have a tabindex
3390 // so just treat this as the beginning of the tab order.
3393 if (startContent
!= rootElement
) {
3394 ignoreTabIndex
= true;
3398 // check if the focus is currently inside a popup. Elements such as the
3399 // autocomplete widget use the noautofocus attribute to allow the focus to
3400 // remain outside the popup when it is opened.
3402 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3403 frame
, LayoutFrameType::MenuPopup
);
3406 if (popupFrame
&& !forDocumentNavigation
) {
3407 // Don't navigate outside of a popup, so pretend that the
3408 // root content is the popup itself
3409 rootElement
= popupFrame
->GetContent()->AsElement();
3410 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3411 } else if (!forward
) {
3412 // If focus moves backward and when current focused node is root
3413 // content or <body> element which is editable by contenteditable
3414 // attribute, focus should move to its parent document.
3415 if (startContent
== rootElement
) {
3416 doNavigation
= false;
3418 Document
* doc
= startContent
->GetComposedDoc();
3420 nsLayoutUtils::GetEditableRootContentByContentEditable(doc
)) {
3421 doNavigation
= false;
3426 if (aType
!= MOVEFOCUS_CARET
) {
3427 // if there is no focus, yet a panel is open, focus the first item in
3429 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
3431 popupFrame
= pm
->GetTopPopup(PopupType::Panel
);
3435 // When there is a popup open, and no starting content, start the search
3436 // at the topmost popup.
3437 startContent
= popupFrame
->GetContent();
3438 NS_ASSERTION(startContent
, "Popup frame doesn't have a content node");
3439 // Unless we are searching for documents, set the root content to the
3440 // popup as well, so that we don't tab-navigate outside the popup.
3441 // When navigating by documents, we start at the popup but can navigate
3442 // outside of it to look for other panels and documents.
3443 if (!forDocumentNavigation
) {
3444 rootElement
= startContent
->AsElement();
3447 doc
= startContent
? startContent
->GetComposedDoc() : nullptr;
3449 // Otherwise, for content shells, start from the location of the caret.
3450 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3451 if (docShell
&& docShell
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
3452 nsCOMPtr
<nsIContent
> endSelectionContent
;
3453 GetSelectionLocation(doc
, presShell
, getter_AddRefs(startContent
),
3454 getter_AddRefs(endSelectionContent
));
3455 // If the selection is on the rootElement, then there is no selection
3456 if (startContent
== rootElement
) {
3457 startContent
= nullptr;
3460 if (aType
== MOVEFOCUS_CARET
) {
3461 // GetFocusInSelection finds a focusable link near the caret.
3462 // If there is no start content though, don't do this to avoid
3463 // focusing something unexpected.
3465 GetFocusInSelection(aWindow
, startContent
, endSelectionContent
,
3472 // when starting from a selection, we always want to find the next or
3473 // previous element in the document. So the tabindex on elements
3474 // should be ignored.
3475 ignoreTabIndex
= true;
3479 if (!startContent
) {
3480 // otherwise, just use the root content as the starting point
3481 startContent
= rootElement
;
3482 NS_ENSURE_TRUE(startContent
, NS_OK
);
3487 // Check if the starting content is the same as the content assigned to the
3488 // retargetdocumentfocus attribute. Is so, we don't want to start searching
3489 // from there but instead from the beginning of the document. Otherwise, the
3490 // content that appears before the retargetdocumentfocus element will never
3491 // get checked as it will be skipped when the focus is retargetted to it.
3492 if (forDocumentNavigation
&& nsContentUtils::IsChromeDoc(doc
)) {
3493 nsAutoString retarget
;
3495 if (rootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
3496 nsIContent
* retargetElement
= doc
->GetElementById(retarget
);
3497 // The common case here is the urlbar where focus is on the anonymous
3498 // input inside the textbox, but the retargetdocumentfocus attribute
3499 // refers to the textbox. The Contains check will return false and the
3500 // IsInclusiveDescendantOf check will return true in this case.
3501 if (retargetElement
&&
3502 (retargetElement
== startContent
||
3503 (!retargetElement
->Contains(startContent
) &&
3504 startContent
->IsInclusiveDescendantOf(retargetElement
)))) {
3505 startContent
= rootElement
;
3510 NS_ASSERTION(startContent
, "starting content not set");
3512 // keep a reference to the starting content. If we find that again, it means
3513 // we've iterated around completely and we don't want to adjust the focus.
3514 // The skipOriginalContentCheck will be set to true only for the first time
3515 // GetNextTabbableContent is called. This ensures that we don't break out
3516 // when nothing is focused to start with. Specifically,
3517 // GetNextTabbableContent first checks the root content -- which happens to
3518 // be the same as the start content -- when nothing is focused and tabbing
3519 // forward. Without skipOriginalContentCheck set to true, we'd end up
3520 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
3521 // will never wrap around on its own, and can only return the original
3522 // content when it is called a second time or later.
3523 bool skipOriginalContentCheck
= true;
3524 const nsCOMPtr
<nsIContent
> originalStartContent
= startContent
;
3526 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent
.get());
3527 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
3528 forward
, tabIndex
, ignoreTabIndex
,
3529 forDocumentNavigation
));
3533 nsCOMPtr
<nsIContent
> nextFocus
;
3534 // TODO: MOZ_KnownLive is reruired due to bug 1770680
3535 nsresult rv
= GetNextTabbableContent(
3536 presShell
, rootElement
,
3537 MOZ_KnownLive(skipOriginalContentCheck
? nullptr
3538 : originalStartContent
.get()),
3539 startContent
, forward
, tabIndex
, ignoreTabIndex
,
3540 forDocumentNavigation
, aNavigateByKey
, getter_AddRefs(nextFocus
));
3541 NS_ENSURE_SUCCESS(rv
, rv
);
3542 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
3543 // Navigation was redirected to a child process, so just return.
3547 // found a content node to focus.
3549 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus
.get());
3551 // as long as the found node was not the same as the starting node,
3552 // set it as the return value. For document navigation, we can return
3553 // the same element in case there is only one content node that could
3554 // be returned, for example, in a child process document.
3555 if (nextFocus
!= originalStartContent
|| forDocumentNavigation
) {
3556 nextFocus
.forget(aNextContent
);
3561 if (popupFrame
&& !forDocumentNavigation
) {
3562 // in a popup, so start again from the beginning of the popup. However,
3563 // if we already started at the beginning, then there isn't anything to
3564 // focus, so just return
3565 if (startContent
!= rootElement
) {
3566 startContent
= rootElement
;
3567 tabIndex
= forward
? 1 : 0;
3574 doNavigation
= true;
3575 skipOriginalContentCheck
= forDocumentNavigation
;
3576 ignoreTabIndex
= false;
3578 if (aNoParentTraversal
) {
3579 if (startContent
== rootElement
) {
3583 startContent
= rootElement
;
3584 tabIndex
= forward
? 1 : 0;
3588 // Reached the beginning or end of the document. Next, navigate up to the
3589 // parent document and try again.
3590 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
3591 NS_ENSURE_TRUE(piWindow
, NS_ERROR_FAILURE
);
3593 nsCOMPtr
<nsIDocShell
> docShell
= piWindow
->GetDocShell();
3594 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
3596 // Get the frame element this window is inside and, from that, get the
3597 // parent document and presshell. If there is no enclosing frame element,
3598 // then this is a top-level, embedded or remote window.
3599 startContent
= piWindow
->GetFrameElementInternal();
3601 doc
= startContent
->GetComposedDoc();
3602 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
3604 rootElement
= doc
->GetRootElement();
3605 presShell
= doc
->GetPresShell();
3607 // We can focus the root element now that we have moved to another
3609 mayFocusRoot
= true;
3611 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3616 tabIndex
= frame
->IsFocusable().mTabIndex
;
3619 ignoreTabIndex
= true;
3622 // if the frame is inside a popup, make sure to scan only within the
3623 // popup. This handles the situation of tabbing amongst elements
3624 // inside an iframe which is itself inside a popup. Otherwise,
3625 // navigation would move outside the popup when tabbing outside the
3627 if (!forDocumentNavigation
) {
3628 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3629 frame
, LayoutFrameType::MenuPopup
);
3631 rootElement
= popupFrame
->GetContent()->AsElement();
3632 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3636 if (aNavigateByKey
) {
3637 // There is no parent, so call the tree owner. This will tell the
3638 // embedder or parent process that it should take the focus.
3640 docShell
->TabToTreeOwner(forward
, forDocumentNavigation
, &tookFocus
);
3641 // If the tree owner took the focus, blur the current element.
3643 RefPtr
<BrowsingContext
> focusedBC
= GetFocusedBrowsingContext();
3644 if (focusedBC
&& focusedBC
->IsInProcess()) {
3645 Blur(focusedBC
, nullptr, true, true, false,
3646 GenerateFocusActionId());
3648 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docShell
->GetWindow();
3649 window
->SetFocusedElement(nullptr);
3655 // If we have reached the end of the top-level document, focus the
3656 // first element in the top-level document. This should always happen
3657 // when navigating by document forwards but when navigating backwards,
3658 // only do this if we started in another document or within a popup frame.
3659 // If the focus started in this window outside a popup however, we should
3660 // continue by looping around to the end again.
3661 if (forDocumentNavigation
&& (forward
|| mayFocusRoot
|| popupFrame
)) {
3662 // HTML content documents can have their root element focused (a focus
3663 // ring appears around the entire content area frame). This root
3664 // appears in the tab order before all of the elements in the document.
3665 // Chrome documents however cannot be focused directly, so instead we
3666 // focus the first focusable element within the window.
3667 // For example, the urlbar.
3668 RefPtr
<Element
> rootElementForFocus
=
3669 GetRootForFocus(piWindow
, doc
, true, true);
3670 return FocusFirst(rootElementForFocus
, aNextContent
);
3673 // Once we have hit the top-level and have iterated to the end again, we
3674 // just want to break out next time we hit this spot to prevent infinite
3676 mayFocusRoot
= true;
3678 // reset the tab index and start again from the beginning or end
3679 startContent
= rootElement
;
3680 tabIndex
= forward
? 1 : 0;
3683 // wrapped all the way around and didn't find anything to move the focus
3684 // to, so just break out
3685 if (startContent
== originalStartContent
) {
3693 uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions
& aOptions
) {
3694 uint32_t flags
= FLAG_BYJS
;
3695 if (aOptions
.mPreventScroll
) {
3696 flags
|= FLAG_NOSCROLL
;
3698 if (aOptions
.mFocusVisible
.WasPassed()) {
3699 flags
|= aOptions
.mFocusVisible
.Value() ? FLAG_SHOWRING
: FLAG_NOSHOWRING
;
3701 if (UserActivation::IsHandlingKeyboardInput()) {
3702 flags
|= FLAG_BYKEY
;
3704 // TODO: We could do a similar thing if we're handling mouse input, but that
3705 // changes focusability of some elements so may be more risky.
3709 static bool IsHostOrSlot(const nsIContent
* aContent
) {
3710 return aContent
&& (aContent
->GetShadowRoot() ||
3711 aContent
->IsHTMLElement(nsGkAtoms::slot
));
3714 // Helper class to iterate contents in scope by traversing flattened tree
3716 class MOZ_STACK_CLASS ScopedContentTraversal
{
3718 ScopedContentTraversal(nsIContent
* aStartContent
, nsIContent
* aOwner
)
3719 : mCurrent(aStartContent
), mOwner(aOwner
) {
3720 MOZ_ASSERT(aStartContent
);
3726 void Reset() { SetCurrent(mOwner
); }
3728 nsIContent
* GetCurrent() const { return mCurrent
; }
3731 void SetCurrent(nsIContent
* aContent
) { mCurrent
= aContent
; }
3733 nsIContent
* mCurrent
;
3737 void ScopedContentTraversal::Next() {
3738 MOZ_ASSERT(mCurrent
);
3740 // Get mCurrent's first child if it's in the same scope.
3741 if (!IsHostOrSlot(mCurrent
) || mCurrent
== mOwner
) {
3742 StyleChildrenIterator
iter(mCurrent
);
3743 nsIContent
* child
= iter
.GetNextChild();
3750 // If mOwner has no children, END traversal
3751 if (mCurrent
== mOwner
) {
3752 SetCurrent(nullptr);
3756 nsIContent
* current
= mCurrent
;
3758 // Create parent's iterator and move to current
3759 nsIContent
* parent
= current
->GetFlattenedTreeParent();
3760 StyleChildrenIterator
parentIter(parent
);
3761 parentIter
.Seek(current
);
3763 // Get next sibling of current
3764 if (nsIContent
* next
= parentIter
.GetNextChild()) {
3769 // If no next sibling and parent is mOwner, END traversal
3770 if (parent
== mOwner
) {
3771 SetCurrent(nullptr);
3779 void ScopedContentTraversal::Prev() {
3780 MOZ_ASSERT(mCurrent
);
3784 if (mCurrent
== mOwner
) {
3785 // Get last child of mOwner
3786 StyleChildrenIterator
ownerIter(mOwner
, false /* aStartAtBeginning */);
3787 last
= ownerIter
.GetPreviousChild();
3791 // Create parent's iterator and move to mCurrent
3792 parent
= mCurrent
->GetFlattenedTreeParent();
3793 StyleChildrenIterator
parentIter(parent
);
3794 parentIter
.Seek(mCurrent
);
3796 // Get previous sibling
3797 last
= parentIter
.GetPreviousChild();
3802 if (IsHostOrSlot(parent
)) {
3803 // Skip contents in other scopes
3808 StyleChildrenIterator
iter(parent
, false /* aStartAtBeginning */);
3809 last
= iter
.GetPreviousChild();
3812 // If parent is mOwner and no previous sibling remains, END traversal
3813 SetCurrent(parent
== mOwner
? nullptr : parent
);
3817 * Returns scope owner of aContent.
3818 * A scope owner is either a shadow host, or slot.
3820 static nsIContent
* FindScopeOwner(nsIContent
* aContent
) {
3821 nsIContent
* currentContent
= aContent
;
3822 while (currentContent
) {
3823 nsIContent
* parent
= currentContent
->GetFlattenedTreeParent();
3825 // Shadow host / Slot
3826 if (IsHostOrSlot(parent
)) {
3830 currentContent
= parent
;
3837 * Host and Slot elements need to be handled as if they had tabindex 0 even
3838 * when they don't have the attribute. This is a helper method to get the
3839 * right value for focus navigation. If aIsFocusable is passed, it is set to
3840 * true if the element itself is focusable.
3842 static int32_t HostOrSlotTabIndexValue(const nsIContent
* aContent
,
3843 bool* aIsFocusable
= nullptr) {
3844 MOZ_ASSERT(IsHostOrSlot(aContent
));
3847 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
3848 *aIsFocusable
= frame
&& frame
->IsFocusable().mTabIndex
>= 0;
3851 const nsAttrValue
* attrVal
=
3852 aContent
->AsElement()->GetParsedAttr(nsGkAtoms::tabindex
);
3857 if (attrVal
->Type() == nsAttrValue::eInteger
) {
3858 return attrVal
->GetIntegerValue();
3864 nsIContent
* nsFocusManager::GetNextTabbableContentInScope(
3865 nsIContent
* aOwner
, nsIContent
* aStartContent
,
3866 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t aCurrentTabIndex
,
3867 bool aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
3869 MOZ_ASSERT(IsHostOrSlot(aOwner
), "Scope owner should be host or slot");
3871 // XXX: Why don't we ignore tabindex when the current tabindex < 0?
3872 MOZ_ASSERT_IF(aCurrentTabIndex
< 0, aIgnoreTabIndex
);
3874 if (!aSkipOwner
&& (aForward
&& aOwner
== aStartContent
)) {
3875 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3876 auto focusable
= frame
->IsFocusable();
3877 if (focusable
&& focusable
.mTabIndex
>= 0) {
3884 // Iterate contents in scope
3886 ScopedContentTraversal
contentTraversal(aStartContent
, aOwner
);
3887 nsCOMPtr
<nsIContent
> iterContent
;
3888 nsIContent
* firstNonChromeOnly
=
3889 aStartContent
->IsInNativeAnonymousSubtree()
3890 ? aStartContent
->FindFirstNonChromeOnlyAccessContent()
3893 // Iterate tab index to find corresponding contents in scope
3896 // Iterate remaining contents in scope to find next content to focus
3899 aForward
? contentTraversal
.Next() : contentTraversal
.Prev();
3900 iterContent
= contentTraversal
.GetCurrent();
3902 if (firstNonChromeOnly
&& firstNonChromeOnly
== iterContent
) {
3903 // We just broke out from the native anonymous content, so move
3904 // to the previous/next node of the native anonymous owner.
3906 contentTraversal
.Next();
3908 contentTraversal
.Prev();
3910 iterContent
= contentTraversal
.GetCurrent();
3917 int32_t tabIndex
= 0;
3918 if (iterContent
->IsInNativeAnonymousSubtree() &&
3919 iterContent
->GetPrimaryFrame()) {
3920 tabIndex
= iterContent
->GetPrimaryFrame()->IsFocusable().mTabIndex
;
3921 } else if (IsHostOrSlot(iterContent
)) {
3922 tabIndex
= HostOrSlotTabIndexValue(iterContent
);
3924 nsIFrame
* frame
= iterContent
->GetPrimaryFrame();
3928 tabIndex
= frame
->IsFocusable().mTabIndex
;
3930 if (tabIndex
< 0 || !(aIgnoreTabIndex
|| tabIndex
== aCurrentTabIndex
)) {
3934 if (!IsHostOrSlot(iterContent
)) {
3935 nsCOMPtr
<nsIContent
> elementInFrame
;
3936 bool checkSubDocument
= true;
3937 if (aForDocumentNavigation
&&
3938 TryDocumentNavigation(iterContent
, &checkSubDocument
,
3939 getter_AddRefs(elementInFrame
))) {
3940 return elementInFrame
;
3942 if (!checkSubDocument
) {
3946 if (TryToMoveFocusToSubDocument(iterContent
, aOriginalStartContent
,
3947 aForward
, aForDocumentNavigation
,
3949 getter_AddRefs(elementInFrame
))) {
3950 return elementInFrame
;
3953 // Found content to focus
3957 // Search in scope owned by iterContent
3958 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
3959 iterContent
, iterContent
, aOriginalStartContent
, aForward
,
3960 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
3961 aNavigateByKey
, false /* aSkipOwner */);
3962 if (contentToFocus
) {
3963 return contentToFocus
;
3967 // If already at lowest priority tab (0), end search completely.
3968 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3969 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
3973 // We've been just trying to find some focusable element, and haven't, so
3975 if (aIgnoreTabIndex
) {
3979 // Continue looking for next highest priority tabindex
3980 aCurrentTabIndex
= GetNextTabIndex(aOwner
, aCurrentTabIndex
, aForward
);
3981 contentTraversal
.Reset();
3984 // Return scope owner at last for backward navigation if its tabindex
3986 if (!aSkipOwner
&& !aForward
) {
3987 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3988 auto focusable
= frame
->IsFocusable();
3989 if (focusable
&& focusable
.mTabIndex
>= 0) {
3998 nsIContent
* nsFocusManager::GetNextTabbableContentInAncestorScopes(
3999 nsIContent
* aStartOwner
, nsCOMPtr
<nsIContent
>& aStartContent
/* inout */,
4000 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t* aCurrentTabIndex
,
4001 bool* aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
) {
4002 MOZ_ASSERT(aStartOwner
== FindScopeOwner(aStartContent
),
4003 "aStartOWner should be the scope owner of aStartContent");
4004 MOZ_ASSERT(IsHostOrSlot(aStartOwner
), "scope owner should be host or slot");
4006 nsCOMPtr
<nsIContent
> owner
= aStartOwner
;
4007 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4008 while (IsHostOrSlot(owner
)) {
4009 int32_t tabIndex
= 0;
4010 if (IsHostOrSlot(startContent
)) {
4011 tabIndex
= HostOrSlotTabIndexValue(startContent
);
4012 } else if (nsIFrame
* frame
= startContent
->GetPrimaryFrame()) {
4013 tabIndex
= frame
->IsFocusable().mTabIndex
;
4015 startContent
->IsFocusable(&tabIndex
);
4017 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4018 owner
, startContent
, aOriginalStartContent
, aForward
, tabIndex
,
4019 tabIndex
< 0, aForDocumentNavigation
, aNavigateByKey
,
4020 false /* aSkipOwner */);
4021 if (contentToFocus
) {
4022 return contentToFocus
;
4025 startContent
= owner
;
4026 owner
= FindScopeOwner(startContent
);
4029 // If not found in shadow DOM, search from the top level shadow host in light
4031 aStartContent
= startContent
;
4032 *aCurrentTabIndex
= HostOrSlotTabIndexValue(startContent
);
4034 if (*aCurrentTabIndex
< 0) {
4035 *aIgnoreTabIndex
= true;
4041 static nsIContent
* GetTopLevelScopeOwner(nsIContent
* aContent
) {
4042 nsIContent
* topLevelScopeOwner
= nullptr;
4044 if (HTMLSlotElement
* slot
= aContent
->GetAssignedSlot()) {
4046 topLevelScopeOwner
= aContent
;
4047 } else if (ShadowRoot
* shadowRoot
= aContent
->GetContainingShadow()) {
4048 aContent
= shadowRoot
->Host();
4049 topLevelScopeOwner
= aContent
;
4051 aContent
= aContent
->GetParent();
4052 if (aContent
&& HTMLSlotElement::FromNode(aContent
)) {
4053 topLevelScopeOwner
= aContent
;
4058 return topLevelScopeOwner
;
4061 nsresult
nsFocusManager::GetNextTabbableContent(
4062 PresShell
* aPresShell
, nsIContent
* aRootContent
,
4063 nsIContent
* aOriginalStartContent
, nsIContent
* aStartContent
, bool aForward
,
4064 int32_t aCurrentTabIndex
, bool aIgnoreTabIndex
, bool aForDocumentNavigation
,
4065 bool aNavigateByKey
, nsIContent
** aResultContent
) {
4066 *aResultContent
= nullptr;
4068 if (!aStartContent
) {
4072 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4073 nsCOMPtr
<nsIContent
> currentTopLevelScopeOwner
=
4074 GetTopLevelScopeOwner(startContent
);
4076 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent
);
4077 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex
));
4079 // If startContent is a shadow host or slot in forward navigation,
4080 // search in scope owned by startContent
4081 if (aForward
&& IsHostOrSlot(startContent
)) {
4082 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4083 startContent
, startContent
, aOriginalStartContent
, aForward
,
4084 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
4085 aNavigateByKey
, true /* aSkipOwner */);
4086 if (contentToFocus
) {
4087 NS_ADDREF(*aResultContent
= contentToFocus
);
4092 // If startContent is in a scope owned by Shadow DOM search from scope
4093 // including startContent
4094 if (nsCOMPtr
<nsIContent
> owner
= FindScopeOwner(startContent
)) {
4095 nsIContent
* contentToFocus
= GetNextTabbableContentInAncestorScopes(
4096 owner
, startContent
/* inout */, aOriginalStartContent
, aForward
,
4097 &aCurrentTabIndex
, &aIgnoreTabIndex
, aForDocumentNavigation
,
4099 if (contentToFocus
) {
4100 NS_ADDREF(*aResultContent
= contentToFocus
);
4105 // If we reach here, it means no next tabbable content in shadow DOM.
4106 // We need to continue searching in light DOM, starting at the top level
4107 // shadow host in light DOM (updated startContent) and its tabindex
4108 // (updated aCurrentTabIndex).
4109 MOZ_ASSERT(!FindScopeOwner(startContent
),
4110 "startContent should not be owned by Shadow DOM at this point");
4112 nsPresContext
* presContext
= aPresShell
->GetPresContext();
4114 bool getNextFrame
= true;
4115 nsCOMPtr
<nsIContent
> iterStartContent
= startContent
;
4116 nsIContent
* topLevelScopeStartContent
= startContent
;
4117 // Iterate tab index to find corresponding contents
4119 nsIFrame
* frame
= iterStartContent
->GetPrimaryFrame();
4120 // if there is no frame, look for another content node that has a frame
4122 // if the root content doesn't have a frame, just return
4123 if (iterStartContent
== aRootContent
) {
4127 // look for the next or previous content node in tree order
4128 iterStartContent
= aForward
? iterStartContent
->GetNextNode()
4129 : iterStartContent
->GetPreviousContent();
4130 if (!iterStartContent
) {
4134 frame
= iterStartContent
->GetPrimaryFrame();
4135 // Host without frame, enter its scope.
4136 if (!frame
&& iterStartContent
->GetShadowRoot()) {
4137 int32_t tabIndex
= HostOrSlotTabIndexValue(iterStartContent
);
4138 if (tabIndex
>= 0 &&
4139 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4140 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4141 iterStartContent
, iterStartContent
, aOriginalStartContent
,
4142 aForward
, aForward
? 1 : 0, aIgnoreTabIndex
,
4143 aForDocumentNavigation
, aNavigateByKey
, true /* aSkipOwner */);
4144 if (contentToFocus
) {
4145 NS_ADDREF(*aResultContent
= contentToFocus
);
4150 // we've already skipped over the initial focused content, so we
4151 // don't want to traverse frames.
4152 getNextFrame
= false;
4155 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
4157 // For tab navigation, pass false for aSkipPopupChecks so that we don't
4158 // iterate into or out of a popup. For document naviation pass true to
4159 // ignore these boundaries.
4160 nsresult rv
= NS_NewFrameTraversal(
4161 getter_AddRefs(frameTraversal
), presContext
, frame
, ePreOrder
,
4163 false, // aLockInScrollView
4164 true, // aFollowOOFs
4165 aForDocumentNavigation
// aSkipPopupChecks
4167 NS_ENSURE_SUCCESS(rv
, rv
);
4169 if (iterStartContent
== aRootContent
) {
4171 frameTraversal
->Last();
4172 } else if (aRootContent
->IsFocusable()) {
4173 frameTraversal
->Next();
4175 frame
= frameTraversal
->CurrentItem();
4176 } else if (getNextFrame
&&
4177 (!iterStartContent
||
4178 !iterStartContent
->IsHTMLElement(nsGkAtoms::area
))) {
4179 // Need to do special check in case we're in an imagemap which has
4180 // multiple content nodes per frame, so don't skip over the starting
4182 frame
= frameTraversal
->Traverse(aForward
);
4186 nsIContent
* oldTopLevelScopeOwner
= nullptr;
4187 // Walk frames to find something tabbable matching aCurrentTabIndex
4189 // Try to find the topmost scope owner, since we want to skip the node
4190 // that is not owned by document in frame traversal.
4191 const nsCOMPtr
<nsIContent
> currentContent
= frame
->GetContent();
4192 if (currentTopLevelScopeOwner
) {
4193 oldTopLevelScopeOwner
= currentTopLevelScopeOwner
;
4195 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(currentContent
);
4196 if (currentTopLevelScopeOwner
&&
4197 currentTopLevelScopeOwner
== oldTopLevelScopeOwner
) {
4198 // We're within non-document scope, continue.
4201 frameTraversal
->Next();
4203 frameTraversal
->Prev();
4205 frame
= static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
4206 // For the usage of GetPrevContinuation, see the comment
4207 // at the end of while (frame) loop.
4208 } while (frame
&& frame
->GetPrevContinuation());
4212 // For document navigation, check if this element is an open panel. Since
4213 // panels aren't focusable (tabIndex would be -1), we'll just assume that
4214 // for document navigation, the tabIndex is 0.
4215 if (aForDocumentNavigation
&& currentContent
&& (aCurrentTabIndex
== 0) &&
4216 currentContent
->IsXULElement(nsGkAtoms::panel
)) {
4217 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
4218 // Check if the panel is open. Closed panels are ignored since you can't
4219 // focus anything in them.
4220 if (popupFrame
&& popupFrame
->IsOpen()) {
4221 // When moving backward, skip the popup we started in otherwise it
4222 // will be selected again.
4223 bool validPopup
= true;
4225 nsIContent
* content
= topLevelScopeStartContent
;
4227 if (content
== currentContent
) {
4232 content
= content
->GetParent();
4237 // Since a panel isn't focusable itself, find the first focusable
4238 // content within the popup. If there isn't any focusable content
4239 // in the popup, skip this popup and continue iterating through the
4240 // frames. We pass the panel itself (currentContent) as the starting
4241 // and root content, so that we only find content within the panel.
4242 // Note also that we pass false for aForDocumentNavigation since we
4243 // want to locate the first content, not the first document.
4244 nsresult rv
= GetNextTabbableContent(
4245 aPresShell
, currentContent
, nullptr, currentContent
, true, 1,
4246 false, false, aNavigateByKey
, aResultContent
);
4247 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4254 // As of now, 2018/04/12, sequential focus navigation is still
4255 // in the obsolete Shadow DOM specification.
4256 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
4257 // "if ELEMENT is focusable, a shadow host, or a slot element,
4258 // append ELEMENT to NAVIGATION-ORDER."
4259 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
4260 // hosts and slots are handled before other elements.
4261 if (currentTopLevelScopeOwner
) {
4262 bool focusableHostSlot
;
4263 int32_t tabIndex
= HostOrSlotTabIndexValue(currentTopLevelScopeOwner
,
4264 &focusableHostSlot
);
4265 // Host or slot itself isn't focusable or going backwards, enter its
4267 if ((!aForward
|| !focusableHostSlot
) && tabIndex
>= 0 &&
4268 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4269 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4270 currentTopLevelScopeOwner
, currentTopLevelScopeOwner
,
4271 aOriginalStartContent
, aForward
, aForward
? 1 : 0,
4272 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4273 true /* aSkipOwner */);
4274 if (contentToFocus
) {
4275 NS_ADDREF(*aResultContent
= contentToFocus
);
4278 // If we've wrapped around already, then carry on.
4279 if (aOriginalStartContent
&&
4280 currentTopLevelScopeOwner
==
4281 GetTopLevelScopeOwner(aOriginalStartContent
)) {
4282 // FIXME: Shouldn't this return null instead? aOriginalStartContent
4283 // isn't focusable after all.
4284 NS_ADDREF(*aResultContent
= aOriginalStartContent
);
4288 // There is no next tabbable content in currentTopLevelScopeOwner's
4289 // scope. We should continue the loop in order to skip all contents that
4290 // is in currentTopLevelScopeOwner's scope.
4294 MOZ_ASSERT(!GetTopLevelScopeOwner(currentContent
),
4295 "currentContent should be in top-level-scope at this point");
4297 // TabIndex not set defaults to 0 for form elements, anchors and other
4298 // elements that are normally focusable. Tabindex defaults to -1
4299 // for elements that are not normally focusable.
4300 // The returned computed tabindex from IsFocusable() is as follows:
4302 // < 0 not tabbable at all
4303 // == 0 in normal tab order (last after positive tabindexed items)
4304 // > 0 can be tabbed to in the order specified by this value
4306 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4308 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame
->GetContent());
4310 (" with tabindex: %d expected: %d", tabIndex
, aCurrentTabIndex
));
4312 if (tabIndex
>= 0) {
4313 NS_ASSERTION(currentContent
,
4314 "IsFocusable set a tabindex for a frame with no content");
4315 if (!aForDocumentNavigation
&&
4316 currentContent
->IsHTMLElement(nsGkAtoms::img
) &&
4317 currentContent
->AsElement()->HasAttr(nsGkAtoms::usemap
)) {
4318 // This is an image with a map. Image map areas are not traversed by
4319 // nsIFrameTraversal so look for the next or previous area element.
4320 nsIContent
* areaContent
= GetNextTabbableMapArea(
4321 aForward
, aCurrentTabIndex
, currentContent
->AsElement(),
4324 NS_ADDREF(*aResultContent
= areaContent
);
4327 } else if (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
) {
4328 // break out if we've wrapped around to the start again.
4329 if (aOriginalStartContent
&&
4330 currentContent
== aOriginalStartContent
) {
4331 NS_ADDREF(*aResultContent
= currentContent
);
4335 // If this is a remote child browser, call NavigateDocument to have
4336 // the child process continue the navigation. Return a special error
4337 // code to have the caller return early. If the child ends up not
4338 // being focusable in some way, the child process will call back
4339 // into document navigation again by calling MoveFocus.
4340 if (BrowserParent
* remote
= BrowserParent::GetFrom(currentContent
)) {
4341 if (aNavigateByKey
) {
4342 remote
->NavigateByKey(aForward
, aForDocumentNavigation
);
4343 return NS_SUCCESS_DOM_NO_OPERATION
;
4348 // Same as above but for out-of-process iframes
4349 if (auto* bbc
= BrowserBridgeChild::GetFrom(currentContent
)) {
4350 if (aNavigateByKey
) {
4351 bbc
->NavigateByKey(aForward
, aForDocumentNavigation
);
4352 return NS_SUCCESS_DOM_NO_OPERATION
;
4357 // Next, for document navigation, check if this a non-remote child
4359 bool checkSubDocument
= true;
4360 if (aForDocumentNavigation
&&
4361 TryDocumentNavigation(currentContent
, &checkSubDocument
,
4366 if (checkSubDocument
) {
4367 // found a node with a matching tab index. Check if it is a child
4368 // frame. If so, navigate into the child frame instead.
4369 if (TryToMoveFocusToSubDocument(
4370 currentContent
, aOriginalStartContent
, aForward
,
4371 aForDocumentNavigation
, aNavigateByKey
, aResultContent
)) {
4372 MOZ_ASSERT(*aResultContent
);
4375 // otherwise, use this as the next content node to tab to, unless
4376 // this was the element we started on. This would happen for
4377 // instance on an element with child frames, where frame navigation
4378 // could return the original element again. In that case, just skip
4379 // it. Also, if the next content node is the root content, then
4380 // return it. This latter case would happen only if someone made a
4382 else if (currentContent
== aRootContent
||
4383 currentContent
!= startContent
) {
4384 NS_ADDREF(*aResultContent
= currentContent
);
4389 } else if (aOriginalStartContent
&&
4390 currentContent
== aOriginalStartContent
) {
4391 // not focusable, so return if we have wrapped around to the original
4392 // content. This is necessary in case the original starting content was
4395 // FIXME: Shouldn't this return null instead? currentContent isn't
4396 // focusable after all.
4397 NS_ADDREF(*aResultContent
= currentContent
);
4401 // Move to the next or previous frame, but ignore continuation frames
4402 // since only the first frame should be involved in focusability.
4403 // Otherwise, a loop will occur in the following example:
4404 // <span tabindex="1">...<a/><a/>...</span>
4405 // where the text wraps onto multiple lines. Tabbing from the second
4406 // link can find one of the span's continuation frames between the link
4407 // and the end of the span, and the span would end up getting focused
4411 frameTraversal
->Next();
4413 frameTraversal
->Prev();
4415 frame
= static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
4416 } while (frame
&& frame
->GetPrevContinuation());
4419 // If already at lowest priority tab (0), end search completely.
4420 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4421 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4422 // if going backwards, the canvas should be focused once the beginning
4423 // has been reached, so get the root element.
4425 nsCOMPtr
<nsPIDOMWindowOuter
> window
= GetCurrentWindow(aRootContent
);
4426 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
4428 RefPtr
<Element
> docRoot
= GetRootForFocus(
4429 window
, aRootContent
->GetComposedDoc(), false, true);
4430 FocusFirst(docRoot
, aResultContent
);
4435 // continue looking for next highest priority tabindex
4437 GetNextTabIndex(aRootContent
, aCurrentTabIndex
, aForward
);
4438 startContent
= iterStartContent
= aRootContent
;
4439 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(startContent
);
4445 bool nsFocusManager::TryDocumentNavigation(nsIContent
* aCurrentContent
,
4446 bool* aCheckSubDocument
,
4447 nsIContent
** aResultContent
) {
4448 *aCheckSubDocument
= true;
4449 if (RefPtr
<Element
> rootElementForChildDocument
=
4450 GetRootForChildDocument(aCurrentContent
)) {
4451 // If GetRootForChildDocument returned something then call
4452 // FocusFirst to find the root or first element to focus within
4453 // the child document. If this is a frameset though, skip this and
4454 // fall through to normal tab navigation to iterate into
4455 // the frameset's frames and locate the first focusable frame.
4456 if (!rootElementForChildDocument
->IsHTMLElement(nsGkAtoms::frameset
)) {
4457 *aCheckSubDocument
= false;
4458 Unused
<< FocusFirst(rootElementForChildDocument
, aResultContent
);
4459 return *aResultContent
!= nullptr;
4462 // Set aCheckSubDocument to false, as this was neither a frame
4463 // type element or a child document that was focusable.
4464 *aCheckSubDocument
= false;
4470 bool nsFocusManager::TryToMoveFocusToSubDocument(
4471 nsIContent
* aCurrentContent
, nsIContent
* aOriginalStartContent
,
4472 bool aForward
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4473 nsIContent
** aResultContent
) {
4474 Document
* doc
= aCurrentContent
->GetComposedDoc();
4475 NS_ASSERTION(doc
, "content not in document");
4476 Document
* subdoc
= doc
->GetSubDocumentFor(aCurrentContent
);
4477 if (subdoc
&& !subdoc
->EventHandlingSuppressed()) {
4479 // When tabbing forward into a frame, return the root
4480 // frame so that the canvas becomes focused.
4481 if (nsCOMPtr
<nsPIDOMWindowOuter
> subframe
= subdoc
->GetWindow()) {
4482 *aResultContent
= GetRootForFocus(subframe
, subdoc
, false, true);
4483 if (*aResultContent
) {
4484 NS_ADDREF(*aResultContent
);
4489 if (RefPtr
<Element
> rootElement
= subdoc
->GetRootElement()) {
4490 if (RefPtr
<PresShell
> subPresShell
= subdoc
->GetPresShell()) {
4491 nsresult rv
= GetNextTabbableContent(
4492 subPresShell
, rootElement
, aOriginalStartContent
, rootElement
,
4493 aForward
, (aForward
? 1 : 0), false, aForDocumentNavigation
,
4494 aNavigateByKey
, aResultContent
);
4495 NS_ENSURE_SUCCESS(rv
, false);
4496 if (*aResultContent
) {
4505 nsIContent
* nsFocusManager::GetNextTabbableMapArea(bool aForward
,
4506 int32_t aCurrentTabIndex
,
4507 Element
* aImageContent
,
4508 nsIContent
* aStartContent
) {
4509 if (aImageContent
->IsInComposedDoc()) {
4510 HTMLImageElement
* imgElement
= HTMLImageElement::FromNode(aImageContent
);
4511 // The caller should check the element type, so we can assert here.
4512 MOZ_ASSERT(imgElement
);
4514 nsCOMPtr
<nsIContent
> mapContent
= imgElement
->FindImageMap();
4518 // First see if the the start content is in this map
4519 Maybe
<uint32_t> indexOfStartContent
=
4520 mapContent
->ComputeIndexOf(aStartContent
);
4522 nsIContent
* scanStartContent
;
4523 if (indexOfStartContent
.isNothing() ||
4524 (aStartContent
->IsFocusable(&tabIndex
) &&
4525 tabIndex
!= aCurrentTabIndex
)) {
4526 // If aStartContent is in this map we must start iterating past it.
4527 // We skip the case where aStartContent has tabindex == aStartContent
4528 // since the next tab ordered element might be before it
4529 // (or after for backwards) in the child list.
4531 aForward
? mapContent
->GetFirstChild() : mapContent
->GetLastChild();
4533 scanStartContent
= aForward
? aStartContent
->GetNextSibling()
4534 : aStartContent
->GetPreviousSibling();
4537 for (nsCOMPtr
<nsIContent
> areaContent
= scanStartContent
; areaContent
;
4538 areaContent
= aForward
? areaContent
->GetNextSibling()
4539 : areaContent
->GetPreviousSibling()) {
4540 if (areaContent
->IsFocusable(&tabIndex
) && tabIndex
== aCurrentTabIndex
) {
4549 int32_t nsFocusManager::GetNextTabIndex(nsIContent
* aParent
,
4550 int32_t aCurrentTabIndex
,
4552 int32_t tabIndex
, childTabIndex
;
4553 StyleChildrenIterator
iter(aParent
);
4557 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4558 child
= iter
.GetNextChild()) {
4559 // Skip child's descendants if child is a shadow host or slot, as they are
4560 // in the focus navigation scope owned by child's shadow root
4561 if (!IsHostOrSlot(child
)) {
4562 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4563 if (childTabIndex
> aCurrentTabIndex
&& childTabIndex
!= tabIndex
) {
4564 tabIndex
= (tabIndex
== 0 || childTabIndex
< tabIndex
) ? childTabIndex
4569 nsAutoString tabIndexStr
;
4570 if (child
->IsElement()) {
4571 child
->AsElement()->GetAttr(nsGkAtoms::tabindex
, tabIndexStr
);
4574 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4575 if (NS_SUCCEEDED(ec
) && val
> aCurrentTabIndex
&& val
!= tabIndex
) {
4576 tabIndex
= (tabIndex
== 0 || val
< tabIndex
) ? val
: tabIndex
;
4579 } else { /* !aForward */
4581 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4582 child
= iter
.GetNextChild()) {
4583 // Skip child's descendants if child is a shadow host or slot, as they are
4584 // in the focus navigation scope owned by child's shadow root
4585 if (!IsHostOrSlot(child
)) {
4586 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4587 if ((aCurrentTabIndex
== 0 && childTabIndex
> tabIndex
) ||
4588 (childTabIndex
< aCurrentTabIndex
&& childTabIndex
> tabIndex
)) {
4589 tabIndex
= childTabIndex
;
4593 nsAutoString tabIndexStr
;
4594 if (child
->IsElement()) {
4595 child
->AsElement()->GetAttr(nsGkAtoms::tabindex
, tabIndexStr
);
4598 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4599 if (NS_SUCCEEDED(ec
)) {
4600 if ((aCurrentTabIndex
== 0 && val
> tabIndex
) ||
4601 (val
< aCurrentTabIndex
&& val
> tabIndex
)) {
4611 nsresult
nsFocusManager::FocusFirst(Element
* aRootElement
,
4612 nsIContent
** aNextContent
) {
4613 if (!aRootElement
) {
4617 Document
* doc
= aRootElement
->GetComposedDoc();
4619 if (nsContentUtils::IsChromeDoc(doc
)) {
4620 // If the redirectdocumentfocus attribute is set, redirect the focus to a
4621 // specific element. This is primarily used to retarget the focus to the
4622 // urlbar during document navigation.
4623 nsAutoString retarget
;
4625 if (aRootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
4626 RefPtr
<Element
> element
= doc
->GetElementById(retarget
);
4627 nsCOMPtr
<nsIContent
> retargetElement
=
4628 FlushAndCheckIfFocusable(element
, 0);
4629 if (retargetElement
) {
4630 retargetElement
.forget(aNextContent
);
4636 nsCOMPtr
<nsIDocShell
> docShell
= doc
->GetDocShell();
4637 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4638 // If the found content is in a chrome shell, navigate forward one
4639 // tabbable item so that the first item is focused. Note that we
4640 // always go forward and not back here.
4641 if (RefPtr
<PresShell
> presShell
= doc
->GetPresShell()) {
4642 return GetNextTabbableContent(presShell
, aRootElement
, nullptr,
4643 aRootElement
, true, 1, false, false, true,
4649 NS_ADDREF(*aNextContent
= aRootElement
);
4653 Element
* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter
* aWindow
,
4654 Document
* aDocument
,
4655 bool aForDocumentNavigation
,
4656 bool aCheckVisibility
) {
4657 if (!aForDocumentNavigation
) {
4658 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
4659 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4664 if (aCheckVisibility
&& !IsWindowVisible(aWindow
)) return nullptr;
4666 // If the body is contenteditable, use the editor's root element rather than
4667 // the actual root element.
4668 RefPtr
<Element
> rootElement
=
4669 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument
);
4670 if (!rootElement
|| !rootElement
->GetPrimaryFrame()) {
4671 rootElement
= aDocument
->GetRootElement();
4677 if (aCheckVisibility
&& !rootElement
->GetPrimaryFrame()) {
4681 // Finally, check if this is a frameset
4682 if (aDocument
&& aDocument
->IsHTMLOrXHTML()) {
4683 Element
* htmlChild
= aDocument
->GetHtmlChildElement(nsGkAtoms::frameset
);
4685 // In document navigation mode, return the frameset so that navigation
4686 // descends into the child frames.
4687 return aForDocumentNavigation
? htmlChild
: nullptr;
4694 Element
* nsFocusManager::GetRootForChildDocument(nsIContent
* aContent
) {
4695 // Check for elements that represent child documents, that is, browsers,
4696 // editors or frames from a frameset. We don't include iframes since we
4697 // consider them to be an integral part of the same window or page.
4698 if (!aContent
|| !(aContent
->IsXULElement(nsGkAtoms::browser
) ||
4699 aContent
->IsXULElement(nsGkAtoms::editor
) ||
4700 aContent
->IsHTMLElement(nsGkAtoms::frame
))) {
4704 Document
* doc
= aContent
->GetComposedDoc();
4709 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
4710 if (!subdoc
|| subdoc
->EventHandlingSuppressed()) {
4714 nsCOMPtr
<nsPIDOMWindowOuter
> window
= subdoc
->GetWindow();
4715 return GetRootForFocus(window
, subdoc
, true, true);
4718 static bool IsLink(nsIContent
* aContent
) {
4719 return aContent
->IsElement() && aContent
->AsElement()->IsLink();
4722 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter
* aWindow
,
4723 nsIContent
* aStartSelection
,
4724 nsIContent
* aEndSelection
,
4725 nsIContent
** aFocusedContent
) {
4726 *aFocusedContent
= nullptr;
4728 nsCOMPtr
<nsIContent
> testContent
= aStartSelection
;
4729 nsCOMPtr
<nsIContent
> nextTestContent
= aEndSelection
;
4731 nsCOMPtr
<nsIContent
> currentFocus
= aWindow
->GetFocusedElement();
4733 // We now have the correct start node in selectionContent!
4734 // Search for focusable elements, starting with selectionContent
4736 // Method #1: Keep going up while we look - an ancestor might be focusable
4737 // We could end the loop earlier, such as when we're no longer
4738 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4739 // with a variable holding the starting selectionContent
4740 while (testContent
) {
4741 // Keep testing while selectionContent is equal to something,
4742 // eventually we'll run out of ancestors
4744 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4745 testContent
.forget(aFocusedContent
);
4750 testContent
= testContent
->GetParent();
4753 // We run this loop again, checking the ancestor chain of the selection's
4755 testContent
= nextTestContent
;
4756 nextTestContent
= nullptr;
4760 // We couldn't find an anchor that was an ancestor of the selection start
4761 // Method #2: look for anchor in selection's primary range (depth first
4764 nsCOMPtr
<nsIContent
> selectionNode
= aStartSelection
;
4765 nsCOMPtr
<nsIContent
> endSelectionNode
= aEndSelection
;
4766 nsCOMPtr
<nsIContent
> testNode
;
4769 testContent
= selectionNode
;
4771 // We're looking for any focusable link that could be part of the
4772 // main document's selection.
4773 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4774 testContent
.forget(aFocusedContent
);
4778 nsIContent
* testNode
= selectionNode
->GetFirstChild();
4780 selectionNode
= testNode
;
4784 if (selectionNode
== endSelectionNode
) {
4787 testNode
= selectionNode
->GetNextSibling();
4789 selectionNode
= testNode
;
4794 // GetParent is OK here, instead of GetParentNode, because the only case
4795 // where the latter returns something different from the former is when
4796 // GetParentNode is the document. But in that case we would simply get
4797 // null for selectionNode when setting it to testNode->GetNextSibling()
4798 // (because a document has no next sibling). And then the next iteration
4799 // of this loop would get null for GetParentNode anyway, and break out of
4801 testNode
= selectionNode
->GetParent();
4802 if (!testNode
|| testNode
== endSelectionNode
) {
4803 selectionNode
= nullptr;
4806 selectionNode
= testNode
->GetNextSibling();
4807 if (selectionNode
) {
4810 selectionNode
= testNode
;
4812 } while (selectionNode
&& selectionNode
!= endSelectionNode
);
4815 static void MaybeUnlockPointer(BrowsingContext
* aCurrentFocusedContext
) {
4816 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext
)) {
4817 PointerLockManager::Unlock();
4821 class PointerUnlocker
: public Runnable
{
4823 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
4824 MOZ_ASSERT(XRE_IsParentProcess());
4825 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker
);
4826 PointerUnlocker::sActiveUnlocker
= this;
4829 ~PointerUnlocker() {
4830 if (PointerUnlocker::sActiveUnlocker
== this) {
4831 PointerUnlocker::sActiveUnlocker
= nullptr;
4835 NS_IMETHOD
Run() override
{
4836 if (PointerUnlocker::sActiveUnlocker
== this) {
4837 PointerUnlocker::sActiveUnlocker
= nullptr;
4839 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
4840 nsPIDOMWindowOuter
* focused
=
4841 nsFocusManager::GetFocusManager()->GetFocusedWindow();
4842 MaybeUnlockPointer(focused
? focused
->GetBrowsingContext() : nullptr);
4846 static PointerUnlocker
* sActiveUnlocker
;
4849 PointerUnlocker
* PointerUnlocker::sActiveUnlocker
= nullptr;
4851 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext
* aContext
,
4852 uint64_t aActionId
) {
4853 if (XRE_IsParentProcess()) {
4856 MOZ_ASSERT(!ActionIdComparableAndLower(
4857 aActionId
, mActionIdForFocusedBrowsingContextInContent
));
4858 mFocusedBrowsingContextInContent
= aContext
;
4859 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
4861 // We don't send the unset but instead expect the set from
4862 // elsewhere to take care of it. XXX Is that bad?
4863 MOZ_ASSERT(aContext
->IsInProcess());
4864 mozilla::dom::ContentChild
* contentChild
=
4865 mozilla::dom::ContentChild::GetSingleton();
4866 MOZ_ASSERT(contentChild
);
4867 contentChild
->SendSetFocusedBrowsingContext(aContext
, aActionId
);
4871 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
4872 BrowsingContext
* aContext
, uint64_t aActionId
) {
4873 MOZ_ASSERT(!XRE_IsParentProcess());
4874 MOZ_ASSERT(aContext
);
4875 if (ActionIdComparableAndLower(aActionId
,
4876 mActionIdForFocusedBrowsingContextInContent
)) {
4877 // Unclear if this ever happens.
4879 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4880 "focused from another process due to stale action id %" PRIu64
".",
4881 aContext
, aActionId
));
4884 if (aContext
->IsInProcess()) {
4885 // This message has been in transit for long enough that
4886 // the process association of aContext has changed since
4887 // the other content process sent the message, because
4888 // an iframe in that process became an out-of-process
4889 // iframe while the IPC broadcast that we're receiving
4890 // was in-flight. Let's just ignore this.
4892 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4893 "focused from another process, actionid: %" PRIu64
".",
4894 aContext
, aActionId
));
4897 mFocusedBrowsingContextInContent
= aContext
;
4898 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
4899 mFocusedElement
= nullptr;
4900 mFocusedWindow
= nullptr;
4903 bool nsFocusManager::SetFocusedBrowsingContextInChrome(
4904 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
4905 MOZ_ASSERT(aActionId
);
4906 if (ProcessPendingFocusedBrowsingContextActionId(aActionId
)) {
4907 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
4908 aActionId
, mActionIdForFocusedBrowsingContextInChrome
));
4909 mFocusedBrowsingContextInChrome
= aContext
;
4910 mActionIdForFocusedBrowsingContextInChrome
= aActionId
;
4916 BrowsingContext
* nsFocusManager::GetFocusedBrowsingContextInChrome() {
4917 return mFocusedBrowsingContextInChrome
;
4920 void nsFocusManager::BrowsingContextDetached(BrowsingContext
* aContext
) {
4921 if (mFocusedBrowsingContextInChrome
== aContext
) {
4922 mFocusedBrowsingContextInChrome
= nullptr;
4923 // Deliberately not adjusting the corresponding action id, because
4924 // we don't want changes from the past to take effect.
4926 if (mActiveBrowsingContextInChrome
== aContext
) {
4927 mActiveBrowsingContextInChrome
= nullptr;
4928 // Deliberately not adjusting the corresponding action id, because
4929 // we don't want changes from the past to take effect.
4933 void nsFocusManager::SetActiveBrowsingContextInContent(
4934 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
4935 MOZ_ASSERT(!XRE_IsParentProcess());
4936 MOZ_ASSERT(!aContext
|| aContext
->IsInProcess());
4937 mozilla::dom::ContentChild
* contentChild
=
4938 mozilla::dom::ContentChild::GetSingleton();
4939 MOZ_ASSERT(contentChild
);
4941 if (ActionIdComparableAndLower(aActionId
,
4942 mActionIdForActiveBrowsingContextInContent
)) {
4944 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4945 "the active browsing context due to a stale action id %" PRIu64
".",
4946 aContext
, aActionId
));
4950 if (aContext
!= mActiveBrowsingContextInContent
) {
4952 contentChild
->SendSetActiveBrowsingContext(aContext
, aActionId
);
4953 } else if (mActiveBrowsingContextInContent
) {
4954 // We want to sync this over only if this isn't happening
4955 // due to the active BrowsingContext switching processes,
4956 // in which case the BrowserChild has already marked itself
4958 nsPIDOMWindowOuter
* outer
=
4959 mActiveBrowsingContextInContent
->GetDOMWindow();
4961 nsPIDOMWindowInner
* inner
= outer
->GetCurrentInnerWindow();
4963 WindowGlobalChild
* globalChild
= inner
->GetWindowGlobalChild();
4965 RefPtr
<BrowserChild
> browserChild
= globalChild
->GetBrowserChild();
4966 if (browserChild
&& !browserChild
->IsDestroyed()) {
4967 contentChild
->SendUnsetActiveBrowsingContext(
4968 mActiveBrowsingContextInContent
, aActionId
);
4975 mActiveBrowsingContextInContentSetFromOtherProcess
= false;
4976 mActiveBrowsingContextInContent
= aContext
;
4977 mActionIdForActiveBrowsingContextInContent
= aActionId
;
4978 MaybeUnlockPointer(aContext
);
4981 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
4982 BrowsingContext
* aContext
, uint64_t aActionId
) {
4983 MOZ_ASSERT(!XRE_IsParentProcess());
4984 MOZ_ASSERT(aContext
);
4985 if (ActionIdComparableAndLower(aActionId
,
4986 mActionIdForActiveBrowsingContextInContent
)) {
4988 ("Ignored an attempt to set active BrowsingContext [%p] from "
4989 "another process due to a stale action id %" PRIu64
".",
4990 aContext
, aActionId
));
4993 if (aContext
->IsInProcess()) {
4994 // This message has been in transit for long enough that
4995 // the process association of aContext has changed since
4996 // the other content process sent the message, because
4997 // an iframe in that process became an out-of-process
4998 // iframe while the IPC broadcast that we're receiving
4999 // was in-flight. Let's just ignore this.
5001 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5002 "active from another process. actionid: %" PRIu64
,
5003 aContext
, aActionId
));
5006 mActiveBrowsingContextInContentSetFromOtherProcess
= true;
5007 mActiveBrowsingContextInContent
= aContext
;
5008 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5009 MaybeUnlockPointer(aContext
);
5012 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
5013 BrowsingContext
* aContext
, uint64_t aActionId
) {
5014 MOZ_ASSERT(!XRE_IsParentProcess());
5015 MOZ_ASSERT(aContext
);
5016 if (ActionIdComparableAndLower(aActionId
,
5017 mActionIdForActiveBrowsingContextInContent
)) {
5019 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5020 "another process due to stale action id: %" PRIu64
".",
5021 aContext
, aActionId
));
5024 if (mActiveBrowsingContextInContent
== aContext
) {
5025 mActiveBrowsingContextInContent
= nullptr;
5026 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5027 MaybeUnlockPointer(nullptr);
5030 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5031 "another process. actionid: %" PRIu64
,
5032 aContext
, aActionId
));
5036 void nsFocusManager::ReviseActiveBrowsingContext(
5037 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5038 uint64_t aNewActionId
) {
5039 MOZ_ASSERT(XRE_IsContentProcess());
5040 if (mActionIdForActiveBrowsingContextInContent
== aOldActionId
) {
5041 LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64
5043 "actionid: %" PRIu64
,
5044 aContext
, aOldActionId
, aNewActionId
));
5045 mActiveBrowsingContextInContent
= aContext
;
5046 mActionIdForActiveBrowsingContextInContent
= aNewActionId
;
5049 ("Ignored a stale attempt to revise the active BrowsingContext [%p]. "
5050 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5051 aContext
, aOldActionId
, aNewActionId
));
5055 void nsFocusManager::ReviseFocusedBrowsingContext(
5056 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5057 uint64_t aNewActionId
) {
5058 MOZ_ASSERT(XRE_IsContentProcess());
5059 if (mActionIdForFocusedBrowsingContextInContent
== aOldActionId
) {
5061 ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64
5063 "actionid: %" PRIu64
,
5064 aContext
, aOldActionId
, aNewActionId
));
5065 mFocusedBrowsingContextInContent
= aContext
;
5066 mActionIdForFocusedBrowsingContextInContent
= aNewActionId
;
5067 mFocusedElement
= nullptr;
5070 ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. "
5071 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5072 aContext
, aOldActionId
, aNewActionId
));
5076 bool nsFocusManager::SetActiveBrowsingContextInChrome(
5077 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5078 MOZ_ASSERT(aActionId
);
5079 if (ProcessPendingActiveBrowsingContextActionId(aActionId
, aContext
)) {
5080 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5081 aActionId
, mActionIdForActiveBrowsingContextInChrome
));
5082 mActiveBrowsingContextInChrome
= aContext
;
5083 mActionIdForActiveBrowsingContextInChrome
= aActionId
;
5089 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
5090 return mActionIdForActiveBrowsingContextInChrome
;
5093 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
5094 return mActionIdForFocusedBrowsingContextInChrome
;
5097 BrowsingContext
* nsFocusManager::GetActiveBrowsingContextInChrome() {
5098 return mActiveBrowsingContextInChrome
;
5101 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId
) {
5102 LOGFOCUS(("InsertNewFocusActionId %" PRIu64
, aActionId
));
5103 MOZ_ASSERT(XRE_IsParentProcess());
5104 MOZ_ASSERT(!mPendingActiveBrowsingContextActions
.Contains(aActionId
));
5105 mPendingActiveBrowsingContextActions
.AppendElement(aActionId
);
5106 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions
.Contains(aActionId
));
5107 mPendingFocusedBrowsingContextActions
.AppendElement(aActionId
);
5110 static void RemoveContentInitiatedActionsUntil(
5111 nsTArray
<uint64_t>& aPendingActions
,
5112 nsTArray
<uint64_t>::index_type aUntil
) {
5113 nsTArray
<uint64_t>::index_type i
= 0;
5114 while (i
< aUntil
) {
5115 auto [actionProc
, actionId
] =
5116 nsContentUtils::SplitProcessSpecificId(aPendingActions
[i
]);
5119 aPendingActions
.RemoveElementAt(i
);
5127 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
5128 uint64_t aActionId
, bool aSettingToNonNull
) {
5129 MOZ_ASSERT(XRE_IsParentProcess());
5130 auto index
= mPendingActiveBrowsingContextActions
.IndexOf(aActionId
);
5131 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5134 // When aSettingToNonNull is true, we need to remove one more
5135 // element to remove the action id itself in addition to
5136 // removing the older ones.
5137 if (aSettingToNonNull
) {
5140 auto [actionProc
, actionId
] =
5141 nsContentUtils::SplitProcessSpecificId(aActionId
);
5144 // Action from content: We allow parent-initiated actions
5145 // to take precedence over content-initiated ones, so we
5146 // remove only prior content-initiated actions.
5147 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions
,
5150 // Action from chrome
5151 mPendingActiveBrowsingContextActions
.RemoveElementsAt(0, index
);
5156 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
5157 uint64_t aActionId
) {
5158 MOZ_ASSERT(XRE_IsParentProcess());
5159 auto index
= mPendingFocusedBrowsingContextActions
.IndexOf(aActionId
);
5160 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5164 auto [actionProc
, actionId
] =
5165 nsContentUtils::SplitProcessSpecificId(aActionId
);
5168 // Action from content: We allow parent-initiated actions
5169 // to take precedence over content-initiated ones, so we
5170 // remove only prior content-initiated actions.
5171 RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions
,
5174 // Action from chrome
5175 mPendingFocusedBrowsingContextActions
.RemoveElementsAt(0, index
);
5181 uint64_t nsFocusManager::GenerateFocusActionId() {
5183 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter
);
5184 if (XRE_IsParentProcess()) {
5185 nsFocusManager
* fm
= GetFocusManager();
5187 fm
->InsertNewFocusActionId(id
);
5190 mozilla::dom::ContentChild
* contentChild
=
5191 mozilla::dom::ContentChild::GetSingleton();
5192 MOZ_ASSERT(contentChild
);
5193 contentChild
->SendInsertNewFocusActionId(id
);
5195 LOGFOCUS(("GenerateFocusActionId %" PRIu64
, id
));
5199 static bool IsInPointerLockContext(nsPIDOMWindowOuter
* aWin
) {
5200 return PointerLockManager::IsInLockContext(aWin
? aWin
->GetBrowsingContext()
5204 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter
* aWindow
,
5206 bool aSyncBrowsingContext
) {
5207 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker
&&
5208 IsInPointerLockContext(mFocusedWindow
) &&
5209 !IsInPointerLockContext(aWindow
)) {
5210 nsCOMPtr
<nsIRunnable
> runnable
= new PointerUnlocker();
5211 NS_DispatchToCurrentThread(runnable
);
5214 // Update the last focus time on any affected documents
5215 if (aWindow
&& aWindow
!= mFocusedWindow
) {
5216 const TimeStamp
now(TimeStamp::Now());
5217 for (Document
* doc
= aWindow
->GetExtantDoc(); doc
;
5218 doc
= doc
->GetInProcessParentDocument()) {
5219 doc
->SetLastFocusTime(now
);
5223 // This function may be called with zero action id to indicate that the
5224 // action id should be ignored.
5225 if (XRE_IsContentProcess() && aActionId
&&
5226 ActionIdComparableAndLower(aActionId
,
5227 mActionIdForFocusedBrowsingContextInContent
)) {
5228 // Unclear if this ever happens.
5230 ("Ignored an attempt to set an in-process BrowsingContext as "
5231 "focused due to stale action id %" PRIu64
".",
5236 mFocusedWindow
= aWindow
;
5237 BrowsingContext
* bc
= aWindow
? aWindow
->GetBrowsingContext() : nullptr;
5238 if (aSyncBrowsingContext
) {
5239 MOZ_ASSERT(aActionId
,
5240 "aActionId must not be zero if aSyncBrowsingContext is true");
5241 SetFocusedBrowsingContext(bc
, aActionId
);
5242 } else if (XRE_IsContentProcess()) {
5243 MOZ_ASSERT(mFocusedBrowsingContextInContent
== bc
,
5244 "Not syncing BrowsingContext even when different.");
5248 void nsFocusManager::NotifyOfReFocus(Element
& aElement
) {
5249 nsPIDOMWindowOuter
* window
= GetCurrentWindow(&aElement
);
5250 if (!window
|| window
!= mFocusedWindow
) {
5253 if (!aElement
.IsInComposedDoc() || IsNonFocusableRoot(&aElement
)) {
5256 nsIDocShell
* docShell
= window
->GetDocShell();
5260 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
5264 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
5268 IMEStateManager::OnReFocus(*presContext
, aElement
);
5271 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration
) {
5276 if (sInstance
->mActiveWindow
) {
5277 sInstance
->mActiveWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5279 if (sInstance
->mFocusedWindow
) {
5280 sInstance
->mFocusedWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5282 if (sInstance
->mWindowBeingLowered
) {
5283 sInstance
->mWindowBeingLowered
->MarkUncollectableForCCGeneration(
5286 if (sInstance
->mFocusedElement
) {
5287 sInstance
->mFocusedElement
->OwnerDoc()->MarkUncollectableForCCGeneration(
5290 if (sInstance
->mFirstBlurEvent
) {
5291 sInstance
->mFirstBlurEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5294 if (sInstance
->mFirstFocusEvent
) {
5295 sInstance
->mFirstFocusEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5300 bool nsFocusManager::CanSkipFocus(nsIContent
* aContent
) {
5305 if (mFocusedElement
== aContent
) {
5309 nsIDocShell
* ds
= aContent
->OwnerDoc()->GetDocShell();
5314 if (XRE_IsParentProcess()) {
5315 nsCOMPtr
<nsIDocShellTreeItem
> root
;
5316 ds
->GetInProcessRootTreeItem(getter_AddRefs(root
));
5317 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
=
5318 root
? root
->GetWindow() : nullptr;
5319 if (mActiveWindow
!= newRootWindow
) {
5320 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5321 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5326 BrowsingContext
* bc
= aContent
->OwnerDoc()->GetBrowsingContext();
5327 BrowsingContext
* top
= bc
? bc
->Top() : nullptr;
5328 if (GetActiveBrowsingContext() != top
) {
5329 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5330 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5340 Element
* nsFocusManager::GetTheFocusableArea(Element
* aTarget
,
5342 MOZ_ASSERT(aTarget
);
5343 nsIFrame
* frame
= aTarget
->GetPrimaryFrame();
5348 // If focus target is the document element of its Document.
5349 if (aTarget
== aTarget
->OwnerDoc()->GetRootElement()) {
5350 // the root content can always be focused,
5351 // except in userfocusignored context.
5355 // If focus target is an area element with one or more shapes that are
5357 if (aTarget
->IsHTMLElement(nsGkAtoms::area
)) {
5358 // HTML areas do not have their own frame, and the img frame we get from
5359 // GetPrimaryFrame() is not relevant as to whether it is focusable or
5360 // not, so we have to do all the relevant checks manually for them.
5361 return frame
->IsVisibleConsideringAncestors() && aTarget
->IsFocusable()
5366 // For these 3 steps mentioned in the spec
5367 // 1. If focus target is an element with one or more scrollable regions that
5368 // are focusable areas
5369 // 2. If focus target is a navigable
5370 // 3. If focus target is a navigable container with a non-null content
5372 // nsIFrame::IsFocusable will effectively perform the checks for them.
5373 if (frame
->IsFocusable(aFlags
& FLAG_BYMOUSE
)) {
5377 // If focus target is a shadow host whose shadow root's delegates focus is
5379 if (ShadowRoot
* root
= aTarget
->GetShadowRoot()) {
5380 if (root
->DelegatesFocus()) {
5381 // If focus target is a shadow-including inclusive ancestor of the
5382 // currently focused area of a top-level browsing context's DOM anchor,
5383 // then return the already-focused element.
5384 if (nsPIDOMWindowInner
* innerWindow
=
5385 aTarget
->OwnerDoc()->GetInnerWindow()) {
5386 if (Element
* focusedElement
= innerWindow
->GetFocusedElement()) {
5387 if (focusedElement
->IsShadowIncludingInclusiveDescendantOf(aTarget
)) {
5388 return focusedElement
;
5393 if (Element
* firstFocusable
=
5394 root
->GetFocusDelegate(aFlags
& FLAG_BYMOUSE
)) {
5395 return firstFocusable
;
5402 nsresult
NS_NewFocusManager(nsIFocusManager
** aResult
) {
5403 NS_IF_ADDREF(*aResult
= nsFocusManager::GetFocusManager());