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/FocusModel.h"
49 #include "mozilla/dom/ContentChild.h"
50 #include "mozilla/dom/Document.h"
51 #include "mozilla/dom/DocumentInlines.h"
52 #include "mozilla/dom/Element.h"
53 #include "mozilla/dom/ElementBinding.h"
54 #include "mozilla/dom/HTMLImageElement.h"
55 #include "mozilla/dom/HTMLInputElement.h"
56 #include "mozilla/dom/HTMLSlotElement.h"
57 #include "mozilla/dom/HTMLAreaElement.h"
58 #include "mozilla/dom/BrowserBridgeChild.h"
59 #include "mozilla/dom/Text.h"
60 #include "mozilla/dom/XULPopupElement.h"
61 #include "mozilla/dom/WindowGlobalParent.h"
62 #include "mozilla/dom/WindowGlobalChild.h"
63 #include "mozilla/EventDispatcher.h"
64 #include "mozilla/EventStateManager.h"
65 #include "mozilla/HTMLEditor.h"
66 #include "mozilla/IMEStateManager.h"
67 #include "mozilla/LookAndFeel.h"
68 #include "mozilla/Maybe.h"
69 #include "mozilla/PointerLockManager.h"
70 #include "mozilla/Preferences.h"
71 #include "mozilla/PresShell.h"
72 #include "mozilla/Services.h"
73 #include "mozilla/Unused.h"
74 #include "mozilla/StaticPrefs_full_screen_api.h"
75 #include "mozilla/Try.h"
76 #include "mozilla/widget/IMEData.h"
79 #include "nsIDOMXULMenuListElement.h"
82 # include "nsAccessibilityService.h"
85 using namespace mozilla
;
86 using namespace mozilla::dom
;
87 using namespace mozilla::widget
;
89 // Two types of focus pr logging are available:
90 // 'Focus' for normal focus manager calls
91 // 'FocusNavigation' for tab and document navigation
92 LazyLogModule
gFocusLog("Focus");
93 LazyLogModule
gFocusNavigationLog("FocusNavigation");
95 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
96 #define LOGFOCUSNAVIGATION(args) \
97 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
99 #define LOGTAG(log, format, content) \
100 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
101 nsAutoCString tag("(none)"_ns); \
103 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
105 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
108 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
109 #define LOGCONTENTNAVIGATION(format, content) \
110 LOGTAG(gFocusNavigationLog, format, content)
112 struct nsDelayedBlurOrFocusEvent
{
113 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage
, PresShell
* aPresShell
,
114 Document
* aDocument
, EventTarget
* aTarget
,
115 EventTarget
* aRelatedTarget
)
116 : mPresShell(aPresShell
),
117 mDocument(aDocument
),
119 mEventMessage(aEventMessage
),
120 mRelatedTarget(aRelatedTarget
) {}
122 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent
& aOther
)
123 : mPresShell(aOther
.mPresShell
),
124 mDocument(aOther
.mDocument
),
125 mTarget(aOther
.mTarget
),
126 mEventMessage(aOther
.mEventMessage
) {}
128 RefPtr
<PresShell
> mPresShell
;
129 nsCOMPtr
<Document
> mDocument
;
130 nsCOMPtr
<EventTarget
> mTarget
;
131 EventMessage mEventMessage
;
132 nsCOMPtr
<EventTarget
> mRelatedTarget
;
135 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent
& aField
) {
136 aField
.mPresShell
= nullptr;
137 aField
.mDocument
= nullptr;
138 aField
.mTarget
= nullptr;
139 aField
.mRelatedTarget
= nullptr;
142 inline void ImplCycleCollectionTraverse(
143 nsCycleCollectionTraversalCallback
& aCallback
,
144 nsDelayedBlurOrFocusEvent
& aField
, const char* aName
, uint32_t aFlags
= 0) {
145 CycleCollectionNoteChild(
146 aCallback
, static_cast<nsIDocumentObserver
*>(aField
.mPresShell
.get()),
148 CycleCollectionNoteChild(aCallback
, aField
.mDocument
.get(), aName
, aFlags
);
149 CycleCollectionNoteChild(aCallback
, aField
.mTarget
.get(), aName
, aFlags
);
150 CycleCollectionNoteChild(aCallback
, aField
.mRelatedTarget
.get(), aName
,
154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager
)
155 NS_INTERFACE_MAP_ENTRY(nsIFocusManager
)
156 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
157 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
158 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIFocusManager
)
161 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager
)
162 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager
)
164 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager
, mActiveWindow
,
165 mActiveBrowsingContextInContent
,
166 mActiveBrowsingContextInChrome
, mFocusedWindow
,
167 mFocusedBrowsingContextInContent
,
168 mFocusedBrowsingContextInChrome
, mFocusedElement
,
169 mFirstBlurEvent
, mFirstFocusEvent
,
170 mWindowBeingLowered
, mDelayedBlurFocusEvents
)
172 StaticRefPtr
<nsFocusManager
> nsFocusManager::sInstance
;
173 bool nsFocusManager::sTestMode
= false;
174 uint64_t nsFocusManager::sFocusActionCounter
= 0;
176 static const char* kObservedPrefs
[] = {"accessibility.browsewithcaret",
177 "focusmanager.testmode", nullptr};
179 nsFocusManager::nsFocusManager()
180 : mActionIdForActiveBrowsingContextInContent(0),
181 mActionIdForActiveBrowsingContextInChrome(0),
182 mActionIdForFocusedBrowsingContextInContent(0),
183 mActionIdForFocusedBrowsingContextInChrome(0),
184 mActiveBrowsingContextInContentSetFromOtherProcess(false),
185 mEventHandlingNeedsFlush(false) {}
187 nsFocusManager::~nsFocusManager() {
188 Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
191 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
193 obs
->RemoveObserver(this, "xpcom-shutdown");
198 nsresult
nsFocusManager::Init() {
199 sInstance
= new nsFocusManager();
201 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
203 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
206 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
208 obs
->AddObserver(sInstance
, "xpcom-shutdown", true);
215 void nsFocusManager::Shutdown() { sInstance
= nullptr; }
218 void nsFocusManager::PrefChanged(const char* aPref
, void* aSelf
) {
219 if (RefPtr
<nsFocusManager
> fm
= static_cast<nsFocusManager
*>(aSelf
)) {
220 fm
->PrefChanged(aPref
);
224 void nsFocusManager::PrefChanged(const char* aPref
) {
225 nsDependentCString
pref(aPref
);
226 if (pref
.EqualsLiteral("accessibility.browsewithcaret")) {
227 UpdateCaretForCaretBrowsingMode();
228 } else if (pref
.EqualsLiteral("focusmanager.testmode")) {
229 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
234 nsFocusManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
235 const char16_t
* aData
) {
236 if (!nsCRT::strcmp(aTopic
, "xpcom-shutdown")) {
237 mActiveWindow
= nullptr;
238 mActiveBrowsingContextInContent
= nullptr;
239 mActionIdForActiveBrowsingContextInContent
= 0;
240 mActionIdForFocusedBrowsingContextInContent
= 0;
241 mActiveBrowsingContextInChrome
= nullptr;
242 mActionIdForActiveBrowsingContextInChrome
= 0;
243 mActionIdForFocusedBrowsingContextInChrome
= 0;
244 mFocusedWindow
= nullptr;
245 mFocusedBrowsingContextInContent
= nullptr;
246 mFocusedBrowsingContextInChrome
= nullptr;
247 mFocusedElement
= nullptr;
248 mFirstBlurEvent
= nullptr;
249 mFirstFocusEvent
= nullptr;
250 mWindowBeingLowered
= nullptr;
251 mDelayedBlurFocusEvents
.Clear();
257 static bool ActionIdComparableAndLower(uint64_t aActionId
,
258 uint64_t aReference
) {
259 MOZ_ASSERT(aActionId
, "Uninitialized action id");
260 auto [actionProc
, actionId
] =
261 nsContentUtils::SplitProcessSpecificId(aActionId
);
262 auto [refProc
, refId
] = nsContentUtils::SplitProcessSpecificId(aReference
);
263 return actionProc
== refProc
&& actionId
< refId
;
266 // given a frame content node, retrieve the nsIDOMWindow displayed in it
267 static nsPIDOMWindowOuter
* GetContentWindow(nsIContent
* aContent
) {
268 Document
* doc
= aContent
->GetComposedDoc();
270 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
272 return subdoc
->GetWindow();
279 bool nsFocusManager::IsFocused(nsIContent
* aContent
) {
280 if (!aContent
|| !mFocusedElement
) {
283 return aContent
== mFocusedElement
;
286 bool nsFocusManager::IsTestMode() { return sTestMode
; }
288 bool nsFocusManager::IsInActiveWindow(BrowsingContext
* aBC
) const {
289 RefPtr
<BrowsingContext
> top
= aBC
->Top();
290 if (XRE_IsParentProcess()) {
291 top
= top
->Canonical()->TopCrossChromeBoundary();
293 return IsSameOrAncestor(top
, GetActiveBrowsingContext());
296 // get the current window for the given content node
297 static nsPIDOMWindowOuter
* GetCurrentWindow(nsIContent
* aContent
) {
298 Document
* doc
= aContent
->GetComposedDoc();
299 return doc
? doc
->GetWindow() : nullptr;
303 Element
* nsFocusManager::GetFocusedDescendant(
304 nsPIDOMWindowOuter
* aWindow
, SearchRange aSearchRange
,
305 nsPIDOMWindowOuter
** aFocusedWindow
) {
306 NS_ENSURE_TRUE(aWindow
, nullptr);
308 *aFocusedWindow
= nullptr;
310 Element
* currentElement
= nullptr;
311 nsPIDOMWindowOuter
* window
= aWindow
;
313 *aFocusedWindow
= window
;
314 currentElement
= window
->GetFocusedElement();
315 if (!currentElement
|| aSearchRange
== eOnlyCurrentWindow
) {
319 window
= GetContentWindow(currentElement
);
324 if (aSearchRange
== eIncludeAllDescendants
) {
328 MOZ_ASSERT(aSearchRange
== eIncludeVisibleDescendants
);
330 // If the child window doesn't have PresShell, it means the window is
332 nsIDocShell
* docShell
= window
->GetDocShell();
336 if (!docShell
->GetPresShell()) {
341 NS_IF_ADDREF(*aFocusedWindow
);
343 return currentElement
;
347 InputContextAction::Cause
nsFocusManager::GetFocusMoveActionCause(
349 if (aFlags
& nsIFocusManager::FLAG_BYTOUCH
) {
350 return InputContextAction::CAUSE_TOUCH
;
351 } else if (aFlags
& nsIFocusManager::FLAG_BYMOUSE
) {
352 return InputContextAction::CAUSE_MOUSE
;
353 } else if (aFlags
& nsIFocusManager::FLAG_BYKEY
) {
354 return InputContextAction::CAUSE_KEY
;
355 } else if (aFlags
& nsIFocusManager::FLAG_BYLONGPRESS
) {
356 return InputContextAction::CAUSE_LONGPRESS
;
358 return InputContextAction::CAUSE_UNKNOWN
;
362 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy
** aWindow
) {
363 MOZ_ASSERT(XRE_IsParentProcess(),
364 "Must not be called outside the parent process.");
365 NS_IF_ADDREF(*aWindow
= mActiveWindow
);
370 nsFocusManager::GetActiveBrowsingContext(BrowsingContext
** aBrowsingContext
) {
371 NS_IF_ADDREF(*aBrowsingContext
= GetActiveBrowsingContext());
375 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter
* aWindow
,
376 CallerType aCallerType
) {
377 if (RefPtr
<nsFocusManager
> fm
= sInstance
) {
378 fm
->SetFocusedWindowWithCallerType(aWindow
, aCallerType
);
383 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy
** aFocusedWindow
) {
384 NS_IF_ADDREF(*aFocusedWindow
= mFocusedWindow
);
389 nsFocusManager::GetFocusedContentBrowsingContext(
390 BrowsingContext
** aBrowsingContext
) {
391 MOZ_DIAGNOSTIC_ASSERT(
392 XRE_IsParentProcess(),
393 "We only have use cases for this in the parent process");
394 NS_IF_ADDREF(*aBrowsingContext
= GetFocusedBrowsingContextInChrome());
398 nsresult
nsFocusManager::SetFocusedWindowWithCallerType(
399 mozIDOMWindowProxy
* aWindowToFocus
, CallerType aCallerType
) {
400 LOGFOCUS(("<<SetFocusedWindow begin>>"));
402 nsCOMPtr
<nsPIDOMWindowOuter
> windowToFocus
=
403 nsPIDOMWindowOuter::From(aWindowToFocus
);
404 NS_ENSURE_TRUE(windowToFocus
, NS_ERROR_FAILURE
);
406 nsCOMPtr
<Element
> frameElement
= windowToFocus
->GetFrameElementInternal();
407 Maybe
<uint64_t> existingActionId
;
409 // pass false for aFocusChanged so that the caret does not get updated
410 // and scrolling does not occur.
411 existingActionId
= SetFocusInner(frameElement
, 0, false, true);
412 } else if (auto* bc
= windowToFocus
->GetBrowsingContext();
413 bc
&& !bc
->IsTop()) {
414 // No frameElement means windowToFocus is an OOP iframe, so
415 // the above SetFocusInner is not called. That means the focus
416 // of the currently focused BC is not going to be cleared. So
417 // we do that manually here.
418 if (RefPtr
<BrowsingContext
> focusedBC
= GetFocusedBrowsingContext()) {
419 // If focusedBC is an ancestor of bc, blur will be handled
420 // correctly by nsFocusManager::AdjustWindowFocus.
421 if (!IsSameOrAncestor(focusedBC
, bc
)) {
422 existingActionId
.emplace(sInstance
->GenerateFocusActionId());
423 Blur(focusedBC
, nullptr, true, true, false, existingActionId
.value());
427 // this is a top-level window. If the window has a child frame focused,
428 // clear the focus. Otherwise, focus should already be in this frame, or
429 // already cleared. This ensures that focus will be in this frame and not
431 nsIContent
* content
= windowToFocus
->GetFocusedElement();
433 if (nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= GetContentWindow(content
))
434 ClearFocus(windowToFocus
);
438 nsCOMPtr
<nsPIDOMWindowOuter
> rootWindow
= windowToFocus
->GetPrivateRoot();
439 const uint64_t actionId
= existingActionId
.isSome()
440 ? existingActionId
.value()
441 : sInstance
->GenerateFocusActionId();
443 RaiseWindow(rootWindow
, aCallerType
, actionId
);
446 LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64
">>", actionId
));
451 NS_IMETHODIMP
nsFocusManager::SetFocusedWindow(
452 mozIDOMWindowProxy
* aWindowToFocus
) {
453 return SetFocusedWindowWithCallerType(aWindowToFocus
, CallerType::System
);
457 nsFocusManager::GetFocusedElement(Element
** aFocusedElement
) {
458 RefPtr
<Element
> focusedElement
= mFocusedElement
;
459 focusedElement
.forget(aFocusedElement
);
463 uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter
* aWindow
) const {
464 nsPIDOMWindowOuter
* window
= aWindow
? aWindow
: mFocusedWindow
.get();
465 uint32_t method
= window
? window
->GetFocusMethod() : 0;
466 NS_ASSERTION((method
& METHOD_MASK
) == method
, "invalid focus method");
471 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy
* aWindow
,
472 uint32_t* aLastFocusMethod
) {
473 *aLastFocusMethod
= GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow
));
478 nsFocusManager::SetFocus(Element
* aElement
, uint32_t aFlags
) {
479 LOGFOCUS(("<<SetFocus begin>>"));
481 NS_ENSURE_ARG(aElement
);
483 SetFocusInner(aElement
, aFlags
, true, true);
485 LOGFOCUS(("<<SetFocus end>>"));
491 nsFocusManager::ElementIsFocusable(Element
* aElement
, uint32_t aFlags
,
492 bool* aIsFocusable
) {
493 NS_ENSURE_TRUE(aElement
, NS_ERROR_INVALID_ARG
);
494 *aIsFocusable
= !!FlushAndCheckIfFocusable(aElement
, aFlags
);
498 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
499 nsFocusManager::MoveFocus(mozIDOMWindowProxy
* aWindow
, Element
* aStartElement
,
500 uint32_t aType
, uint32_t aFlags
, Element
** aElement
) {
503 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType
, aFlags
));
505 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
) && mFocusedWindow
) {
506 Document
* doc
= mFocusedWindow
->GetExtantDoc();
507 if (doc
&& doc
->GetDocumentURI()) {
508 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow
.get(),
509 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
513 LOGCONTENT(" Current Focus: %s", mFocusedElement
.get());
515 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
516 // the other focus methods is already set, or we're just moving to the root
517 // or caret position.
518 if (aType
!= MOVEFOCUS_ROOT
&& aType
!= MOVEFOCUS_CARET
&&
519 (aFlags
& METHOD_MASK
) == 0) {
520 aFlags
|= FLAG_BYMOVEFOCUS
;
523 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
525 window
= GetCurrentWindow(aStartElement
);
527 window
= aWindow
? nsPIDOMWindowOuter::From(aWindow
) : mFocusedWindow
.get();
530 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
532 // Flush to ensure that focusability of descendants is computed correctly.
533 if (RefPtr
<Document
> doc
= window
->GetExtantDoc()) {
534 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
537 bool noParentTraversal
= aFlags
& FLAG_NOPARENTFRAME
;
538 nsCOMPtr
<nsIContent
> newFocus
;
539 nsresult rv
= DetermineElementToMoveFocus(window
, aStartElement
, aType
,
540 noParentTraversal
, true,
541 getter_AddRefs(newFocus
));
542 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
546 NS_ENSURE_SUCCESS(rv
, rv
);
548 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus
.get());
550 if (newFocus
&& newFocus
->IsElement()) {
551 // for caret movement, pass false for the aFocusChanged argument,
552 // otherwise the caret will end up moving to the focus position. This
553 // would be a problem because the caret would move to the beginning of the
554 // focused link making it impossible to navigate the caret over a link.
555 SetFocusInner(MOZ_KnownLive(newFocus
->AsElement()), aFlags
,
556 aType
!= MOVEFOCUS_CARET
, true);
557 *aElement
= do_AddRef(newFocus
->AsElement()).take();
558 } else if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_CARET
) {
559 // no content was found, so clear the focus for these two types.
563 LOGFOCUS(("<<MoveFocus end>>"));
569 nsFocusManager::ClearFocus(mozIDOMWindowProxy
* aWindow
) {
570 LOGFOCUS(("<<ClearFocus begin>>"));
572 // if the window to clear is the focused window or an ancestor of the
573 // focused window, then blur the existing focused content. Otherwise, the
574 // focus is somewhere else so just update the current node.
575 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
576 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
578 if (IsSameOrAncestor(window
, GetFocusedBrowsingContext())) {
579 RefPtr
<BrowsingContext
> bc
= window
->GetBrowsingContext();
580 bool isAncestor
= (GetFocusedBrowsingContext() != bc
);
581 uint64_t actionId
= GenerateFocusActionId();
582 if (Blur(bc
, nullptr, isAncestor
, true, false, actionId
)) {
583 // if we are clearing the focus on an ancestor of the focused window,
584 // the ancestor will become the new focused window, so focus it
586 Focus(window
, nullptr, 0, true, false, false, true, actionId
);
590 window
->SetFocusedElement(nullptr);
593 LOGFOCUS(("<<ClearFocus end>>"));
599 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy
* aWindow
,
601 mozIDOMWindowProxy
** aFocusedWindow
,
602 Element
** aElement
) {
604 if (aFocusedWindow
) {
605 *aFocusedWindow
= nullptr;
608 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
609 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
611 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
612 RefPtr
<Element
> focusedElement
=
613 GetFocusedDescendant(window
,
614 aDeep
? nsFocusManager::eIncludeAllDescendants
615 : nsFocusManager::eOnlyCurrentWindow
,
616 getter_AddRefs(focusedWindow
));
618 focusedElement
.forget(aElement
);
620 if (aFocusedWindow
) {
621 NS_IF_ADDREF(*aFocusedWindow
= focusedWindow
);
628 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy
* aWindow
) {
629 nsCOMPtr
<nsIWebNavigation
> webnav
= do_GetInterface(aWindow
);
630 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(webnav
);
632 if (dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
633 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(dsti
);
634 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
636 // don't move the caret for editable documents
638 docShell
->GetEditable(&isEditable
);
643 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
644 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
646 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
647 if (RefPtr
<Element
> focusedElement
= window
->GetFocusedElement()) {
648 MoveCaretToFocus(presShell
, focusedElement
);
656 void nsFocusManager::WindowRaised(mozIDOMWindowProxy
* aWindow
,
657 uint64_t aActionId
) {
662 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
663 BrowsingContext
* bc
= window
->GetBrowsingContext();
665 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
666 LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64
, aWindow
,
667 mActiveWindow
.get(), mFocusedWindow
.get(), aActionId
));
668 Document
* doc
= window
->GetExtantDoc();
669 if (doc
&& doc
->GetDocumentURI()) {
670 LOGFOCUS((" Raised Window: %p %s", aWindow
,
671 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
674 doc
= mActiveWindow
->GetExtantDoc();
675 if (doc
&& doc
->GetDocumentURI()) {
676 LOGFOCUS((" Active Window: %p %s", mActiveWindow
.get(),
677 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
682 if (XRE_IsParentProcess()) {
683 if (mActiveWindow
== window
) {
684 // The window is already active, so there is no need to focus anything,
685 // but make sure that the right widget is focused. This is a special case
686 // for Windows because when restoring a minimized window, a second
687 // activation will occur and the top-level widget could be focused instead
688 // of the child we want. We solve this by calling SetFocus to ensure that
689 // what the focus manager thinks should be the current widget is actually
691 EnsureCurrentWidgetFocused(CallerType::System
);
695 // lower the existing window, if any. This shouldn't happen usually.
696 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
) {
697 WindowLowered(activeWindow
, aActionId
);
699 } else if (bc
->IsTop()) {
700 BrowsingContext
* active
= GetActiveBrowsingContext();
701 if (active
== bc
&& !mActiveBrowsingContextInContentSetFromOtherProcess
) {
702 // EnsureCurrentWidgetFocused() should not be necessary with
707 if (active
&& active
!= bc
) {
708 if (active
->IsInProcess()) {
709 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= active
->GetDOMWindow();
710 WindowLowered(activeWindow
, aActionId
);
712 // No else, because trying to lower other-process windows
713 // from here can result in the BrowsingContext no longer
714 // existing in the parent process by the time it deserializes
719 nsCOMPtr
<nsIDocShellTreeItem
> docShellAsItem
= window
->GetDocShell();
720 // If there's no docShellAsItem, this window must have been closed,
721 // in that case there is no tree owner.
722 if (!docShellAsItem
) {
726 // set this as the active window
727 if (XRE_IsParentProcess()) {
728 mActiveWindow
= window
;
729 } else if (bc
->IsTop()) {
730 SetActiveBrowsingContextInContent(bc
, aActionId
);
733 // ensure that the window is enabled and visible
734 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
735 docShellAsItem
->GetTreeOwner(getter_AddRefs(treeOwner
));
736 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(treeOwner
);
738 bool isEnabled
= true;
739 if (NS_SUCCEEDED(baseWindow
->GetEnabled(&isEnabled
)) && !isEnabled
) {
743 baseWindow
->SetVisibility(true);
746 if (XRE_IsParentProcess()) {
747 // Unsetting top-level focus upon lowering was inhibited to accommodate
748 // ATOK, so we need to do it here.
749 BrowserParent::UnsetTopLevelWebFocusAll();
750 ActivateOrDeactivate(window
, true);
753 // retrieve the last focused element within the window that was raised
754 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
755 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
756 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
758 NS_ASSERTION(currentWindow
, "window raised with no window current");
759 if (!currentWindow
) {
763 nsCOMPtr
<nsIAppWindow
> appWin(do_GetInterface(baseWindow
));
764 // We use mFocusedWindow here is basically for the case that iframe navigate
765 // from a.com to b.com for example, so it ends up being loaded in a different
766 // process after Fission, but
767 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
768 // still be true because focused browsing context is synced, and we won't
769 // fire a focus event while focusing if we use it as condition.
770 Focus(currentWindow
, currentFocus
, 0, currentWindow
!= mFocusedWindow
, false,
771 appWin
!= nullptr, true, aActionId
);
774 void nsFocusManager::WindowLowered(mozIDOMWindowProxy
* aWindow
,
775 uint64_t aActionId
) {
780 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
782 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
783 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow
,
784 mActiveWindow
.get(), mFocusedWindow
.get()));
785 Document
* doc
= window
->GetExtantDoc();
786 if (doc
&& doc
->GetDocumentURI()) {
787 LOGFOCUS((" Lowered Window: %s",
788 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
791 doc
= mActiveWindow
->GetExtantDoc();
792 if (doc
&& doc
->GetDocumentURI()) {
793 LOGFOCUS((" Active Window: %s",
794 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
799 if (XRE_IsParentProcess()) {
800 if (mActiveWindow
!= window
) {
804 BrowsingContext
* bc
= window
->GetBrowsingContext();
805 BrowsingContext
* active
= GetActiveBrowsingContext();
806 if (active
!= bc
->Top()) {
811 // clear the mouse capture as the active window has changed
812 PresShell::ReleaseCapturingContent();
814 // In addition, reset the drag state to ensure that we are no longer in
816 if (mFocusedWindow
) {
817 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
819 if (PresShell
* presShell
= docShell
->GetPresShell()) {
820 RefPtr
<nsFrameSelection
> frameSelection
= presShell
->FrameSelection();
821 frameSelection
->SetDragState(false);
826 if (XRE_IsParentProcess()) {
827 ActivateOrDeactivate(window
, false);
830 // keep track of the window being lowered, so that attempts to raise the
831 // window can be prevented until we return. Otherwise, focus can get into
833 mWindowBeingLowered
= window
;
834 if (XRE_IsParentProcess()) {
835 mActiveWindow
= nullptr;
837 BrowsingContext
* bc
= window
->GetBrowsingContext();
838 if (bc
== bc
->Top()) {
839 SetActiveBrowsingContextInContent(nullptr, aActionId
);
843 if (mFocusedWindow
) {
844 Blur(nullptr, nullptr, true, true, false, aActionId
);
847 mWindowBeingLowered
= nullptr;
850 nsresult
nsFocusManager::ContentRemoved(Document
* aDocument
,
851 nsIContent
* aContent
) {
852 NS_ENSURE_ARG(aDocument
);
853 NS_ENSURE_ARG(aContent
);
855 nsPIDOMWindowOuter
* windowPtr
= aDocument
->GetWindow();
860 // if the content is currently focused in the window, or is an
861 // shadow-including inclusive ancestor of the currently focused element,
862 // reset the focus within that window.
863 Element
* previousFocusedElementPtr
= windowPtr
->GetFocusedElement();
864 if (!previousFocusedElementPtr
) {
868 if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
869 previousFocusedElementPtr
, aContent
)) {
873 RefPtr
<nsPIDOMWindowOuter
> window
= windowPtr
;
874 RefPtr
<Element
> previousFocusedElement
= previousFocusedElementPtr
;
876 RefPtr
<Element
> newFocusedElement
= [&]() -> Element
* {
877 if (auto* sr
= ShadowRoot::FromNode(aContent
)) {
878 if (sr
->IsUAWidget() && sr
->Host()->IsHTMLElement(nsGkAtoms::input
)) {
885 window
->SetFocusedElement(newFocusedElement
);
887 // if this window is currently focused, clear the global focused
888 // element as well, but don't fire any events.
889 if (window
->GetBrowsingContext() == GetFocusedBrowsingContext()) {
890 mFocusedElement
= newFocusedElement
;
891 } else if (Document
* subdoc
=
892 aDocument
->GetSubDocumentFor(previousFocusedElement
)) {
893 // Check if the node that was focused is an iframe or similar by looking if
894 // it has a subdocument. This would indicate that this focused iframe
895 // and its descendants will be going away. We will need to move the focus
896 // somewhere else, so just clear the focus in the toplevel window so that no
897 // element is focused.
899 // The Fission case is handled in FlushAndCheckIfFocusable().
900 if (nsCOMPtr
<nsIDocShell
> docShell
= subdoc
->GetDocShell()) {
901 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= docShell
->GetWindow();
903 IsSameOrAncestor(childWindow
, GetFocusedBrowsingContext())) {
904 if (XRE_IsParentProcess()) {
905 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
906 ClearFocus(activeWindow
);
908 BrowsingContext
* active
= GetActiveBrowsingContext();
910 if (active
->IsInProcess()) {
911 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
912 active
->GetDOMWindow();
913 ClearFocus(activeWindow
);
915 mozilla::dom::ContentChild
* contentChild
=
916 mozilla::dom::ContentChild::GetSingleton();
917 MOZ_ASSERT(contentChild
);
918 contentChild
->SendClearFocus(active
);
920 } // no else, because ClearFocus does nothing with nullptr
926 // Notify the editor in case we removed its ancestor limiter.
927 if (previousFocusedElement
->IsEditable()) {
928 if (nsCOMPtr
<nsIDocShell
> docShell
= aDocument
->GetDocShell()) {
929 if (RefPtr
<HTMLEditor
> htmlEditor
= docShell
->GetHTMLEditor()) {
930 RefPtr
<Selection
> selection
= htmlEditor
->GetSelection();
931 if (selection
&& selection
->GetFrameSelection() &&
932 previousFocusedElement
==
933 selection
->GetFrameSelection()->GetAncestorLimiter()) {
934 htmlEditor
->FinalizeSelection();
940 if (!newFocusedElement
) {
941 NotifyFocusStateChange(previousFocusedElement
, newFocusedElement
, 0,
942 /* aGettingFocus = */ false, false);
944 // We should already have the right state, which is managed by the <input>
946 MOZ_ASSERT(newFocusedElement
->State().HasState(ElementState::FOCUS
));
949 // If we changed focused element and the element still has focus, let's
950 // notify IME of focus. Note that if new focus move has already occurred
951 // by running script, we should not let IMEStateManager of outdated focus
953 if (mFocusedElement
== newFocusedElement
&& mFocusedWindow
== window
) {
954 RefPtr
<nsPresContext
> presContext(aDocument
->GetPresContext());
955 IMEStateManager::OnChangeFocus(presContext
, newFocusedElement
,
956 InputContextAction::Cause::CAUSE_UNKNOWN
);
962 void nsFocusManager::WindowShown(mozIDOMWindowProxy
* aWindow
,
968 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
970 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
971 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window
.get(),
972 mActiveWindow
.get(), mFocusedWindow
.get()));
973 Document
* doc
= window
->GetExtantDoc();
974 if (doc
&& doc
->GetDocumentURI()) {
975 LOGFOCUS(("Shown Window: %s",
976 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
979 if (mFocusedWindow
) {
980 doc
= mFocusedWindow
->GetExtantDoc();
981 if (doc
&& doc
->GetDocumentURI()) {
982 LOGFOCUS((" Focused Window: %s",
983 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
988 if (XRE_IsParentProcess()) {
989 if (BrowsingContext
* bc
= window
->GetBrowsingContext()) {
991 bc
->SetIsActiveBrowserWindow(bc
->GetIsActiveBrowserWindow());
996 if (XRE_IsParentProcess()) {
997 if (mFocusedWindow
!= window
) {
1001 BrowsingContext
* bc
= window
->GetBrowsingContext();
1002 if (!bc
|| mFocusedBrowsingContextInContent
!= bc
) {
1005 // Sync the window for a newly-created OOP iframe
1006 // Set actionId to zero to signify that it should be ignored.
1007 SetFocusedWindowInternal(window
, 0, false);
1011 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
1012 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
1013 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
1015 if (currentWindow
) {
1016 Focus(currentWindow
, currentFocus
, 0, true, false, false, true,
1017 GenerateFocusActionId());
1020 // Sometimes, an element in a window can be focused before the window is
1021 // visible, which would mean that the widget may not be properly focused.
1022 // When the window becomes visible, make sure the right widget is focused.
1023 EnsureCurrentWidgetFocused(CallerType::System
);
1027 void nsFocusManager::WindowHidden(mozIDOMWindowProxy
* aWindow
,
1028 uint64_t aActionId
) {
1029 // if there is no window or it is not the same or an ancestor of the
1030 // currently focused window, just return, as the current focus will not
1037 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
1039 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
1040 LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64
,
1041 window
.get(), mActiveWindow
.get(), mFocusedWindow
.get(),
1044 Document
* doc
= window
->GetExtantDoc();
1045 if (doc
&& doc
->GetDocumentURI()) {
1046 LOGFOCUS((" Hide Window: %s",
1047 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1050 if (mFocusedWindow
) {
1051 doc
= mFocusedWindow
->GetExtantDoc();
1052 if (doc
&& doc
->GetDocumentURI()) {
1053 LOGFOCUS((" Focused Window: %s",
1054 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1058 if (mActiveWindow
) {
1059 doc
= mActiveWindow
->GetExtantDoc();
1060 if (doc
&& doc
->GetDocumentURI()) {
1061 LOGFOCUS((" Active Window: %s",
1062 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1067 if (!IsSameOrAncestor(window
, mFocusedWindow
)) {
1071 // at this point, we know that the window being hidden is either the focused
1072 // window, or an ancestor of the focused window. Either way, the focus is no
1073 // longer valid, so it needs to be updated.
1075 const RefPtr
<Element
> oldFocusedElement
= std::move(mFocusedElement
);
1077 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
1078 if (!focusedDocShell
) {
1082 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
1084 if (oldFocusedElement
&& oldFocusedElement
->IsInComposedDoc()) {
1085 NotifyFocusStateChange(oldFocusedElement
, nullptr, 0, false, false);
1086 window
->UpdateCommands(u
"focus"_ns
);
1089 RefPtr
<Document
> composedDoc
= oldFocusedElement
->GetComposedDoc();
1090 SendFocusOrBlurEvent(eBlur
, presShell
, composedDoc
, oldFocusedElement
,
1095 const RefPtr
<nsPresContext
> focusedPresContext
=
1096 presShell
? presShell
->GetPresContext() : nullptr;
1097 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
1098 GetFocusMoveActionCause(0));
1100 SetCaretVisible(presShell
, false, nullptr);
1103 // If a window is being "hidden" because its BrowsingContext is changing
1104 // remoteness, we don't want to handle docshell destruction by moving focus.
1105 // Instead, the focused browsing context should stay the way it is (so that
1106 // the newly "shown" window in the other process knows to take focus) and
1107 // we should just null out the process-local field.
1108 nsCOMPtr
<nsIDocShell
> docShellBeingHidden
= window
->GetDocShell();
1109 // Check if we're currently hiding a non-remote nsDocShell due to its
1110 // BrowsingContext navigating to become remote. Normally, when a focused
1111 // subframe is hidden, focus is moved to the frame element, but focus should
1112 // stay with the BrowsingContext when performing a process switch. We don't
1113 // need to consider process switches where the hiding docshell is already
1114 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
1115 // frame element is handled elsewhere.
1116 if (docShellBeingHidden
&&
1117 nsDocShell::Cast(docShellBeingHidden
)->WillChangeProcess() &&
1118 docShellBeingHidden
->GetBrowsingContext()->GetEmbedderElement()) {
1119 if (mFocusedWindow
!= window
) {
1120 // The window being hidden is an ancestor of the focused window.
1122 BrowsingContext
* ancestor
= window
->GetBrowsingContext();
1123 BrowsingContext
* bc
= mFocusedWindow
->GetBrowsingContext();
1126 MOZ_ASSERT(false, "Should have found ancestor");
1128 bc
= bc
->GetParent();
1129 if (ancestor
== bc
) {
1134 // This call adjusts the focused browsing context and window.
1135 // The latter gets nulled out immediately below.
1136 SetFocusedWindowInternal(window
, aActionId
);
1138 mFocusedWindow
= nullptr;
1139 window
->SetFocusedElement(nullptr);
1143 // if the docshell being hidden is being destroyed, then we want to move
1144 // focus somewhere else. Call ClearFocus on the toplevel window, which
1145 // will have the effect of clearing the focus and moving the focused window
1146 // to the toplevel window. But if the window isn't being destroyed, we are
1147 // likely just loading a new document in it, so we want to maintain the
1148 // focused window so that the new document gets properly focused.
1149 bool beingDestroyed
= !docShellBeingHidden
;
1150 if (docShellBeingHidden
) {
1151 docShellBeingHidden
->IsBeingDestroyed(&beingDestroyed
);
1153 if (beingDestroyed
) {
1154 // There is usually no need to do anything if a toplevel window is going
1155 // away, as we assume that WindowLowered will be called. However, this may
1156 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1157 // a leak. So if the active window is being destroyed, call WindowLowered
1160 if (XRE_IsParentProcess()) {
1161 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
1162 if (activeWindow
== mFocusedWindow
|| activeWindow
== window
) {
1163 WindowLowered(activeWindow
, aActionId
);
1165 ClearFocus(activeWindow
);
1168 BrowsingContext
* active
= GetActiveBrowsingContext();
1170 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
1171 active
->GetDOMWindow()) {
1172 if ((mFocusedWindow
&&
1173 mFocusedWindow
->GetBrowsingContext() == active
) ||
1174 (window
->GetBrowsingContext() == active
)) {
1175 WindowLowered(activeWindow
, aActionId
);
1177 ClearFocus(activeWindow
);
1179 } // else do nothing when an out-of-process iframe is torn down
1185 if (!XRE_IsParentProcess() &&
1186 mActiveBrowsingContextInContent
==
1187 docShellBeingHidden
->GetBrowsingContext() &&
1188 mActiveBrowsingContextInContent
->GetIsInBFCache()) {
1189 SetActiveBrowsingContextInContent(nullptr, aActionId
);
1192 // if the window being hidden is an ancestor of the focused window, adjust
1193 // the focused window so that it points to the one being hidden. This
1194 // ensures that the focused window isn't in a chain of frames that doesn't
1196 if (window
!= mFocusedWindow
) {
1197 nsCOMPtr
<nsIDocShellTreeItem
> dsti
=
1198 mFocusedWindow
? mFocusedWindow
->GetDocShell() : nullptr;
1200 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1201 dsti
->GetInProcessParent(getter_AddRefs(parentDsti
));
1203 if (nsCOMPtr
<nsPIDOMWindowOuter
> parentWindow
=
1204 parentDsti
->GetWindow()) {
1205 parentWindow
->SetFocusedElement(nullptr);
1210 SetFocusedWindowInternal(window
, aActionId
);
1214 void nsFocusManager::FireDelayedEvents(Document
* aDocument
) {
1215 MOZ_ASSERT(aDocument
);
1217 // fire any delayed focus and blur events in the same order that they were
1219 for (uint32_t i
= 0; i
< mDelayedBlurFocusEvents
.Length(); i
++) {
1220 if (mDelayedBlurFocusEvents
[i
].mDocument
== aDocument
) {
1221 if (!aDocument
->GetInnerWindow() ||
1222 !aDocument
->GetInnerWindow()->IsCurrentInnerWindow()) {
1223 // If the document was navigated away from or is defunct, don't bother
1224 // firing events on it. Note the symmetry between this condition and
1225 // the similar one in Document.cpp:FireOrClearDelayedEvents.
1226 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1228 } else if (!aDocument
->EventHandlingSuppressed()) {
1229 EventMessage message
= mDelayedBlurFocusEvents
[i
].mEventMessage
;
1230 nsCOMPtr
<EventTarget
> target
= mDelayedBlurFocusEvents
[i
].mTarget
;
1231 RefPtr
<PresShell
> presShell
= mDelayedBlurFocusEvents
[i
].mPresShell
;
1232 nsCOMPtr
<EventTarget
> relatedTarget
=
1233 mDelayedBlurFocusEvents
[i
].mRelatedTarget
;
1234 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1236 FireFocusOrBlurEvent(message
, presShell
, target
, false, false,
1244 void nsFocusManager::WasNuked(nsPIDOMWindowOuter
* aWindow
) {
1245 MOZ_ASSERT(aWindow
, "Expected non-null window.");
1246 MOZ_ASSERT(aWindow
!= mActiveWindow
,
1247 "How come we're nuking a window that's still active?");
1248 if (aWindow
== mFocusedWindow
) {
1249 mFocusedWindow
= nullptr;
1250 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
1251 mFocusedElement
= nullptr;
1255 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element
& aElement
)
1256 : mElement(aElement
) {}
1258 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
1260 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
1261 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter
* aWindow
,
1262 const Element
& aElement
,
1263 int32_t aFocusFlags
) {
1264 // If we were explicitly requested to show the ring, do it.
1265 if (aFocusFlags
& nsIFocusManager::FLAG_SHOWRING
) {
1269 if (aFocusFlags
& nsIFocusManager::FLAG_NOSHOWRING
) {
1273 if (aWindow
->ShouldShowFocusRing()) {
1274 // The window decision also trumps any other heuristic.
1278 // Any element which supports keyboard input (such as an input element, or any
1279 // other element which may trigger a virtual keyboard to be shown on focus if
1280 // a physical keyboard is not present) should always match :focus-visible when
1283 if (aElement
.IsHTMLElement(nsGkAtoms::textarea
) || aElement
.IsEditable()) {
1287 if (auto* input
= HTMLInputElement::FromNode(aElement
)) {
1288 if (input
->IsSingleLineTextControl()) {
1294 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags
)) {
1295 case InputContextAction::CAUSE_KEY
:
1296 // If the user interacts with the page via the keyboard, the currently
1297 // focused element should match :focus-visible (i.e. keyboard usage may
1298 // change whether this pseudo-class matches even if it doesn't affect
1301 case InputContextAction::CAUSE_UNKNOWN
:
1302 // We render outlines if the last "known" focus method was by key or there
1303 // was no previous known focus method, otherwise we don't.
1304 return aWindow
->UnknownFocusMethodShouldShowOutline();
1305 case InputContextAction::CAUSE_MOUSE
:
1306 case InputContextAction::CAUSE_TOUCH
:
1307 case InputContextAction::CAUSE_LONGPRESS
:
1308 // If the user interacts with the page via a pointing device, such that
1309 // the focus is moved to a new element which does not support user input,
1310 // the newly focused element should not match :focus-visible.
1312 case InputContextAction::CAUSE_UNKNOWN_CHROME
:
1313 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT
:
1314 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT
:
1315 // TODO(emilio): We could return some of these though, looking at
1316 // UserActivation. We may want to suppress focus rings for unknown /
1317 // programatic focus if the user is interacting with the page but not
1318 // during keyboard input, or such.
1319 MOZ_ASSERT_UNREACHABLE(
1320 "These don't get returned by GetFocusMoveActionCause");
1327 void nsFocusManager::NotifyFocusStateChange(Element
* aElement
,
1328 Element
* aElementToFocus
,
1329 int32_t aFlags
, bool aGettingFocus
,
1330 bool aShouldShowFocusRing
) {
1331 MOZ_ASSERT_IF(aElementToFocus
, !aGettingFocus
);
1332 nsIContent
* commonAncestor
= nullptr;
1333 if (aElementToFocus
) {
1334 commonAncestor
= nsContentUtils::GetCommonFlattenedTreeAncestor(
1335 aElement
, aElementToFocus
);
1338 if (aGettingFocus
) {
1339 ElementState stateToAdd
= ElementState::FOCUS
;
1340 if (aShouldShowFocusRing
) {
1341 stateToAdd
|= ElementState::FOCUSRING
;
1343 aElement
->AddStates(stateToAdd
);
1345 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1346 host
= host
->GetContainingShadowHost()) {
1347 host
->AsElement()->AddStates(ElementState::FOCUS
);
1350 constexpr auto kStatesToRemove
=
1351 ElementState::FOCUS
| ElementState::FOCUSRING
;
1352 aElement
->RemoveStates(kStatesToRemove
);
1353 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1354 host
= host
->GetContainingShadowHost()) {
1355 host
->AsElement()->RemoveStates(kStatesToRemove
);
1359 // Special case for <input type="checkbox"> and <input type="radio">.
1360 // The other browsers cancel active state when they gets lost focus, but
1361 // does not do it for the other elements such as <button> and <a href="...">.
1362 // Additionally, they may be activated with <label>, but they will get focus
1363 // at `click`, but activated at `mousedown`. Therefore, we need to cancel
1364 // active state at moving focus.
1365 if (RefPtr
<nsPresContext
> presContext
=
1366 aElement
->GetPresContext(Element::PresContextFor::eForComposedDoc
)) {
1367 RefPtr
<EventStateManager
> esm
= presContext
->EventStateManager();
1368 auto* activeInputElement
=
1369 HTMLInputElement::FromNodeOrNull(esm
->GetActiveContent());
1370 if (activeInputElement
&&
1371 (activeInputElement
->ControlType() == FormControlType::InputCheckbox
||
1372 activeInputElement
->ControlType() == FormControlType::InputRadio
) &&
1373 !activeInputElement
->State().HasState(ElementState::FOCUS
)) {
1374 esm
->SetContentState(nullptr, ElementState::ACTIVE
);
1378 for (nsIContent
* content
= aElement
; content
&& content
!= commonAncestor
;
1379 content
= content
->GetFlattenedTreeParent()) {
1380 Element
* element
= Element::FromNode(content
);
1385 if (aGettingFocus
) {
1386 if (element
->State().HasState(ElementState::FOCUS_WITHIN
)) {
1389 element
->AddStates(ElementState::FOCUS_WITHIN
);
1391 element
->RemoveStates(ElementState::FOCUS_WITHIN
);
1397 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType
) {
1398 if (!mFocusedWindow
|| sTestMode
) return;
1400 // get the main child widget for the focused window and ensure that the
1401 // platform knows that this widget is focused.
1402 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
1406 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1410 nsViewManager
* vm
= presShell
->GetViewManager();
1414 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
1418 widget
->SetFocus(nsIWidget::Raise::No
, aCallerType
);
1421 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter
* aWindow
,
1423 MOZ_ASSERT(XRE_IsParentProcess());
1428 if (BrowsingContext
* bc
= aWindow
->GetBrowsingContext()) {
1429 MOZ_ASSERT(bc
->IsTop());
1431 RefPtr
<CanonicalBrowsingContext
> chromeTop
=
1432 bc
->Canonical()->TopCrossChromeBoundary();
1433 MOZ_ASSERT(bc
== chromeTop
);
1435 chromeTop
->SetIsActiveBrowserWindow(aActive
);
1436 chromeTop
->CallOnAllTopDescendants(
1437 [aActive
](CanonicalBrowsingContext
* aBrowsingContext
) {
1438 aBrowsingContext
->SetIsActiveBrowserWindow(aActive
);
1439 return CallState::Continue
;
1441 /* aIncludeNestedBrowsers = */ true);
1444 if (aWindow
->GetExtantDoc()) {
1445 nsContentUtils::DispatchEventOnlyToChrome(
1446 aWindow
->GetExtantDoc(),
1447 nsGlobalWindowInner::Cast(aWindow
->GetCurrentInnerWindow()),
1448 aActive
? u
"activate"_ns
: u
"deactivate"_ns
, CanBubble::eYes
,
1449 Cancelable::eYes
, nullptr);
1453 // Retrieves innerWindowId of the window of the last focused element to
1454 // log a warning to the website console.
1455 void LogWarningFullscreenWindowRaise(Element
* aElement
) {
1456 nsCOMPtr
<nsFrameLoaderOwner
> frameLoaderOwner(do_QueryInterface(aElement
));
1457 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1459 RefPtr
<nsFrameLoader
> frameLoader
= frameLoaderOwner
->GetFrameLoader();
1460 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1462 RefPtr
<BrowsingContext
> browsingContext
= frameLoader
->GetBrowsingContext();
1463 NS_ENSURE_TRUE_VOID(browsingContext
);
1465 WindowGlobalParent
* windowGlobalParent
=
1466 browsingContext
->Canonical()->GetCurrentWindowGlobal();
1467 NS_ENSURE_TRUE_VOID(windowGlobalParent
);
1470 nsAutoString localizedMsg
;
1471 nsTArray
<nsString
> params
;
1472 nsresult rv
= nsContentUtils::FormatLocalizedString(
1473 nsContentUtils::eDOM_PROPERTIES
, "FullscreenExitWindowFocus", params
,
1476 NS_ENSURE_SUCCESS_VOID(rv
);
1478 Unused
<< nsContentUtils::ReportToConsoleByWindowID(
1479 localizedMsg
, nsIScriptError::warningFlag
, "DOM"_ns
,
1480 windowGlobalParent
->InnerWindowId(),
1481 windowGlobalParent
->GetDocumentURI());
1484 // Ensure that when an embedded popup with a noautofocus attribute
1485 // like a date picker is opened and focused, the parent page does not blur
1486 static bool IsEmeddededInNoautofocusPopup(BrowsingContext
& aBc
) {
1487 auto* embedder
= aBc
.GetEmbedderElement();
1491 nsIFrame
* f
= embedder
->GetPrimaryFrame();
1492 if (!f
|| !f
->HasAnyStateBits(NS_FRAME_IN_POPUP
)) {
1496 nsIFrame
* menuPopup
=
1497 nsLayoutUtils::GetClosestFrameOfType(f
, LayoutFrameType::MenuPopup
);
1498 MOZ_ASSERT(menuPopup
, "NS_FRAME_IN_POPUP lied?");
1499 return static_cast<nsMenuPopupFrame
*>(menuPopup
)
1501 .GetXULBoolAttr(nsGkAtoms::noautofocus
);
1504 Maybe
<uint64_t> nsFocusManager::SetFocusInner(Element
* aNewContent
,
1507 bool aAdjustWidget
) {
1508 // if the element is not focusable, just return and leave the focus as is
1509 RefPtr
<Element
> elementToFocus
=
1510 FlushAndCheckIfFocusable(aNewContent
, aFlags
);
1511 if (!elementToFocus
) {
1515 const RefPtr
<BrowsingContext
> focusedBrowsingContext
=
1516 GetFocusedBrowsingContext();
1518 // check if the element to focus is a frame (iframe) containing a child
1519 // document. Frames are never directly focused; instead focusing a frame
1520 // means focus what is inside the frame. To do this, the descendant content
1521 // within the frame is retrieved and that will be focused instead.
1522 nsCOMPtr
<nsPIDOMWindowOuter
> newWindow
;
1523 nsCOMPtr
<nsPIDOMWindowOuter
> subWindow
= GetContentWindow(elementToFocus
);
1525 elementToFocus
= GetFocusedDescendant(subWindow
, eIncludeAllDescendants
,
1526 getter_AddRefs(newWindow
));
1528 // since a window is being refocused, clear aFocusChanged so that the
1529 // caret position isn't updated.
1530 aFocusChanged
= false;
1533 // unless it was set above, retrieve the window for the element to focus
1535 newWindow
= GetCurrentWindow(elementToFocus
);
1538 RefPtr
<BrowsingContext
> newBrowsingContext
;
1540 newBrowsingContext
= newWindow
->GetBrowsingContext();
1543 // if the element is already focused, just return. Note that this happens
1544 // after the frame check above so that we compare the element that will be
1545 // focused rather than the frame it is in.
1546 if (!newWindow
|| (newBrowsingContext
== GetFocusedBrowsingContext() &&
1547 elementToFocus
== mFocusedElement
)) {
1551 MOZ_ASSERT(newBrowsingContext
);
1553 BrowsingContext
* browsingContextToFocus
= newBrowsingContext
;
1554 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(elementToFocus
)) {
1555 // Only look at pre-existing browsing contexts. If this function is
1556 // called during reflow, calling GetBrowsingContext() could cause frame
1557 // loader initialization at a time when it isn't safe.
1558 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
1559 // If focus is already in the subtree rooted at bc, return early
1560 // to match the single-process focus semantics. Otherwise, we'd
1561 // blur and immediately refocus whatever is focused.
1562 BrowsingContext
* walk
= focusedBrowsingContext
;
1567 walk
= walk
->GetParent();
1569 browsingContextToFocus
= bc
;
1573 // don't allow focus to be placed in docshells or descendants of docshells
1574 // that are being destroyed. Also, ensure that the page hasn't been
1575 // unloaded. The prevents content from being refocused during an unload event.
1576 nsCOMPtr
<nsIDocShell
> newDocShell
= newWindow
->GetDocShell();
1577 nsCOMPtr
<nsIDocShell
> docShell
= newDocShell
;
1580 docShell
->GetIsInUnload(&inUnload
);
1585 bool beingDestroyed
;
1586 docShell
->IsBeingDestroyed(&beingDestroyed
);
1587 if (beingDestroyed
) {
1591 BrowsingContext
* bc
= docShell
->GetBrowsingContext();
1593 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1594 docShell
->GetInProcessParent(getter_AddRefs(parentDsti
));
1595 docShell
= do_QueryInterface(parentDsti
);
1596 if (!docShell
&& !XRE_IsParentProcess()) {
1597 // We don't have an in-process parent, but let's see if we have
1598 // an in-process ancestor or if an out-of-process ancestor
1601 bc
= bc
->GetParent();
1602 if (bc
&& bc
->IsDiscarded()) {
1605 } while (bc
&& !bc
->IsInProcess());
1607 docShell
= bc
->GetDocShell();
1614 bool focusMovesToDifferentBC
=
1615 (focusedBrowsingContext
!= browsingContextToFocus
);
1617 if (focusedBrowsingContext
&& focusMovesToDifferentBC
&&
1618 nsContentUtils::IsHandlingKeyBoardEvent() &&
1619 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1620 MOZ_ASSERT(browsingContextToFocus
,
1621 "BrowsingContext to focus should be non-null.");
1623 nsIPrincipal
* focusedPrincipal
= nullptr;
1624 nsIPrincipal
* newPrincipal
= nullptr;
1626 if (XRE_IsParentProcess()) {
1627 if (WindowGlobalParent
* focusedWindowGlobalParent
=
1628 focusedBrowsingContext
->Canonical()->GetCurrentWindowGlobal()) {
1629 focusedPrincipal
= focusedWindowGlobalParent
->DocumentPrincipal();
1632 if (WindowGlobalParent
* newWindowGlobalParent
=
1633 browsingContextToFocus
->Canonical()->GetCurrentWindowGlobal()) {
1634 newPrincipal
= newWindowGlobalParent
->DocumentPrincipal();
1636 } else if (focusedBrowsingContext
->IsInProcess() &&
1637 browsingContextToFocus
->IsInProcess()) {
1638 nsCOMPtr
<nsIScriptObjectPrincipal
> focused
=
1639 do_QueryInterface(focusedBrowsingContext
->GetDOMWindow());
1640 nsCOMPtr
<nsIScriptObjectPrincipal
> newFocus
=
1641 do_QueryInterface(browsingContextToFocus
->GetDOMWindow());
1642 MOZ_ASSERT(focused
&& newFocus
,
1643 "BrowsingContext should always have a window here.");
1644 focusedPrincipal
= focused
->GetPrincipal();
1645 newPrincipal
= newFocus
->GetPrincipal();
1648 if (!focusedPrincipal
|| !newPrincipal
) {
1652 if (!focusedPrincipal
->Subsumes(newPrincipal
)) {
1653 NS_WARNING("Not allowed to focus the new window!");
1658 // to check if the new element is in the active window, compare the
1659 // new root docshell for the new element with the active window's docshell.
1660 RefPtr
<BrowsingContext
> newRootBrowsingContext
= nullptr;
1661 bool isElementInActiveWindow
= false;
1662 if (XRE_IsParentProcess()) {
1663 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
= nullptr;
1664 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= newWindow
->GetDocShell();
1666 nsCOMPtr
<nsIDocShellTreeItem
> root
;
1667 dsti
->GetInProcessRootTreeItem(getter_AddRefs(root
));
1668 newRootWindow
= root
? root
->GetWindow() : nullptr;
1670 isElementInActiveWindow
=
1671 (mActiveWindow
&& newRootWindow
== mActiveWindow
);
1673 if (newRootWindow
) {
1674 newRootBrowsingContext
= newRootWindow
->GetBrowsingContext();
1677 // XXX This is wrong for `<iframe mozbrowser>` and for XUL
1678 // `<browser remote="true">`. See:
1679 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
1680 newRootBrowsingContext
= newBrowsingContext
->Top();
1681 // to check if the new element is in the active window, compare the
1682 // new root docshell for the new element with the active window's docshell.
1683 isElementInActiveWindow
=
1684 (GetActiveBrowsingContext() == newRootBrowsingContext
);
1687 // Exit fullscreen if a website focuses another window
1688 if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
1689 !isElementInActiveWindow
&& (aFlags
& FLAG_RAISE
)) {
1690 if (XRE_IsParentProcess()) {
1691 if (Document
* doc
= mActiveWindow
? mActiveWindow
->GetDoc() : nullptr) {
1692 Document::ClearPendingFullscreenRequests(doc
);
1693 if (doc
->GetFullscreenElement()) {
1694 LogWarningFullscreenWindowRaise(mFocusedElement
);
1695 Document::AsyncExitFullscreen(doc
);
1699 BrowsingContext
* activeBrowsingContext
= GetActiveBrowsingContext();
1700 if (activeBrowsingContext
) {
1701 nsIDocShell
* shell
= activeBrowsingContext
->GetDocShell();
1703 if (Document
* doc
= shell
->GetDocument()) {
1704 Document::ClearPendingFullscreenRequests(doc
);
1705 if (doc
->GetFullscreenElement()) {
1706 Document::AsyncExitFullscreen(doc
);
1710 mozilla::dom::ContentChild
* contentChild
=
1711 mozilla::dom::ContentChild::GetSingleton();
1712 MOZ_ASSERT(contentChild
);
1713 contentChild
->SendMaybeExitFullscreen(activeBrowsingContext
);
1719 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1720 // shifted away from the current element if the new shell to focus is
1721 // the same or an ancestor shell of the currently focused shell.
1722 bool allowFrameSwitch
= !(aFlags
& FLAG_NOSWITCHFRAME
) ||
1723 IsSameOrAncestor(newWindow
, focusedBrowsingContext
);
1725 // if the element is in the active window, frame switching is allowed and
1726 // the content is in a visible window, fire blur and focus events.
1727 bool sendFocusEvent
=
1728 isElementInActiveWindow
&& allowFrameSwitch
&& IsWindowVisible(newWindow
);
1730 // Don't allow to steal the focus from chrome nodes if the caller cannot
1732 if (sendFocusEvent
&& mFocusedElement
&&
1733 mFocusedElement
->OwnerDoc() != aNewContent
->OwnerDoc() &&
1734 mFocusedElement
->NodePrincipal()->IsSystemPrincipal() &&
1735 !nsContentUtils::LegacyIsCallerNativeCode() &&
1736 !nsContentUtils::CanCallerAccess(mFocusedElement
)) {
1737 sendFocusEvent
= false;
1740 LOGCONTENT("Shift Focus: %s", elementToFocus
.get());
1741 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1742 aFlags
, mFocusedWindow
.get(), newWindow
.get(),
1743 mFocusedElement
.get()));
1744 const uint64_t actionId
= GenerateFocusActionId();
1746 (" In Active Window: %d Moves to different BrowsingContext: %d "
1747 "SendFocus: %d actionid: %" PRIu64
,
1748 isElementInActiveWindow
, focusMovesToDifferentBC
, sendFocusEvent
,
1751 if (sendFocusEvent
) {
1752 Maybe
<BlurredElementInfo
> blurredInfo
;
1753 if (mFocusedElement
) {
1754 blurredInfo
.emplace(*mFocusedElement
);
1756 // return if blurring fails or the focus changes during the blur
1757 if (focusedBrowsingContext
) {
1758 // find the common ancestor of the currently focused window and the new
1759 // window. The ancestor will need to have its currently focused node
1760 // cleared once the document has been blurred. Otherwise, we'll be in a
1761 // state where a document is blurred yet the chain of windows above it
1762 // still points to that document.
1763 // For instance, in the following frame tree:
1767 // D is focused and we want to focus C. Once D has been blurred, we need
1768 // to clear out the focus in A, otherwise A would still maintain that B
1769 // was focused, and B that D was focused.
1770 RefPtr
<BrowsingContext
> commonAncestor
=
1771 focusMovesToDifferentBC
1772 ? GetCommonAncestor(newWindow
, focusedBrowsingContext
)
1775 const bool needToClearFocusedElement
= [&] {
1776 if (focusedBrowsingContext
->IsChrome()) {
1777 // Always reset focused element if focus is currently in chrome
1778 // window, unless we're moving focus to a popup.
1779 return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1781 if (focusedBrowsingContext
->Top() != browsingContextToFocus
->Top()) {
1782 // Only reset focused element if focus moves within the same top-level
1786 // XXX for the case that we try to focus an
1787 // already-focused-remote-frame, we would still send blur and focus
1788 // IPC to it, but they will not generate blur or focus event, we don't
1789 // want to reset activeElement on the remote frame.
1790 return focusMovesToDifferentBC
|| focusedBrowsingContext
->IsInProcess();
1793 const bool remainActive
=
1794 focusMovesToDifferentBC
&&
1795 IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1797 // TODO: MOZ_KnownLive is required due to bug 1770680
1798 if (!Blur(MOZ_KnownLive(needToClearFocusedElement
1799 ? focusedBrowsingContext
.get()
1801 commonAncestor
, focusMovesToDifferentBC
, aAdjustWidget
,
1802 remainActive
, actionId
, elementToFocus
)) {
1803 return Some(actionId
);
1807 Focus(newWindow
, elementToFocus
, aFlags
, focusMovesToDifferentBC
,
1808 aFocusChanged
, false, aAdjustWidget
, actionId
, blurredInfo
);
1810 // otherwise, for inactive windows and when the caller cannot steal the
1811 // focus, update the node in the window, and raise the window if desired.
1812 if (allowFrameSwitch
) {
1813 AdjustWindowFocus(newBrowsingContext
, true, IsWindowVisible(newWindow
),
1814 actionId
, false /* aShouldClearAncestorFocus */,
1815 nullptr /* aAncestorBrowsingContextToFocus */);
1818 // set the focus node and method as needed
1819 uint32_t focusMethod
=
1820 aFocusChanged
? aFlags
& METHODANDRING_MASK
1821 : newWindow
->GetFocusMethod() |
1822 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
1823 newWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
1824 if (aFocusChanged
) {
1825 if (nsCOMPtr
<nsIDocShell
> docShell
= newWindow
->GetDocShell()) {
1826 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1827 if (presShell
&& presShell
->DidInitialize()) {
1828 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
1833 // update the commands even when inactive so that the attributes for that
1834 // window are up to date.
1835 if (allowFrameSwitch
) {
1836 newWindow
->UpdateCommands(u
"focus"_ns
);
1839 if (aFlags
& FLAG_RAISE
) {
1840 if (newRootBrowsingContext
) {
1841 if (XRE_IsParentProcess() || newRootBrowsingContext
->IsInProcess()) {
1842 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
=
1843 newRootBrowsingContext
->GetDOMWindow();
1844 RaiseWindow(outerWindow
,
1845 aFlags
& FLAG_NONSYSTEMCALLER
? CallerType::NonSystem
1846 : CallerType::System
,
1849 mozilla::dom::ContentChild
* contentChild
=
1850 mozilla::dom::ContentChild::GetSingleton();
1851 MOZ_ASSERT(contentChild
);
1852 contentChild
->SendRaiseWindow(newRootBrowsingContext
,
1853 aFlags
& FLAG_NONSYSTEMCALLER
1854 ? CallerType::NonSystem
1855 : CallerType::System
,
1861 return Some(actionId
);
1864 static BrowsingContext
* GetParentIgnoreChromeBoundary(BrowsingContext
* aBC
) {
1865 // Chrome BrowsingContexts are only available in the parent process, so if
1866 // we're in a content process, we only worry about the context tree.
1867 if (XRE_IsParentProcess()) {
1868 return aBC
->Canonical()->GetParentCrossChromeBoundary();
1870 return aBC
->GetParent();
1873 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1874 BrowsingContext
* aContext
) const {
1875 if (!aPossibleAncestor
) {
1879 for (BrowsingContext
* bc
= aContext
; bc
;
1880 bc
= GetParentIgnoreChromeBoundary(bc
)) {
1881 if (bc
== aPossibleAncestor
) {
1889 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1890 nsPIDOMWindowOuter
* aWindow
) const {
1891 if (aWindow
&& aPossibleAncestor
) {
1892 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(),
1893 aWindow
->GetBrowsingContext());
1898 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1899 BrowsingContext
* aContext
) const {
1900 if (aPossibleAncestor
) {
1901 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(), aContext
);
1906 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1907 nsPIDOMWindowOuter
* aWindow
) const {
1909 return IsSameOrAncestor(aPossibleAncestor
, aWindow
->GetBrowsingContext());
1914 mozilla::dom::BrowsingContext
* nsFocusManager::GetCommonAncestor(
1915 nsPIDOMWindowOuter
* aWindow
, mozilla::dom::BrowsingContext
* aContext
) {
1916 NS_ENSURE_TRUE(aWindow
&& aContext
, nullptr);
1918 if (XRE_IsParentProcess()) {
1919 nsCOMPtr
<nsIDocShellTreeItem
> dsti1
= aWindow
->GetDocShell();
1920 NS_ENSURE_TRUE(dsti1
, nullptr);
1922 nsCOMPtr
<nsIDocShellTreeItem
> dsti2
= aContext
->GetDocShell();
1923 NS_ENSURE_TRUE(dsti2
, nullptr);
1925 AutoTArray
<nsIDocShellTreeItem
*, 30> parents1
, parents2
;
1927 parents1
.AppendElement(dsti1
);
1928 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti1
;
1929 dsti1
->GetInProcessParent(getter_AddRefs(parentDsti1
));
1930 dsti1
.swap(parentDsti1
);
1933 parents2
.AppendElement(dsti2
);
1934 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti2
;
1935 dsti2
->GetInProcessParent(getter_AddRefs(parentDsti2
));
1936 dsti2
.swap(parentDsti2
);
1939 uint32_t pos1
= parents1
.Length();
1940 uint32_t pos2
= parents2
.Length();
1941 nsIDocShellTreeItem
* parent
= nullptr;
1943 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1944 nsIDocShellTreeItem
* child1
= parents1
.ElementAt(--pos1
);
1945 nsIDocShellTreeItem
* child2
= parents2
.ElementAt(--pos2
);
1946 if (child1
!= child2
) {
1952 return parent
? parent
->GetBrowsingContext() : nullptr;
1955 BrowsingContext
* bc1
= aWindow
->GetBrowsingContext();
1956 NS_ENSURE_TRUE(bc1
, nullptr);
1958 BrowsingContext
* bc2
= aContext
;
1960 AutoTArray
<BrowsingContext
*, 30> parents1
, parents2
;
1962 parents1
.AppendElement(bc1
);
1963 bc1
= bc1
->GetParent();
1966 parents2
.AppendElement(bc2
);
1967 bc2
= bc2
->GetParent();
1970 uint32_t pos1
= parents1
.Length();
1971 uint32_t pos2
= parents2
.Length();
1972 BrowsingContext
* parent
= nullptr;
1974 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1975 BrowsingContext
* child1
= parents1
.ElementAt(--pos1
);
1976 BrowsingContext
* child2
= parents2
.ElementAt(--pos2
);
1977 if (child1
!= child2
) {
1986 bool nsFocusManager::AdjustInProcessWindowFocus(
1987 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
1988 uint64_t aActionId
, bool aShouldClearAncestorFocus
,
1989 BrowsingContext
* aAncestorBrowsingContextToFocus
) {
1990 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus
, aShouldClearAncestorFocus
);
1991 if (ActionIdComparableAndLower(aActionId
,
1992 mActionIdForFocusedBrowsingContextInContent
)) {
1994 ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as "
1995 "focused from another process due to stale action id %" PRIu64
".",
1996 aBrowsingContext
, aActionId
));
2000 BrowsingContext
* bc
= aBrowsingContext
;
2001 bool needToNotifyOtherProcess
= false;
2003 // get the containing <iframe> or equivalent element so that it can be
2005 nsCOMPtr
<Element
> frameElement
= bc
->GetEmbedderElement();
2006 BrowsingContext
* parent
= bc
->GetParent();
2007 if (!parent
&& XRE_IsParentProcess()) {
2008 CanonicalBrowsingContext
* canonical
= bc
->Canonical();
2009 RefPtr
<WindowGlobalParent
> embedder
=
2010 canonical
->GetEmbedderWindowGlobal();
2012 parent
= embedder
->BrowsingContext();
2019 if (!frameElement
&& XRE_IsContentProcess()) {
2020 needToNotifyOtherProcess
= true;
2024 nsCOMPtr
<nsPIDOMWindowOuter
> window
= bc
->GetDOMWindow();
2026 // if the parent window is visible but the original window was not, then we
2027 // have likely moved up and out from a hidden tab to the browser window, or
2028 // a similar such arrangement. Stop adjusting the current nodes.
2029 if (IsWindowVisible(window
) != aIsVisible
) {
2033 // When aCheckPermission is true, we should check whether the caller can
2034 // access the window or not. If it cannot access, we should stop the
2036 if (aCheckPermission
&& !nsContentUtils::LegacyIsCallerNativeCode() &&
2037 !nsContentUtils::CanCallerAccess(window
->GetCurrentInnerWindow())) {
2041 if (aShouldClearAncestorFocus
) {
2042 // This is the BrowsingContext that receives the focus, no need to clear
2043 // its focused element and the rest of the ancestors.
2044 if (window
->GetBrowsingContext() == aAncestorBrowsingContextToFocus
) {
2048 window
->SetFocusedElement(nullptr);
2052 if (frameElement
!= window
->GetFocusedElement()) {
2053 window
->SetFocusedElement(frameElement
);
2055 RefPtr
<nsFrameLoaderOwner
> loaderOwner
= do_QueryObject(frameElement
);
2056 MOZ_ASSERT(loaderOwner
);
2057 RefPtr
<nsFrameLoader
> loader
= loaderOwner
->GetFrameLoader();
2058 if (loader
&& loader
->IsRemoteFrame() &&
2059 GetFocusedBrowsingContext() == bc
) {
2060 Blur(nullptr, nullptr, true, true, false, aActionId
);
2064 return needToNotifyOtherProcess
;
2067 void nsFocusManager::AdjustWindowFocus(
2068 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
2069 uint64_t aActionId
, bool aShouldClearAncestorFocus
,
2070 BrowsingContext
* aAncestorBrowsingContextToFocus
) {
2071 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus
, aShouldClearAncestorFocus
);
2072 if (AdjustInProcessWindowFocus(aBrowsingContext
, aCheckPermission
, aIsVisible
,
2073 aActionId
, aShouldClearAncestorFocus
,
2074 aAncestorBrowsingContextToFocus
)) {
2075 // Some ancestors of aBrowsingContext isn't in this process, so notify other
2076 // processes to adjust their focused element.
2077 mozilla::dom::ContentChild
* contentChild
=
2078 mozilla::dom::ContentChild::GetSingleton();
2079 MOZ_ASSERT(contentChild
);
2080 contentChild
->SendAdjustWindowFocus(aBrowsingContext
, aIsVisible
, aActionId
,
2081 aShouldClearAncestorFocus
,
2082 aAncestorBrowsingContextToFocus
);
2086 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter
* aWindow
) {
2087 if (!aWindow
|| aWindow
->IsFrozen()) {
2091 // Check if the inner window is frozen as well. This can happen when a focus
2092 // change occurs while restoring a previous page.
2093 nsPIDOMWindowInner
* innerWindow
= aWindow
->GetCurrentInnerWindow();
2094 if (!innerWindow
|| innerWindow
->IsFrozen()) {
2098 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2099 nsCOMPtr
<nsIBaseWindow
> baseWin(do_QueryInterface(docShell
));
2104 bool visible
= false;
2105 baseWin
->GetVisibility(&visible
);
2109 bool nsFocusManager::IsNonFocusableRoot(nsIContent
* aContent
) {
2110 MOZ_ASSERT(aContent
, "aContent must not be NULL");
2111 MOZ_ASSERT(aContent
->IsInComposedDoc(), "aContent must be in a document");
2113 // If the uncomposed document of aContent is in designMode, the root element
2114 // is not focusable.
2115 // NOTE: Most elements whose uncomposed document is in design mode are not
2116 // focusable, just the document is focusable. However, if it's in a
2117 // shadow tree, it may be focus able even if the shadow host is in
2119 // Also, if aContent is not editable and it's not in designMode, it's not
2121 // And in userfocusignored context nothing is focusable.
2122 Document
* doc
= aContent
->GetComposedDoc();
2123 NS_ASSERTION(doc
, "aContent must have current document");
2124 return aContent
== doc
->GetRootElement() &&
2125 (aContent
->IsInDesignMode() || !aContent
->IsEditable());
2128 Element
* nsFocusManager::FlushAndCheckIfFocusable(Element
* aElement
,
2134 nsCOMPtr
<Document
> doc
= aElement
->GetComposedDoc();
2135 // can't focus elements that are not in documents
2137 LOGCONTENT("Cannot focus %s because content not in document", aElement
)
2141 // Make sure that our frames are up to date while ensuring the presshell is
2142 // also initialized in case we come from a script calling focus() early.
2143 mEventHandlingNeedsFlush
= false;
2144 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2146 PresShell
* presShell
= doc
->GetPresShell();
2151 // If this is an iframe that doesn't have an in-process subdocument, it is
2152 // either an OOP iframe or an in-process iframe without lazy about:blank
2153 // creation having taken place. In the OOP case, iframe is always focusable.
2154 // In the in-process case, create the initial about:blank for in-process
2155 // BrowsingContexts in order to have the `GetSubDocumentFor` call after this
2156 // block return something.
2158 // TODO(emilio): This block can probably go after bug 543435 lands.
2159 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2160 if (!aElement
->IsXULElement()) {
2161 // Only look at pre-existing browsing contexts. If this function is
2162 // called during reflow, calling GetBrowsingContext() could cause frame
2163 // loader initialization at a time when it isn't safe.
2164 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2165 // This call may create a contentViewer-created about:blank.
2166 // That's intentional, so we can move focus there.
2167 Unused
<< bc
->GetDocument();
2172 return GetTheFocusableArea(aElement
, aFlags
);
2175 bool nsFocusManager::Blur(BrowsingContext
* aBrowsingContextToClear
,
2176 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2177 bool aIsLeavingDocument
, bool aAdjustWidget
,
2178 bool aRemainActive
, uint64_t aActionId
,
2179 Element
* aElementToFocus
) {
2180 if (XRE_IsParentProcess()) {
2181 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2182 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2183 aElementToFocus
, aActionId
);
2185 mozilla::dom::ContentChild
* contentChild
=
2186 mozilla::dom::ContentChild::GetSingleton();
2187 MOZ_ASSERT(contentChild
);
2188 bool windowToClearHandled
= false;
2189 bool ancestorWindowToFocusHandled
= false;
2191 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2192 if (focusedBrowsingContext
&& focusedBrowsingContext
->IsDiscarded()) {
2193 focusedBrowsingContext
= nullptr;
2195 if (!focusedBrowsingContext
) {
2196 mFocusedElement
= nullptr;
2199 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsDiscarded()) {
2200 aBrowsingContextToClear
= nullptr;
2202 if (aAncestorBrowsingContextToFocus
&&
2203 aAncestorBrowsingContextToFocus
->IsDiscarded()) {
2204 aAncestorBrowsingContextToFocus
= nullptr;
2206 // XXX should more early returns from BlurImpl be hoisted here to avoid
2207 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
2208 // other processes when BlurImpl returns early in this process? Or should the
2209 // IPC messages for those be sent by BlurImpl itself, in which case they could
2211 if (focusedBrowsingContext
->IsInProcess()) {
2212 if (aBrowsingContextToClear
&& !aBrowsingContextToClear
->IsInProcess()) {
2213 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus
&&
2214 !aAncestorBrowsingContextToFocus
->IsInProcess()),
2215 "Both aBrowsingContextToClear and "
2216 "aAncestorBrowsingContextToFocus are "
2218 contentChild
->SendSetFocusedElement(aBrowsingContextToClear
, false);
2220 if (aAncestorBrowsingContextToFocus
&&
2221 !aAncestorBrowsingContextToFocus
->IsInProcess()) {
2222 contentChild
->SendSetFocusedElement(aAncestorBrowsingContextToFocus
,
2225 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2226 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2227 aElementToFocus
, aActionId
);
2229 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsInProcess()) {
2230 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2231 MOZ_ASSERT(windowToClear
);
2232 windowToClear
->SetFocusedElement(nullptr);
2233 windowToClearHandled
= true;
2235 if (aAncestorBrowsingContextToFocus
&&
2236 aAncestorBrowsingContextToFocus
->IsInProcess()) {
2237 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2238 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2239 MOZ_ASSERT(ancestorWindowToFocus
);
2240 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2241 ancestorWindowToFocusHandled
= true;
2243 // The expectation is that the blurring would eventually result in an IPC
2244 // message doing this anyway, but this doesn't happen if the focus is in OOP
2245 // iframe which won't try to bounce an IPC message to its parent frame.
2246 SetFocusedWindowInternal(nullptr, aActionId
);
2247 contentChild
->SendBlurToParent(
2248 focusedBrowsingContext
, aBrowsingContextToClear
,
2249 aAncestorBrowsingContextToFocus
, aIsLeavingDocument
, aAdjustWidget
,
2250 windowToClearHandled
, ancestorWindowToFocusHandled
, aActionId
);
2254 void nsFocusManager::BlurFromOtherProcess(
2255 mozilla::dom::BrowsingContext
* aFocusedBrowsingContext
,
2256 mozilla::dom::BrowsingContext
* aBrowsingContextToClear
,
2257 mozilla::dom::BrowsingContext
* aAncestorBrowsingContextToFocus
,
2258 bool aIsLeavingDocument
, bool aAdjustWidget
, uint64_t aActionId
) {
2259 if (aFocusedBrowsingContext
!= GetFocusedBrowsingContext()) {
2262 BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2263 aIsLeavingDocument
, aAdjustWidget
, /* aRemainActive = */ false,
2264 nullptr, aActionId
);
2267 bool nsFocusManager::BlurImpl(BrowsingContext
* aBrowsingContextToClear
,
2268 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2269 bool aIsLeavingDocument
, bool aAdjustWidget
,
2270 bool aRemainActive
, Element
* aElementToFocus
,
2271 uint64_t aActionId
) {
2272 LOGFOCUS(("<<Blur begin actionid: %" PRIu64
">>", aActionId
));
2274 // hold a reference to the focused content, which may be null
2275 RefPtr
<Element
> element
= mFocusedElement
;
2277 if (!element
->IsInComposedDoc()) {
2278 mFocusedElement
= nullptr;
2281 if (element
== mFirstBlurEvent
) {
2286 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2287 // hold a reference to the focused window
2288 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
2289 if (focusedBrowsingContext
) {
2290 window
= focusedBrowsingContext
->GetDOMWindow();
2293 mFocusedElement
= nullptr;
2297 nsCOMPtr
<nsIDocShell
> docShell
= window
->GetDocShell();
2299 if (XRE_IsContentProcess() &&
2300 ActionIdComparableAndLower(
2301 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2302 // Unclear if this ever happens.
2304 ("Ignored an attempt to null out focused BrowsingContext when "
2305 "docShell is null due to a stale action id %" PRIu64
".",
2310 mFocusedWindow
= nullptr;
2311 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2313 SetFocusedBrowsingContext(nullptr, aActionId
);
2314 mFocusedElement
= nullptr;
2318 // Keep a ref to presShell since dispatching the DOM event may cause
2319 // the document to be destroyed.
2320 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2322 if (XRE_IsContentProcess() &&
2323 ActionIdComparableAndLower(
2324 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2325 // Unclear if this ever happens.
2327 ("Ignored an attempt to null out focused BrowsingContext when "
2328 "presShell is null due to a stale action id %" PRIu64
".",
2332 mFocusedElement
= nullptr;
2333 mFocusedWindow
= nullptr;
2334 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2336 SetFocusedBrowsingContext(nullptr, aActionId
);
2340 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2341 if (!mFirstBlurEvent
) {
2342 ar
.emplace(mFirstBlurEvent
);
2343 mFirstBlurEvent
= element
;
2346 const RefPtr
<nsPresContext
> focusedPresContext
=
2347 GetActiveBrowsingContext() ? presShell
->GetPresContext() : nullptr;
2348 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
2349 GetFocusMoveActionCause(0));
2351 // now adjust the actual focus, by clearing the fields in the focus manager
2352 // and in the window.
2353 mFocusedElement
= nullptr;
2354 if (aBrowsingContextToClear
) {
2355 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2356 if (windowToClear
) {
2357 windowToClear
->SetFocusedElement(nullptr);
2361 LOGCONTENT("Element %s has been blurred", element
.get());
2363 // Don't fire blur event on the root content which isn't editable.
2364 bool sendBlurEvent
=
2365 element
&& element
->IsInComposedDoc() && !IsNonFocusableRoot(element
);
2367 if (sendBlurEvent
) {
2368 NotifyFocusStateChange(element
, aElementToFocus
, 0, false, false);
2371 if (!aRemainActive
) {
2372 bool windowBeingLowered
= !aBrowsingContextToClear
&&
2373 !aAncestorBrowsingContextToFocus
&&
2374 aIsLeavingDocument
&& aAdjustWidget
;
2375 // If the object being blurred is a remote browser, deactivate remote
2377 if (BrowserParent
* remote
= BrowserParent::GetFrom(element
)) {
2378 MOZ_ASSERT(XRE_IsParentProcess());
2379 // Let's deactivate all remote browsers.
2380 BrowsingContext
* topLevelBrowsingContext
= remote
->GetBrowsingContext();
2381 topLevelBrowsingContext
->PreOrderWalk([&](BrowsingContext
* aContext
) {
2382 if (WindowGlobalParent
* windowGlobalParent
=
2383 aContext
->Canonical()->GetCurrentWindowGlobal()) {
2384 if (RefPtr
<BrowserParent
> browserParent
=
2385 windowGlobalParent
->GetBrowserParent()) {
2386 browserParent
->Deactivate(windowBeingLowered
, aActionId
);
2388 ("%s remote browser deactivated %p, %d, actionid: %" PRIu64
,
2389 aContext
== topLevelBrowsingContext
? "Top-level"
2391 browserParent
.get(), windowBeingLowered
, aActionId
));
2397 // Same as above but for out-of-process iframes
2398 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(element
)) {
2399 bbc
->Deactivate(windowBeingLowered
, aActionId
);
2401 ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64
,
2402 bbc
, windowBeingLowered
, aActionId
));
2408 if (sendBlurEvent
) {
2409 // if there is an active window, update commands. If there isn't an active
2410 // window, then this was a blur caused by the active window being lowered,
2411 // so there is no need to update the commands
2412 if (GetActiveBrowsingContext()) {
2413 window
->UpdateCommands(u
"focus"_ns
);
2416 SendFocusOrBlurEvent(eBlur
, presShell
, element
->GetComposedDoc(), element
,
2417 false, false, aElementToFocus
);
2420 // if we are leaving the document or the window was lowered, make the caret
2422 if (aIsLeavingDocument
|| !GetActiveBrowsingContext()) {
2423 SetCaretVisible(presShell
, false, nullptr);
2426 RefPtr
<AccessibleCaretEventHub
> eventHub
=
2427 presShell
->GetAccessibleCaretEventHub();
2429 eventHub
->NotifyBlur(aIsLeavingDocument
|| !GetActiveBrowsingContext());
2432 // at this point, it is expected that this window will be still be
2433 // focused, but the focused element will be null, as it was cleared before
2434 // the event. If this isn't the case, then something else was focused during
2435 // the blur event above and we should just return. However, if
2436 // aIsLeavingDocument is set, a new document is desired, so make sure to
2437 // blur the document and window.
2438 if (GetFocusedBrowsingContext() != window
->GetBrowsingContext() ||
2439 (mFocusedElement
!= nullptr && !aIsLeavingDocument
)) {
2441 } else if (aIsLeavingDocument
) {
2442 window
->TakeFocus(false, 0);
2444 // clear the focus so that the ancestor frame hierarchy is in the correct
2445 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
2446 // focused at this point.
2447 if (aAncestorBrowsingContextToFocus
) {
2448 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2449 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2450 if (ancestorWindowToFocus
) {
2451 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2454 // When the focus of aBrowsingContextToClear is cleared, it should
2455 // also clear its ancestors's focus because ancestors should no longer
2456 // be considered aBrowsingContextToClear is focused.
2458 // We don't need to do this when aBrowsingContextToClear and
2459 // aAncestorBrowsingContextToFocus is equal because ancestors don't
2461 if (aBrowsingContextToClear
&&
2462 aBrowsingContextToClear
!= aAncestorBrowsingContextToFocus
) {
2464 aBrowsingContextToClear
, false,
2465 IsWindowVisible(aBrowsingContextToClear
->GetDOMWindow()), aActionId
,
2466 true /* aShouldClearAncestorFocus */,
2467 aAncestorBrowsingContextToFocus
);
2471 SetFocusedWindowInternal(nullptr, aActionId
);
2472 mFocusedElement
= nullptr;
2474 RefPtr
<Document
> doc
= window
->GetExtantDoc();
2476 SendFocusOrBlurEvent(eBlur
, presShell
, doc
, doc
, false);
2478 if (!GetFocusedBrowsingContext()) {
2479 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2480 window
->GetCurrentInnerWindow();
2481 // MOZ_KnownLive due to bug 1506441
2482 SendFocusOrBlurEvent(
2483 eBlur
, presShell
, doc
,
2484 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), false);
2487 // check if a different window was focused
2488 result
= (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
2489 } else if (GetActiveBrowsingContext()) {
2490 // Otherwise, the blur of the element without blurring the document
2491 // occurred normally. Call UpdateCaret to redisplay the caret at the right
2492 // location within the document. This is needed to ensure that the caret
2493 // used for caret browsing is made visible again when an input field is
2495 UpdateCaret(false, true, nullptr);
2501 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element
& aElement
,
2502 uint64_t aActionId
) {
2503 if (BrowserParent
* remote
= BrowserParent::GetFrom(&aElement
)) {
2504 remote
->Activate(aActionId
);
2506 ("Remote browser activated %p, actionid: %" PRIu64
, remote
, aActionId
));
2509 // Same as above but for out-of-process iframes
2510 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(&aElement
)) {
2511 bbc
->Activate(aActionId
);
2512 LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64
, bbc
,
2517 void nsFocusManager::Focus(
2518 nsPIDOMWindowOuter
* aWindow
, Element
* aElement
, uint32_t aFlags
,
2519 bool aIsNewDocument
, bool aFocusChanged
, bool aWindowRaised
,
2520 bool aAdjustWidget
, uint64_t aActionId
,
2521 const Maybe
<BlurredElementInfo
>& aBlurredElementInfo
) {
2522 LOGFOCUS(("<<Focus begin actionid: %" PRIu64
">>", aActionId
));
2529 (aElement
== mFirstFocusEvent
|| aElement
== mFirstBlurEvent
)) {
2533 // Keep a reference to the presShell since dispatching the DOM event may
2534 // cause the document to be destroyed.
2535 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2540 const RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2545 bool focusInOtherContentProcess
= false;
2546 // Keep mochitest-browser-chrome harness happy by ignoring
2547 // focusInOtherContentProcess in the chrome process, because the harness
2549 if (!XRE_IsParentProcess()) {
2550 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2551 // Only look at pre-existing browsing contexts. If this function is
2552 // called during reflow, calling GetBrowsingContext() could cause frame
2553 // loader initialization at a time when it isn't safe.
2554 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2555 focusInOtherContentProcess
= !bc
->IsInProcess();
2559 if (ActionIdComparableAndLower(
2560 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2561 // Unclear if this ever happens.
2563 ("Ignored an attempt to focus an element due to stale action id "
2570 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
2571 // Otherwise, just get the current focus method and use that. This ensures
2572 // that the method is set during the document and window focus events.
2573 uint32_t focusMethod
= aFocusChanged
2574 ? aFlags
& METHODANDRING_MASK
2575 : aWindow
->GetFocusMethod() |
2576 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
2578 if (!IsWindowVisible(aWindow
)) {
2579 // if the window isn't visible, for instance because it is a hidden tab,
2580 // update the current focus and scroll it into view but don't do anything
2582 if (RefPtr elementToFocus
= FlushAndCheckIfFocusable(aElement
, aFlags
)) {
2583 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
2584 if (aFocusChanged
) {
2585 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2591 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2592 if (!mFirstFocusEvent
) {
2593 ar
.emplace(mFirstFocusEvent
);
2594 mFirstFocusEvent
= aElement
;
2597 LOGCONTENT("Element %s has been focused", aElement
);
2599 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
2600 Document
* docm
= aWindow
->GetExtantDoc();
2602 LOGCONTENT(" from %s", docm
->GetRootElement());
2605 (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64
2607 aIsNewDocument
, aFocusChanged
, aWindowRaised
, aFlags
, aActionId
));
2610 if (aIsNewDocument
) {
2611 // if this is a new document, update the parent chain of frames so that
2612 // focus can be traversed from the top level down to the newly focused
2614 RefPtr
<BrowsingContext
> bc
= aWindow
->GetBrowsingContext();
2615 AdjustWindowFocus(bc
, false, IsWindowVisible(aWindow
), aActionId
,
2616 false /* aShouldClearAncestorFocus */,
2617 nullptr /* aAncestorBrowsingContextToFocus */);
2620 // indicate that the window has taken focus.
2621 if (aWindow
->TakeFocus(true, focusMethod
)) {
2622 aIsNewDocument
= true;
2625 SetFocusedWindowInternal(aWindow
, aActionId
);
2627 if (aAdjustWidget
&& !sTestMode
) {
2628 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
2629 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
2631 widget
->SetFocus(nsIWidget::Raise::No
, aFlags
& FLAG_NONSYSTEMCALLER
2632 ? CallerType::NonSystem
2633 : CallerType::System
);
2637 // if switching to a new document, first fire the focus event on the
2638 // document and then the window.
2639 if (aIsNewDocument
) {
2640 RefPtr
<Document
> doc
= aWindow
->GetExtantDoc();
2641 // The focus change should be notified to IMEStateManager from here if:
2642 // * the focused element is in design mode or
2643 // * nobody gets focus and the document is in design mode
2644 // since any element whose uncomposed document is in design mode won't
2645 // receive focus event.
2646 if (doc
&& ((aElement
&& aElement
->IsInDesignMode()) ||
2647 (!aElement
&& doc
->IsInDesignMode()))) {
2648 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2649 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2650 GetFocusMoveActionCause(aFlags
));
2652 if (doc
&& !focusInOtherContentProcess
) {
2653 SendFocusOrBlurEvent(eFocus
, presShell
, doc
, doc
, aWindowRaised
);
2655 if (GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext() &&
2656 !mFocusedElement
&& !focusInOtherContentProcess
) {
2657 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2658 aWindow
->GetCurrentInnerWindow();
2659 // MOZ_KnownLive due to bug 1506441
2660 SendFocusOrBlurEvent(
2661 eFocus
, presShell
, doc
,
2662 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), aWindowRaised
);
2666 // check to ensure that the element is still focusable, and that nothing
2667 // else was focused during the events above.
2668 // Note that the focusing element may have already been moved to another
2669 // document/window. In that case, we should stop setting focus to it
2670 // because setting focus to the new window would cause redirecting focus
2672 RefPtr elementToFocus
=
2673 aElement
&& aElement
->IsInComposedDoc() &&
2674 aElement
->GetComposedDoc() == aWindow
->GetExtantDoc()
2675 ? FlushAndCheckIfFocusable(aElement
, aFlags
)
2677 if (elementToFocus
&& !mFocusedElement
&&
2678 GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext()) {
2679 mFocusedElement
= elementToFocus
;
2681 nsIContent
* focusedNode
= aWindow
->GetFocusedElement();
2682 const bool sendFocusEvent
= elementToFocus
->IsInComposedDoc() &&
2683 !IsNonFocusableRoot(elementToFocus
);
2684 const bool isRefocus
= focusedNode
&& focusedNode
== elementToFocus
;
2685 const bool shouldShowFocusRing
=
2687 ShouldMatchFocusVisible(aWindow
, *elementToFocus
, aFlags
);
2689 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
, false);
2691 const RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2692 if (sendFocusEvent
) {
2693 NotifyFocusStateChange(elementToFocus
, nullptr, aFlags
,
2694 /* aGettingFocus = */ true, shouldShowFocusRing
);
2696 // If this is a remote browser, focus its widget and activate remote
2697 // content. Note that we might no longer be in the same document,
2698 // due to the events we fired above when aIsNewDocument.
2699 if (presShell
->GetDocument() == elementToFocus
->GetComposedDoc()) {
2700 ActivateRemoteFrameIfNeeded(*elementToFocus
, aActionId
);
2703 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2704 GetFocusMoveActionCause(aFlags
));
2706 // as long as this focus wasn't because a window was raised, update the
2708 // XXXndeakin P2 someone could adjust the focus during the update
2709 if (!aWindowRaised
) {
2710 aWindow
->UpdateCommands(u
"focus"_ns
);
2713 // If the focused element changed, scroll it into view
2714 if (aFocusChanged
) {
2715 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2718 if (!focusInOtherContentProcess
) {
2719 RefPtr
<Document
> composedDocument
= elementToFocus
->GetComposedDoc();
2720 RefPtr
<Element
> relatedTargetElement
=
2721 aBlurredElementInfo
? aBlurredElementInfo
->mElement
.get() : nullptr;
2722 SendFocusOrBlurEvent(eFocus
, presShell
, composedDocument
,
2723 elementToFocus
, aWindowRaised
, isRefocus
,
2724 relatedTargetElement
);
2727 // We should notify IMEStateManager of actual focused element even if it
2728 // won't get focus event because the other IMEStateManager users do not
2729 // want to depend on this check, but IMEStateManager wants to verify
2730 // passed focused element for avoidng to overrride nested calls.
2731 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2732 GetFocusMoveActionCause(aFlags
));
2733 if (!aWindowRaised
) {
2734 aWindow
->UpdateCommands(u
"focus"_ns
);
2736 if (aFocusChanged
) {
2737 // If the focused element changed, scroll it into view
2738 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2742 if (!mFocusedElement
&& mFocusedWindow
== aWindow
) {
2743 // When there is no focused element, IMEStateManager needs to adjust IME
2744 // enabled state with the document.
2745 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2746 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2747 GetFocusMoveActionCause(aFlags
));
2750 if (!aWindowRaised
) {
2751 aWindow
->UpdateCommands(u
"focus"_ns
);
2755 // update the caret visibility and position to match the newly focused
2756 // element. However, don't update the position if this was a focus due to a
2757 // mouse click as the selection code would already have moved the caret as
2758 // needed. If this is a different document than was focused before, also
2759 // update the caret's visibility. If this is the same document, the caret
2760 // visibility should be the same as before so there is no need to update it.
2761 if (mFocusedElement
== elementToFocus
) {
2762 RefPtr
<Element
> focusedElement
= mFocusedElement
;
2763 UpdateCaret(aFocusChanged
&& !(aFlags
& FLAG_BYMOUSE
), aIsNewDocument
,
2768 class FocusBlurEvent
: public Runnable
{
2770 FocusBlurEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2771 nsPresContext
* aContext
, bool aWindowRaised
, bool aIsRefocus
,
2772 EventTarget
* aRelatedTarget
)
2773 : mozilla::Runnable("FocusBlurEvent"),
2776 mEventMessage(aEventMessage
),
2777 mWindowRaised(aWindowRaised
),
2778 mIsRefocus(aIsRefocus
),
2779 mRelatedTarget(aRelatedTarget
) {}
2781 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2782 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2783 InternalFocusEvent
event(true, mEventMessage
);
2784 event
.mFlags
.mBubbles
= false;
2785 event
.mFlags
.mCancelable
= false;
2786 event
.mFromRaise
= mWindowRaised
;
2787 event
.mIsRefocus
= mIsRefocus
;
2788 event
.mRelatedTarget
= mRelatedTarget
;
2789 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2792 const nsCOMPtr
<EventTarget
> mTarget
;
2793 const RefPtr
<nsPresContext
> mContext
;
2794 EventMessage mEventMessage
;
2797 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2800 class FocusInOutEvent
: public Runnable
{
2802 FocusInOutEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2803 nsPresContext
* aContext
,
2804 nsPIDOMWindowOuter
* aOriginalFocusedWindow
,
2805 nsIContent
* aOriginalFocusedContent
,
2806 EventTarget
* aRelatedTarget
)
2807 : mozilla::Runnable("FocusInOutEvent"),
2810 mEventMessage(aEventMessage
),
2811 mOriginalFocusedWindow(aOriginalFocusedWindow
),
2812 mOriginalFocusedContent(aOriginalFocusedContent
),
2813 mRelatedTarget(aRelatedTarget
) {}
2815 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2816 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2817 nsCOMPtr
<nsIContent
> originalWindowFocus
=
2818 mOriginalFocusedWindow
? mOriginalFocusedWindow
->GetFocusedElement()
2820 // Blink does not check that focus is the same after blur, but WebKit does.
2821 // Opt to follow Blink's behavior (see bug 687787).
2822 if (mEventMessage
== eFocusOut
||
2823 originalWindowFocus
== mOriginalFocusedContent
) {
2824 InternalFocusEvent
event(true, mEventMessage
);
2825 event
.mFlags
.mBubbles
= true;
2826 event
.mFlags
.mCancelable
= false;
2827 event
.mRelatedTarget
= mRelatedTarget
;
2828 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2833 const nsCOMPtr
<EventTarget
> mTarget
;
2834 const RefPtr
<nsPresContext
> mContext
;
2835 EventMessage mEventMessage
;
2836 nsCOMPtr
<nsPIDOMWindowOuter
> mOriginalFocusedWindow
;
2837 nsCOMPtr
<nsIContent
> mOriginalFocusedContent
;
2838 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2841 static Document
* GetDocumentHelper(EventTarget
* aTarget
) {
2845 if (const nsINode
* node
= nsINode::FromEventTarget(aTarget
)) {
2846 return node
->OwnerDoc();
2848 nsPIDOMWindowInner
* win
= nsPIDOMWindowInner::FromEventTarget(aTarget
);
2849 return win
? win
->GetExtantDoc() : nullptr;
2852 void nsFocusManager::FireFocusInOrOutEvent(
2853 EventMessage aEventMessage
, PresShell
* aPresShell
, EventTarget
* aTarget
,
2854 nsPIDOMWindowOuter
* aCurrentFocusedWindow
,
2855 nsIContent
* aCurrentFocusedContent
, EventTarget
* aRelatedTarget
) {
2856 NS_ASSERTION(aEventMessage
== eFocusIn
|| aEventMessage
== eFocusOut
,
2857 "Wrong event type for FireFocusInOrOutEvent");
2859 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2860 aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2861 aCurrentFocusedWindow
, aCurrentFocusedContent
, aRelatedTarget
));
2864 void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage
,
2865 PresShell
* aPresShell
,
2866 Document
* aDocument
,
2867 EventTarget
* aTarget
,
2868 bool aWindowRaised
, bool aIsRefocus
,
2869 EventTarget
* aRelatedTarget
) {
2870 NS_ASSERTION(aEventMessage
== eFocus
|| aEventMessage
== eBlur
,
2871 "Wrong event type for SendFocusOrBlurEvent");
2873 nsCOMPtr
<Document
> eventTargetDoc
= GetDocumentHelper(aTarget
);
2874 nsCOMPtr
<Document
> relatedTargetDoc
= GetDocumentHelper(aRelatedTarget
);
2876 // set aRelatedTarget to null if it's not in the same document as aTarget
2877 if (eventTargetDoc
!= relatedTargetDoc
) {
2878 aRelatedTarget
= nullptr;
2881 if (aDocument
&& aDocument
->EventHandlingSuppressed()) {
2882 // if this event was already queued, remove it and append it to the end
2883 mDelayedBlurFocusEvents
.RemoveElementsBy([&](const auto& event
) {
2884 return event
.mEventMessage
== aEventMessage
&&
2885 event
.mPresShell
== aPresShell
&& event
.mDocument
== aDocument
&&
2886 event
.mTarget
== aTarget
&& event
.mRelatedTarget
== aRelatedTarget
;
2889 mDelayedBlurFocusEvents
.EmplaceBack(aEventMessage
, aPresShell
, aDocument
,
2890 aTarget
, aRelatedTarget
);
2894 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2895 // that belongs to this doc, if yes, fire them first.
2896 if (aDocument
&& !aDocument
->EventHandlingSuppressed() &&
2897 mDelayedBlurFocusEvents
.Length()) {
2898 FireDelayedEvents(aDocument
);
2901 FireFocusOrBlurEvent(aEventMessage
, aPresShell
, aTarget
, aWindowRaised
,
2902 aIsRefocus
, aRelatedTarget
);
2905 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage
,
2906 PresShell
* aPresShell
,
2907 EventTarget
* aTarget
,
2908 bool aWindowRaised
, bool aIsRefocus
,
2909 EventTarget
* aRelatedTarget
) {
2910 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
= mFocusedWindow
;
2911 nsCOMPtr
<nsPIDOMWindowInner
> targetWindow
= do_QueryInterface(aTarget
);
2912 nsCOMPtr
<Document
> targetDocument
= do_QueryInterface(aTarget
);
2913 nsCOMPtr
<nsIContent
> currentFocusedContent
=
2914 currentWindow
? currentWindow
->GetFocusedElement() : nullptr;
2916 #ifdef ACCESSIBILITY
2917 nsAccessibilityService
* accService
= GetAccService();
2919 if (aEventMessage
== eFocus
) {
2920 accService
->NotifyOfDOMFocus(aTarget
);
2922 accService
->NotifyOfDOMBlur(aTarget
);
2927 aPresShell
->ScheduleContentRelevancyUpdate(
2928 ContentRelevancyReason::FocusInSubtree
);
2930 nsContentUtils::AddScriptRunner(
2931 new FocusBlurEvent(aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2932 aWindowRaised
, aIsRefocus
, aRelatedTarget
));
2934 // Check that the target is not a window or document before firing
2935 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2936 // despite being required in the spec, so follow their behavior.
2938 // As for document, we should not even fire focus/blur, but until then, we
2939 // need this check. targetDocument should be removed once bug 1228802 is
2941 if (!targetWindow
&& !targetDocument
) {
2942 EventMessage focusInOrOutMessage
=
2943 aEventMessage
== eFocus
? eFocusIn
: eFocusOut
;
2944 FireFocusInOrOutEvent(focusInOrOutMessage
, aPresShell
, aTarget
,
2945 currentWindow
, currentFocusedContent
, aRelatedTarget
);
2949 void nsFocusManager::ScrollIntoView(PresShell
* aPresShell
, nsIContent
* aContent
,
2951 if (aFlags
& FLAG_NOSCROLL
) {
2955 // If the noscroll flag isn't set, scroll the newly focused element into view.
2956 const ScrollAxis
axis(WhereToScroll::Center
, WhenToScroll::IfNotVisible
);
2957 aPresShell
->ScrollContentIntoView(aContent
, axis
, axis
,
2958 ScrollFlags::ScrollOverflowHidden
);
2959 // Scroll the input / textarea selection into view, unless focused with the
2960 // mouse, see bug 572649.
2961 if (aFlags
& FLAG_BYMOUSE
) {
2964 // ScrollContentIntoView flushes layout, so no need to flush again here.
2965 if (nsTextControlFrame
* tf
= do_QueryFrame(aContent
->GetPrimaryFrame())) {
2966 tf
->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes
);
2970 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter
* aWindow
,
2971 CallerType aCallerType
, uint64_t aActionId
) {
2972 // don't raise windows that are already raised or are in the process of
2975 if (!aWindow
|| aWindow
== mWindowBeingLowered
) {
2979 if (XRE_IsParentProcess()) {
2980 if (aWindow
== mActiveWindow
) {
2984 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2985 // TODO: Deeper OOP frame hierarchies are
2986 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
2987 if (bc
== GetActiveBrowsingContext()) {
2990 if (bc
== GetFocusedBrowsingContext()) {
2996 // In test mode, emulate raising the window. WindowRaised takes
2997 // care of lowering the present active window. This happens in
2998 // a separate runnable to avoid touching multiple windows in
2999 // the current runnable.
3001 nsCOMPtr
<nsPIDOMWindowOuter
> window(aWindow
);
3002 RefPtr
<nsFocusManager
> self(this);
3003 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
3004 "nsFocusManager::RaiseWindow",
3005 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093)
3006 [self
, window
]() MOZ_CAN_RUN_SCRIPT_BOUNDARY
-> void {
3007 self
->WindowRaised(window
, GenerateFocusActionId());
3012 if (XRE_IsContentProcess()) {
3013 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
3015 // Assume the raise below will succeed and run the raising synchronously
3016 // in this process to make the focus event that is observable in this
3017 // process fire in the right order relative to mouseup when we are here
3018 // thanks to a mousedown.
3019 WindowRaised(aWindow
, aActionId
);
3024 // Windows would rather we focus the child widget, otherwise, the toplevel
3025 // widget will always end up being focused. Fortunately, focusing the child
3026 // widget will also have the effect of raising the window this widget is in.
3027 // But on other platforms, we can just focus the toplevel widget to raise
3029 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
;
3030 GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3031 getter_AddRefs(childWindow
));
3033 childWindow
= aWindow
;
3036 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3041 PresShell
* presShell
= docShell
->GetPresShell();
3046 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
3047 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
3049 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3053 nsCOMPtr
<nsIBaseWindow
> treeOwnerAsWin
=
3054 do_QueryInterface(aWindow
->GetDocShell());
3055 if (treeOwnerAsWin
) {
3056 nsCOMPtr
<nsIWidget
> widget
;
3057 treeOwnerAsWin
->GetMainWidget(getter_AddRefs(widget
));
3059 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3065 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
3066 RefPtr
<Element
> focusedElement
= mFocusedElement
;
3067 UpdateCaret(false, true, focusedElement
);
3070 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus
, bool aUpdateVisibility
,
3071 nsIContent
* aContent
) {
3072 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus
, aUpdateVisibility
));
3074 if (!mFocusedWindow
) {
3078 // this is called when a document is focused or when the caretbrowsing
3079 // preference is changed
3080 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
3081 if (!focusedDocShell
) {
3085 if (focusedDocShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
3086 return; // Never browse with caret in chrome
3089 bool browseWithCaret
= Preferences::GetBool("accessibility.browsewithcaret");
3091 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
3096 // If this is an editable document which isn't contentEditable, or a
3097 // contentEditable document and the node to focus is contentEditable,
3098 // return, so that we don't mess with caret visibility.
3099 bool isEditable
= false;
3100 focusedDocShell
->GetEditable(&isEditable
);
3103 Document
* doc
= presShell
->GetDocument();
3105 bool isContentEditableDoc
=
3107 doc
->GetEditingState() == Document::EditingState::eContentEditable
;
3109 bool isFocusEditable
= aContent
&& aContent
->HasFlag(NODE_IS_EDITABLE
);
3110 if (!isContentEditableDoc
|| isFocusEditable
) {
3115 if (!isEditable
&& aMoveCaretToFocus
) {
3116 MoveCaretToFocus(presShell
, aContent
);
3119 // The above MoveCaretToFocus call may run scripts which
3120 // may clear mFocusWindow
3121 if (!mFocusedWindow
) {
3125 if (!aUpdateVisibility
) {
3129 // XXXndeakin this doesn't seem right. It should be checking for this only
3130 // on the nearest ancestor frame which is a chrome frame. But this is
3131 // what the existing code does, so just leave it for now.
3132 if (!browseWithCaret
) {
3133 nsCOMPtr
<Element
> docElement
= mFocusedWindow
->GetFrameElementInternal();
3135 browseWithCaret
= docElement
->AttrValueIs(
3136 kNameSpaceID_None
, nsGkAtoms::showcaret
, u
"true"_ns
, eCaseMatters
);
3139 SetCaretVisible(presShell
, browseWithCaret
, aContent
);
3142 void nsFocusManager::MoveCaretToFocus(PresShell
* aPresShell
,
3143 nsIContent
* aContent
) {
3144 nsCOMPtr
<Document
> doc
= aPresShell
->GetDocument();
3146 RefPtr
<nsFrameSelection
> frameSelection
= aPresShell
->FrameSelection();
3147 RefPtr
<Selection
> domSelection
=
3148 frameSelection
->GetSelection(SelectionType::eNormal
);
3150 // First clear the selection. This way, if there is no currently focused
3151 // content, the selection will just be cleared.
3152 domSelection
->RemoveAllRanges(IgnoreErrors());
3155 RefPtr
<nsRange
> newRange
= doc
->CreateRange(rv
);
3156 if (NS_WARN_IF(rv
.Failed())) {
3157 rv
.SuppressException();
3161 // Set the range to the start of the currently focused node
3162 // Make sure it's collapsed
3163 newRange
->SelectNodeContents(*aContent
, IgnoreErrors());
3165 if (!aContent
->GetFirstChild() ||
3166 aContent
->IsHTMLFormControlElement()) {
3167 // If current focus node is a leaf, set range to before the
3168 // node by using the parent as a container.
3169 // This prevents it from appearing as selected.
3170 newRange
->SetStartBefore(*aContent
, IgnoreErrors());
3171 newRange
->SetEndBefore(*aContent
, IgnoreErrors());
3173 domSelection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
3175 domSelection
->CollapseToStart(IgnoreErrors());
3181 nsresult
nsFocusManager::SetCaretVisible(PresShell
* aPresShell
, bool aVisible
,
3182 nsIContent
* aContent
) {
3183 // When browsing with caret, make sure caret is visible after new focus
3184 // Return early if there is no caret. This can happen for the testcase
3185 // for bug 308025 where a window is closed in a blur handler.
3186 RefPtr
<nsCaret
> caret
= aPresShell
->GetCaret();
3191 bool caretVisible
= caret
->IsVisible();
3192 if (!aVisible
&& !caretVisible
) {
3196 RefPtr
<nsFrameSelection
> frameSelection
;
3198 NS_ASSERTION(aContent
->GetComposedDoc() == aPresShell
->GetDocument(),
3200 nsIFrame
* focusFrame
= aContent
->GetPrimaryFrame();
3202 frameSelection
= focusFrame
->GetFrameSelection();
3206 RefPtr
<nsFrameSelection
> docFrameSelection
= aPresShell
->FrameSelection();
3208 if (docFrameSelection
&& caret
&&
3209 (frameSelection
== docFrameSelection
|| !aContent
)) {
3210 Selection
* domSelection
=
3211 docFrameSelection
->GetSelection(SelectionType::eNormal
);
3213 // First, hide the caret to prevent attempting to show it in
3214 // SetCaretDOMSelection
3215 aPresShell
->SetCaretEnabled(false);
3217 // Caret must blink on non-editable elements
3218 caret
->SetIgnoreUserModify(true);
3219 // Tell the caret which selection to use
3220 caret
->SetSelection(domSelection
);
3222 // In content, we need to set the caret. The only special case is edit
3223 // fields, which have a different frame selection from the document.
3224 // They will take care of making the caret visible themselves.
3226 aPresShell
->SetCaretReadOnly(false);
3227 aPresShell
->SetCaretEnabled(aVisible
);
3234 void nsFocusManager::GetSelectionLocation(Document
* aDocument
,
3235 PresShell
* aPresShell
,
3236 nsIContent
** aStartContent
,
3237 nsIContent
** aEndContent
) {
3238 *aStartContent
= *aEndContent
= nullptr;
3240 nsPresContext
* presContext
= aPresShell
->GetPresContext();
3241 NS_ASSERTION(presContext
, "mPresContent is null!!");
3243 RefPtr
<Selection
> domSelection
=
3244 aPresShell
->ConstFrameSelection()->GetSelection(SelectionType::eNormal
);
3245 if (!domSelection
) {
3249 const nsRange
* domRange
= domSelection
->GetRangeAt(0);
3250 if (!domRange
|| !domRange
->IsPositioned()) {
3253 nsIContent
* start
= nsIContent::FromNode(domRange
->GetStartContainer());
3254 nsIContent
* end
= nsIContent::FromNode(domRange
->GetEndContainer());
3255 if (nsIContent
* child
= domRange
->StartRef().GetChildAtOffset()) {
3258 if (nsIContent
* child
= domRange
->EndRef().GetChildAtOffset()) {
3262 // Next check to see if our caret is at the very end of a text node. If so,
3263 // the caret is actually sitting in front of the next logical frame's primary
3264 // node - so for this case we need to change the content to that node.
3265 // Note that if the text does not have text frame, we do not need to retreive
3266 // caret frame. This could occur if text frame has only collapsisble white-
3267 // spaces and is around a block boundary or an ancestor of it is invisible.
3268 // XXX If there is a visible text sibling, should we return it in the former
3270 if (auto* text
= Text::FromNodeOrNull(start
);
3271 text
&& text
->GetPrimaryFrame() &&
3272 text
->TextDataLength() == domRange
->StartOffset() &&
3273 domSelection
->IsCollapsed()) {
3274 nsIFrame
* startFrame
= start
->GetPrimaryFrame();
3275 // Yes, indeed we were at the end of the last node
3277 domSelection
&& domSelection
->GetAncestorLimiter()
3278 ? domSelection
->GetAncestorLimiter()->GetPrimaryFrame()
3280 nsFrameIterator
frameIterator(presContext
, startFrame
,
3281 nsFrameIterator::Type::Leaf
,
3283 false, // aLockInScrollView
3284 true, // aFollowOOFs
3285 false, // aSkipPopupChecks
3288 nsIFrame
* newCaretFrame
= nullptr;
3289 nsIContent
* newCaretContent
= start
;
3290 const bool endOfSelectionInStartNode
= start
== end
;
3292 // Continue getting the next frame until the primary content for the
3293 // frame we are on changes - we don't want to be stuck in the same
3295 frameIterator
.Next();
3296 newCaretFrame
= frameIterator
.CurrentItem();
3297 if (!newCaretFrame
) {
3300 newCaretContent
= newCaretFrame
->GetContent();
3301 } while (!newCaretContent
|| newCaretContent
== start
);
3303 if (newCaretFrame
&& newCaretContent
) {
3304 // If the caret is exactly at the same position of the new frame,
3305 // then we can use the newCaretFrame and newCaretContent for our
3308 if (nsIFrame
* frame
= nsCaret::GetGeometry(domSelection
, &caretRect
)) {
3309 nsPoint caretWidgetOffset
;
3310 nsIWidget
* widget
= frame
->GetNearestWidget(caretWidgetOffset
);
3311 caretRect
.MoveBy(caretWidgetOffset
);
3312 nsPoint newCaretOffset
;
3313 nsIWidget
* newCaretWidget
=
3314 newCaretFrame
->GetNearestWidget(newCaretOffset
);
3315 if (widget
== newCaretWidget
&& caretRect
.TopLeft() == newCaretOffset
) {
3316 // The caret is at the start of the new element.
3317 startFrame
= newCaretFrame
;
3318 start
= newCaretContent
;
3319 if (endOfSelectionInStartNode
) {
3320 end
= newCaretContent
; // Ensure end of selection is
3328 NS_IF_ADDREF(*aStartContent
= start
);
3329 NS_IF_ADDREF(*aEndContent
= end
);
3332 nsresult
nsFocusManager::DetermineElementToMoveFocus(
3333 nsPIDOMWindowOuter
* aWindow
, nsIContent
* aStartContent
, int32_t aType
,
3334 bool aNoParentTraversal
, bool aNavigateByKey
, nsIContent
** aNextContent
) {
3335 *aNextContent
= nullptr;
3337 // This is used for document navigation only. It will be set to true if we
3338 // start navigating from a starting point. If this starting point is near the
3339 // end of the document (for example, an element on a statusbar), and there
3340 // are no child documents or panels before the end of the document, then we
3341 // will need to ensure that we don't consider the root chrome window when we
3342 // loop around and instead find the next child document/panel, as focus is
3343 // already in that window. This flag will be cleared once we navigate into
3344 // another document.
3345 bool mayFocusRoot
= (aStartContent
!= nullptr);
3347 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
3348 if (!startContent
&& aType
!= MOVEFOCUS_CARET
) {
3349 if (aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
) {
3350 // When moving between documents, make sure to get the right
3351 // starting content in a descendant.
3352 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
3353 startContent
= GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3354 getter_AddRefs(focusedWindow
));
3355 } else if (aType
!= MOVEFOCUS_LASTDOC
) {
3356 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
3357 // then we are document-navigating backwards from chrome to the content
3358 // process, and we don't want to use this so that we start from the end
3360 startContent
= aWindow
->GetFocusedElement();
3364 nsCOMPtr
<Document
> doc
;
3366 doc
= startContent
->GetComposedDoc();
3368 doc
= aWindow
->GetExtantDoc();
3369 if (!doc
) return NS_OK
;
3371 // True if we are navigating by document (F6/Shift+F6) or false if we are
3372 // navigating by element (Tab/Shift+Tab).
3373 const bool forDocumentNavigation
=
3374 aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
||
3375 aType
== MOVEFOCUS_FIRSTDOC
|| aType
== MOVEFOCUS_LASTDOC
;
3377 // If moving to the root or first document, find the root element and return.
3378 if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_FIRSTDOC
) {
3379 NS_IF_ADDREF(*aNextContent
= GetRootForFocus(aWindow
, doc
, false, false));
3380 if (!*aNextContent
&& aType
== MOVEFOCUS_FIRSTDOC
) {
3381 // When looking for the first document, if the root wasn't focusable,
3382 // find the next focusable document.
3383 aType
= MOVEFOCUS_FORWARDDOC
;
3389 // rootElement and presShell may be set to sub-document's ones so that they
3390 // cannot be `const`.
3391 RefPtr
<Element
> rootElement
= doc
->GetRootElement();
3392 NS_ENSURE_TRUE(rootElement
, NS_OK
);
3394 RefPtr
<PresShell
> presShell
= doc
->GetPresShell();
3395 NS_ENSURE_TRUE(presShell
, NS_OK
);
3397 if (aType
== MOVEFOCUS_FIRST
) {
3398 if (!aStartContent
) {
3399 startContent
= rootElement
;
3401 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3402 startContent
, true, 1, false, false,
3403 aNavigateByKey
, false, false, aNextContent
);
3405 if (aType
== MOVEFOCUS_LAST
) {
3406 if (!aStartContent
) {
3407 startContent
= rootElement
;
3409 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3410 startContent
, false, 0, false, false,
3411 aNavigateByKey
, false, false, aNextContent
);
3414 bool forward
= (aType
== MOVEFOCUS_FORWARD
|| aType
== MOVEFOCUS_FORWARDDOC
||
3415 aType
== MOVEFOCUS_CARET
);
3416 bool doNavigation
= true;
3417 bool ignoreTabIndex
= false;
3418 // when a popup is open, we want to ensure that tab navigation occurs only
3419 // within the most recently opened panel. If a popup is open, its frame will
3420 // be stored in popupFrame.
3421 nsIFrame
* popupFrame
= nullptr;
3423 int32_t tabIndex
= forward
? 1 : 0;
3425 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3426 tabIndex
= (frame
&& !startContent
->IsHTMLElement(nsGkAtoms::area
))
3427 ? frame
->IsFocusable().mTabIndex
3428 : startContent
->IsFocusableWithoutStyle().mTabIndex
;
3430 // if the current element isn't tabbable, ignore the tabindex and just
3431 // look for the next element. The root content won't have a tabindex
3432 // so just treat this as the beginning of the tab order.
3435 if (startContent
!= rootElement
) {
3436 ignoreTabIndex
= true;
3440 // check if the focus is currently inside a popup. Elements such as the
3441 // autocomplete widget use the noautofocus attribute to allow the focus to
3442 // remain outside the popup when it is opened.
3444 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3445 frame
, LayoutFrameType::MenuPopup
);
3448 if (popupFrame
&& !forDocumentNavigation
) {
3449 // Don't navigate outside of a popup, so pretend that the
3450 // root content is the popup itself
3451 rootElement
= popupFrame
->GetContent()->AsElement();
3452 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3453 } else if (!forward
) {
3454 // If focus moves backward and when current focused node is root
3455 // content or <body> element which is editable by contenteditable
3456 // attribute, focus should move to its parent document.
3457 if (startContent
== rootElement
) {
3458 doNavigation
= false;
3460 Document
* doc
= startContent
->GetComposedDoc();
3462 nsLayoutUtils::GetEditableRootContentByContentEditable(doc
)) {
3463 doNavigation
= false;
3468 if (aType
!= MOVEFOCUS_CARET
) {
3469 // if there is no focus, yet a panel is open, focus the first item in
3471 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
3473 popupFrame
= pm
->GetTopPopup(PopupType::Panel
);
3477 // When there is a popup open, and no starting content, start the search
3478 // at the topmost popup.
3479 startContent
= popupFrame
->GetContent();
3480 NS_ASSERTION(startContent
, "Popup frame doesn't have a content node");
3481 // Unless we are searching for documents, set the root content to the
3482 // popup as well, so that we don't tab-navigate outside the popup.
3483 // When navigating by documents, we start at the popup but can navigate
3484 // outside of it to look for other panels and documents.
3485 if (!forDocumentNavigation
) {
3486 rootElement
= startContent
->AsElement();
3489 doc
= startContent
? startContent
->GetComposedDoc() : nullptr;
3491 // Otherwise, for content shells, start from the location of the caret.
3492 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3493 if (docShell
&& docShell
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
3494 nsCOMPtr
<nsIContent
> endSelectionContent
;
3495 GetSelectionLocation(doc
, presShell
, getter_AddRefs(startContent
),
3496 getter_AddRefs(endSelectionContent
));
3497 // If the selection is on the rootElement, then there is no selection
3498 if (startContent
== rootElement
) {
3499 startContent
= nullptr;
3502 if (aType
== MOVEFOCUS_CARET
) {
3503 // GetFocusInSelection finds a focusable link near the caret.
3504 // If there is no start content though, don't do this to avoid
3505 // focusing something unexpected.
3507 GetFocusInSelection(aWindow
, startContent
, endSelectionContent
,
3514 // when starting from a selection, we always want to find the next or
3515 // previous element in the document. So the tabindex on elements
3516 // should be ignored.
3517 ignoreTabIndex
= true;
3521 if (!startContent
) {
3522 // otherwise, just use the root content as the starting point
3523 startContent
= rootElement
;
3524 NS_ENSURE_TRUE(startContent
, NS_OK
);
3529 // Check if the starting content is the same as the content assigned to the
3530 // retargetdocumentfocus attribute. Is so, we don't want to start searching
3531 // from there but instead from the beginning of the document. Otherwise, the
3532 // content that appears before the retargetdocumentfocus element will never
3533 // get checked as it will be skipped when the focus is retargetted to it.
3534 if (forDocumentNavigation
&& nsContentUtils::IsChromeDoc(doc
)) {
3535 nsAutoString retarget
;
3537 if (rootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
3538 nsIContent
* retargetElement
= doc
->GetElementById(retarget
);
3539 // The common case here is the urlbar where focus is on the anonymous
3540 // input inside the textbox, but the retargetdocumentfocus attribute
3541 // refers to the textbox. The Contains check will return false and the
3542 // IsInclusiveDescendantOf check will return true in this case.
3543 if (retargetElement
&&
3544 (retargetElement
== startContent
||
3545 (!retargetElement
->Contains(startContent
) &&
3546 startContent
->IsInclusiveDescendantOf(retargetElement
)))) {
3547 startContent
= rootElement
;
3552 NS_ASSERTION(startContent
, "starting content not set");
3554 // keep a reference to the starting content. If we find that again, it means
3555 // we've iterated around completely and we don't want to adjust the focus.
3556 // The skipOriginalContentCheck will be set to true only for the first time
3557 // GetNextTabbableContent is called. This ensures that we don't break out
3558 // when nothing is focused to start with. Specifically,
3559 // GetNextTabbableContent first checks the root content -- which happens to
3560 // be the same as the start content -- when nothing is focused and tabbing
3561 // forward. Without skipOriginalContentCheck set to true, we'd end up
3562 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
3563 // will never wrap around on its own, and can only return the original
3564 // content when it is called a second time or later.
3565 bool skipOriginalContentCheck
= true;
3566 const nsCOMPtr
<nsIContent
> originalStartContent
= startContent
;
3568 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent
.get());
3569 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
3570 forward
, tabIndex
, ignoreTabIndex
,
3571 forDocumentNavigation
));
3575 nsCOMPtr
<nsIContent
> nextFocus
;
3576 // TODO: MOZ_KnownLive is reruired due to bug 1770680
3577 nsresult rv
= GetNextTabbableContent(
3578 presShell
, rootElement
,
3579 MOZ_KnownLive(skipOriginalContentCheck
? nullptr
3580 : originalStartContent
.get()),
3581 startContent
, forward
, tabIndex
, ignoreTabIndex
,
3582 forDocumentNavigation
, aNavigateByKey
, false, false,
3583 getter_AddRefs(nextFocus
));
3584 NS_ENSURE_SUCCESS(rv
, rv
);
3585 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
3586 // Navigation was redirected to a child process, so just return.
3590 // found a content node to focus.
3592 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus
.get());
3594 // as long as the found node was not the same as the starting node,
3595 // set it as the return value. For document navigation, we can return
3596 // the same element in case there is only one content node that could
3597 // be returned, for example, in a child process document.
3598 if (nextFocus
!= originalStartContent
|| forDocumentNavigation
) {
3599 nextFocus
.forget(aNextContent
);
3604 if (popupFrame
&& !forDocumentNavigation
) {
3605 // in a popup, so start again from the beginning of the popup. However,
3606 // if we already started at the beginning, then there isn't anything to
3607 // focus, so just return
3608 if (startContent
!= rootElement
) {
3609 startContent
= rootElement
;
3610 tabIndex
= forward
? 1 : 0;
3617 doNavigation
= true;
3618 skipOriginalContentCheck
= forDocumentNavigation
;
3619 ignoreTabIndex
= false;
3621 if (aNoParentTraversal
) {
3622 if (startContent
== rootElement
) {
3626 startContent
= rootElement
;
3627 tabIndex
= forward
? 1 : 0;
3631 // Reached the beginning or end of the document. Next, navigate up to the
3632 // parent document and try again.
3633 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
3634 NS_ENSURE_TRUE(piWindow
, NS_ERROR_FAILURE
);
3636 nsCOMPtr
<nsIDocShell
> docShell
= piWindow
->GetDocShell();
3637 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
3639 // Get the frame element this window is inside and, from that, get the
3640 // parent document and presshell. If there is no enclosing frame element,
3641 // then this is a top-level, embedded or remote window.
3642 startContent
= piWindow
->GetFrameElementInternal();
3644 doc
= startContent
->GetComposedDoc();
3645 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
3647 rootElement
= doc
->GetRootElement();
3648 presShell
= doc
->GetPresShell();
3650 // We can focus the root element now that we have moved to another
3652 mayFocusRoot
= true;
3654 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3659 tabIndex
= frame
->IsFocusable().mTabIndex
;
3662 ignoreTabIndex
= true;
3665 // if the frame is inside a popup, make sure to scan only within the
3666 // popup. This handles the situation of tabbing amongst elements
3667 // inside an iframe which is itself inside a popup. Otherwise,
3668 // navigation would move outside the popup when tabbing outside the
3670 if (!forDocumentNavigation
) {
3671 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3672 frame
, LayoutFrameType::MenuPopup
);
3674 rootElement
= popupFrame
->GetContent()->AsElement();
3675 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3679 if (aNavigateByKey
) {
3680 // There is no parent, so move the focus to the parent process.
3681 if (auto* child
= BrowserChild::GetFrom(docShell
)) {
3682 child
->SendMoveFocus(forward
, forDocumentNavigation
);
3683 // Blur the current element.
3684 RefPtr
<BrowsingContext
> focusedBC
= GetFocusedBrowsingContext();
3685 if (focusedBC
&& focusedBC
->IsInProcess()) {
3686 Blur(focusedBC
, nullptr, true, true, false,
3687 GenerateFocusActionId());
3689 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docShell
->GetWindow();
3690 window
->SetFocusedElement(nullptr);
3696 // If we have reached the end of the top-level document, focus the
3697 // first element in the top-level document. This should always happen
3698 // when navigating by document forwards but when navigating backwards,
3699 // only do this if we started in another document or within a popup frame.
3700 // If the focus started in this window outside a popup however, we should
3701 // continue by looping around to the end again.
3702 if (forDocumentNavigation
&& (forward
|| mayFocusRoot
|| popupFrame
)) {
3703 // HTML content documents can have their root element focused by
3704 // pressing F6(a focus ring appears around the entire content area
3705 // frame). This root appears in the tab order before all of the elements
3706 // in the document. Chrome documents however cannot be focused directly,
3707 // so instead we focus the first focusable element within the window.
3708 // For example, the urlbar.
3709 RefPtr
<Element
> rootElementForFocus
=
3710 GetRootForFocus(piWindow
, doc
, true, true);
3711 return FocusFirst(rootElementForFocus
, aNextContent
,
3712 true /* aReachedToEndForDocumentNavigation */);
3715 // Once we have hit the top-level and have iterated to the end again, we
3716 // just want to break out next time we hit this spot to prevent infinite
3718 mayFocusRoot
= true;
3720 // reset the tab index and start again from the beginning or end
3721 startContent
= rootElement
;
3722 tabIndex
= forward
? 1 : 0;
3725 // wrapped all the way around and didn't find anything to move the focus
3726 // to, so just break out
3727 if (startContent
== originalStartContent
) {
3735 uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions
& aOptions
) {
3736 uint32_t flags
= FLAG_BYJS
;
3737 if (aOptions
.mPreventScroll
) {
3738 flags
|= FLAG_NOSCROLL
;
3740 if (aOptions
.mFocusVisible
.WasPassed()) {
3741 flags
|= aOptions
.mFocusVisible
.Value() ? FLAG_SHOWRING
: FLAG_NOSHOWRING
;
3743 if (UserActivation::IsHandlingKeyboardInput()) {
3744 flags
|= FLAG_BYKEY
;
3746 // TODO: We could do a similar thing if we're handling mouse input, but that
3747 // changes focusability of some elements so may be more risky.
3751 static bool IsHostOrSlot(const nsIContent
* aContent
) {
3752 return aContent
&& (aContent
->GetShadowRoot() ||
3753 aContent
->IsHTMLElement(nsGkAtoms::slot
));
3756 // Helper class to iterate contents in scope by traversing flattened tree
3758 class MOZ_STACK_CLASS ScopedContentTraversal
{
3760 ScopedContentTraversal(nsIContent
* aStartContent
, nsIContent
* aOwner
)
3761 : mCurrent(aStartContent
), mOwner(aOwner
) {
3762 MOZ_ASSERT(aStartContent
);
3768 void Reset() { SetCurrent(mOwner
); }
3770 nsIContent
* GetCurrent() const { return mCurrent
; }
3773 void SetCurrent(nsIContent
* aContent
) { mCurrent
= aContent
; }
3775 nsIContent
* mCurrent
;
3779 void ScopedContentTraversal::Next() {
3780 MOZ_ASSERT(mCurrent
);
3782 // Get mCurrent's first child if it's in the same scope.
3783 if (!IsHostOrSlot(mCurrent
) || mCurrent
== mOwner
) {
3784 StyleChildrenIterator
iter(mCurrent
);
3785 nsIContent
* child
= iter
.GetNextChild();
3792 // If mOwner has no children, END traversal
3793 if (mCurrent
== mOwner
) {
3794 SetCurrent(nullptr);
3798 nsIContent
* current
= mCurrent
;
3800 // Create parent's iterator and move to current
3801 nsIContent
* parent
= current
->GetFlattenedTreeParent();
3802 StyleChildrenIterator
parentIter(parent
);
3803 parentIter
.Seek(current
);
3805 // Get next sibling of current
3806 if (nsIContent
* next
= parentIter
.GetNextChild()) {
3811 // If no next sibling and parent is mOwner, END traversal
3812 if (parent
== mOwner
) {
3813 SetCurrent(nullptr);
3821 void ScopedContentTraversal::Prev() {
3822 MOZ_ASSERT(mCurrent
);
3826 if (mCurrent
== mOwner
) {
3827 // Get last child of mOwner
3828 StyleChildrenIterator
ownerIter(mOwner
, false /* aStartAtBeginning */);
3829 last
= ownerIter
.GetPreviousChild();
3833 // Create parent's iterator and move to mCurrent
3834 parent
= mCurrent
->GetFlattenedTreeParent();
3835 StyleChildrenIterator
parentIter(parent
);
3836 parentIter
.Seek(mCurrent
);
3838 // Get previous sibling
3839 last
= parentIter
.GetPreviousChild();
3844 if (IsHostOrSlot(parent
)) {
3845 // Skip contents in other scopes
3850 StyleChildrenIterator
iter(parent
, false /* aStartAtBeginning */);
3851 last
= iter
.GetPreviousChild();
3854 // If parent is mOwner and no previous sibling remains, END traversal
3855 SetCurrent(parent
== mOwner
? nullptr : parent
);
3858 static bool IsOpenPopoverWithInvoker(nsIContent
* aContent
) {
3859 if (auto* popover
= Element::FromNode(aContent
)) {
3860 return popover
&& popover
->IsPopoverOpen() &&
3861 popover
->GetPopoverData()->GetInvoker();
3866 static nsIContent
* InvokerForPopoverShowingState(nsIContent
* aContent
) {
3867 Element
* invoker
= Element::FromNode(aContent
);
3872 nsGenericHTMLElement
* popover
= invoker
->GetEffectivePopoverTargetElement();
3873 if (popover
&& popover
->IsPopoverOpen() &&
3874 popover
->GetPopoverData()->GetInvoker() == invoker
) {
3882 * Returns scope owner of aContent.
3883 * A scope owner is either a shadow host, or slot.
3885 static nsIContent
* FindScopeOwner(nsIContent
* aContent
) {
3886 nsIContent
* currentContent
= aContent
;
3887 while (currentContent
) {
3888 nsIContent
* parent
= currentContent
->GetFlattenedTreeParent();
3890 // Shadow host / Slot
3891 if (IsHostOrSlot(parent
)) {
3895 currentContent
= parent
;
3902 * Host and Slot elements need to be handled as if they had tabindex 0 even
3903 * when they don't have the attribute. This is a helper method to get the
3904 * right value for focus navigation. If aIsFocusable is passed, it is set to
3905 * true if the element itself is focusable.
3907 static int32_t HostOrSlotTabIndexValue(const nsIContent
* aContent
,
3908 bool* aIsFocusable
= nullptr) {
3909 MOZ_ASSERT(IsHostOrSlot(aContent
));
3912 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
3913 *aIsFocusable
= frame
&& frame
->IsFocusable().mTabIndex
>= 0;
3916 const nsAttrValue
* attrVal
=
3917 aContent
->AsElement()->GetParsedAttr(nsGkAtoms::tabindex
);
3922 if (attrVal
->Type() == nsAttrValue::eInteger
) {
3923 return attrVal
->GetIntegerValue();
3929 nsIContent
* nsFocusManager::GetNextTabbableContentInScope(
3930 nsIContent
* aOwner
, nsIContent
* aStartContent
,
3931 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t aCurrentTabIndex
,
3932 bool aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
3933 bool aSkipOwner
, bool aReachedToEndForDocumentNavigation
) {
3935 IsHostOrSlot(aOwner
) || IsOpenPopoverWithInvoker(aOwner
),
3936 "Scope owner should be host, slot or an open popover with invoker set.");
3938 // XXX: Why don't we ignore tabindex when the current tabindex < 0?
3939 MOZ_ASSERT_IF(aCurrentTabIndex
< 0, aIgnoreTabIndex
);
3941 if (!aSkipOwner
&& (aForward
&& aOwner
== aStartContent
)) {
3942 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3943 auto focusable
= frame
->IsFocusable();
3944 if (focusable
&& focusable
.mTabIndex
>= 0) {
3951 // Iterate contents in scope
3953 ScopedContentTraversal
contentTraversal(aStartContent
, aOwner
);
3954 nsCOMPtr
<nsIContent
> iterContent
;
3955 nsIContent
* firstNonChromeOnly
=
3956 aStartContent
->IsInNativeAnonymousSubtree()
3957 ? aStartContent
->FindFirstNonChromeOnlyAccessContent()
3960 // Iterate tab index to find corresponding contents in scope
3963 // Iterate remaining contents in scope to find next content to focus
3966 aForward
? contentTraversal
.Next() : contentTraversal
.Prev();
3967 iterContent
= contentTraversal
.GetCurrent();
3969 if (firstNonChromeOnly
&& firstNonChromeOnly
== iterContent
) {
3970 // We just broke out from the native anonymous content, so move
3971 // to the previous/next node of the native anonymous owner.
3973 contentTraversal
.Next();
3975 contentTraversal
.Prev();
3977 iterContent
= contentTraversal
.GetCurrent();
3984 int32_t tabIndex
= 0;
3985 if (iterContent
->IsInNativeAnonymousSubtree() &&
3986 iterContent
->GetPrimaryFrame()) {
3987 tabIndex
= iterContent
->GetPrimaryFrame()->IsFocusable().mTabIndex
;
3988 } else if (IsHostOrSlot(iterContent
)) {
3989 tabIndex
= HostOrSlotTabIndexValue(iterContent
);
3991 nsIFrame
* frame
= iterContent
->GetPrimaryFrame();
3995 tabIndex
= frame
->IsFocusable().mTabIndex
;
3997 if (tabIndex
< 0 || !(aIgnoreTabIndex
|| tabIndex
== aCurrentTabIndex
)) {
4001 if (!IsHostOrSlot(iterContent
)) {
4002 nsCOMPtr
<nsIContent
> elementInFrame
;
4003 bool checkSubDocument
= true;
4004 if (aForDocumentNavigation
&&
4005 TryDocumentNavigation(iterContent
, &checkSubDocument
,
4006 getter_AddRefs(elementInFrame
))) {
4007 return elementInFrame
;
4009 if (!checkSubDocument
) {
4010 if (aReachedToEndForDocumentNavigation
&&
4011 StaticPrefs::dom_disable_tab_focus_to_root_element() &&
4012 nsContentUtils::IsChromeDoc(iterContent
->GetComposedDoc())) {
4013 // aReachedToEndForDocumentNavigation is true means
4014 // 1. This is a document navigation (i.e, VK_F6, Control + Tab)
4015 // 2. This is the top-level document (Note that we may start from
4017 // 3. We've searched through the this top-level document already
4018 if (!GetRootForChildDocument(iterContent
)) {
4019 // We'd like to focus the first focusable element of this
4020 // top-level chrome document.
4027 if (TryToMoveFocusToSubDocument(iterContent
, aOriginalStartContent
,
4028 aForward
, aForDocumentNavigation
,
4030 aReachedToEndForDocumentNavigation
,
4031 getter_AddRefs(elementInFrame
))) {
4032 return elementInFrame
;
4035 // Found content to focus
4039 // Search in scope owned by iterContent
4040 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4041 iterContent
, iterContent
, aOriginalStartContent
, aForward
,
4042 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
4043 aNavigateByKey
, false /* aSkipOwner */,
4044 aReachedToEndForDocumentNavigation
);
4045 if (contentToFocus
) {
4046 return contentToFocus
;
4050 // If already at lowest priority tab (0), end search completely.
4051 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4052 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4056 // We've been just trying to find some focusable element, and haven't, so
4058 if (aIgnoreTabIndex
) {
4062 // Continue looking for next highest priority tabindex
4063 aCurrentTabIndex
= GetNextTabIndex(aOwner
, aCurrentTabIndex
, aForward
);
4064 contentTraversal
.Reset();
4067 // Return scope owner at last for backward navigation if its tabindex
4069 if (!aSkipOwner
&& !aForward
) {
4070 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
4071 auto focusable
= frame
->IsFocusable();
4072 if (focusable
&& focusable
.mTabIndex
>= 0) {
4081 nsIContent
* nsFocusManager::GetNextTabbableContentInAncestorScopes(
4082 nsIContent
* aStartOwner
, nsCOMPtr
<nsIContent
>& aStartContent
/* inout */,
4083 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t* aCurrentTabIndex
,
4084 bool* aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4085 bool aReachedToEndForDocumentNavigation
) {
4086 MOZ_ASSERT(aStartOwner
== FindScopeOwner(aStartContent
),
4087 "aStartOWner should be the scope owner of aStartContent");
4088 MOZ_ASSERT(IsHostOrSlot(aStartOwner
), "scope owner should be host or slot");
4090 nsCOMPtr
<nsIContent
> owner
= aStartOwner
;
4091 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4092 while (IsHostOrSlot(owner
)) {
4093 int32_t tabIndex
= 0;
4094 if (IsHostOrSlot(startContent
)) {
4095 tabIndex
= HostOrSlotTabIndexValue(startContent
);
4096 } else if (nsIFrame
* frame
= startContent
->GetPrimaryFrame()) {
4097 tabIndex
= frame
->IsFocusable().mTabIndex
;
4099 tabIndex
= startContent
->IsFocusableWithoutStyle().mTabIndex
;
4101 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4102 owner
, startContent
, aOriginalStartContent
, aForward
, tabIndex
,
4103 tabIndex
< 0, aForDocumentNavigation
, aNavigateByKey
,
4104 false /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4105 if (contentToFocus
) {
4106 return contentToFocus
;
4109 startContent
= owner
;
4110 owner
= FindScopeOwner(startContent
);
4113 // If not found in shadow DOM, search from the top level shadow host in light
4115 aStartContent
= startContent
;
4116 *aCurrentTabIndex
= HostOrSlotTabIndexValue(startContent
);
4118 if (*aCurrentTabIndex
< 0) {
4119 *aIgnoreTabIndex
= true;
4125 static nsIContent
* GetTopLevelScopeOwner(nsIContent
* aContent
) {
4126 nsIContent
* topLevelScopeOwner
= nullptr;
4128 if (HTMLSlotElement
* slot
= aContent
->GetAssignedSlot()) {
4130 topLevelScopeOwner
= aContent
;
4131 } else if (ShadowRoot
* shadowRoot
= aContent
->GetContainingShadow()) {
4132 aContent
= shadowRoot
->Host();
4133 topLevelScopeOwner
= aContent
;
4135 aContent
= aContent
->GetParent();
4136 if (aContent
&& (HTMLSlotElement::FromNode(aContent
) ||
4137 IsOpenPopoverWithInvoker(aContent
))) {
4138 topLevelScopeOwner
= aContent
;
4143 return topLevelScopeOwner
;
4146 nsresult
nsFocusManager::GetNextTabbableContent(
4147 PresShell
* aPresShell
, nsIContent
* aRootContent
,
4148 nsIContent
* aOriginalStartContent
, nsIContent
* aStartContent
, bool aForward
,
4149 int32_t aCurrentTabIndex
, bool aIgnoreTabIndex
, bool aForDocumentNavigation
,
4150 bool aNavigateByKey
, bool aSkipPopover
,
4151 bool aReachedToEndForDocumentNavigation
, nsIContent
** aResultContent
) {
4152 *aResultContent
= nullptr;
4154 if (!aStartContent
) {
4158 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4159 nsCOMPtr
<nsIContent
> currentTopLevelScopeOwner
=
4160 GetTopLevelScopeOwner(startContent
);
4162 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent
);
4163 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex
));
4165 // If startContent is a shadow host or slot in forward navigation,
4166 // search in scope owned by startContent
4167 if (aForward
&& IsHostOrSlot(startContent
)) {
4168 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4169 startContent
, startContent
, aOriginalStartContent
, aForward
, 1,
4170 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4171 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4172 if (contentToFocus
) {
4173 NS_ADDREF(*aResultContent
= contentToFocus
);
4178 // If startContent is a popover invoker, search the popover scope.
4179 if (!aSkipPopover
) {
4180 if (InvokerForPopoverShowingState(startContent
)) {
4182 RefPtr
<nsIContent
> popover
=
4183 startContent
->GetEffectivePopoverTargetElement();
4184 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4185 popover
, popover
, aOriginalStartContent
, aForward
, 1,
4186 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4187 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4188 if (contentToFocus
) {
4189 NS_ADDREF(*aResultContent
= contentToFocus
);
4196 // If startContent is in a scope owned by Shadow DOM search from scope
4197 // including startContent
4198 if (nsCOMPtr
<nsIContent
> owner
= FindScopeOwner(startContent
)) {
4199 nsIContent
* contentToFocus
= GetNextTabbableContentInAncestorScopes(
4200 owner
, startContent
/* inout */, aOriginalStartContent
, aForward
,
4201 &aCurrentTabIndex
, &aIgnoreTabIndex
, aForDocumentNavigation
,
4202 aNavigateByKey
, aReachedToEndForDocumentNavigation
);
4203 if (contentToFocus
) {
4204 NS_ADDREF(*aResultContent
= contentToFocus
);
4209 // If we reach here, it means no next tabbable content in shadow DOM.
4210 // We need to continue searching in light DOM, starting at the top level
4211 // shadow host in light DOM (updated startContent) and its tabindex
4212 // (updated aCurrentTabIndex).
4213 MOZ_ASSERT(!FindScopeOwner(startContent
),
4214 "startContent should not be owned by Shadow DOM at this point");
4216 nsPresContext
* presContext
= aPresShell
->GetPresContext();
4218 bool getNextFrame
= true;
4219 nsCOMPtr
<nsIContent
> iterStartContent
= startContent
;
4220 nsIContent
* topLevelScopeStartContent
= startContent
;
4221 // Iterate tab index to find corresponding contents
4223 nsIFrame
* frame
= iterStartContent
->GetPrimaryFrame();
4224 // if there is no frame, look for another content node that has a frame
4226 // if the root content doesn't have a frame, just return
4227 if (iterStartContent
== aRootContent
) {
4231 // look for the next or previous content node in tree order
4232 iterStartContent
= aForward
? iterStartContent
->GetNextNode()
4233 : iterStartContent
->GetPrevNode();
4234 if (!iterStartContent
) {
4238 frame
= iterStartContent
->GetPrimaryFrame();
4239 // Host without frame, enter its scope.
4240 if (!frame
&& iterStartContent
->GetShadowRoot()) {
4241 int32_t tabIndex
= HostOrSlotTabIndexValue(iterStartContent
);
4242 if (tabIndex
>= 0 &&
4243 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4244 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4245 iterStartContent
, iterStartContent
, aOriginalStartContent
,
4246 aForward
, aForward
? 1 : 0, aIgnoreTabIndex
,
4247 aForDocumentNavigation
, aNavigateByKey
, true /* aSkipOwner */,
4248 aReachedToEndForDocumentNavigation
);
4249 if (contentToFocus
) {
4250 NS_ADDREF(*aResultContent
= contentToFocus
);
4255 // we've already skipped over the initial focused content, so we
4256 // don't want to traverse frames.
4257 getNextFrame
= false;
4260 Maybe
<nsFrameIterator
> frameIterator
;
4262 // For tab navigation, pass false for aSkipPopupChecks so that we don't
4263 // iterate into or out of a popup. For document naviation pass true to
4264 // ignore these boundaries.
4265 frameIterator
.emplace(presContext
, frame
, nsFrameIterator::Type::PreOrder
,
4267 false, // aLockInScrollView
4268 true, // aFollowOOFs
4269 aForDocumentNavigation
// aSkipPopupChecks
4271 MOZ_ASSERT(frameIterator
);
4273 if (iterStartContent
== aRootContent
) {
4275 frameIterator
->Last();
4276 } else if (aRootContent
->IsFocusableWithoutStyle()) {
4277 frameIterator
->Next();
4279 frame
= frameIterator
->CurrentItem();
4280 } else if (getNextFrame
&&
4281 (!iterStartContent
||
4282 !iterStartContent
->IsHTMLElement(nsGkAtoms::area
))) {
4283 // Need to do special check in case we're in an imagemap which has
4284 // multiple content nodes per frame, so don't skip over the starting
4286 frame
= frameIterator
->Traverse(aForward
);
4290 nsIContent
* oldTopLevelScopeOwner
= nullptr;
4291 // Walk frames to find something tabbable matching aCurrentTabIndex
4293 // Try to find the topmost scope owner, since we want to skip the node
4294 // that is not owned by document in frame traversal.
4295 const nsCOMPtr
<nsIContent
> currentContent
= frame
->GetContent();
4296 if (currentTopLevelScopeOwner
) {
4297 oldTopLevelScopeOwner
= currentTopLevelScopeOwner
;
4299 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(currentContent
);
4301 // We handle popover case separately.
4302 if (currentTopLevelScopeOwner
&&
4303 currentTopLevelScopeOwner
== oldTopLevelScopeOwner
&&
4304 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner
)) {
4305 // We're within non-document scope, continue.
4308 frameIterator
->Next();
4310 frameIterator
->Prev();
4312 frame
= frameIterator
->CurrentItem();
4313 // For the usage of GetPrevContinuation, see the comment
4314 // at the end of while (frame) loop.
4315 } while (frame
&& frame
->GetPrevContinuation());
4319 // Stepping out popover scope.
4320 // For forward, search for the next tabbable content after invoker.
4321 // For backward, we should get back to the invoker if the invoker is
4322 // focusable. Otherwise search for the next tabbable content after
4324 if (oldTopLevelScopeOwner
&&
4325 IsOpenPopoverWithInvoker(oldTopLevelScopeOwner
) &&
4326 currentTopLevelScopeOwner
!= oldTopLevelScopeOwner
) {
4327 if (auto* popover
= Element::FromNode(oldTopLevelScopeOwner
)) {
4328 RefPtr
<nsIContent
> invokerContent
=
4329 popover
->GetPopoverData()->GetInvoker()->AsContent();
4330 RefPtr
<nsIContent
> rootElement
= invokerContent
;
4331 if (auto* doc
= invokerContent
->GetComposedDoc()) {
4332 rootElement
= doc
->GetRootElement();
4335 nsIFrame
* frame
= invokerContent
->GetPrimaryFrame();
4336 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4337 if (tabIndex
>= 0 &&
4338 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4339 nsresult rv
= GetNextTabbableContent(
4340 aPresShell
, rootElement
, nullptr, invokerContent
, true,
4341 tabIndex
, false, false, aNavigateByKey
, true,
4342 aReachedToEndForDocumentNavigation
, aResultContent
);
4343 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4347 } else if (invokerContent
) {
4348 nsIFrame
* frame
= invokerContent
->GetPrimaryFrame();
4349 if (frame
&& frame
->IsFocusable()) {
4350 invokerContent
.forget(aResultContent
);
4353 nsresult rv
= GetNextTabbableContent(
4354 aPresShell
, rootElement
, aOriginalStartContent
, invokerContent
,
4355 false, 0, true, false, aNavigateByKey
, true,
4356 aReachedToEndForDocumentNavigation
, aResultContent
);
4357 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4365 if (InvokerForPopoverShowingState(currentContent
)) {
4366 RefPtr
<nsIContent
> popover
=
4367 currentContent
->GetEffectivePopoverTargetElement();
4368 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4369 popover
, popover
, aOriginalStartContent
, aForward
, 0,
4370 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4371 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4373 if (contentToFocus
) {
4374 NS_ADDREF(*aResultContent
= contentToFocus
);
4379 // For document navigation, check if this element is an open panel. Since
4380 // panels aren't focusable (tabIndex would be -1), we'll just assume that
4381 // for document navigation, the tabIndex is 0.
4382 if (aForDocumentNavigation
&& currentContent
&& (aCurrentTabIndex
== 0) &&
4383 currentContent
->IsXULElement(nsGkAtoms::panel
)) {
4384 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
4385 // Check if the panel is open. Closed panels are ignored since you can't
4386 // focus anything in them.
4387 if (popupFrame
&& popupFrame
->IsOpen()) {
4388 // When moving backward, skip the popup we started in otherwise it
4389 // will be selected again.
4390 bool validPopup
= true;
4392 nsIContent
* content
= topLevelScopeStartContent
;
4394 if (content
== currentContent
) {
4399 content
= content
->GetParent();
4404 // Since a panel isn't focusable itself, find the first focusable
4405 // content within the popup. If there isn't any focusable content
4406 // in the popup, skip this popup and continue iterating through the
4407 // frames. We pass the panel itself (currentContent) as the starting
4408 // and root content, so that we only find content within the panel.
4409 // Note also that we pass false for aForDocumentNavigation since we
4410 // want to locate the first content, not the first document.
4411 nsresult rv
= GetNextTabbableContent(
4412 aPresShell
, currentContent
, nullptr, currentContent
, true, 1,
4413 false, false, aNavigateByKey
, false,
4414 aReachedToEndForDocumentNavigation
, aResultContent
);
4415 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4422 // As of now, 2018/04/12, sequential focus navigation is still
4423 // in the obsolete Shadow DOM specification.
4424 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
4425 // "if ELEMENT is focusable, a shadow host, or a slot element,
4426 // append ELEMENT to NAVIGATION-ORDER."
4427 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
4428 // hosts and slots are handled before other elements.
4429 if (currentTopLevelScopeOwner
&&
4430 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner
)) {
4431 bool focusableHostSlot
;
4432 int32_t tabIndex
= HostOrSlotTabIndexValue(currentTopLevelScopeOwner
,
4433 &focusableHostSlot
);
4434 // Host or slot itself isn't focusable or going backwards, enter its
4436 if ((!aForward
|| !focusableHostSlot
) && tabIndex
>= 0 &&
4437 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4438 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4439 currentTopLevelScopeOwner
, currentTopLevelScopeOwner
,
4440 aOriginalStartContent
, aForward
, aForward
? 1 : 0,
4441 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4442 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4443 if (contentToFocus
) {
4444 NS_ADDREF(*aResultContent
= contentToFocus
);
4447 // If we've wrapped around already, then carry on.
4448 if (aOriginalStartContent
&&
4449 currentTopLevelScopeOwner
==
4450 GetTopLevelScopeOwner(aOriginalStartContent
)) {
4451 // FIXME: Shouldn't this return null instead? aOriginalStartContent
4452 // isn't focusable after all.
4453 NS_ADDREF(*aResultContent
= aOriginalStartContent
);
4457 // There is no next tabbable content in currentTopLevelScopeOwner's
4458 // scope. We should continue the loop in order to skip all contents that
4459 // is in currentTopLevelScopeOwner's scope.
4464 !GetTopLevelScopeOwner(currentContent
) ||
4465 IsOpenPopoverWithInvoker(GetTopLevelScopeOwner(currentContent
)),
4466 "currentContent should be in top-level-scope at this point unless "
4467 "for popover case");
4469 // TabIndex not set defaults to 0 for form elements, anchors and other
4470 // elements that are normally focusable. Tabindex defaults to -1
4471 // for elements that are not normally focusable.
4472 // The returned computed tabindex from IsFocusable() is as follows:
4474 // < 0 not tabbable at all
4475 // == 0 in normal tab order (last after positive tabindexed items)
4476 // > 0 can be tabbed to in the order specified by this value
4478 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4480 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame
->GetContent());
4482 (" with tabindex: %d expected: %d", tabIndex
, aCurrentTabIndex
));
4484 if (tabIndex
>= 0) {
4485 NS_ASSERTION(currentContent
,
4486 "IsFocusable set a tabindex for a frame with no content");
4487 if (!aForDocumentNavigation
&&
4488 currentContent
->IsHTMLElement(nsGkAtoms::img
) &&
4489 currentContent
->AsElement()->HasAttr(nsGkAtoms::usemap
)) {
4490 // This is an image with a map. Image map areas are not traversed by
4491 // nsFrameIterator so look for the next or previous area element.
4492 nsIContent
* areaContent
= GetNextTabbableMapArea(
4493 aForward
, aCurrentTabIndex
, currentContent
->AsElement(),
4496 NS_ADDREF(*aResultContent
= areaContent
);
4499 } else if (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
) {
4500 // break out if we've wrapped around to the start again.
4501 if (aOriginalStartContent
&&
4502 currentContent
== aOriginalStartContent
) {
4503 NS_ADDREF(*aResultContent
= currentContent
);
4507 // If this is a remote child browser, call NavigateDocument to have
4508 // the child process continue the navigation. Return a special error
4509 // code to have the caller return early. If the child ends up not
4510 // being focusable in some way, the child process will call back
4511 // into document navigation again by calling MoveFocus.
4512 if (BrowserParent
* remote
= BrowserParent::GetFrom(currentContent
)) {
4513 if (aNavigateByKey
) {
4514 remote
->NavigateByKey(aForward
, aForDocumentNavigation
);
4515 return NS_SUCCESS_DOM_NO_OPERATION
;
4520 // Same as above but for out-of-process iframes
4521 if (auto* bbc
= BrowserBridgeChild::GetFrom(currentContent
)) {
4522 if (aNavigateByKey
) {
4523 bbc
->NavigateByKey(aForward
, aForDocumentNavigation
);
4524 return NS_SUCCESS_DOM_NO_OPERATION
;
4529 // Next, for document navigation, check if this a non-remote child
4531 bool checkSubDocument
= true;
4532 if (aForDocumentNavigation
&&
4533 TryDocumentNavigation(currentContent
, &checkSubDocument
,
4538 if (checkSubDocument
) {
4539 // found a node with a matching tab index. Check if it is a child
4540 // frame. If so, navigate into the child frame instead.
4541 if (TryToMoveFocusToSubDocument(
4542 currentContent
, aOriginalStartContent
, aForward
,
4543 aForDocumentNavigation
, aNavigateByKey
,
4544 aReachedToEndForDocumentNavigation
, aResultContent
)) {
4545 MOZ_ASSERT(*aResultContent
);
4548 // otherwise, use this as the next content node to tab to, unless
4549 // this was the element we started on. This would happen for
4550 // instance on an element with child frames, where frame navigation
4551 // could return the original element again. In that case, just skip
4552 // it. Also, if the next content node is the root content, then
4553 // return it. This latter case would happen only if someone made a
4555 else if (currentContent
== aRootContent
||
4556 currentContent
!= startContent
) {
4557 NS_ADDREF(*aResultContent
= currentContent
);
4560 } else if (currentContent
&& aReachedToEndForDocumentNavigation
&&
4561 StaticPrefs::dom_disable_tab_focus_to_root_element() &&
4562 nsContentUtils::IsChromeDoc(
4563 currentContent
->GetComposedDoc())) {
4564 // aReachedToEndForDocumentNavigation is true means
4565 // 1. This is a document navigation (i.e, VK_F6, Control + Tab)
4566 // 2. This is the top-level document (Note that we may start from
4568 // 3. We've searched through the this top-level document already
4569 if (!GetRootForChildDocument(currentContent
)) {
4570 // We'd like to focus the first focusable element of this
4571 // top-level chrome document.
4572 if (currentContent
== aRootContent
||
4573 currentContent
!= startContent
) {
4574 NS_ADDREF(*aResultContent
= currentContent
);
4580 } else if (aOriginalStartContent
&&
4581 currentContent
== aOriginalStartContent
) {
4582 // not focusable, so return if we have wrapped around to the original
4583 // content. This is necessary in case the original starting content was
4586 // FIXME: Shouldn't this return null instead? currentContent isn't
4587 // focusable after all.
4588 NS_ADDREF(*aResultContent
= currentContent
);
4592 // Move to the next or previous frame, but ignore continuation frames
4593 // since only the first frame should be involved in focusability.
4594 // Otherwise, a loop will occur in the following example:
4595 // <span tabindex="1">...<a/><a/>...</span>
4596 // where the text wraps onto multiple lines. Tabbing from the second
4597 // link can find one of the span's continuation frames between the link
4598 // and the end of the span, and the span would end up getting focused
4602 frameIterator
->Next();
4604 frameIterator
->Prev();
4606 frame
= frameIterator
->CurrentItem();
4607 } while (frame
&& frame
->GetPrevContinuation());
4610 // If already at lowest priority tab (0), end search completely.
4611 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4612 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4613 // if going backwards, the canvas should be focused once the beginning
4614 // has been reached, so get the root element.
4615 if (!aForward
&& !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4616 nsCOMPtr
<nsPIDOMWindowOuter
> window
= GetCurrentWindow(aRootContent
);
4617 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
4619 RefPtr
<Element
> docRoot
= GetRootForFocus(
4620 window
, aRootContent
->GetComposedDoc(), false, true);
4621 FocusFirst(docRoot
, aResultContent
,
4622 false /* aReachedToEndForDocumentNavigation */);
4627 // continue looking for next highest priority tabindex
4629 GetNextTabIndex(aRootContent
, aCurrentTabIndex
, aForward
);
4630 startContent
= iterStartContent
= aRootContent
;
4631 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(startContent
);
4637 bool nsFocusManager::TryDocumentNavigation(nsIContent
* aCurrentContent
,
4638 bool* aCheckSubDocument
,
4639 nsIContent
** aResultContent
) {
4640 *aCheckSubDocument
= true;
4641 if (RefPtr
<Element
> rootElementForChildDocument
=
4642 GetRootForChildDocument(aCurrentContent
)) {
4643 // If GetRootForChildDocument returned something then call
4644 // FocusFirst to find the root or first element to focus within
4645 // the child document. If this is a frameset though, skip this and
4646 // fall through to normal tab navigation to iterate into
4647 // the frameset's frames and locate the first focusable frame.
4648 if (!rootElementForChildDocument
->IsHTMLElement(nsGkAtoms::frameset
)) {
4649 *aCheckSubDocument
= false;
4650 Unused
<< FocusFirst(rootElementForChildDocument
, aResultContent
,
4651 false /* aReachedToEndForDocumentNavigation */);
4652 return *aResultContent
!= nullptr;
4655 // Set aCheckSubDocument to false, as this was neither a frame
4656 // type element or a child document that was focusable.
4657 *aCheckSubDocument
= false;
4663 bool nsFocusManager::TryToMoveFocusToSubDocument(
4664 nsIContent
* aCurrentContent
, nsIContent
* aOriginalStartContent
,
4665 bool aForward
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4666 bool aReachedToEndForDocumentNavigation
, nsIContent
** aResultContent
) {
4667 Document
* doc
= aCurrentContent
->GetComposedDoc();
4668 NS_ASSERTION(doc
, "content not in document");
4669 Document
* subdoc
= doc
->GetSubDocumentFor(aCurrentContent
);
4670 if (subdoc
&& !subdoc
->EventHandlingSuppressed()) {
4671 if (aForward
&& !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4672 // When tabbing forward into a frame, return the root
4673 // frame so that the canvas becomes focused.
4674 if (nsCOMPtr
<nsPIDOMWindowOuter
> subframe
= subdoc
->GetWindow()) {
4675 *aResultContent
= GetRootForFocus(subframe
, subdoc
, false, true);
4676 if (*aResultContent
) {
4677 NS_ADDREF(*aResultContent
);
4682 if (RefPtr
<Element
> rootElement
= subdoc
->GetRootElement()) {
4683 if (RefPtr
<PresShell
> subPresShell
= subdoc
->GetPresShell()) {
4684 nsresult rv
= GetNextTabbableContent(
4685 subPresShell
, rootElement
, aOriginalStartContent
, rootElement
,
4686 aForward
, (aForward
? 1 : 0), false, aForDocumentNavigation
,
4687 aNavigateByKey
, false, aReachedToEndForDocumentNavigation
,
4689 NS_ENSURE_SUCCESS(rv
, false);
4690 if (*aResultContent
) {
4693 if (rootElement
->IsEditable() &&
4694 StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4695 // Only move to the root element with a valid reason
4696 *aResultContent
= rootElement
;
4697 NS_ADDREF(*aResultContent
);
4706 nsIContent
* nsFocusManager::GetNextTabbableMapArea(bool aForward
,
4707 int32_t aCurrentTabIndex
,
4708 Element
* aImageContent
,
4709 nsIContent
* aStartContent
) {
4710 if (aImageContent
->IsInComposedDoc()) {
4711 HTMLImageElement
* imgElement
= HTMLImageElement::FromNode(aImageContent
);
4712 // The caller should check the element type, so we can assert here.
4713 MOZ_ASSERT(imgElement
);
4715 nsCOMPtr
<nsIContent
> mapContent
= imgElement
->FindImageMap();
4719 // First see if the the start content is in this map
4720 Maybe
<uint32_t> indexOfStartContent
=
4721 mapContent
->ComputeIndexOf(aStartContent
);
4722 nsIContent
* scanStartContent
;
4723 Focusable focusable
;
4724 if (indexOfStartContent
.isNothing() ||
4725 ((focusable
= aStartContent
->IsFocusableWithoutStyle()) &&
4726 focusable
.mTabIndex
!= aCurrentTabIndex
)) {
4727 // If aStartContent is in this map we must start iterating past it.
4728 // We skip the case where aStartContent has tabindex == aStartContent
4729 // since the next tab ordered element might be before it
4730 // (or after for backwards) in the child list.
4732 aForward
? mapContent
->GetFirstChild() : mapContent
->GetLastChild();
4734 scanStartContent
= aForward
? aStartContent
->GetNextSibling()
4735 : aStartContent
->GetPreviousSibling();
4738 for (nsCOMPtr
<nsIContent
> areaContent
= scanStartContent
; areaContent
;
4739 areaContent
= aForward
? areaContent
->GetNextSibling()
4740 : areaContent
->GetPreviousSibling()) {
4741 focusable
= areaContent
->IsFocusableWithoutStyle();
4742 if (focusable
&& focusable
.mTabIndex
== aCurrentTabIndex
) {
4751 int32_t nsFocusManager::GetNextTabIndex(nsIContent
* aParent
,
4752 int32_t aCurrentTabIndex
,
4754 int32_t tabIndex
, childTabIndex
;
4755 StyleChildrenIterator
iter(aParent
);
4759 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4760 child
= iter
.GetNextChild()) {
4761 // Skip child's descendants if child is a shadow host or slot, as they are
4762 // in the focus navigation scope owned by child's shadow root
4763 if (!IsHostOrSlot(child
)) {
4764 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4765 if (childTabIndex
> aCurrentTabIndex
&& childTabIndex
!= tabIndex
) {
4766 tabIndex
= (tabIndex
== 0 || childTabIndex
< tabIndex
) ? childTabIndex
4771 nsAutoString tabIndexStr
;
4772 if (child
->IsElement()) {
4773 child
->AsElement()->GetAttr(nsGkAtoms::tabindex
, tabIndexStr
);
4776 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4777 if (NS_SUCCEEDED(ec
) && val
> aCurrentTabIndex
&& val
!= tabIndex
) {
4778 tabIndex
= (tabIndex
== 0 || val
< tabIndex
) ? val
: tabIndex
;
4781 } else { /* !aForward */
4783 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4784 child
= iter
.GetNextChild()) {
4785 // Skip child's descendants if child is a shadow host or slot, as they are
4786 // in the focus navigation scope owned by child's shadow root
4787 if (!IsHostOrSlot(child
)) {
4788 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4789 if ((aCurrentTabIndex
== 0 && childTabIndex
> tabIndex
) ||
4790 (childTabIndex
< aCurrentTabIndex
&& childTabIndex
> tabIndex
)) {
4791 tabIndex
= childTabIndex
;
4795 nsAutoString tabIndexStr
;
4796 if (child
->IsElement()) {
4797 child
->AsElement()->GetAttr(nsGkAtoms::tabindex
, tabIndexStr
);
4800 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4801 if (NS_SUCCEEDED(ec
)) {
4802 if ((aCurrentTabIndex
== 0 && val
> tabIndex
) ||
4803 (val
< aCurrentTabIndex
&& val
> tabIndex
)) {
4813 nsresult
nsFocusManager::FocusFirst(Element
* aRootElement
,
4814 nsIContent
** aNextContent
,
4815 bool aReachedToEndForDocumentNavigation
) {
4816 if (!aRootElement
) {
4820 Document
* doc
= aRootElement
->GetComposedDoc();
4822 if (nsContentUtils::IsChromeDoc(doc
)) {
4823 // If the redirectdocumentfocus attribute is set, redirect the focus to a
4824 // specific element. This is primarily used to retarget the focus to the
4825 // urlbar during document navigation.
4826 nsAutoString retarget
;
4828 if (aRootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
4829 RefPtr
<Element
> element
= doc
->GetElementById(retarget
);
4830 nsCOMPtr
<nsIContent
> retargetElement
=
4831 FlushAndCheckIfFocusable(element
, 0);
4832 if (retargetElement
) {
4833 retargetElement
.forget(aNextContent
);
4839 nsCOMPtr
<nsIDocShell
> docShell
= doc
->GetDocShell();
4840 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4841 // If the found content is in a chrome shell, navigate forward one
4842 // tabbable item so that the first item is focused. Note that we
4843 // always go forward and not back here.
4844 if (RefPtr
<PresShell
> presShell
= doc
->GetPresShell()) {
4845 return GetNextTabbableContent(
4846 presShell
, aRootElement
, nullptr, aRootElement
, true, 1, false,
4847 StaticPrefs::dom_disable_tab_focus_to_root_element()
4848 ? aReachedToEndForDocumentNavigation
4850 true, false, aReachedToEndForDocumentNavigation
, aNextContent
);
4855 NS_ADDREF(*aNextContent
= aRootElement
);
4859 Element
* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter
* aWindow
,
4860 Document
* aDocument
,
4861 bool aForDocumentNavigation
,
4862 bool aCheckVisibility
) {
4863 if (!aForDocumentNavigation
) {
4864 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
4865 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4870 if (aCheckVisibility
&& !IsWindowVisible(aWindow
)) return nullptr;
4872 // If the body is contenteditable, use the editor's root element rather than
4873 // the actual root element.
4874 RefPtr
<Element
> rootElement
=
4875 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument
);
4876 if (!rootElement
|| !rootElement
->GetPrimaryFrame()) {
4877 rootElement
= aDocument
->GetRootElement();
4883 if (aCheckVisibility
&& !rootElement
->GetPrimaryFrame()) {
4887 // Finally, check if this is a frameset
4888 if (aDocument
&& aDocument
->IsHTMLOrXHTML()) {
4889 Element
* htmlChild
= aDocument
->GetHtmlChildElement(nsGkAtoms::frameset
);
4891 // In document navigation mode, return the frameset so that navigation
4892 // descends into the child frames.
4893 return aForDocumentNavigation
? htmlChild
: nullptr;
4900 Element
* nsFocusManager::GetRootForChildDocument(nsIContent
* aContent
) {
4901 // Check for elements that represent child documents, that is, browsers,
4902 // editors or frames from a frameset. We don't include iframes since we
4903 // consider them to be an integral part of the same window or page.
4904 if (!aContent
|| !(aContent
->IsXULElement(nsGkAtoms::browser
) ||
4905 aContent
->IsXULElement(nsGkAtoms::editor
) ||
4906 aContent
->IsHTMLElement(nsGkAtoms::frame
))) {
4910 Document
* doc
= aContent
->GetComposedDoc();
4915 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
4916 if (!subdoc
|| subdoc
->EventHandlingSuppressed()) {
4920 nsCOMPtr
<nsPIDOMWindowOuter
> window
= subdoc
->GetWindow();
4921 return GetRootForFocus(window
, subdoc
, true, true);
4924 static bool IsLink(nsIContent
* aContent
) {
4925 return aContent
->IsElement() && aContent
->AsElement()->IsLink();
4928 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter
* aWindow
,
4929 nsIContent
* aStartSelection
,
4930 nsIContent
* aEndSelection
,
4931 nsIContent
** aFocusedContent
) {
4932 *aFocusedContent
= nullptr;
4934 nsCOMPtr
<nsIContent
> testContent
= aStartSelection
;
4935 nsCOMPtr
<nsIContent
> nextTestContent
= aEndSelection
;
4937 nsCOMPtr
<nsIContent
> currentFocus
= aWindow
->GetFocusedElement();
4939 // We now have the correct start node in selectionContent!
4940 // Search for focusable elements, starting with selectionContent
4942 // Method #1: Keep going up while we look - an ancestor might be focusable
4943 // We could end the loop earlier, such as when we're no longer
4944 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4945 // with a variable holding the starting selectionContent
4946 while (testContent
) {
4947 // Keep testing while selectionContent is equal to something,
4948 // eventually we'll run out of ancestors
4950 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4951 testContent
.forget(aFocusedContent
);
4956 testContent
= testContent
->GetParent();
4959 // We run this loop again, checking the ancestor chain of the selection's
4961 testContent
= nextTestContent
;
4962 nextTestContent
= nullptr;
4966 // We couldn't find an anchor that was an ancestor of the selection start
4967 // Method #2: look for anchor in selection's primary range (depth first
4970 nsCOMPtr
<nsIContent
> selectionNode
= aStartSelection
;
4971 nsCOMPtr
<nsIContent
> endSelectionNode
= aEndSelection
;
4972 nsCOMPtr
<nsIContent
> testNode
;
4975 testContent
= selectionNode
;
4977 // We're looking for any focusable link that could be part of the
4978 // main document's selection.
4979 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4980 testContent
.forget(aFocusedContent
);
4984 nsIContent
* testNode
= selectionNode
->GetFirstChild();
4986 selectionNode
= testNode
;
4990 if (selectionNode
== endSelectionNode
) {
4993 testNode
= selectionNode
->GetNextSibling();
4995 selectionNode
= testNode
;
5000 // GetParent is OK here, instead of GetParentNode, because the only case
5001 // where the latter returns something different from the former is when
5002 // GetParentNode is the document. But in that case we would simply get
5003 // null for selectionNode when setting it to testNode->GetNextSibling()
5004 // (because a document has no next sibling). And then the next iteration
5005 // of this loop would get null for GetParentNode anyway, and break out of
5007 testNode
= selectionNode
->GetParent();
5008 if (!testNode
|| testNode
== endSelectionNode
) {
5009 selectionNode
= nullptr;
5012 selectionNode
= testNode
->GetNextSibling();
5013 if (selectionNode
) {
5016 selectionNode
= testNode
;
5018 } while (selectionNode
&& selectionNode
!= endSelectionNode
);
5021 static void MaybeUnlockPointer(BrowsingContext
* aCurrentFocusedContext
) {
5022 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext
)) {
5023 PointerLockManager::Unlock();
5027 class PointerUnlocker
: public Runnable
{
5029 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
5030 MOZ_ASSERT(XRE_IsParentProcess());
5031 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker
);
5032 PointerUnlocker::sActiveUnlocker
= this;
5035 ~PointerUnlocker() {
5036 if (PointerUnlocker::sActiveUnlocker
== this) {
5037 PointerUnlocker::sActiveUnlocker
= nullptr;
5041 NS_IMETHOD
Run() override
{
5042 if (PointerUnlocker::sActiveUnlocker
== this) {
5043 PointerUnlocker::sActiveUnlocker
= nullptr;
5045 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
5046 nsPIDOMWindowOuter
* focused
=
5047 nsFocusManager::GetFocusManager()->GetFocusedWindow();
5048 MaybeUnlockPointer(focused
? focused
->GetBrowsingContext() : nullptr);
5052 static PointerUnlocker
* sActiveUnlocker
;
5055 PointerUnlocker
* PointerUnlocker::sActiveUnlocker
= nullptr;
5057 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext
* aContext
,
5058 uint64_t aActionId
) {
5059 if (XRE_IsParentProcess()) {
5062 MOZ_ASSERT(!ActionIdComparableAndLower(
5063 aActionId
, mActionIdForFocusedBrowsingContextInContent
));
5064 mFocusedBrowsingContextInContent
= aContext
;
5065 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
5067 // We don't send the unset but instead expect the set from
5068 // elsewhere to take care of it. XXX Is that bad?
5069 MOZ_ASSERT(aContext
->IsInProcess());
5070 mozilla::dom::ContentChild
* contentChild
=
5071 mozilla::dom::ContentChild::GetSingleton();
5072 MOZ_ASSERT(contentChild
);
5073 contentChild
->SendSetFocusedBrowsingContext(aContext
, aActionId
);
5077 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
5078 BrowsingContext
* aContext
, uint64_t aActionId
) {
5079 MOZ_ASSERT(!XRE_IsParentProcess());
5080 MOZ_ASSERT(aContext
);
5081 if (ActionIdComparableAndLower(aActionId
,
5082 mActionIdForFocusedBrowsingContextInContent
)) {
5083 // Unclear if this ever happens.
5085 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5086 "focused from another process due to stale action id %" PRIu64
".",
5087 aContext
, aActionId
));
5090 if (aContext
->IsInProcess()) {
5091 // This message has been in transit for long enough that
5092 // the process association of aContext has changed since
5093 // the other content process sent the message, because
5094 // an iframe in that process became an out-of-process
5095 // iframe while the IPC broadcast that we're receiving
5096 // was in-flight. Let's just ignore this.
5098 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5099 "focused from another process, actionid: %" PRIu64
".",
5100 aContext
, aActionId
));
5103 mFocusedBrowsingContextInContent
= aContext
;
5104 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
5105 mFocusedElement
= nullptr;
5106 mFocusedWindow
= nullptr;
5109 bool nsFocusManager::SetFocusedBrowsingContextInChrome(
5110 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5111 MOZ_ASSERT(aActionId
);
5112 if (ProcessPendingFocusedBrowsingContextActionId(aActionId
)) {
5113 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5114 aActionId
, mActionIdForFocusedBrowsingContextInChrome
));
5115 mFocusedBrowsingContextInChrome
= aContext
;
5116 mActionIdForFocusedBrowsingContextInChrome
= aActionId
;
5122 BrowsingContext
* nsFocusManager::GetFocusedBrowsingContextInChrome() {
5123 return mFocusedBrowsingContextInChrome
;
5126 void nsFocusManager::BrowsingContextDetached(BrowsingContext
* aContext
) {
5127 if (mFocusedBrowsingContextInChrome
== aContext
) {
5128 mFocusedBrowsingContextInChrome
= nullptr;
5129 // Deliberately not adjusting the corresponding action id, because
5130 // we don't want changes from the past to take effect.
5132 if (mActiveBrowsingContextInChrome
== aContext
) {
5133 mActiveBrowsingContextInChrome
= nullptr;
5134 // Deliberately not adjusting the corresponding action id, because
5135 // we don't want changes from the past to take effect.
5139 void nsFocusManager::SetActiveBrowsingContextInContent(
5140 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5141 MOZ_ASSERT(!XRE_IsParentProcess());
5142 MOZ_ASSERT(!aContext
|| aContext
->IsInProcess());
5143 mozilla::dom::ContentChild
* contentChild
=
5144 mozilla::dom::ContentChild::GetSingleton();
5145 MOZ_ASSERT(contentChild
);
5147 if (ActionIdComparableAndLower(aActionId
,
5148 mActionIdForActiveBrowsingContextInContent
)) {
5150 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5151 "the active browsing context due to a stale action id %" PRIu64
".",
5152 aContext
, aActionId
));
5156 if (aContext
!= mActiveBrowsingContextInContent
) {
5158 contentChild
->SendSetActiveBrowsingContext(aContext
, aActionId
);
5159 } else if (mActiveBrowsingContextInContent
) {
5160 // We want to sync this over only if this isn't happening
5161 // due to the active BrowsingContext switching processes,
5162 // in which case the BrowserChild has already marked itself
5164 nsPIDOMWindowOuter
* outer
=
5165 mActiveBrowsingContextInContent
->GetDOMWindow();
5167 nsPIDOMWindowInner
* inner
= outer
->GetCurrentInnerWindow();
5169 WindowGlobalChild
* globalChild
= inner
->GetWindowGlobalChild();
5171 RefPtr
<BrowserChild
> browserChild
= globalChild
->GetBrowserChild();
5172 if (browserChild
&& !browserChild
->IsDestroyed()) {
5173 contentChild
->SendUnsetActiveBrowsingContext(
5174 mActiveBrowsingContextInContent
, aActionId
);
5181 mActiveBrowsingContextInContentSetFromOtherProcess
= false;
5182 mActiveBrowsingContextInContent
= aContext
;
5183 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5184 MaybeUnlockPointer(aContext
);
5187 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
5188 BrowsingContext
* aContext
, uint64_t aActionId
) {
5189 MOZ_ASSERT(!XRE_IsParentProcess());
5190 MOZ_ASSERT(aContext
);
5191 if (ActionIdComparableAndLower(aActionId
,
5192 mActionIdForActiveBrowsingContextInContent
)) {
5194 ("Ignored an attempt to set active BrowsingContext [%p] from "
5195 "another process due to a stale action id %" PRIu64
".",
5196 aContext
, aActionId
));
5199 if (aContext
->IsInProcess()) {
5200 // This message has been in transit for long enough that
5201 // the process association of aContext has changed since
5202 // the other content process sent the message, because
5203 // an iframe in that process became an out-of-process
5204 // iframe while the IPC broadcast that we're receiving
5205 // was in-flight. Let's just ignore this.
5207 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5208 "active from another process. actionid: %" PRIu64
,
5209 aContext
, aActionId
));
5212 mActiveBrowsingContextInContentSetFromOtherProcess
= true;
5213 mActiveBrowsingContextInContent
= aContext
;
5214 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5215 MaybeUnlockPointer(aContext
);
5218 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
5219 BrowsingContext
* aContext
, uint64_t aActionId
) {
5220 MOZ_ASSERT(!XRE_IsParentProcess());
5221 MOZ_ASSERT(aContext
);
5222 if (ActionIdComparableAndLower(aActionId
,
5223 mActionIdForActiveBrowsingContextInContent
)) {
5225 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5226 "another process due to stale action id: %" PRIu64
".",
5227 aContext
, aActionId
));
5230 if (mActiveBrowsingContextInContent
== aContext
) {
5231 mActiveBrowsingContextInContent
= nullptr;
5232 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5233 MaybeUnlockPointer(nullptr);
5236 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5237 "another process. actionid: %" PRIu64
,
5238 aContext
, aActionId
));
5242 void nsFocusManager::ReviseActiveBrowsingContext(
5243 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5244 uint64_t aNewActionId
) {
5245 MOZ_ASSERT(XRE_IsContentProcess());
5246 if (mActionIdForActiveBrowsingContextInContent
== aOldActionId
) {
5247 LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64
5249 "actionid: %" PRIu64
,
5250 aContext
, aOldActionId
, aNewActionId
));
5251 mActiveBrowsingContextInContent
= aContext
;
5252 mActionIdForActiveBrowsingContextInContent
= aNewActionId
;
5255 ("Ignored a stale attempt to revise the active BrowsingContext [%p]. "
5256 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5257 aContext
, aOldActionId
, aNewActionId
));
5261 void nsFocusManager::ReviseFocusedBrowsingContext(
5262 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5263 uint64_t aNewActionId
) {
5264 MOZ_ASSERT(XRE_IsContentProcess());
5265 if (mActionIdForFocusedBrowsingContextInContent
== aOldActionId
) {
5267 ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64
5269 "actionid: %" PRIu64
,
5270 aContext
, aOldActionId
, aNewActionId
));
5271 mFocusedBrowsingContextInContent
= aContext
;
5272 mActionIdForFocusedBrowsingContextInContent
= aNewActionId
;
5273 mFocusedElement
= nullptr;
5276 ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. "
5277 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5278 aContext
, aOldActionId
, aNewActionId
));
5282 bool nsFocusManager::SetActiveBrowsingContextInChrome(
5283 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5284 MOZ_ASSERT(aActionId
);
5285 if (ProcessPendingActiveBrowsingContextActionId(aActionId
, aContext
)) {
5286 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5287 aActionId
, mActionIdForActiveBrowsingContextInChrome
));
5288 mActiveBrowsingContextInChrome
= aContext
;
5289 mActionIdForActiveBrowsingContextInChrome
= aActionId
;
5295 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
5296 return mActionIdForActiveBrowsingContextInChrome
;
5299 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
5300 return mActionIdForFocusedBrowsingContextInChrome
;
5303 BrowsingContext
* nsFocusManager::GetActiveBrowsingContextInChrome() {
5304 return mActiveBrowsingContextInChrome
;
5307 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId
) {
5308 LOGFOCUS(("InsertNewFocusActionId %" PRIu64
, aActionId
));
5309 MOZ_ASSERT(XRE_IsParentProcess());
5310 MOZ_ASSERT(!mPendingActiveBrowsingContextActions
.Contains(aActionId
));
5311 mPendingActiveBrowsingContextActions
.AppendElement(aActionId
);
5312 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions
.Contains(aActionId
));
5313 mPendingFocusedBrowsingContextActions
.AppendElement(aActionId
);
5316 static void RemoveContentInitiatedActionsUntil(
5317 nsTArray
<uint64_t>& aPendingActions
,
5318 nsTArray
<uint64_t>::index_type aUntil
) {
5319 nsTArray
<uint64_t>::index_type i
= 0;
5320 while (i
< aUntil
) {
5321 auto [actionProc
, actionId
] =
5322 nsContentUtils::SplitProcessSpecificId(aPendingActions
[i
]);
5325 aPendingActions
.RemoveElementAt(i
);
5333 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
5334 uint64_t aActionId
, bool aSettingToNonNull
) {
5335 MOZ_ASSERT(XRE_IsParentProcess());
5336 auto index
= mPendingActiveBrowsingContextActions
.IndexOf(aActionId
);
5337 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5340 // When aSettingToNonNull is true, we need to remove one more
5341 // element to remove the action id itself in addition to
5342 // removing the older ones.
5343 if (aSettingToNonNull
) {
5346 auto [actionProc
, actionId
] =
5347 nsContentUtils::SplitProcessSpecificId(aActionId
);
5350 // Action from content: We allow parent-initiated actions
5351 // to take precedence over content-initiated ones, so we
5352 // remove only prior content-initiated actions.
5353 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions
,
5356 // Action from chrome
5357 mPendingActiveBrowsingContextActions
.RemoveElementsAt(0, index
);
5362 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
5363 uint64_t aActionId
) {
5364 MOZ_ASSERT(XRE_IsParentProcess());
5365 auto index
= mPendingFocusedBrowsingContextActions
.IndexOf(aActionId
);
5366 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5370 auto [actionProc
, actionId
] =
5371 nsContentUtils::SplitProcessSpecificId(aActionId
);
5374 // Action from content: We allow parent-initiated actions
5375 // to take precedence over content-initiated ones, so we
5376 // remove only prior content-initiated actions.
5377 RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions
,
5380 // Action from chrome
5381 mPendingFocusedBrowsingContextActions
.RemoveElementsAt(0, index
);
5387 uint64_t nsFocusManager::GenerateFocusActionId() {
5389 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter
);
5390 if (XRE_IsParentProcess()) {
5391 nsFocusManager
* fm
= GetFocusManager();
5393 fm
->InsertNewFocusActionId(id
);
5396 mozilla::dom::ContentChild
* contentChild
=
5397 mozilla::dom::ContentChild::GetSingleton();
5398 MOZ_ASSERT(contentChild
);
5399 contentChild
->SendInsertNewFocusActionId(id
);
5401 LOGFOCUS(("GenerateFocusActionId %" PRIu64
, id
));
5405 static bool IsInPointerLockContext(nsPIDOMWindowOuter
* aWin
) {
5406 return PointerLockManager::IsInLockContext(aWin
? aWin
->GetBrowsingContext()
5410 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter
* aWindow
,
5412 bool aSyncBrowsingContext
) {
5413 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker
&&
5414 IsInPointerLockContext(mFocusedWindow
) &&
5415 !IsInPointerLockContext(aWindow
)) {
5416 nsCOMPtr
<nsIRunnable
> runnable
= new PointerUnlocker();
5417 NS_DispatchToCurrentThread(runnable
);
5420 // Update the last focus time on any affected documents
5421 if (aWindow
&& aWindow
!= mFocusedWindow
) {
5422 const TimeStamp
now(TimeStamp::Now());
5423 for (Document
* doc
= aWindow
->GetExtantDoc(); doc
;
5424 doc
= doc
->GetInProcessParentDocument()) {
5425 doc
->SetLastFocusTime(now
);
5429 // This function may be called with zero action id to indicate that the
5430 // action id should be ignored.
5431 if (XRE_IsContentProcess() && aActionId
&&
5432 ActionIdComparableAndLower(aActionId
,
5433 mActionIdForFocusedBrowsingContextInContent
)) {
5434 // Unclear if this ever happens.
5436 ("Ignored an attempt to set an in-process BrowsingContext as "
5437 "focused due to stale action id %" PRIu64
".",
5442 mFocusedWindow
= aWindow
;
5443 BrowsingContext
* bc
= aWindow
? aWindow
->GetBrowsingContext() : nullptr;
5444 if (aSyncBrowsingContext
) {
5445 MOZ_ASSERT(aActionId
,
5446 "aActionId must not be zero if aSyncBrowsingContext is true");
5447 SetFocusedBrowsingContext(bc
, aActionId
);
5448 } else if (XRE_IsContentProcess()) {
5449 MOZ_ASSERT(mFocusedBrowsingContextInContent
== bc
,
5450 "Not syncing BrowsingContext even when different.");
5454 void nsFocusManager::NotifyOfReFocus(Element
& aElement
) {
5455 nsPIDOMWindowOuter
* window
= GetCurrentWindow(&aElement
);
5456 if (!window
|| window
!= mFocusedWindow
) {
5459 if (!aElement
.IsInComposedDoc() || IsNonFocusableRoot(&aElement
)) {
5462 nsIDocShell
* docShell
= window
->GetDocShell();
5466 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
5470 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
5474 IMEStateManager::OnReFocus(*presContext
, aElement
);
5477 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration
) {
5482 if (sInstance
->mActiveWindow
) {
5483 sInstance
->mActiveWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5485 if (sInstance
->mFocusedWindow
) {
5486 sInstance
->mFocusedWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5488 if (sInstance
->mWindowBeingLowered
) {
5489 sInstance
->mWindowBeingLowered
->MarkUncollectableForCCGeneration(
5492 if (sInstance
->mFocusedElement
) {
5493 sInstance
->mFocusedElement
->OwnerDoc()->MarkUncollectableForCCGeneration(
5496 if (sInstance
->mFirstBlurEvent
) {
5497 sInstance
->mFirstBlurEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5500 if (sInstance
->mFirstFocusEvent
) {
5501 sInstance
->mFirstFocusEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5506 bool nsFocusManager::CanSkipFocus(nsIContent
* aContent
) {
5511 if (mFocusedElement
== aContent
) {
5515 nsIDocShell
* ds
= aContent
->OwnerDoc()->GetDocShell();
5520 if (XRE_IsParentProcess()) {
5521 nsCOMPtr
<nsIDocShellTreeItem
> root
;
5522 ds
->GetInProcessRootTreeItem(getter_AddRefs(root
));
5523 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
=
5524 root
? root
->GetWindow() : nullptr;
5525 if (mActiveWindow
!= newRootWindow
) {
5526 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5527 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5532 BrowsingContext
* bc
= aContent
->OwnerDoc()->GetBrowsingContext();
5533 BrowsingContext
* top
= bc
? bc
->Top() : nullptr;
5534 if (GetActiveBrowsingContext() != top
) {
5535 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5536 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5545 static IsFocusableFlags
FocusManagerFlagsToIsFocusableFlags(uint32_t aFlags
) {
5546 auto flags
= IsFocusableFlags(0);
5547 if (aFlags
& nsIFocusManager::FLAG_BYMOUSE
) {
5548 flags
|= IsFocusableFlags::WithMouse
;
5554 Element
* nsFocusManager::GetTheFocusableArea(Element
* aTarget
,
5556 MOZ_ASSERT(aTarget
);
5557 nsIFrame
* frame
= aTarget
->GetPrimaryFrame();
5562 // If focus target is the document element of its Document.
5563 if (aTarget
== aTarget
->OwnerDoc()->GetRootElement()) {
5564 // the root content can always be focused,
5565 // except in userfocusignored context.
5569 // If focus target is an area element with one or more shapes that are
5571 if (auto* area
= HTMLAreaElement::FromNode(aTarget
)) {
5572 return IsAreaElementFocusable(*area
) ? area
: nullptr;
5575 // For these 3 steps mentioned in the spec
5576 // 1. If focus target is an element with one or more scrollable regions that
5577 // are focusable areas
5578 // 2. If focus target is a navigable
5579 // 3. If focus target is a navigable container with a non-null content
5581 // nsIFrame::IsFocusable will effectively perform the checks for them.
5582 IsFocusableFlags flags
= FocusManagerFlagsToIsFocusableFlags(aFlags
);
5583 if (frame
->IsFocusable(flags
)) {
5587 // If focus target is a shadow host whose shadow root's delegates focus is
5589 if (ShadowRoot
* root
= aTarget
->GetShadowRoot()) {
5590 if (root
->DelegatesFocus()) {
5591 // If focus target is a shadow-including inclusive ancestor of the
5592 // currently focused area of a top-level browsing context's DOM anchor,
5593 // then return the already-focused element.
5594 if (nsPIDOMWindowInner
* innerWindow
=
5595 aTarget
->OwnerDoc()->GetInnerWindow()) {
5596 if (Element
* focusedElement
= innerWindow
->GetFocusedElement()) {
5597 if (focusedElement
->IsShadowIncludingInclusiveDescendantOf(aTarget
)) {
5598 return focusedElement
;
5603 if (Element
* firstFocusable
= root
->GetFocusDelegate(flags
)) {
5604 return firstFocusable
;
5612 bool nsFocusManager::IsAreaElementFocusable(HTMLAreaElement
& aArea
) {
5613 nsIFrame
* frame
= aArea
.GetPrimaryFrame();
5617 // HTML areas do not have their own frame, and the img frame we get from
5618 // GetPrimaryFrame() is not relevant as to whether it is focusable or
5619 // not, so we have to do all the relevant checks manually for them.
5620 return frame
->IsVisibleConsideringAncestors() &&
5621 aArea
.IsFocusableWithoutStyle();
5624 nsresult
NS_NewFocusManager(nsIFocusManager
** aResult
) {
5625 NS_IF_ADDREF(*aResult
= nsFocusManager::GetFocusManager());