1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/BrowserParent.h"
9 #include "nsFocusManager.h"
11 #include "LayoutConstants.h"
12 #include "ChildIterator.h"
13 #include "nsIInterfaceRequestorUtils.h"
14 #include "nsGkAtoms.h"
15 #include "nsContentUtils.h"
16 #include "ContentParent.h"
17 #include "nsPIDOMWindow.h"
18 #include "nsIContentInlines.h"
19 #include "nsIDocShell.h"
20 #include "nsIDocShellTreeOwner.h"
21 #include "nsIFormControl.h"
22 #include "nsLayoutUtils.h"
23 #include "nsFrameTraversal.h"
24 #include "nsIWebNavigation.h"
26 #include "nsIBaseWindow.h"
27 #include "nsIAppWindow.h"
28 #include "nsTextControlFrame.h"
29 #include "nsViewManager.h"
30 #include "nsFrameSelection.h"
31 #include "mozilla/dom/Selection.h"
32 #include "nsXULPopupManager.h"
33 #include "nsMenuPopupFrame.h"
34 #include "nsIScriptError.h"
35 #include "nsIScriptObjectPrincipal.h"
36 #include "nsIPrincipal.h"
37 #include "nsIObserverService.h"
38 #include "BrowserChild.h"
39 #include "nsFrameLoader.h"
40 #include "nsHTMLDocument.h"
41 #include "nsNetUtil.h"
43 #include "nsFrameLoaderOwner.h"
44 #include "nsQueryObject.h"
46 #include "mozilla/AccessibleCaretEventHub.h"
47 #include "mozilla/ContentEvents.h"
48 #include "mozilla/dom/ContentChild.h"
49 #include "mozilla/dom/Document.h"
50 #include "mozilla/dom/DocumentInlines.h"
51 #include "mozilla/dom/Element.h"
52 #include "mozilla/dom/ElementBinding.h"
53 #include "mozilla/dom/HTMLImageElement.h"
54 #include "mozilla/dom/HTMLInputElement.h"
55 #include "mozilla/dom/HTMLSlotElement.h"
56 #include "mozilla/dom/HTMLAreaElement.h"
57 #include "mozilla/dom/BrowserBridgeChild.h"
58 #include "mozilla/dom/Text.h"
59 #include "mozilla/dom/XULPopupElement.h"
60 #include "mozilla/dom/WindowGlobalParent.h"
61 #include "mozilla/dom/WindowGlobalChild.h"
62 #include "mozilla/EventDispatcher.h"
63 #include "mozilla/EventStateManager.h"
64 #include "mozilla/HTMLEditor.h"
65 #include "mozilla/IMEStateManager.h"
66 #include "mozilla/LookAndFeel.h"
67 #include "mozilla/Maybe.h"
68 #include "mozilla/PointerLockManager.h"
69 #include "mozilla/Preferences.h"
70 #include "mozilla/PresShell.h"
71 #include "mozilla/Services.h"
72 #include "mozilla/Unused.h"
73 #include "mozilla/StaticPrefs_full_screen_api.h"
74 #include "mozilla/Try.h"
75 #include "mozilla/widget/IMEData.h"
78 #include "nsIDOMXULMenuListElement.h"
81 # include "nsAccessibilityService.h"
84 using namespace mozilla
;
85 using namespace mozilla::dom
;
86 using namespace mozilla::widget
;
88 // Two types of focus pr logging are available:
89 // 'Focus' for normal focus manager calls
90 // 'FocusNavigation' for tab and document navigation
91 LazyLogModule
gFocusLog("Focus");
92 LazyLogModule
gFocusNavigationLog("FocusNavigation");
94 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
95 #define LOGFOCUSNAVIGATION(args) \
96 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
98 #define LOGTAG(log, format, content) \
99 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
100 nsAutoCString tag("(none)"_ns); \
102 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
104 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
107 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
108 #define LOGCONTENTNAVIGATION(format, content) \
109 LOGTAG(gFocusNavigationLog, format, content)
111 struct nsDelayedBlurOrFocusEvent
{
112 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage
, PresShell
* aPresShell
,
113 Document
* aDocument
, EventTarget
* aTarget
,
114 EventTarget
* aRelatedTarget
)
115 : mPresShell(aPresShell
),
116 mDocument(aDocument
),
118 mEventMessage(aEventMessage
),
119 mRelatedTarget(aRelatedTarget
) {}
121 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent
& aOther
)
122 : mPresShell(aOther
.mPresShell
),
123 mDocument(aOther
.mDocument
),
124 mTarget(aOther
.mTarget
),
125 mEventMessage(aOther
.mEventMessage
) {}
127 RefPtr
<PresShell
> mPresShell
;
128 nsCOMPtr
<Document
> mDocument
;
129 nsCOMPtr
<EventTarget
> mTarget
;
130 EventMessage mEventMessage
;
131 nsCOMPtr
<EventTarget
> mRelatedTarget
;
134 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent
& aField
) {
135 aField
.mPresShell
= nullptr;
136 aField
.mDocument
= nullptr;
137 aField
.mTarget
= nullptr;
138 aField
.mRelatedTarget
= nullptr;
141 inline void ImplCycleCollectionTraverse(
142 nsCycleCollectionTraversalCallback
& aCallback
,
143 nsDelayedBlurOrFocusEvent
& aField
, const char* aName
, uint32_t aFlags
= 0) {
144 CycleCollectionNoteChild(
145 aCallback
, static_cast<nsIDocumentObserver
*>(aField
.mPresShell
.get()),
147 CycleCollectionNoteChild(aCallback
, aField
.mDocument
.get(), aName
, aFlags
);
148 CycleCollectionNoteChild(aCallback
, aField
.mTarget
.get(), aName
, aFlags
);
149 CycleCollectionNoteChild(aCallback
, aField
.mRelatedTarget
.get(), aName
,
153 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager
)
154 NS_INTERFACE_MAP_ENTRY(nsIFocusManager
)
155 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
156 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
157 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIFocusManager
)
160 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager
)
161 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager
)
163 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager
, mActiveWindow
,
164 mActiveBrowsingContextInContent
,
165 mActiveBrowsingContextInChrome
, mFocusedWindow
,
166 mFocusedBrowsingContextInContent
,
167 mFocusedBrowsingContextInChrome
, mFocusedElement
,
168 mFirstBlurEvent
, mFirstFocusEvent
,
169 mWindowBeingLowered
, mDelayedBlurFocusEvents
)
171 StaticRefPtr
<nsFocusManager
> nsFocusManager::sInstance
;
172 bool nsFocusManager::sTestMode
= false;
173 uint64_t nsFocusManager::sFocusActionCounter
= 0;
175 static const char* kObservedPrefs
[] = {"accessibility.browsewithcaret",
176 "accessibility.tabfocus_applies_to_xul",
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 nsIContent::sTabFocusModelAppliesToXUL
=
202 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
203 nsIContent::sTabFocusModelAppliesToXUL
);
205 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
207 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
210 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
212 obs
->AddObserver(sInstance
, "xpcom-shutdown", true);
219 void nsFocusManager::Shutdown() { sInstance
= nullptr; }
222 void nsFocusManager::PrefChanged(const char* aPref
, void* aSelf
) {
223 if (RefPtr
<nsFocusManager
> fm
= static_cast<nsFocusManager
*>(aSelf
)) {
224 fm
->PrefChanged(aPref
);
228 void nsFocusManager::PrefChanged(const char* aPref
) {
229 nsDependentCString
pref(aPref
);
230 if (pref
.EqualsLiteral("accessibility.browsewithcaret")) {
231 UpdateCaretForCaretBrowsingMode();
232 } else if (pref
.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
233 nsIContent::sTabFocusModelAppliesToXUL
=
234 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
235 nsIContent::sTabFocusModelAppliesToXUL
);
236 } else if (pref
.EqualsLiteral("focusmanager.testmode")) {
237 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
242 nsFocusManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
243 const char16_t
* aData
) {
244 if (!nsCRT::strcmp(aTopic
, "xpcom-shutdown")) {
245 mActiveWindow
= nullptr;
246 mActiveBrowsingContextInContent
= nullptr;
247 mActionIdForActiveBrowsingContextInContent
= 0;
248 mActionIdForFocusedBrowsingContextInContent
= 0;
249 mActiveBrowsingContextInChrome
= nullptr;
250 mActionIdForActiveBrowsingContextInChrome
= 0;
251 mActionIdForFocusedBrowsingContextInChrome
= 0;
252 mFocusedWindow
= nullptr;
253 mFocusedBrowsingContextInContent
= nullptr;
254 mFocusedBrowsingContextInChrome
= nullptr;
255 mFocusedElement
= nullptr;
256 mFirstBlurEvent
= nullptr;
257 mFirstFocusEvent
= nullptr;
258 mWindowBeingLowered
= nullptr;
259 mDelayedBlurFocusEvents
.Clear();
265 static bool ActionIdComparableAndLower(uint64_t aActionId
,
266 uint64_t aReference
) {
267 MOZ_ASSERT(aActionId
, "Uninitialized action id");
268 auto [actionProc
, actionId
] =
269 nsContentUtils::SplitProcessSpecificId(aActionId
);
270 auto [refProc
, refId
] = nsContentUtils::SplitProcessSpecificId(aReference
);
271 return actionProc
== refProc
&& actionId
< refId
;
274 // given a frame content node, retrieve the nsIDOMWindow displayed in it
275 static nsPIDOMWindowOuter
* GetContentWindow(nsIContent
* aContent
) {
276 Document
* doc
= aContent
->GetComposedDoc();
278 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
280 return subdoc
->GetWindow();
287 bool nsFocusManager::IsFocused(nsIContent
* aContent
) {
288 if (!aContent
|| !mFocusedElement
) {
291 return aContent
== mFocusedElement
;
294 bool nsFocusManager::IsTestMode() { return sTestMode
; }
296 bool nsFocusManager::IsInActiveWindow(BrowsingContext
* aBC
) const {
297 RefPtr
<BrowsingContext
> top
= aBC
->Top();
298 if (XRE_IsParentProcess()) {
299 top
= top
->Canonical()->TopCrossChromeBoundary();
301 return IsSameOrAncestor(top
, GetActiveBrowsingContext());
304 // get the current window for the given content node
305 static nsPIDOMWindowOuter
* GetCurrentWindow(nsIContent
* aContent
) {
306 Document
* doc
= aContent
->GetComposedDoc();
307 return doc
? doc
->GetWindow() : nullptr;
311 Element
* nsFocusManager::GetFocusedDescendant(
312 nsPIDOMWindowOuter
* aWindow
, SearchRange aSearchRange
,
313 nsPIDOMWindowOuter
** aFocusedWindow
) {
314 NS_ENSURE_TRUE(aWindow
, nullptr);
316 *aFocusedWindow
= nullptr;
318 Element
* currentElement
= nullptr;
319 nsPIDOMWindowOuter
* window
= aWindow
;
321 *aFocusedWindow
= window
;
322 currentElement
= window
->GetFocusedElement();
323 if (!currentElement
|| aSearchRange
== eOnlyCurrentWindow
) {
327 window
= GetContentWindow(currentElement
);
332 if (aSearchRange
== eIncludeAllDescendants
) {
336 MOZ_ASSERT(aSearchRange
== eIncludeVisibleDescendants
);
338 // If the child window doesn't have PresShell, it means the window is
340 nsIDocShell
* docShell
= window
->GetDocShell();
344 if (!docShell
->GetPresShell()) {
349 NS_IF_ADDREF(*aFocusedWindow
);
351 return currentElement
;
355 InputContextAction::Cause
nsFocusManager::GetFocusMoveActionCause(
357 if (aFlags
& nsIFocusManager::FLAG_BYTOUCH
) {
358 return InputContextAction::CAUSE_TOUCH
;
359 } else if (aFlags
& nsIFocusManager::FLAG_BYMOUSE
) {
360 return InputContextAction::CAUSE_MOUSE
;
361 } else if (aFlags
& nsIFocusManager::FLAG_BYKEY
) {
362 return InputContextAction::CAUSE_KEY
;
363 } else if (aFlags
& nsIFocusManager::FLAG_BYLONGPRESS
) {
364 return InputContextAction::CAUSE_LONGPRESS
;
366 return InputContextAction::CAUSE_UNKNOWN
;
370 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy
** aWindow
) {
371 MOZ_ASSERT(XRE_IsParentProcess(),
372 "Must not be called outside the parent process.");
373 NS_IF_ADDREF(*aWindow
= mActiveWindow
);
378 nsFocusManager::GetActiveBrowsingContext(BrowsingContext
** aBrowsingContext
) {
379 NS_IF_ADDREF(*aBrowsingContext
= GetActiveBrowsingContext());
383 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter
* aWindow
,
384 CallerType aCallerType
) {
385 if (RefPtr
<nsFocusManager
> fm
= sInstance
) {
386 fm
->SetFocusedWindowWithCallerType(aWindow
, aCallerType
);
391 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy
** aFocusedWindow
) {
392 NS_IF_ADDREF(*aFocusedWindow
= mFocusedWindow
);
397 nsFocusManager::GetFocusedContentBrowsingContext(
398 BrowsingContext
** aBrowsingContext
) {
399 MOZ_DIAGNOSTIC_ASSERT(
400 XRE_IsParentProcess(),
401 "We only have use cases for this in the parent process");
402 NS_IF_ADDREF(*aBrowsingContext
= GetFocusedBrowsingContextInChrome());
406 nsresult
nsFocusManager::SetFocusedWindowWithCallerType(
407 mozIDOMWindowProxy
* aWindowToFocus
, CallerType aCallerType
) {
408 LOGFOCUS(("<<SetFocusedWindow begin>>"));
410 nsCOMPtr
<nsPIDOMWindowOuter
> windowToFocus
=
411 nsPIDOMWindowOuter::From(aWindowToFocus
);
412 NS_ENSURE_TRUE(windowToFocus
, NS_ERROR_FAILURE
);
414 nsCOMPtr
<Element
> frameElement
= windowToFocus
->GetFrameElementInternal();
415 Maybe
<uint64_t> actionIdFromSetFocusInner
;
417 // pass false for aFocusChanged so that the caret does not get updated
418 // and scrolling does not occur.
419 actionIdFromSetFocusInner
= SetFocusInner(frameElement
, 0, false, true);
421 // this is a top-level window. If the window has a child frame focused,
422 // clear the focus. Otherwise, focus should already be in this frame, or
423 // already cleared. This ensures that focus will be in this frame and not
425 nsIContent
* content
= windowToFocus
->GetFocusedElement();
427 if (nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= GetContentWindow(content
))
428 ClearFocus(windowToFocus
);
432 nsCOMPtr
<nsPIDOMWindowOuter
> rootWindow
= windowToFocus
->GetPrivateRoot();
433 const uint64_t actionId
= actionIdFromSetFocusInner
.isSome()
434 ? actionIdFromSetFocusInner
.value()
435 : sInstance
->GenerateFocusActionId();
437 RaiseWindow(rootWindow
, aCallerType
, actionId
);
440 LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64
">>", actionId
));
445 NS_IMETHODIMP
nsFocusManager::SetFocusedWindow(
446 mozIDOMWindowProxy
* aWindowToFocus
) {
447 return SetFocusedWindowWithCallerType(aWindowToFocus
, CallerType::System
);
451 nsFocusManager::GetFocusedElement(Element
** aFocusedElement
) {
452 RefPtr
<Element
> focusedElement
= mFocusedElement
;
453 focusedElement
.forget(aFocusedElement
);
457 uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter
* aWindow
) const {
458 nsPIDOMWindowOuter
* window
= aWindow
? aWindow
: mFocusedWindow
.get();
459 uint32_t method
= window
? window
->GetFocusMethod() : 0;
460 NS_ASSERTION((method
& METHOD_MASK
) == method
, "invalid focus method");
465 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy
* aWindow
,
466 uint32_t* aLastFocusMethod
) {
467 *aLastFocusMethod
= GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow
));
472 nsFocusManager::SetFocus(Element
* aElement
, uint32_t aFlags
) {
473 LOGFOCUS(("<<SetFocus begin>>"));
475 NS_ENSURE_ARG(aElement
);
477 SetFocusInner(aElement
, aFlags
, true, true);
479 LOGFOCUS(("<<SetFocus end>>"));
485 nsFocusManager::ElementIsFocusable(Element
* aElement
, uint32_t aFlags
,
486 bool* aIsFocusable
) {
487 NS_ENSURE_TRUE(aElement
, NS_ERROR_INVALID_ARG
);
488 *aIsFocusable
= !!FlushAndCheckIfFocusable(aElement
, aFlags
);
492 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
493 nsFocusManager::MoveFocus(mozIDOMWindowProxy
* aWindow
, Element
* aStartElement
,
494 uint32_t aType
, uint32_t aFlags
, Element
** aElement
) {
497 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType
, aFlags
));
499 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
) && mFocusedWindow
) {
500 Document
* doc
= mFocusedWindow
->GetExtantDoc();
501 if (doc
&& doc
->GetDocumentURI()) {
502 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow
.get(),
503 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
507 LOGCONTENT(" Current Focus: %s", mFocusedElement
.get());
509 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
510 // the other focus methods is already set, or we're just moving to the root
511 // or caret position.
512 if (aType
!= MOVEFOCUS_ROOT
&& aType
!= MOVEFOCUS_CARET
&&
513 (aFlags
& METHOD_MASK
) == 0) {
514 aFlags
|= FLAG_BYMOVEFOCUS
;
517 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
519 window
= GetCurrentWindow(aStartElement
);
521 window
= aWindow
? nsPIDOMWindowOuter::From(aWindow
) : mFocusedWindow
.get();
524 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
526 // Flush to ensure that focusability of descendants is computed correctly.
527 if (RefPtr
<Document
> doc
= window
->GetExtantDoc()) {
528 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
531 bool noParentTraversal
= aFlags
& FLAG_NOPARENTFRAME
;
532 nsCOMPtr
<nsIContent
> newFocus
;
533 nsresult rv
= DetermineElementToMoveFocus(window
, aStartElement
, aType
,
534 noParentTraversal
, true,
535 getter_AddRefs(newFocus
));
536 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
540 NS_ENSURE_SUCCESS(rv
, rv
);
542 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus
.get());
544 if (newFocus
&& newFocus
->IsElement()) {
545 // for caret movement, pass false for the aFocusChanged argument,
546 // otherwise the caret will end up moving to the focus position. This
547 // would be a problem because the caret would move to the beginning of the
548 // focused link making it impossible to navigate the caret over a link.
549 SetFocusInner(MOZ_KnownLive(newFocus
->AsElement()), aFlags
,
550 aType
!= MOVEFOCUS_CARET
, true);
551 *aElement
= do_AddRef(newFocus
->AsElement()).take();
552 } else if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_CARET
) {
553 // no content was found, so clear the focus for these two types.
557 LOGFOCUS(("<<MoveFocus end>>"));
563 nsFocusManager::ClearFocus(mozIDOMWindowProxy
* aWindow
) {
564 LOGFOCUS(("<<ClearFocus begin>>"));
566 // if the window to clear is the focused window or an ancestor of the
567 // focused window, then blur the existing focused content. Otherwise, the
568 // focus is somewhere else so just update the current node.
569 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
570 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
572 if (IsSameOrAncestor(window
, GetFocusedBrowsingContext())) {
573 RefPtr
<BrowsingContext
> bc
= window
->GetBrowsingContext();
574 bool isAncestor
= (GetFocusedBrowsingContext() != bc
);
575 uint64_t actionId
= GenerateFocusActionId();
576 if (Blur(bc
, nullptr, isAncestor
, true, false, actionId
)) {
577 // if we are clearing the focus on an ancestor of the focused window,
578 // the ancestor will become the new focused window, so focus it
580 Focus(window
, nullptr, 0, true, false, false, true, actionId
);
584 window
->SetFocusedElement(nullptr);
587 LOGFOCUS(("<<ClearFocus end>>"));
593 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy
* aWindow
,
595 mozIDOMWindowProxy
** aFocusedWindow
,
596 Element
** aElement
) {
598 if (aFocusedWindow
) {
599 *aFocusedWindow
= nullptr;
602 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
603 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
605 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
606 RefPtr
<Element
> focusedElement
=
607 GetFocusedDescendant(window
,
608 aDeep
? nsFocusManager::eIncludeAllDescendants
609 : nsFocusManager::eOnlyCurrentWindow
,
610 getter_AddRefs(focusedWindow
));
612 focusedElement
.forget(aElement
);
614 if (aFocusedWindow
) {
615 NS_IF_ADDREF(*aFocusedWindow
= focusedWindow
);
622 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy
* aWindow
) {
623 nsCOMPtr
<nsIWebNavigation
> webnav
= do_GetInterface(aWindow
);
624 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(webnav
);
626 if (dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
627 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(dsti
);
628 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
630 // don't move the caret for editable documents
632 docShell
->GetEditable(&isEditable
);
637 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
638 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
640 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
641 if (RefPtr
<Element
> focusedElement
= window
->GetFocusedElement()) {
642 MoveCaretToFocus(presShell
, focusedElement
);
650 void nsFocusManager::WindowRaised(mozIDOMWindowProxy
* aWindow
,
651 uint64_t aActionId
) {
656 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
657 BrowsingContext
* bc
= window
->GetBrowsingContext();
659 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
660 LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64
, aWindow
,
661 mActiveWindow
.get(), mFocusedWindow
.get(), aActionId
));
662 Document
* doc
= window
->GetExtantDoc();
663 if (doc
&& doc
->GetDocumentURI()) {
664 LOGFOCUS((" Raised Window: %p %s", aWindow
,
665 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
668 doc
= mActiveWindow
->GetExtantDoc();
669 if (doc
&& doc
->GetDocumentURI()) {
670 LOGFOCUS((" Active Window: %p %s", mActiveWindow
.get(),
671 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
676 if (XRE_IsParentProcess()) {
677 if (mActiveWindow
== window
) {
678 // The window is already active, so there is no need to focus anything,
679 // but make sure that the right widget is focused. This is a special case
680 // for Windows because when restoring a minimized window, a second
681 // activation will occur and the top-level widget could be focused instead
682 // of the child we want. We solve this by calling SetFocus to ensure that
683 // what the focus manager thinks should be the current widget is actually
685 EnsureCurrentWidgetFocused(CallerType::System
);
689 // lower the existing window, if any. This shouldn't happen usually.
690 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
) {
691 WindowLowered(activeWindow
, aActionId
);
693 } else if (bc
->IsTop()) {
694 BrowsingContext
* active
= GetActiveBrowsingContext();
695 if (active
== bc
&& !mActiveBrowsingContextInContentSetFromOtherProcess
) {
696 // EnsureCurrentWidgetFocused() should not be necessary with
701 if (active
&& active
!= bc
) {
702 if (active
->IsInProcess()) {
703 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= active
->GetDOMWindow();
704 WindowLowered(activeWindow
, aActionId
);
706 // No else, because trying to lower other-process windows
707 // from here can result in the BrowsingContext no longer
708 // existing in the parent process by the time it deserializes
713 nsCOMPtr
<nsIDocShellTreeItem
> docShellAsItem
= window
->GetDocShell();
714 // If there's no docShellAsItem, this window must have been closed,
715 // in that case there is no tree owner.
716 if (!docShellAsItem
) {
720 // set this as the active window
721 if (XRE_IsParentProcess()) {
722 mActiveWindow
= window
;
723 } else if (bc
->IsTop()) {
724 SetActiveBrowsingContextInContent(bc
, aActionId
);
727 // ensure that the window is enabled and visible
728 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
729 docShellAsItem
->GetTreeOwner(getter_AddRefs(treeOwner
));
730 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(treeOwner
);
732 bool isEnabled
= true;
733 if (NS_SUCCEEDED(baseWindow
->GetEnabled(&isEnabled
)) && !isEnabled
) {
737 baseWindow
->SetVisibility(true);
740 if (XRE_IsParentProcess()) {
741 // Unsetting top-level focus upon lowering was inhibited to accommodate
742 // ATOK, so we need to do it here.
743 BrowserParent::UnsetTopLevelWebFocusAll();
744 ActivateOrDeactivate(window
, true);
747 // retrieve the last focused element within the window that was raised
748 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
749 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
750 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
752 NS_ASSERTION(currentWindow
, "window raised with no window current");
753 if (!currentWindow
) {
757 nsCOMPtr
<nsIAppWindow
> appWin(do_GetInterface(baseWindow
));
758 // We use mFocusedWindow here is basically for the case that iframe navigate
759 // from a.com to b.com for example, so it ends up being loaded in a different
760 // process after Fission, but
761 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
762 // still be true because focused browsing context is synced, and we won't
763 // fire a focus event while focusing if we use it as condition.
764 Focus(currentWindow
, currentFocus
, 0, currentWindow
!= mFocusedWindow
, false,
765 appWin
!= nullptr, true, aActionId
);
768 void nsFocusManager::WindowLowered(mozIDOMWindowProxy
* aWindow
,
769 uint64_t aActionId
) {
774 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
776 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
777 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow
,
778 mActiveWindow
.get(), mFocusedWindow
.get()));
779 Document
* doc
= window
->GetExtantDoc();
780 if (doc
&& doc
->GetDocumentURI()) {
781 LOGFOCUS((" Lowered Window: %s",
782 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
785 doc
= mActiveWindow
->GetExtantDoc();
786 if (doc
&& doc
->GetDocumentURI()) {
787 LOGFOCUS((" Active Window: %s",
788 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
793 if (XRE_IsParentProcess()) {
794 if (mActiveWindow
!= window
) {
798 BrowsingContext
* bc
= window
->GetBrowsingContext();
799 BrowsingContext
* active
= GetActiveBrowsingContext();
800 if (active
!= bc
->Top()) {
805 // clear the mouse capture as the active window has changed
806 PresShell::ReleaseCapturingContent();
808 // In addition, reset the drag state to ensure that we are no longer in
810 if (mFocusedWindow
) {
811 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
813 if (PresShell
* presShell
= docShell
->GetPresShell()) {
814 RefPtr
<nsFrameSelection
> frameSelection
= presShell
->FrameSelection();
815 frameSelection
->SetDragState(false);
820 if (XRE_IsParentProcess()) {
821 ActivateOrDeactivate(window
, false);
824 // keep track of the window being lowered, so that attempts to raise the
825 // window can be prevented until we return. Otherwise, focus can get into
827 mWindowBeingLowered
= window
;
828 if (XRE_IsParentProcess()) {
829 mActiveWindow
= nullptr;
831 BrowsingContext
* bc
= window
->GetBrowsingContext();
832 if (bc
== bc
->Top()) {
833 SetActiveBrowsingContextInContent(nullptr, aActionId
);
837 if (mFocusedWindow
) {
838 Blur(nullptr, nullptr, true, true, false, aActionId
);
841 mWindowBeingLowered
= nullptr;
844 nsresult
nsFocusManager::ContentRemoved(Document
* aDocument
,
845 nsIContent
* aContent
) {
846 NS_ENSURE_ARG(aDocument
);
847 NS_ENSURE_ARG(aContent
);
849 nsPIDOMWindowOuter
* windowPtr
= aDocument
->GetWindow();
854 // if the content is currently focused in the window, or is an
855 // shadow-including inclusive ancestor of the currently focused element,
856 // reset the focus within that window.
857 Element
* previousFocusedElementPtr
= windowPtr
->GetFocusedElement();
858 if (!previousFocusedElementPtr
) {
862 if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
863 previousFocusedElementPtr
, aContent
)) {
867 RefPtr
<nsPIDOMWindowOuter
> window
= windowPtr
;
868 RefPtr
<Element
> previousFocusedElement
= previousFocusedElementPtr
;
870 RefPtr
<Element
> newFocusedElement
= [&]() -> Element
* {
871 if (auto* sr
= ShadowRoot::FromNode(aContent
)) {
872 if (sr
->IsUAWidget() && sr
->Host()->IsHTMLElement(nsGkAtoms::input
)) {
879 window
->SetFocusedElement(newFocusedElement
);
881 // if this window is currently focused, clear the global focused
882 // element as well, but don't fire any events.
883 if (window
->GetBrowsingContext() == GetFocusedBrowsingContext()) {
884 mFocusedElement
= newFocusedElement
;
885 } else if (Document
* subdoc
=
886 aDocument
->GetSubDocumentFor(previousFocusedElement
)) {
887 // Check if the node that was focused is an iframe or similar by looking if
888 // it has a subdocument. This would indicate that this focused iframe
889 // and its descendants will be going away. We will need to move the focus
890 // somewhere else, so just clear the focus in the toplevel window so that no
891 // element is focused.
893 // The Fission case is handled in FlushAndCheckIfFocusable().
894 if (nsCOMPtr
<nsIDocShell
> docShell
= subdoc
->GetDocShell()) {
895 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= docShell
->GetWindow();
897 IsSameOrAncestor(childWindow
, GetFocusedBrowsingContext())) {
898 if (XRE_IsParentProcess()) {
899 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
900 ClearFocus(activeWindow
);
902 BrowsingContext
* active
= GetActiveBrowsingContext();
904 if (active
->IsInProcess()) {
905 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
906 active
->GetDOMWindow();
907 ClearFocus(activeWindow
);
909 mozilla::dom::ContentChild
* contentChild
=
910 mozilla::dom::ContentChild::GetSingleton();
911 MOZ_ASSERT(contentChild
);
912 contentChild
->SendClearFocus(active
);
914 } // no else, because ClearFocus does nothing with nullptr
920 // Notify the editor in case we removed its ancestor limiter.
921 if (previousFocusedElement
->IsEditable()) {
922 if (nsCOMPtr
<nsIDocShell
> docShell
= aDocument
->GetDocShell()) {
923 if (RefPtr
<HTMLEditor
> htmlEditor
= docShell
->GetHTMLEditor()) {
924 RefPtr
<Selection
> selection
= htmlEditor
->GetSelection();
925 if (selection
&& selection
->GetFrameSelection() &&
926 previousFocusedElement
==
927 selection
->GetFrameSelection()->GetAncestorLimiter()) {
928 htmlEditor
->FinalizeSelection();
934 if (!newFocusedElement
) {
935 NotifyFocusStateChange(previousFocusedElement
, newFocusedElement
, 0,
936 /* aGettingFocus = */ false, false);
938 // We should already have the right state, which is managed by the <input>
940 MOZ_ASSERT(newFocusedElement
->State().HasState(ElementState::FOCUS
));
943 // If we changed focused element and the element still has focus, let's
944 // notify IME of focus. Note that if new focus move has already occurred
945 // by running script, we should not let IMEStateManager of outdated focus
947 if (mFocusedElement
== newFocusedElement
&& mFocusedWindow
== window
) {
948 RefPtr
<nsPresContext
> presContext(aDocument
->GetPresContext());
949 IMEStateManager::OnChangeFocus(presContext
, newFocusedElement
,
950 InputContextAction::Cause::CAUSE_UNKNOWN
);
956 void nsFocusManager::WindowShown(mozIDOMWindowProxy
* aWindow
,
962 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
964 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
965 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window
.get(),
966 mActiveWindow
.get(), mFocusedWindow
.get()));
967 Document
* doc
= window
->GetExtantDoc();
968 if (doc
&& doc
->GetDocumentURI()) {
969 LOGFOCUS(("Shown Window: %s",
970 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
973 if (mFocusedWindow
) {
974 doc
= mFocusedWindow
->GetExtantDoc();
975 if (doc
&& doc
->GetDocumentURI()) {
976 LOGFOCUS((" Focused Window: %s",
977 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
982 if (XRE_IsParentProcess()) {
983 if (BrowsingContext
* bc
= window
->GetBrowsingContext()) {
985 bc
->SetIsActiveBrowserWindow(bc
->GetIsActiveBrowserWindow());
990 if (XRE_IsParentProcess()) {
991 if (mFocusedWindow
!= window
) {
995 BrowsingContext
* bc
= window
->GetBrowsingContext();
996 if (!bc
|| mFocusedBrowsingContextInContent
!= bc
) {
999 // Sync the window for a newly-created OOP iframe
1000 // Set actionId to zero to signify that it should be ignored.
1001 SetFocusedWindowInternal(window
, 0, false);
1005 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
1006 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
1007 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
1009 if (currentWindow
) {
1010 Focus(currentWindow
, currentFocus
, 0, true, false, false, true,
1011 GenerateFocusActionId());
1014 // Sometimes, an element in a window can be focused before the window is
1015 // visible, which would mean that the widget may not be properly focused.
1016 // When the window becomes visible, make sure the right widget is focused.
1017 EnsureCurrentWidgetFocused(CallerType::System
);
1021 void nsFocusManager::WindowHidden(mozIDOMWindowProxy
* aWindow
,
1022 uint64_t aActionId
) {
1023 // if there is no window or it is not the same or an ancestor of the
1024 // currently focused window, just return, as the current focus will not
1031 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
1033 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
1034 LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64
,
1035 window
.get(), mActiveWindow
.get(), mFocusedWindow
.get(),
1038 Document
* doc
= window
->GetExtantDoc();
1039 if (doc
&& doc
->GetDocumentURI()) {
1040 LOGFOCUS((" Hide Window: %s",
1041 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1044 if (mFocusedWindow
) {
1045 doc
= mFocusedWindow
->GetExtantDoc();
1046 if (doc
&& doc
->GetDocumentURI()) {
1047 LOGFOCUS((" Focused Window: %s",
1048 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1052 if (mActiveWindow
) {
1053 doc
= mActiveWindow
->GetExtantDoc();
1054 if (doc
&& doc
->GetDocumentURI()) {
1055 LOGFOCUS((" Active Window: %s",
1056 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1061 if (!IsSameOrAncestor(window
, mFocusedWindow
)) {
1065 // at this point, we know that the window being hidden is either the focused
1066 // window, or an ancestor of the focused window. Either way, the focus is no
1067 // longer valid, so it needs to be updated.
1069 const RefPtr
<Element
> oldFocusedElement
= std::move(mFocusedElement
);
1071 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
1072 if (!focusedDocShell
) {
1076 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
1078 if (oldFocusedElement
&& oldFocusedElement
->IsInComposedDoc()) {
1079 NotifyFocusStateChange(oldFocusedElement
, nullptr, 0, false, false);
1080 window
->UpdateCommands(u
"focus"_ns
);
1083 RefPtr
<Document
> composedDoc
= oldFocusedElement
->GetComposedDoc();
1084 SendFocusOrBlurEvent(eBlur
, presShell
, composedDoc
, oldFocusedElement
,
1089 const RefPtr
<nsPresContext
> focusedPresContext
=
1090 presShell
? presShell
->GetPresContext() : nullptr;
1091 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
1092 GetFocusMoveActionCause(0));
1094 SetCaretVisible(presShell
, false, nullptr);
1097 // If a window is being "hidden" because its BrowsingContext is changing
1098 // remoteness, we don't want to handle docshell destruction by moving focus.
1099 // Instead, the focused browsing context should stay the way it is (so that
1100 // the newly "shown" window in the other process knows to take focus) and
1101 // we should just null out the process-local field.
1102 nsCOMPtr
<nsIDocShell
> docShellBeingHidden
= window
->GetDocShell();
1103 // Check if we're currently hiding a non-remote nsDocShell due to its
1104 // BrowsingContext navigating to become remote. Normally, when a focused
1105 // subframe is hidden, focus is moved to the frame element, but focus should
1106 // stay with the BrowsingContext when performing a process switch. We don't
1107 // need to consider process switches where the hiding docshell is already
1108 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
1109 // frame element is handled elsewhere.
1110 if (docShellBeingHidden
&&
1111 nsDocShell::Cast(docShellBeingHidden
)->WillChangeProcess() &&
1112 docShellBeingHidden
->GetBrowsingContext()->GetEmbedderElement()) {
1113 if (mFocusedWindow
!= window
) {
1114 // The window being hidden is an ancestor of the focused window.
1116 BrowsingContext
* ancestor
= window
->GetBrowsingContext();
1117 BrowsingContext
* bc
= mFocusedWindow
->GetBrowsingContext();
1120 MOZ_ASSERT(false, "Should have found ancestor");
1122 bc
= bc
->GetParent();
1123 if (ancestor
== bc
) {
1128 // This call adjusts the focused browsing context and window.
1129 // The latter gets nulled out immediately below.
1130 SetFocusedWindowInternal(window
, aActionId
);
1132 mFocusedWindow
= nullptr;
1133 window
->SetFocusedElement(nullptr);
1137 // if the docshell being hidden is being destroyed, then we want to move
1138 // focus somewhere else. Call ClearFocus on the toplevel window, which
1139 // will have the effect of clearing the focus and moving the focused window
1140 // to the toplevel window. But if the window isn't being destroyed, we are
1141 // likely just loading a new document in it, so we want to maintain the
1142 // focused window so that the new document gets properly focused.
1143 bool beingDestroyed
= !docShellBeingHidden
;
1144 if (docShellBeingHidden
) {
1145 docShellBeingHidden
->IsBeingDestroyed(&beingDestroyed
);
1147 if (beingDestroyed
) {
1148 // There is usually no need to do anything if a toplevel window is going
1149 // away, as we assume that WindowLowered will be called. However, this may
1150 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1151 // a leak. So if the active window is being destroyed, call WindowLowered
1154 if (XRE_IsParentProcess()) {
1155 nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
= mActiveWindow
;
1156 if (activeWindow
== mFocusedWindow
|| activeWindow
== window
) {
1157 WindowLowered(activeWindow
, aActionId
);
1159 ClearFocus(activeWindow
);
1162 BrowsingContext
* active
= GetActiveBrowsingContext();
1164 if (nsCOMPtr
<nsPIDOMWindowOuter
> activeWindow
=
1165 active
->GetDOMWindow()) {
1166 if ((mFocusedWindow
&&
1167 mFocusedWindow
->GetBrowsingContext() == active
) ||
1168 (window
->GetBrowsingContext() == active
)) {
1169 WindowLowered(activeWindow
, aActionId
);
1171 ClearFocus(activeWindow
);
1173 } // else do nothing when an out-of-process iframe is torn down
1179 if (!XRE_IsParentProcess() &&
1180 mActiveBrowsingContextInContent
==
1181 docShellBeingHidden
->GetBrowsingContext() &&
1182 mActiveBrowsingContextInContent
->GetIsInBFCache()) {
1183 SetActiveBrowsingContextInContent(nullptr, aActionId
);
1186 // if the window being hidden is an ancestor of the focused window, adjust
1187 // the focused window so that it points to the one being hidden. This
1188 // ensures that the focused window isn't in a chain of frames that doesn't
1190 if (window
!= mFocusedWindow
) {
1191 nsCOMPtr
<nsIDocShellTreeItem
> dsti
=
1192 mFocusedWindow
? mFocusedWindow
->GetDocShell() : nullptr;
1194 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1195 dsti
->GetInProcessParent(getter_AddRefs(parentDsti
));
1197 if (nsCOMPtr
<nsPIDOMWindowOuter
> parentWindow
=
1198 parentDsti
->GetWindow()) {
1199 parentWindow
->SetFocusedElement(nullptr);
1204 SetFocusedWindowInternal(window
, aActionId
);
1208 void nsFocusManager::FireDelayedEvents(Document
* aDocument
) {
1209 MOZ_ASSERT(aDocument
);
1211 // fire any delayed focus and blur events in the same order that they were
1213 for (uint32_t i
= 0; i
< mDelayedBlurFocusEvents
.Length(); i
++) {
1214 if (mDelayedBlurFocusEvents
[i
].mDocument
== aDocument
) {
1215 if (!aDocument
->GetInnerWindow() ||
1216 !aDocument
->GetInnerWindow()->IsCurrentInnerWindow()) {
1217 // If the document was navigated away from or is defunct, don't bother
1218 // firing events on it. Note the symmetry between this condition and
1219 // the similar one in Document.cpp:FireOrClearDelayedEvents.
1220 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1222 } else if (!aDocument
->EventHandlingSuppressed()) {
1223 EventMessage message
= mDelayedBlurFocusEvents
[i
].mEventMessage
;
1224 nsCOMPtr
<EventTarget
> target
= mDelayedBlurFocusEvents
[i
].mTarget
;
1225 RefPtr
<PresShell
> presShell
= mDelayedBlurFocusEvents
[i
].mPresShell
;
1226 nsCOMPtr
<EventTarget
> relatedTarget
=
1227 mDelayedBlurFocusEvents
[i
].mRelatedTarget
;
1228 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1230 FireFocusOrBlurEvent(message
, presShell
, target
, false, false,
1238 void nsFocusManager::WasNuked(nsPIDOMWindowOuter
* aWindow
) {
1239 MOZ_ASSERT(aWindow
, "Expected non-null window.");
1240 MOZ_ASSERT(aWindow
!= mActiveWindow
,
1241 "How come we're nuking a window that's still active?");
1242 if (aWindow
== mFocusedWindow
) {
1243 mFocusedWindow
= nullptr;
1244 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
1245 mFocusedElement
= nullptr;
1249 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element
& aElement
)
1250 : mElement(aElement
) {}
1252 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
1254 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
1255 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter
* aWindow
,
1256 const Element
& aElement
,
1257 int32_t aFocusFlags
) {
1258 // If we were explicitly requested to show the ring, do it.
1259 if (aFocusFlags
& nsIFocusManager::FLAG_SHOWRING
) {
1263 if (aFocusFlags
& nsIFocusManager::FLAG_NOSHOWRING
) {
1267 if (aWindow
->ShouldShowFocusRing()) {
1268 // The window decision also trumps any other heuristic.
1272 // Any element which supports keyboard input (such as an input element, or any
1273 // other element which may trigger a virtual keyboard to be shown on focus if
1274 // a physical keyboard is not present) should always match :focus-visible when
1277 if (aElement
.IsHTMLElement(nsGkAtoms::textarea
) || aElement
.IsEditable()) {
1281 if (auto* input
= HTMLInputElement::FromNode(aElement
)) {
1282 if (input
->IsSingleLineTextControl()) {
1288 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags
)) {
1289 case InputContextAction::CAUSE_KEY
:
1290 // If the user interacts with the page via the keyboard, the currently
1291 // focused element should match :focus-visible (i.e. keyboard usage may
1292 // change whether this pseudo-class matches even if it doesn't affect
1295 case InputContextAction::CAUSE_UNKNOWN
:
1296 // We render outlines if the last "known" focus method was by key or there
1297 // was no previous known focus method, otherwise we don't.
1298 return aWindow
->UnknownFocusMethodShouldShowOutline();
1299 case InputContextAction::CAUSE_MOUSE
:
1300 case InputContextAction::CAUSE_TOUCH
:
1301 case InputContextAction::CAUSE_LONGPRESS
:
1302 // If the user interacts with the page via a pointing device, such that
1303 // the focus is moved to a new element which does not support user input,
1304 // the newly focused element should not match :focus-visible.
1306 case InputContextAction::CAUSE_UNKNOWN_CHROME
:
1307 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT
:
1308 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT
:
1309 // TODO(emilio): We could return some of these though, looking at
1310 // UserActivation. We may want to suppress focus rings for unknown /
1311 // programatic focus if the user is interacting with the page but not
1312 // during keyboard input, or such.
1313 MOZ_ASSERT_UNREACHABLE(
1314 "These don't get returned by GetFocusMoveActionCause");
1321 void nsFocusManager::NotifyFocusStateChange(Element
* aElement
,
1322 Element
* aElementToFocus
,
1323 int32_t aFlags
, bool aGettingFocus
,
1324 bool aShouldShowFocusRing
) {
1325 MOZ_ASSERT_IF(aElementToFocus
, !aGettingFocus
);
1326 nsIContent
* commonAncestor
= nullptr;
1327 if (aElementToFocus
) {
1328 commonAncestor
= nsContentUtils::GetCommonFlattenedTreeAncestor(
1329 aElement
, aElementToFocus
);
1332 if (aGettingFocus
) {
1333 ElementState stateToAdd
= ElementState::FOCUS
;
1334 if (aShouldShowFocusRing
) {
1335 stateToAdd
|= ElementState::FOCUSRING
;
1337 aElement
->AddStates(stateToAdd
);
1339 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1340 host
= host
->GetContainingShadowHost()) {
1341 host
->AsElement()->AddStates(ElementState::FOCUS
);
1344 constexpr auto kStatesToRemove
=
1345 ElementState::FOCUS
| ElementState::FOCUSRING
;
1346 aElement
->RemoveStates(kStatesToRemove
);
1347 for (nsIContent
* host
= aElement
->GetContainingShadowHost(); host
;
1348 host
= host
->GetContainingShadowHost()) {
1349 host
->AsElement()->RemoveStates(kStatesToRemove
);
1353 // Special case for <input type="checkbox"> and <input type="radio">.
1354 // The other browsers cancel active state when they gets lost focus, but
1355 // does not do it for the other elements such as <button> and <a href="...">.
1356 // Additionally, they may be activated with <label>, but they will get focus
1357 // at `click`, but activated at `mousedown`. Therefore, we need to cancel
1358 // active state at moving focus.
1359 if (RefPtr
<nsPresContext
> presContext
=
1360 aElement
->GetPresContext(Element::PresContextFor::eForComposedDoc
)) {
1361 RefPtr
<EventStateManager
> esm
= presContext
->EventStateManager();
1362 auto* activeInputElement
=
1363 HTMLInputElement::FromNodeOrNull(esm
->GetActiveContent());
1364 if (activeInputElement
&&
1365 (activeInputElement
->ControlType() == FormControlType::InputCheckbox
||
1366 activeInputElement
->ControlType() == FormControlType::InputRadio
) &&
1367 !activeInputElement
->State().HasState(ElementState::FOCUS
)) {
1368 esm
->SetContentState(nullptr, ElementState::ACTIVE
);
1372 for (nsIContent
* content
= aElement
; content
&& content
!= commonAncestor
;
1373 content
= content
->GetFlattenedTreeParent()) {
1374 Element
* element
= Element::FromNode(content
);
1379 if (aGettingFocus
) {
1380 if (element
->State().HasState(ElementState::FOCUS_WITHIN
)) {
1383 element
->AddStates(ElementState::FOCUS_WITHIN
);
1385 element
->RemoveStates(ElementState::FOCUS_WITHIN
);
1391 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType
) {
1392 if (!mFocusedWindow
|| sTestMode
) return;
1394 // get the main child widget for the focused window and ensure that the
1395 // platform knows that this widget is focused.
1396 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
1400 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1404 nsViewManager
* vm
= presShell
->GetViewManager();
1408 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
1412 widget
->SetFocus(nsIWidget::Raise::No
, aCallerType
);
1415 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter
* aWindow
,
1417 MOZ_ASSERT(XRE_IsParentProcess());
1422 if (BrowsingContext
* bc
= aWindow
->GetBrowsingContext()) {
1423 MOZ_ASSERT(bc
->IsTop());
1425 RefPtr
<CanonicalBrowsingContext
> chromeTop
=
1426 bc
->Canonical()->TopCrossChromeBoundary();
1427 MOZ_ASSERT(bc
== chromeTop
);
1429 chromeTop
->SetIsActiveBrowserWindow(aActive
);
1430 chromeTop
->CallOnAllTopDescendants(
1431 [aActive
](CanonicalBrowsingContext
* aBrowsingContext
) {
1432 aBrowsingContext
->SetIsActiveBrowserWindow(aActive
);
1433 return CallState::Continue
;
1435 /* aIncludeNestedBrowsers = */ true);
1438 if (aWindow
->GetExtantDoc()) {
1439 nsContentUtils::DispatchEventOnlyToChrome(
1440 aWindow
->GetExtantDoc(),
1441 nsGlobalWindowInner::Cast(aWindow
->GetCurrentInnerWindow()),
1442 aActive
? u
"activate"_ns
: u
"deactivate"_ns
, CanBubble::eYes
,
1443 Cancelable::eYes
, nullptr);
1447 // Retrieves innerWindowId of the window of the last focused element to
1448 // log a warning to the website console.
1449 void LogWarningFullscreenWindowRaise(Element
* aElement
) {
1450 nsCOMPtr
<nsFrameLoaderOwner
> frameLoaderOwner(do_QueryInterface(aElement
));
1451 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1453 RefPtr
<nsFrameLoader
> frameLoader
= frameLoaderOwner
->GetFrameLoader();
1454 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1456 RefPtr
<BrowsingContext
> browsingContext
= frameLoader
->GetBrowsingContext();
1457 NS_ENSURE_TRUE_VOID(browsingContext
);
1459 WindowGlobalParent
* windowGlobalParent
=
1460 browsingContext
->Canonical()->GetCurrentWindowGlobal();
1461 NS_ENSURE_TRUE_VOID(windowGlobalParent
);
1464 nsAutoString localizedMsg
;
1465 nsTArray
<nsString
> params
;
1466 nsresult rv
= nsContentUtils::FormatLocalizedString(
1467 nsContentUtils::eDOM_PROPERTIES
, "FullscreenExitWindowFocus", params
,
1470 NS_ENSURE_SUCCESS_VOID(rv
);
1472 Unused
<< nsContentUtils::ReportToConsoleByWindowID(
1473 localizedMsg
, nsIScriptError::warningFlag
, "DOM"_ns
,
1474 windowGlobalParent
->InnerWindowId(),
1475 windowGlobalParent
->GetDocumentURI());
1478 // Ensure that when an embedded popup with a noautofocus attribute
1479 // like a date picker is opened and focused, the parent page does not blur
1480 static bool IsEmeddededInNoautofocusPopup(BrowsingContext
& aBc
) {
1481 auto* embedder
= aBc
.GetEmbedderElement();
1485 nsIFrame
* f
= embedder
->GetPrimaryFrame();
1486 if (!f
|| !f
->HasAnyStateBits(NS_FRAME_IN_POPUP
)) {
1490 nsIFrame
* menuPopup
=
1491 nsLayoutUtils::GetClosestFrameOfType(f
, LayoutFrameType::MenuPopup
);
1492 MOZ_ASSERT(menuPopup
, "NS_FRAME_IN_POPUP lied?");
1493 return static_cast<nsMenuPopupFrame
*>(menuPopup
)
1495 .GetXULBoolAttr(nsGkAtoms::noautofocus
);
1498 Maybe
<uint64_t> nsFocusManager::SetFocusInner(Element
* aNewContent
,
1501 bool aAdjustWidget
) {
1502 // if the element is not focusable, just return and leave the focus as is
1503 RefPtr
<Element
> elementToFocus
=
1504 FlushAndCheckIfFocusable(aNewContent
, aFlags
);
1505 if (!elementToFocus
) {
1509 const RefPtr
<BrowsingContext
> focusedBrowsingContext
=
1510 GetFocusedBrowsingContext();
1512 // check if the element to focus is a frame (iframe) containing a child
1513 // document. Frames are never directly focused; instead focusing a frame
1514 // means focus what is inside the frame. To do this, the descendant content
1515 // within the frame is retrieved and that will be focused instead.
1516 nsCOMPtr
<nsPIDOMWindowOuter
> newWindow
;
1517 nsCOMPtr
<nsPIDOMWindowOuter
> subWindow
= GetContentWindow(elementToFocus
);
1519 elementToFocus
= GetFocusedDescendant(subWindow
, eIncludeAllDescendants
,
1520 getter_AddRefs(newWindow
));
1522 // since a window is being refocused, clear aFocusChanged so that the
1523 // caret position isn't updated.
1524 aFocusChanged
= false;
1527 // unless it was set above, retrieve the window for the element to focus
1529 newWindow
= GetCurrentWindow(elementToFocus
);
1532 RefPtr
<BrowsingContext
> newBrowsingContext
;
1534 newBrowsingContext
= newWindow
->GetBrowsingContext();
1537 // if the element is already focused, just return. Note that this happens
1538 // after the frame check above so that we compare the element that will be
1539 // focused rather than the frame it is in.
1540 if (!newWindow
|| (newBrowsingContext
== GetFocusedBrowsingContext() &&
1541 elementToFocus
== mFocusedElement
)) {
1545 MOZ_ASSERT(newBrowsingContext
);
1547 BrowsingContext
* browsingContextToFocus
= newBrowsingContext
;
1548 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(elementToFocus
)) {
1549 // Only look at pre-existing browsing contexts. If this function is
1550 // called during reflow, calling GetBrowsingContext() could cause frame
1551 // loader initialization at a time when it isn't safe.
1552 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
1553 // If focus is already in the subtree rooted at bc, return early
1554 // to match the single-process focus semantics. Otherwise, we'd
1555 // blur and immediately refocus whatever is focused.
1556 BrowsingContext
* walk
= focusedBrowsingContext
;
1561 walk
= walk
->GetParent();
1563 browsingContextToFocus
= bc
;
1567 // don't allow focus to be placed in docshells or descendants of docshells
1568 // that are being destroyed. Also, ensure that the page hasn't been
1569 // unloaded. The prevents content from being refocused during an unload event.
1570 nsCOMPtr
<nsIDocShell
> newDocShell
= newWindow
->GetDocShell();
1571 nsCOMPtr
<nsIDocShell
> docShell
= newDocShell
;
1574 docShell
->GetIsInUnload(&inUnload
);
1579 bool beingDestroyed
;
1580 docShell
->IsBeingDestroyed(&beingDestroyed
);
1581 if (beingDestroyed
) {
1585 BrowsingContext
* bc
= docShell
->GetBrowsingContext();
1587 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1588 docShell
->GetInProcessParent(getter_AddRefs(parentDsti
));
1589 docShell
= do_QueryInterface(parentDsti
);
1590 if (!docShell
&& !XRE_IsParentProcess()) {
1591 // We don't have an in-process parent, but let's see if we have
1592 // an in-process ancestor or if an out-of-process ancestor
1595 bc
= bc
->GetParent();
1596 if (bc
&& bc
->IsDiscarded()) {
1599 } while (bc
&& !bc
->IsInProcess());
1601 docShell
= bc
->GetDocShell();
1608 bool focusMovesToDifferentBC
=
1609 (focusedBrowsingContext
!= browsingContextToFocus
);
1611 if (focusedBrowsingContext
&& focusMovesToDifferentBC
&&
1612 nsContentUtils::IsHandlingKeyBoardEvent() &&
1613 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1614 MOZ_ASSERT(browsingContextToFocus
,
1615 "BrowsingContext to focus should be non-null.");
1617 nsIPrincipal
* focusedPrincipal
= nullptr;
1618 nsIPrincipal
* newPrincipal
= nullptr;
1620 if (XRE_IsParentProcess()) {
1621 if (WindowGlobalParent
* focusedWindowGlobalParent
=
1622 focusedBrowsingContext
->Canonical()->GetCurrentWindowGlobal()) {
1623 focusedPrincipal
= focusedWindowGlobalParent
->DocumentPrincipal();
1626 if (WindowGlobalParent
* newWindowGlobalParent
=
1627 browsingContextToFocus
->Canonical()->GetCurrentWindowGlobal()) {
1628 newPrincipal
= newWindowGlobalParent
->DocumentPrincipal();
1630 } else if (focusedBrowsingContext
->IsInProcess() &&
1631 browsingContextToFocus
->IsInProcess()) {
1632 nsCOMPtr
<nsIScriptObjectPrincipal
> focused
=
1633 do_QueryInterface(focusedBrowsingContext
->GetDOMWindow());
1634 nsCOMPtr
<nsIScriptObjectPrincipal
> newFocus
=
1635 do_QueryInterface(browsingContextToFocus
->GetDOMWindow());
1636 MOZ_ASSERT(focused
&& newFocus
,
1637 "BrowsingContext should always have a window here.");
1638 focusedPrincipal
= focused
->GetPrincipal();
1639 newPrincipal
= newFocus
->GetPrincipal();
1642 if (!focusedPrincipal
|| !newPrincipal
) {
1646 if (!focusedPrincipal
->Subsumes(newPrincipal
)) {
1647 NS_WARNING("Not allowed to focus the new window!");
1652 // to check if the new element is in the active window, compare the
1653 // new root docshell for the new element with the active window's docshell.
1654 RefPtr
<BrowsingContext
> newRootBrowsingContext
= nullptr;
1655 bool isElementInActiveWindow
= false;
1656 if (XRE_IsParentProcess()) {
1657 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
= nullptr;
1658 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= newWindow
->GetDocShell();
1660 nsCOMPtr
<nsIDocShellTreeItem
> root
;
1661 dsti
->GetInProcessRootTreeItem(getter_AddRefs(root
));
1662 newRootWindow
= root
? root
->GetWindow() : nullptr;
1664 isElementInActiveWindow
=
1665 (mActiveWindow
&& newRootWindow
== mActiveWindow
);
1667 if (newRootWindow
) {
1668 newRootBrowsingContext
= newRootWindow
->GetBrowsingContext();
1671 // XXX This is wrong for `<iframe mozbrowser>` and for XUL
1672 // `<browser remote="true">`. See:
1673 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
1674 newRootBrowsingContext
= newBrowsingContext
->Top();
1675 // to check if the new element is in the active window, compare the
1676 // new root docshell for the new element with the active window's docshell.
1677 isElementInActiveWindow
=
1678 (GetActiveBrowsingContext() == newRootBrowsingContext
);
1681 // Exit fullscreen if a website focuses another window
1682 if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
1683 !isElementInActiveWindow
&& (aFlags
& FLAG_RAISE
)) {
1684 if (XRE_IsParentProcess()) {
1685 if (Document
* doc
= mActiveWindow
? mActiveWindow
->GetDoc() : nullptr) {
1686 Document::ClearPendingFullscreenRequests(doc
);
1687 if (doc
->GetFullscreenElement()) {
1688 LogWarningFullscreenWindowRaise(mFocusedElement
);
1689 Document::AsyncExitFullscreen(doc
);
1693 BrowsingContext
* activeBrowsingContext
= GetActiveBrowsingContext();
1694 if (activeBrowsingContext
) {
1695 nsIDocShell
* shell
= activeBrowsingContext
->GetDocShell();
1697 if (Document
* doc
= shell
->GetDocument()) {
1698 Document::ClearPendingFullscreenRequests(doc
);
1699 if (doc
->GetFullscreenElement()) {
1700 Document::AsyncExitFullscreen(doc
);
1704 mozilla::dom::ContentChild
* contentChild
=
1705 mozilla::dom::ContentChild::GetSingleton();
1706 MOZ_ASSERT(contentChild
);
1707 contentChild
->SendMaybeExitFullscreen(activeBrowsingContext
);
1713 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1714 // shifted away from the current element if the new shell to focus is
1715 // the same or an ancestor shell of the currently focused shell.
1716 bool allowFrameSwitch
= !(aFlags
& FLAG_NOSWITCHFRAME
) ||
1717 IsSameOrAncestor(newWindow
, focusedBrowsingContext
);
1719 // if the element is in the active window, frame switching is allowed and
1720 // the content is in a visible window, fire blur and focus events.
1721 bool sendFocusEvent
=
1722 isElementInActiveWindow
&& allowFrameSwitch
&& IsWindowVisible(newWindow
);
1724 // Don't allow to steal the focus from chrome nodes if the caller cannot
1726 if (sendFocusEvent
&& mFocusedElement
&&
1727 mFocusedElement
->OwnerDoc() != aNewContent
->OwnerDoc() &&
1728 mFocusedElement
->NodePrincipal()->IsSystemPrincipal() &&
1729 !nsContentUtils::LegacyIsCallerNativeCode() &&
1730 !nsContentUtils::CanCallerAccess(mFocusedElement
)) {
1731 sendFocusEvent
= false;
1734 LOGCONTENT("Shift Focus: %s", elementToFocus
.get());
1735 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1736 aFlags
, mFocusedWindow
.get(), newWindow
.get(),
1737 mFocusedElement
.get()));
1738 const uint64_t actionId
= GenerateFocusActionId();
1740 (" In Active Window: %d Moves to different BrowsingContext: %d "
1741 "SendFocus: %d actionid: %" PRIu64
,
1742 isElementInActiveWindow
, focusMovesToDifferentBC
, sendFocusEvent
,
1745 if (sendFocusEvent
) {
1746 Maybe
<BlurredElementInfo
> blurredInfo
;
1747 if (mFocusedElement
) {
1748 blurredInfo
.emplace(*mFocusedElement
);
1750 // return if blurring fails or the focus changes during the blur
1751 if (focusedBrowsingContext
) {
1752 // find the common ancestor of the currently focused window and the new
1753 // window. The ancestor will need to have its currently focused node
1754 // cleared once the document has been blurred. Otherwise, we'll be in a
1755 // state where a document is blurred yet the chain of windows above it
1756 // still points to that document.
1757 // For instance, in the following frame tree:
1761 // D is focused and we want to focus C. Once D has been blurred, we need
1762 // to clear out the focus in A, otherwise A would still maintain that B
1763 // was focused, and B that D was focused.
1764 RefPtr
<BrowsingContext
> commonAncestor
=
1765 focusMovesToDifferentBC
1766 ? GetCommonAncestor(newWindow
, focusedBrowsingContext
)
1769 const bool needToClearFocusedElement
= [&] {
1770 if (focusedBrowsingContext
->IsChrome()) {
1771 // Always reset focused element if focus is currently in chrome
1772 // window, unless we're moving focus to a popup.
1773 return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1775 if (focusedBrowsingContext
->Top() != browsingContextToFocus
->Top()) {
1776 // Only reset focused element if focus moves within the same top-level
1780 // XXX for the case that we try to focus an
1781 // already-focused-remote-frame, we would still send blur and focus
1782 // IPC to it, but they will not generate blur or focus event, we don't
1783 // want to reset activeElement on the remote frame.
1784 return focusMovesToDifferentBC
|| focusedBrowsingContext
->IsInProcess();
1787 const bool remainActive
=
1788 focusMovesToDifferentBC
&&
1789 IsEmeddededInNoautofocusPopup(*browsingContextToFocus
);
1791 // TODO: MOZ_KnownLive is required due to bug 1770680
1792 if (!Blur(MOZ_KnownLive(needToClearFocusedElement
1793 ? focusedBrowsingContext
.get()
1795 commonAncestor
, focusMovesToDifferentBC
, aAdjustWidget
,
1796 remainActive
, actionId
, elementToFocus
)) {
1797 return Some(actionId
);
1801 Focus(newWindow
, elementToFocus
, aFlags
, focusMovesToDifferentBC
,
1802 aFocusChanged
, false, aAdjustWidget
, actionId
, blurredInfo
);
1804 // otherwise, for inactive windows and when the caller cannot steal the
1805 // focus, update the node in the window, and raise the window if desired.
1806 if (allowFrameSwitch
) {
1807 AdjustWindowFocus(newBrowsingContext
, true, IsWindowVisible(newWindow
),
1808 actionId
, false /* aShouldClearAncestorFocus */,
1809 nullptr /* aAncestorBrowsingContextToFocus */);
1812 // set the focus node and method as needed
1813 uint32_t focusMethod
=
1814 aFocusChanged
? aFlags
& METHODANDRING_MASK
1815 : newWindow
->GetFocusMethod() |
1816 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
1817 newWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
1818 if (aFocusChanged
) {
1819 if (nsCOMPtr
<nsIDocShell
> docShell
= newWindow
->GetDocShell()) {
1820 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1821 if (presShell
&& presShell
->DidInitialize()) {
1822 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
1827 // update the commands even when inactive so that the attributes for that
1828 // window are up to date.
1829 if (allowFrameSwitch
) {
1830 newWindow
->UpdateCommands(u
"focus"_ns
);
1833 if (aFlags
& FLAG_RAISE
) {
1834 if (newRootBrowsingContext
) {
1835 if (XRE_IsParentProcess() || newRootBrowsingContext
->IsInProcess()) {
1836 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
=
1837 newRootBrowsingContext
->GetDOMWindow();
1838 RaiseWindow(outerWindow
,
1839 aFlags
& FLAG_NONSYSTEMCALLER
? CallerType::NonSystem
1840 : CallerType::System
,
1843 mozilla::dom::ContentChild
* contentChild
=
1844 mozilla::dom::ContentChild::GetSingleton();
1845 MOZ_ASSERT(contentChild
);
1846 contentChild
->SendRaiseWindow(newRootBrowsingContext
,
1847 aFlags
& FLAG_NONSYSTEMCALLER
1848 ? CallerType::NonSystem
1849 : CallerType::System
,
1855 return Some(actionId
);
1858 static BrowsingContext
* GetParentIgnoreChromeBoundary(BrowsingContext
* aBC
) {
1859 // Chrome BrowsingContexts are only available in the parent process, so if
1860 // we're in a content process, we only worry about the context tree.
1861 if (XRE_IsParentProcess()) {
1862 return aBC
->Canonical()->GetParentCrossChromeBoundary();
1864 return aBC
->GetParent();
1867 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1868 BrowsingContext
* aContext
) const {
1869 if (!aPossibleAncestor
) {
1873 for (BrowsingContext
* bc
= aContext
; bc
;
1874 bc
= GetParentIgnoreChromeBoundary(bc
)) {
1875 if (bc
== aPossibleAncestor
) {
1883 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1884 nsPIDOMWindowOuter
* aWindow
) const {
1885 if (aWindow
&& aPossibleAncestor
) {
1886 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(),
1887 aWindow
->GetBrowsingContext());
1892 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1893 BrowsingContext
* aContext
) const {
1894 if (aPossibleAncestor
) {
1895 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(), aContext
);
1900 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1901 nsPIDOMWindowOuter
* aWindow
) const {
1903 return IsSameOrAncestor(aPossibleAncestor
, aWindow
->GetBrowsingContext());
1908 mozilla::dom::BrowsingContext
* nsFocusManager::GetCommonAncestor(
1909 nsPIDOMWindowOuter
* aWindow
, mozilla::dom::BrowsingContext
* aContext
) {
1910 NS_ENSURE_TRUE(aWindow
&& aContext
, nullptr);
1912 if (XRE_IsParentProcess()) {
1913 nsCOMPtr
<nsIDocShellTreeItem
> dsti1
= aWindow
->GetDocShell();
1914 NS_ENSURE_TRUE(dsti1
, nullptr);
1916 nsCOMPtr
<nsIDocShellTreeItem
> dsti2
= aContext
->GetDocShell();
1917 NS_ENSURE_TRUE(dsti2
, nullptr);
1919 AutoTArray
<nsIDocShellTreeItem
*, 30> parents1
, parents2
;
1921 parents1
.AppendElement(dsti1
);
1922 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti1
;
1923 dsti1
->GetInProcessParent(getter_AddRefs(parentDsti1
));
1924 dsti1
.swap(parentDsti1
);
1927 parents2
.AppendElement(dsti2
);
1928 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti2
;
1929 dsti2
->GetInProcessParent(getter_AddRefs(parentDsti2
));
1930 dsti2
.swap(parentDsti2
);
1933 uint32_t pos1
= parents1
.Length();
1934 uint32_t pos2
= parents2
.Length();
1935 nsIDocShellTreeItem
* parent
= nullptr;
1937 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1938 nsIDocShellTreeItem
* child1
= parents1
.ElementAt(--pos1
);
1939 nsIDocShellTreeItem
* child2
= parents2
.ElementAt(--pos2
);
1940 if (child1
!= child2
) {
1946 return parent
? parent
->GetBrowsingContext() : nullptr;
1949 BrowsingContext
* bc1
= aWindow
->GetBrowsingContext();
1950 NS_ENSURE_TRUE(bc1
, nullptr);
1952 BrowsingContext
* bc2
= aContext
;
1954 AutoTArray
<BrowsingContext
*, 30> parents1
, parents2
;
1956 parents1
.AppendElement(bc1
);
1957 bc1
= bc1
->GetParent();
1960 parents2
.AppendElement(bc2
);
1961 bc2
= bc2
->GetParent();
1964 uint32_t pos1
= parents1
.Length();
1965 uint32_t pos2
= parents2
.Length();
1966 BrowsingContext
* parent
= nullptr;
1968 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1969 BrowsingContext
* child1
= parents1
.ElementAt(--pos1
);
1970 BrowsingContext
* child2
= parents2
.ElementAt(--pos2
);
1971 if (child1
!= child2
) {
1980 bool nsFocusManager::AdjustInProcessWindowFocus(
1981 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
1982 uint64_t aActionId
, bool aShouldClearAncestorFocus
,
1983 BrowsingContext
* aAncestorBrowsingContextToFocus
) {
1984 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus
, aShouldClearAncestorFocus
);
1985 if (ActionIdComparableAndLower(aActionId
,
1986 mActionIdForFocusedBrowsingContextInContent
)) {
1988 ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as "
1989 "focused from another process due to stale action id %" PRIu64
".",
1990 aBrowsingContext
, aActionId
));
1994 BrowsingContext
* bc
= aBrowsingContext
;
1995 bool needToNotifyOtherProcess
= false;
1997 // get the containing <iframe> or equivalent element so that it can be
1999 nsCOMPtr
<Element
> frameElement
= bc
->GetEmbedderElement();
2000 BrowsingContext
* parent
= bc
->GetParent();
2001 if (!parent
&& XRE_IsParentProcess()) {
2002 CanonicalBrowsingContext
* canonical
= bc
->Canonical();
2003 RefPtr
<WindowGlobalParent
> embedder
=
2004 canonical
->GetEmbedderWindowGlobal();
2006 parent
= embedder
->BrowsingContext();
2013 if (!frameElement
&& XRE_IsContentProcess()) {
2014 needToNotifyOtherProcess
= true;
2018 nsCOMPtr
<nsPIDOMWindowOuter
> window
= bc
->GetDOMWindow();
2020 // if the parent window is visible but the original window was not, then we
2021 // have likely moved up and out from a hidden tab to the browser window, or
2022 // a similar such arrangement. Stop adjusting the current nodes.
2023 if (IsWindowVisible(window
) != aIsVisible
) {
2027 // When aCheckPermission is true, we should check whether the caller can
2028 // access the window or not. If it cannot access, we should stop the
2030 if (aCheckPermission
&& !nsContentUtils::LegacyIsCallerNativeCode() &&
2031 !nsContentUtils::CanCallerAccess(window
->GetCurrentInnerWindow())) {
2035 if (aShouldClearAncestorFocus
) {
2036 // This is the BrowsingContext that receives the focus, no need to clear
2037 // its focused element and the rest of the ancestors.
2038 if (window
->GetBrowsingContext() == aAncestorBrowsingContextToFocus
) {
2042 window
->SetFocusedElement(nullptr);
2046 if (frameElement
!= window
->GetFocusedElement()) {
2047 window
->SetFocusedElement(frameElement
);
2049 RefPtr
<nsFrameLoaderOwner
> loaderOwner
= do_QueryObject(frameElement
);
2050 MOZ_ASSERT(loaderOwner
);
2051 RefPtr
<nsFrameLoader
> loader
= loaderOwner
->GetFrameLoader();
2052 if (loader
&& loader
->IsRemoteFrame() &&
2053 GetFocusedBrowsingContext() == bc
) {
2054 Blur(nullptr, nullptr, true, true, false, aActionId
);
2058 return needToNotifyOtherProcess
;
2061 void nsFocusManager::AdjustWindowFocus(
2062 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
2063 uint64_t aActionId
, bool aShouldClearAncestorFocus
,
2064 BrowsingContext
* aAncestorBrowsingContextToFocus
) {
2065 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus
, aShouldClearAncestorFocus
);
2066 if (AdjustInProcessWindowFocus(aBrowsingContext
, aCheckPermission
, aIsVisible
,
2067 aActionId
, aShouldClearAncestorFocus
,
2068 aAncestorBrowsingContextToFocus
)) {
2069 // Some ancestors of aBrowsingContext isn't in this process, so notify other
2070 // processes to adjust their focused element.
2071 mozilla::dom::ContentChild
* contentChild
=
2072 mozilla::dom::ContentChild::GetSingleton();
2073 MOZ_ASSERT(contentChild
);
2074 contentChild
->SendAdjustWindowFocus(aBrowsingContext
, aIsVisible
, aActionId
,
2075 aShouldClearAncestorFocus
,
2076 aAncestorBrowsingContextToFocus
);
2080 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter
* aWindow
) {
2081 if (!aWindow
|| aWindow
->IsFrozen()) {
2085 // Check if the inner window is frozen as well. This can happen when a focus
2086 // change occurs while restoring a previous page.
2087 nsPIDOMWindowInner
* innerWindow
= aWindow
->GetCurrentInnerWindow();
2088 if (!innerWindow
|| innerWindow
->IsFrozen()) {
2092 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2093 nsCOMPtr
<nsIBaseWindow
> baseWin(do_QueryInterface(docShell
));
2098 bool visible
= false;
2099 baseWin
->GetVisibility(&visible
);
2103 bool nsFocusManager::IsNonFocusableRoot(nsIContent
* aContent
) {
2104 MOZ_ASSERT(aContent
, "aContent must not be NULL");
2105 MOZ_ASSERT(aContent
->IsInComposedDoc(), "aContent must be in a document");
2107 // If the uncomposed document of aContent is in designMode, the root element
2108 // is not focusable.
2109 // NOTE: Most elements whose uncomposed document is in design mode are not
2110 // focusable, just the document is focusable. However, if it's in a
2111 // shadow tree, it may be focus able even if the shadow host is in
2113 // Also, if aContent is not editable and it's not in designMode, it's not
2115 // And in userfocusignored context nothing is focusable.
2116 Document
* doc
= aContent
->GetComposedDoc();
2117 NS_ASSERTION(doc
, "aContent must have current document");
2118 return aContent
== doc
->GetRootElement() &&
2119 (aContent
->IsInDesignMode() || !aContent
->IsEditable());
2122 Element
* nsFocusManager::FlushAndCheckIfFocusable(Element
* aElement
,
2128 nsCOMPtr
<Document
> doc
= aElement
->GetComposedDoc();
2129 // can't focus elements that are not in documents
2131 LOGCONTENT("Cannot focus %s because content not in document", aElement
)
2135 // Make sure that our frames are up to date while ensuring the presshell is
2136 // also initialized in case we come from a script calling focus() early.
2137 mEventHandlingNeedsFlush
= false;
2138 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2140 PresShell
* presShell
= doc
->GetPresShell();
2145 // If this is an iframe that doesn't have an in-process subdocument, it is
2146 // either an OOP iframe or an in-process iframe without lazy about:blank
2147 // creation having taken place. In the OOP case, iframe is always focusable.
2148 // In the in-process case, create the initial about:blank for in-process
2149 // BrowsingContexts in order to have the `GetSubDocumentFor` call after this
2150 // block return something.
2152 // TODO(emilio): This block can probably go after bug 543435 lands.
2153 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2154 if (!aElement
->IsXULElement()) {
2155 // Only look at pre-existing browsing contexts. If this function is
2156 // called during reflow, calling GetBrowsingContext() could cause frame
2157 // loader initialization at a time when it isn't safe.
2158 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2159 // This call may create a contentViewer-created about:blank.
2160 // That's intentional, so we can move focus there.
2161 Unused
<< bc
->GetDocument();
2166 return GetTheFocusableArea(aElement
, aFlags
);
2169 bool nsFocusManager::Blur(BrowsingContext
* aBrowsingContextToClear
,
2170 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2171 bool aIsLeavingDocument
, bool aAdjustWidget
,
2172 bool aRemainActive
, uint64_t aActionId
,
2173 Element
* aElementToFocus
) {
2174 if (XRE_IsParentProcess()) {
2175 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2176 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2177 aElementToFocus
, aActionId
);
2179 mozilla::dom::ContentChild
* contentChild
=
2180 mozilla::dom::ContentChild::GetSingleton();
2181 MOZ_ASSERT(contentChild
);
2182 bool windowToClearHandled
= false;
2183 bool ancestorWindowToFocusHandled
= false;
2185 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2186 if (focusedBrowsingContext
&& focusedBrowsingContext
->IsDiscarded()) {
2187 focusedBrowsingContext
= nullptr;
2189 if (!focusedBrowsingContext
) {
2190 mFocusedElement
= nullptr;
2193 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsDiscarded()) {
2194 aBrowsingContextToClear
= nullptr;
2196 if (aAncestorBrowsingContextToFocus
&&
2197 aAncestorBrowsingContextToFocus
->IsDiscarded()) {
2198 aAncestorBrowsingContextToFocus
= nullptr;
2200 // XXX should more early returns from BlurImpl be hoisted here to avoid
2201 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
2202 // other processes when BlurImpl returns early in this process? Or should the
2203 // IPC messages for those be sent by BlurImpl itself, in which case they could
2205 if (focusedBrowsingContext
->IsInProcess()) {
2206 if (aBrowsingContextToClear
&& !aBrowsingContextToClear
->IsInProcess()) {
2207 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus
&&
2208 !aAncestorBrowsingContextToFocus
->IsInProcess()),
2209 "Both aBrowsingContextToClear and "
2210 "aAncestorBrowsingContextToFocus are "
2212 contentChild
->SendSetFocusedElement(aBrowsingContextToClear
, false);
2214 if (aAncestorBrowsingContextToFocus
&&
2215 !aAncestorBrowsingContextToFocus
->IsInProcess()) {
2216 contentChild
->SendSetFocusedElement(aAncestorBrowsingContextToFocus
,
2219 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2220 aIsLeavingDocument
, aAdjustWidget
, aRemainActive
,
2221 aElementToFocus
, aActionId
);
2223 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsInProcess()) {
2224 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2225 MOZ_ASSERT(windowToClear
);
2226 windowToClear
->SetFocusedElement(nullptr);
2227 windowToClearHandled
= true;
2229 if (aAncestorBrowsingContextToFocus
&&
2230 aAncestorBrowsingContextToFocus
->IsInProcess()) {
2231 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2232 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2233 MOZ_ASSERT(ancestorWindowToFocus
);
2234 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2235 ancestorWindowToFocusHandled
= true;
2237 // The expectation is that the blurring would eventually result in an IPC
2238 // message doing this anyway, but this doesn't happen if the focus is in OOP
2239 // iframe which won't try to bounce an IPC message to its parent frame.
2240 SetFocusedWindowInternal(nullptr, aActionId
);
2241 contentChild
->SendBlurToParent(
2242 focusedBrowsingContext
, aBrowsingContextToClear
,
2243 aAncestorBrowsingContextToFocus
, aIsLeavingDocument
, aAdjustWidget
,
2244 windowToClearHandled
, ancestorWindowToFocusHandled
, aActionId
);
2248 void nsFocusManager::BlurFromOtherProcess(
2249 mozilla::dom::BrowsingContext
* aFocusedBrowsingContext
,
2250 mozilla::dom::BrowsingContext
* aBrowsingContextToClear
,
2251 mozilla::dom::BrowsingContext
* aAncestorBrowsingContextToFocus
,
2252 bool aIsLeavingDocument
, bool aAdjustWidget
, uint64_t aActionId
) {
2253 if (aFocusedBrowsingContext
!= GetFocusedBrowsingContext()) {
2256 BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2257 aIsLeavingDocument
, aAdjustWidget
, /* aRemainActive = */ false,
2258 nullptr, aActionId
);
2261 bool nsFocusManager::BlurImpl(BrowsingContext
* aBrowsingContextToClear
,
2262 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2263 bool aIsLeavingDocument
, bool aAdjustWidget
,
2264 bool aRemainActive
, Element
* aElementToFocus
,
2265 uint64_t aActionId
) {
2266 LOGFOCUS(("<<Blur begin actionid: %" PRIu64
">>", aActionId
));
2268 // hold a reference to the focused content, which may be null
2269 RefPtr
<Element
> element
= mFocusedElement
;
2271 if (!element
->IsInComposedDoc()) {
2272 mFocusedElement
= nullptr;
2275 if (element
== mFirstBlurEvent
) {
2280 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2281 // hold a reference to the focused window
2282 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
2283 if (focusedBrowsingContext
) {
2284 window
= focusedBrowsingContext
->GetDOMWindow();
2287 mFocusedElement
= nullptr;
2291 nsCOMPtr
<nsIDocShell
> docShell
= window
->GetDocShell();
2293 if (XRE_IsContentProcess() &&
2294 ActionIdComparableAndLower(
2295 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2296 // Unclear if this ever happens.
2298 ("Ignored an attempt to null out focused BrowsingContext when "
2299 "docShell is null due to a stale action id %" PRIu64
".",
2304 mFocusedWindow
= nullptr;
2305 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2307 SetFocusedBrowsingContext(nullptr, aActionId
);
2308 mFocusedElement
= nullptr;
2312 // Keep a ref to presShell since dispatching the DOM event may cause
2313 // the document to be destroyed.
2314 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2316 if (XRE_IsContentProcess() &&
2317 ActionIdComparableAndLower(
2318 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2319 // Unclear if this ever happens.
2321 ("Ignored an attempt to null out focused BrowsingContext when "
2322 "presShell is null due to a stale action id %" PRIu64
".",
2326 mFocusedElement
= nullptr;
2327 mFocusedWindow
= nullptr;
2328 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2330 SetFocusedBrowsingContext(nullptr, aActionId
);
2334 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2335 if (!mFirstBlurEvent
) {
2336 ar
.emplace(mFirstBlurEvent
);
2337 mFirstBlurEvent
= element
;
2340 const RefPtr
<nsPresContext
> focusedPresContext
=
2341 GetActiveBrowsingContext() ? presShell
->GetPresContext() : nullptr;
2342 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
2343 GetFocusMoveActionCause(0));
2345 // now adjust the actual focus, by clearing the fields in the focus manager
2346 // and in the window.
2347 mFocusedElement
= nullptr;
2348 if (aBrowsingContextToClear
) {
2349 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2350 if (windowToClear
) {
2351 windowToClear
->SetFocusedElement(nullptr);
2355 LOGCONTENT("Element %s has been blurred", element
.get());
2357 // Don't fire blur event on the root content which isn't editable.
2358 bool sendBlurEvent
=
2359 element
&& element
->IsInComposedDoc() && !IsNonFocusableRoot(element
);
2361 if (sendBlurEvent
) {
2362 NotifyFocusStateChange(element
, aElementToFocus
, 0, false, false);
2365 if (!aRemainActive
) {
2366 bool windowBeingLowered
= !aBrowsingContextToClear
&&
2367 !aAncestorBrowsingContextToFocus
&&
2368 aIsLeavingDocument
&& aAdjustWidget
;
2369 // If the object being blurred is a remote browser, deactivate remote
2371 if (BrowserParent
* remote
= BrowserParent::GetFrom(element
)) {
2372 MOZ_ASSERT(XRE_IsParentProcess());
2373 // Let's deactivate all remote browsers.
2374 BrowsingContext
* topLevelBrowsingContext
= remote
->GetBrowsingContext();
2375 topLevelBrowsingContext
->PreOrderWalk([&](BrowsingContext
* aContext
) {
2376 if (WindowGlobalParent
* windowGlobalParent
=
2377 aContext
->Canonical()->GetCurrentWindowGlobal()) {
2378 if (RefPtr
<BrowserParent
> browserParent
=
2379 windowGlobalParent
->GetBrowserParent()) {
2380 browserParent
->Deactivate(windowBeingLowered
, aActionId
);
2382 ("%s remote browser deactivated %p, %d, actionid: %" PRIu64
,
2383 aContext
== topLevelBrowsingContext
? "Top-level"
2385 browserParent
.get(), windowBeingLowered
, aActionId
));
2391 // Same as above but for out-of-process iframes
2392 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(element
)) {
2393 bbc
->Deactivate(windowBeingLowered
, aActionId
);
2395 ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64
,
2396 bbc
, windowBeingLowered
, aActionId
));
2402 if (sendBlurEvent
) {
2403 // if there is an active window, update commands. If there isn't an active
2404 // window, then this was a blur caused by the active window being lowered,
2405 // so there is no need to update the commands
2406 if (GetActiveBrowsingContext()) {
2407 window
->UpdateCommands(u
"focus"_ns
);
2410 SendFocusOrBlurEvent(eBlur
, presShell
, element
->GetComposedDoc(), element
,
2411 false, false, aElementToFocus
);
2414 // if we are leaving the document or the window was lowered, make the caret
2416 if (aIsLeavingDocument
|| !GetActiveBrowsingContext()) {
2417 SetCaretVisible(presShell
, false, nullptr);
2420 RefPtr
<AccessibleCaretEventHub
> eventHub
=
2421 presShell
->GetAccessibleCaretEventHub();
2423 eventHub
->NotifyBlur(aIsLeavingDocument
|| !GetActiveBrowsingContext());
2426 // at this point, it is expected that this window will be still be
2427 // focused, but the focused element will be null, as it was cleared before
2428 // the event. If this isn't the case, then something else was focused during
2429 // the blur event above and we should just return. However, if
2430 // aIsLeavingDocument is set, a new document is desired, so make sure to
2431 // blur the document and window.
2432 if (GetFocusedBrowsingContext() != window
->GetBrowsingContext() ||
2433 (mFocusedElement
!= nullptr && !aIsLeavingDocument
)) {
2435 } else if (aIsLeavingDocument
) {
2436 window
->TakeFocus(false, 0);
2438 // clear the focus so that the ancestor frame hierarchy is in the correct
2439 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
2440 // focused at this point.
2441 if (aAncestorBrowsingContextToFocus
) {
2442 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2443 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2444 if (ancestorWindowToFocus
) {
2445 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2448 // When the focus of aBrowsingContextToClear is cleared, it should
2449 // also clear its ancestors's focus because ancestors should no longer
2450 // be considered aBrowsingContextToClear is focused.
2452 // We don't need to do this when aBrowsingContextToClear and
2453 // aAncestorBrowsingContextToFocus is equal because ancestors don't
2455 if (aBrowsingContextToClear
&&
2456 aBrowsingContextToClear
!= aAncestorBrowsingContextToFocus
) {
2458 aBrowsingContextToClear
, false,
2459 IsWindowVisible(aBrowsingContextToClear
->GetDOMWindow()), aActionId
,
2460 true /* aShouldClearAncestorFocus */,
2461 aAncestorBrowsingContextToFocus
);
2465 SetFocusedWindowInternal(nullptr, aActionId
);
2466 mFocusedElement
= nullptr;
2468 RefPtr
<Document
> doc
= window
->GetExtantDoc();
2470 SendFocusOrBlurEvent(eBlur
, presShell
, doc
, doc
, false);
2472 if (!GetFocusedBrowsingContext()) {
2473 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2474 window
->GetCurrentInnerWindow();
2475 // MOZ_KnownLive due to bug 1506441
2476 SendFocusOrBlurEvent(
2477 eBlur
, presShell
, doc
,
2478 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), false);
2481 // check if a different window was focused
2482 result
= (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
2483 } else if (GetActiveBrowsingContext()) {
2484 // Otherwise, the blur of the element without blurring the document
2485 // occurred normally. Call UpdateCaret to redisplay the caret at the right
2486 // location within the document. This is needed to ensure that the caret
2487 // used for caret browsing is made visible again when an input field is
2489 UpdateCaret(false, true, nullptr);
2495 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element
& aElement
,
2496 uint64_t aActionId
) {
2497 if (BrowserParent
* remote
= BrowserParent::GetFrom(&aElement
)) {
2498 remote
->Activate(aActionId
);
2500 ("Remote browser activated %p, actionid: %" PRIu64
, remote
, aActionId
));
2503 // Same as above but for out-of-process iframes
2504 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(&aElement
)) {
2505 bbc
->Activate(aActionId
);
2506 LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64
, bbc
,
2511 void nsFocusManager::Focus(
2512 nsPIDOMWindowOuter
* aWindow
, Element
* aElement
, uint32_t aFlags
,
2513 bool aIsNewDocument
, bool aFocusChanged
, bool aWindowRaised
,
2514 bool aAdjustWidget
, uint64_t aActionId
,
2515 const Maybe
<BlurredElementInfo
>& aBlurredElementInfo
) {
2516 LOGFOCUS(("<<Focus begin actionid: %" PRIu64
">>", aActionId
));
2523 (aElement
== mFirstFocusEvent
|| aElement
== mFirstBlurEvent
)) {
2527 // Keep a reference to the presShell since dispatching the DOM event may
2528 // cause the document to be destroyed.
2529 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2534 const RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2539 bool focusInOtherContentProcess
= false;
2540 // Keep mochitest-browser-chrome harness happy by ignoring
2541 // focusInOtherContentProcess in the chrome process, because the harness
2543 if (!XRE_IsParentProcess()) {
2544 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2545 // Only look at pre-existing browsing contexts. If this function is
2546 // called during reflow, calling GetBrowsingContext() could cause frame
2547 // loader initialization at a time when it isn't safe.
2548 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2549 focusInOtherContentProcess
= !bc
->IsInProcess();
2553 if (ActionIdComparableAndLower(
2554 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2555 // Unclear if this ever happens.
2557 ("Ignored an attempt to focus an element due to stale action id "
2564 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
2565 // Otherwise, just get the current focus method and use that. This ensures
2566 // that the method is set during the document and window focus events.
2567 uint32_t focusMethod
= aFocusChanged
2568 ? aFlags
& METHODANDRING_MASK
2569 : aWindow
->GetFocusMethod() |
2570 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
2572 if (!IsWindowVisible(aWindow
)) {
2573 // if the window isn't visible, for instance because it is a hidden tab,
2574 // update the current focus and scroll it into view but don't do anything
2576 if (RefPtr elementToFocus
= FlushAndCheckIfFocusable(aElement
, aFlags
)) {
2577 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
2578 if (aFocusChanged
) {
2579 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2585 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2586 if (!mFirstFocusEvent
) {
2587 ar
.emplace(mFirstFocusEvent
);
2588 mFirstFocusEvent
= aElement
;
2591 LOGCONTENT("Element %s has been focused", aElement
);
2593 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
2594 Document
* docm
= aWindow
->GetExtantDoc();
2596 LOGCONTENT(" from %s", docm
->GetRootElement());
2599 (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64
2601 aIsNewDocument
, aFocusChanged
, aWindowRaised
, aFlags
, aActionId
));
2604 if (aIsNewDocument
) {
2605 // if this is a new document, update the parent chain of frames so that
2606 // focus can be traversed from the top level down to the newly focused
2608 RefPtr
<BrowsingContext
> bc
= aWindow
->GetBrowsingContext();
2609 AdjustWindowFocus(bc
, false, IsWindowVisible(aWindow
), aActionId
,
2610 false /* aShouldClearAncestorFocus */,
2611 nullptr /* aAncestorBrowsingContextToFocus */);
2614 // indicate that the window has taken focus.
2615 if (aWindow
->TakeFocus(true, focusMethod
)) {
2616 aIsNewDocument
= true;
2619 SetFocusedWindowInternal(aWindow
, aActionId
);
2621 if (aAdjustWidget
&& !sTestMode
) {
2622 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
2623 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
2625 widget
->SetFocus(nsIWidget::Raise::No
, aFlags
& FLAG_NONSYSTEMCALLER
2626 ? CallerType::NonSystem
2627 : CallerType::System
);
2631 // if switching to a new document, first fire the focus event on the
2632 // document and then the window.
2633 if (aIsNewDocument
) {
2634 RefPtr
<Document
> doc
= aWindow
->GetExtantDoc();
2635 // The focus change should be notified to IMEStateManager from here if:
2636 // * the focused element is in design mode or
2637 // * nobody gets focus and the document is in design mode
2638 // since any element whose uncomposed document is in design mode won't
2639 // receive focus event.
2640 if (doc
&& ((aElement
&& aElement
->IsInDesignMode()) ||
2641 (!aElement
&& doc
->IsInDesignMode()))) {
2642 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2643 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2644 GetFocusMoveActionCause(aFlags
));
2646 if (doc
&& !focusInOtherContentProcess
) {
2647 SendFocusOrBlurEvent(eFocus
, presShell
, doc
, doc
, aWindowRaised
);
2649 if (GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext() &&
2650 !mFocusedElement
&& !focusInOtherContentProcess
) {
2651 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2652 aWindow
->GetCurrentInnerWindow();
2653 // MOZ_KnownLive due to bug 1506441
2654 SendFocusOrBlurEvent(
2655 eFocus
, presShell
, doc
,
2656 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow
)), aWindowRaised
);
2660 // check to ensure that the element is still focusable, and that nothing
2661 // else was focused during the events above.
2662 // Note that the focusing element may have already been moved to another
2663 // document/window. In that case, we should stop setting focus to it
2664 // because setting focus to the new window would cause redirecting focus
2666 RefPtr elementToFocus
=
2667 aElement
&& aElement
->IsInComposedDoc() &&
2668 aElement
->GetComposedDoc() == aWindow
->GetExtantDoc()
2669 ? FlushAndCheckIfFocusable(aElement
, aFlags
)
2671 if (elementToFocus
&& !mFocusedElement
&&
2672 GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext()) {
2673 mFocusedElement
= elementToFocus
;
2675 nsIContent
* focusedNode
= aWindow
->GetFocusedElement();
2676 const bool sendFocusEvent
= elementToFocus
->IsInComposedDoc() &&
2677 !IsNonFocusableRoot(elementToFocus
);
2678 const bool isRefocus
= focusedNode
&& focusedNode
== elementToFocus
;
2679 const bool shouldShowFocusRing
=
2681 ShouldMatchFocusVisible(aWindow
, *elementToFocus
, aFlags
);
2683 aWindow
->SetFocusedElement(elementToFocus
, focusMethod
, false);
2685 const RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2686 if (sendFocusEvent
) {
2687 NotifyFocusStateChange(elementToFocus
, nullptr, aFlags
,
2688 /* aGettingFocus = */ true, shouldShowFocusRing
);
2690 // If this is a remote browser, focus its widget and activate remote
2691 // content. Note that we might no longer be in the same document,
2692 // due to the events we fired above when aIsNewDocument.
2693 if (presShell
->GetDocument() == elementToFocus
->GetComposedDoc()) {
2694 ActivateRemoteFrameIfNeeded(*elementToFocus
, aActionId
);
2697 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2698 GetFocusMoveActionCause(aFlags
));
2700 // as long as this focus wasn't because a window was raised, update the
2702 // XXXndeakin P2 someone could adjust the focus during the update
2703 if (!aWindowRaised
) {
2704 aWindow
->UpdateCommands(u
"focus"_ns
);
2707 // If the focused element changed, scroll it into view
2708 if (aFocusChanged
) {
2709 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2712 if (!focusInOtherContentProcess
) {
2713 RefPtr
<Document
> composedDocument
= elementToFocus
->GetComposedDoc();
2714 RefPtr
<Element
> relatedTargetElement
=
2715 aBlurredElementInfo
? aBlurredElementInfo
->mElement
.get() : nullptr;
2716 SendFocusOrBlurEvent(eFocus
, presShell
, composedDocument
,
2717 elementToFocus
, aWindowRaised
, isRefocus
,
2718 relatedTargetElement
);
2721 // We should notify IMEStateManager of actual focused element even if it
2722 // won't get focus event because the other IMEStateManager users do not
2723 // want to depend on this check, but IMEStateManager wants to verify
2724 // passed focused element for avoidng to overrride nested calls.
2725 IMEStateManager::OnChangeFocus(presContext
, elementToFocus
,
2726 GetFocusMoveActionCause(aFlags
));
2727 if (!aWindowRaised
) {
2728 aWindow
->UpdateCommands(u
"focus"_ns
);
2730 if (aFocusChanged
) {
2731 // If the focused element changed, scroll it into view
2732 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
2736 if (!mFocusedElement
&& mFocusedWindow
== aWindow
) {
2737 // When there is no focused element, IMEStateManager needs to adjust IME
2738 // enabled state with the document.
2739 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
2740 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2741 GetFocusMoveActionCause(aFlags
));
2744 if (!aWindowRaised
) {
2745 aWindow
->UpdateCommands(u
"focus"_ns
);
2749 // update the caret visibility and position to match the newly focused
2750 // element. However, don't update the position if this was a focus due to a
2751 // mouse click as the selection code would already have moved the caret as
2752 // needed. If this is a different document than was focused before, also
2753 // update the caret's visibility. If this is the same document, the caret
2754 // visibility should be the same as before so there is no need to update it.
2755 if (mFocusedElement
== elementToFocus
) {
2756 RefPtr
<Element
> focusedElement
= mFocusedElement
;
2757 UpdateCaret(aFocusChanged
&& !(aFlags
& FLAG_BYMOUSE
), aIsNewDocument
,
2762 class FocusBlurEvent
: public Runnable
{
2764 FocusBlurEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2765 nsPresContext
* aContext
, bool aWindowRaised
, bool aIsRefocus
,
2766 EventTarget
* aRelatedTarget
)
2767 : mozilla::Runnable("FocusBlurEvent"),
2770 mEventMessage(aEventMessage
),
2771 mWindowRaised(aWindowRaised
),
2772 mIsRefocus(aIsRefocus
),
2773 mRelatedTarget(aRelatedTarget
) {}
2775 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2776 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2777 InternalFocusEvent
event(true, mEventMessage
);
2778 event
.mFlags
.mBubbles
= false;
2779 event
.mFlags
.mCancelable
= false;
2780 event
.mFromRaise
= mWindowRaised
;
2781 event
.mIsRefocus
= mIsRefocus
;
2782 event
.mRelatedTarget
= mRelatedTarget
;
2783 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2786 const nsCOMPtr
<EventTarget
> mTarget
;
2787 const RefPtr
<nsPresContext
> mContext
;
2788 EventMessage mEventMessage
;
2791 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2794 class FocusInOutEvent
: public Runnable
{
2796 FocusInOutEvent(EventTarget
* aTarget
, EventMessage aEventMessage
,
2797 nsPresContext
* aContext
,
2798 nsPIDOMWindowOuter
* aOriginalFocusedWindow
,
2799 nsIContent
* aOriginalFocusedContent
,
2800 EventTarget
* aRelatedTarget
)
2801 : mozilla::Runnable("FocusInOutEvent"),
2804 mEventMessage(aEventMessage
),
2805 mOriginalFocusedWindow(aOriginalFocusedWindow
),
2806 mOriginalFocusedContent(aOriginalFocusedContent
),
2807 mRelatedTarget(aRelatedTarget
) {}
2809 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2810 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
2811 nsCOMPtr
<nsIContent
> originalWindowFocus
=
2812 mOriginalFocusedWindow
? mOriginalFocusedWindow
->GetFocusedElement()
2814 // Blink does not check that focus is the same after blur, but WebKit does.
2815 // Opt to follow Blink's behavior (see bug 687787).
2816 if (mEventMessage
== eFocusOut
||
2817 originalWindowFocus
== mOriginalFocusedContent
) {
2818 InternalFocusEvent
event(true, mEventMessage
);
2819 event
.mFlags
.mBubbles
= true;
2820 event
.mFlags
.mCancelable
= false;
2821 event
.mRelatedTarget
= mRelatedTarget
;
2822 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2827 const nsCOMPtr
<EventTarget
> mTarget
;
2828 const RefPtr
<nsPresContext
> mContext
;
2829 EventMessage mEventMessage
;
2830 nsCOMPtr
<nsPIDOMWindowOuter
> mOriginalFocusedWindow
;
2831 nsCOMPtr
<nsIContent
> mOriginalFocusedContent
;
2832 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2835 static Document
* GetDocumentHelper(EventTarget
* aTarget
) {
2839 if (const nsINode
* node
= nsINode::FromEventTarget(aTarget
)) {
2840 return node
->OwnerDoc();
2842 nsPIDOMWindowInner
* win
= nsPIDOMWindowInner::FromEventTarget(aTarget
);
2843 return win
? win
->GetExtantDoc() : nullptr;
2846 void nsFocusManager::FireFocusInOrOutEvent(
2847 EventMessage aEventMessage
, PresShell
* aPresShell
, EventTarget
* aTarget
,
2848 nsPIDOMWindowOuter
* aCurrentFocusedWindow
,
2849 nsIContent
* aCurrentFocusedContent
, EventTarget
* aRelatedTarget
) {
2850 NS_ASSERTION(aEventMessage
== eFocusIn
|| aEventMessage
== eFocusOut
,
2851 "Wrong event type for FireFocusInOrOutEvent");
2853 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2854 aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2855 aCurrentFocusedWindow
, aCurrentFocusedContent
, aRelatedTarget
));
2858 void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage
,
2859 PresShell
* aPresShell
,
2860 Document
* aDocument
,
2861 EventTarget
* aTarget
,
2862 bool aWindowRaised
, bool aIsRefocus
,
2863 EventTarget
* aRelatedTarget
) {
2864 NS_ASSERTION(aEventMessage
== eFocus
|| aEventMessage
== eBlur
,
2865 "Wrong event type for SendFocusOrBlurEvent");
2867 nsCOMPtr
<Document
> eventTargetDoc
= GetDocumentHelper(aTarget
);
2868 nsCOMPtr
<Document
> relatedTargetDoc
= GetDocumentHelper(aRelatedTarget
);
2870 // set aRelatedTarget to null if it's not in the same document as aTarget
2871 if (eventTargetDoc
!= relatedTargetDoc
) {
2872 aRelatedTarget
= nullptr;
2875 if (aDocument
&& aDocument
->EventHandlingSuppressed()) {
2876 // if this event was already queued, remove it and append it to the end
2877 mDelayedBlurFocusEvents
.RemoveElementsBy([&](const auto& event
) {
2878 return event
.mEventMessage
== aEventMessage
&&
2879 event
.mPresShell
== aPresShell
&& event
.mDocument
== aDocument
&&
2880 event
.mTarget
== aTarget
&& event
.mRelatedTarget
== aRelatedTarget
;
2883 mDelayedBlurFocusEvents
.EmplaceBack(aEventMessage
, aPresShell
, aDocument
,
2884 aTarget
, aRelatedTarget
);
2888 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2889 // that belongs to this doc, if yes, fire them first.
2890 if (aDocument
&& !aDocument
->EventHandlingSuppressed() &&
2891 mDelayedBlurFocusEvents
.Length()) {
2892 FireDelayedEvents(aDocument
);
2895 FireFocusOrBlurEvent(aEventMessage
, aPresShell
, aTarget
, aWindowRaised
,
2896 aIsRefocus
, aRelatedTarget
);
2899 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage
,
2900 PresShell
* aPresShell
,
2901 EventTarget
* aTarget
,
2902 bool aWindowRaised
, bool aIsRefocus
,
2903 EventTarget
* aRelatedTarget
) {
2904 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
= mFocusedWindow
;
2905 nsCOMPtr
<nsPIDOMWindowInner
> targetWindow
= do_QueryInterface(aTarget
);
2906 nsCOMPtr
<Document
> targetDocument
= do_QueryInterface(aTarget
);
2907 nsCOMPtr
<nsIContent
> currentFocusedContent
=
2908 currentWindow
? currentWindow
->GetFocusedElement() : nullptr;
2910 #ifdef ACCESSIBILITY
2911 nsAccessibilityService
* accService
= GetAccService();
2913 if (aEventMessage
== eFocus
) {
2914 accService
->NotifyOfDOMFocus(aTarget
);
2916 accService
->NotifyOfDOMBlur(aTarget
);
2921 aPresShell
->ScheduleContentRelevancyUpdate(
2922 ContentRelevancyReason::FocusInSubtree
);
2924 nsContentUtils::AddScriptRunner(
2925 new FocusBlurEvent(aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2926 aWindowRaised
, aIsRefocus
, aRelatedTarget
));
2928 // Check that the target is not a window or document before firing
2929 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2930 // despite being required in the spec, so follow their behavior.
2932 // As for document, we should not even fire focus/blur, but until then, we
2933 // need this check. targetDocument should be removed once bug 1228802 is
2935 if (!targetWindow
&& !targetDocument
) {
2936 EventMessage focusInOrOutMessage
=
2937 aEventMessage
== eFocus
? eFocusIn
: eFocusOut
;
2938 FireFocusInOrOutEvent(focusInOrOutMessage
, aPresShell
, aTarget
,
2939 currentWindow
, currentFocusedContent
, aRelatedTarget
);
2943 void nsFocusManager::ScrollIntoView(PresShell
* aPresShell
, nsIContent
* aContent
,
2945 if (aFlags
& FLAG_NOSCROLL
) {
2949 // If the noscroll flag isn't set, scroll the newly focused element into view.
2950 const ScrollAxis
axis(WhereToScroll::Center
, WhenToScroll::IfNotVisible
);
2951 aPresShell
->ScrollContentIntoView(aContent
, axis
, axis
,
2952 ScrollFlags::ScrollOverflowHidden
);
2953 // Scroll the input / textarea selection into view, unless focused with the
2954 // mouse, see bug 572649.
2955 if (aFlags
& FLAG_BYMOUSE
) {
2958 // ScrollContentIntoView flushes layout, so no need to flush again here.
2959 if (nsTextControlFrame
* tf
= do_QueryFrame(aContent
->GetPrimaryFrame())) {
2960 tf
->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes
);
2964 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter
* aWindow
,
2965 CallerType aCallerType
, uint64_t aActionId
) {
2966 // don't raise windows that are already raised or are in the process of
2969 if (!aWindow
|| aWindow
== mWindowBeingLowered
) {
2973 if (XRE_IsParentProcess()) {
2974 if (aWindow
== mActiveWindow
) {
2978 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2979 // TODO: Deeper OOP frame hierarchies are
2980 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
2981 if (bc
== GetActiveBrowsingContext()) {
2984 if (bc
== GetFocusedBrowsingContext()) {
2990 // In test mode, emulate raising the window. WindowRaised takes
2991 // care of lowering the present active window. This happens in
2992 // a separate runnable to avoid touching multiple windows in
2993 // the current runnable.
2995 nsCOMPtr
<nsPIDOMWindowOuter
> window(aWindow
);
2996 RefPtr
<nsFocusManager
> self(this);
2997 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2998 "nsFocusManager::RaiseWindow",
2999 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093)
3000 [self
, window
]() MOZ_CAN_RUN_SCRIPT_BOUNDARY
-> void {
3001 self
->WindowRaised(window
, GenerateFocusActionId());
3006 if (XRE_IsContentProcess()) {
3007 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
3009 // Assume the raise below will succeed and run the raising synchronously
3010 // in this process to make the focus event that is observable in this
3011 // process fire in the right order relative to mouseup when we are here
3012 // thanks to a mousedown.
3013 WindowRaised(aWindow
, aActionId
);
3018 // Windows would rather we focus the child widget, otherwise, the toplevel
3019 // widget will always end up being focused. Fortunately, focusing the child
3020 // widget will also have the effect of raising the window this widget is in.
3021 // But on other platforms, we can just focus the toplevel widget to raise
3023 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
;
3024 GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3025 getter_AddRefs(childWindow
));
3027 childWindow
= aWindow
;
3030 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3035 PresShell
* presShell
= docShell
->GetPresShell();
3040 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
3041 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
3043 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3047 nsCOMPtr
<nsIBaseWindow
> treeOwnerAsWin
=
3048 do_QueryInterface(aWindow
->GetDocShell());
3049 if (treeOwnerAsWin
) {
3050 nsCOMPtr
<nsIWidget
> widget
;
3051 treeOwnerAsWin
->GetMainWidget(getter_AddRefs(widget
));
3053 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
3059 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
3060 RefPtr
<Element
> focusedElement
= mFocusedElement
;
3061 UpdateCaret(false, true, focusedElement
);
3064 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus
, bool aUpdateVisibility
,
3065 nsIContent
* aContent
) {
3066 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus
, aUpdateVisibility
));
3068 if (!mFocusedWindow
) {
3072 // this is called when a document is focused or when the caretbrowsing
3073 // preference is changed
3074 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
3075 if (!focusedDocShell
) {
3079 if (focusedDocShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
3080 return; // Never browse with caret in chrome
3083 bool browseWithCaret
= Preferences::GetBool("accessibility.browsewithcaret");
3085 const RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
3090 // If this is an editable document which isn't contentEditable, or a
3091 // contentEditable document and the node to focus is contentEditable,
3092 // return, so that we don't mess with caret visibility.
3093 bool isEditable
= false;
3094 focusedDocShell
->GetEditable(&isEditable
);
3097 Document
* doc
= presShell
->GetDocument();
3099 bool isContentEditableDoc
=
3101 doc
->GetEditingState() == Document::EditingState::eContentEditable
;
3103 bool isFocusEditable
= aContent
&& aContent
->HasFlag(NODE_IS_EDITABLE
);
3104 if (!isContentEditableDoc
|| isFocusEditable
) {
3109 if (!isEditable
&& aMoveCaretToFocus
) {
3110 MoveCaretToFocus(presShell
, aContent
);
3113 // The above MoveCaretToFocus call may run scripts which
3114 // may clear mFocusWindow
3115 if (!mFocusedWindow
) {
3119 if (!aUpdateVisibility
) {
3123 // XXXndeakin this doesn't seem right. It should be checking for this only
3124 // on the nearest ancestor frame which is a chrome frame. But this is
3125 // what the existing code does, so just leave it for now.
3126 if (!browseWithCaret
) {
3127 nsCOMPtr
<Element
> docElement
= mFocusedWindow
->GetFrameElementInternal();
3129 browseWithCaret
= docElement
->AttrValueIs(
3130 kNameSpaceID_None
, nsGkAtoms::showcaret
, u
"true"_ns
, eCaseMatters
);
3133 SetCaretVisible(presShell
, browseWithCaret
, aContent
);
3136 void nsFocusManager::MoveCaretToFocus(PresShell
* aPresShell
,
3137 nsIContent
* aContent
) {
3138 nsCOMPtr
<Document
> doc
= aPresShell
->GetDocument();
3140 RefPtr
<nsFrameSelection
> frameSelection
= aPresShell
->FrameSelection();
3141 RefPtr
<Selection
> domSelection
=
3142 frameSelection
->GetSelection(SelectionType::eNormal
);
3144 // First clear the selection. This way, if there is no currently focused
3145 // content, the selection will just be cleared.
3146 domSelection
->RemoveAllRanges(IgnoreErrors());
3149 RefPtr
<nsRange
> newRange
= doc
->CreateRange(rv
);
3150 if (NS_WARN_IF(rv
.Failed())) {
3151 rv
.SuppressException();
3155 // Set the range to the start of the currently focused node
3156 // Make sure it's collapsed
3157 newRange
->SelectNodeContents(*aContent
, IgnoreErrors());
3159 if (!aContent
->GetFirstChild() ||
3160 aContent
->IsHTMLFormControlElement()) {
3161 // If current focus node is a leaf, set range to before the
3162 // node by using the parent as a container.
3163 // This prevents it from appearing as selected.
3164 newRange
->SetStartBefore(*aContent
, IgnoreErrors());
3165 newRange
->SetEndBefore(*aContent
, IgnoreErrors());
3167 domSelection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
3169 domSelection
->CollapseToStart(IgnoreErrors());
3175 nsresult
nsFocusManager::SetCaretVisible(PresShell
* aPresShell
, bool aVisible
,
3176 nsIContent
* aContent
) {
3177 // When browsing with caret, make sure caret is visible after new focus
3178 // Return early if there is no caret. This can happen for the testcase
3179 // for bug 308025 where a window is closed in a blur handler.
3180 RefPtr
<nsCaret
> caret
= aPresShell
->GetCaret();
3185 bool caretVisible
= caret
->IsVisible();
3186 if (!aVisible
&& !caretVisible
) {
3190 RefPtr
<nsFrameSelection
> frameSelection
;
3192 NS_ASSERTION(aContent
->GetComposedDoc() == aPresShell
->GetDocument(),
3194 nsIFrame
* focusFrame
= aContent
->GetPrimaryFrame();
3196 frameSelection
= focusFrame
->GetFrameSelection();
3200 RefPtr
<nsFrameSelection
> docFrameSelection
= aPresShell
->FrameSelection();
3202 if (docFrameSelection
&& caret
&&
3203 (frameSelection
== docFrameSelection
|| !aContent
)) {
3204 Selection
* domSelection
=
3205 docFrameSelection
->GetSelection(SelectionType::eNormal
);
3207 // First, hide the caret to prevent attempting to show it in
3208 // SetCaretDOMSelection
3209 aPresShell
->SetCaretEnabled(false);
3211 // Caret must blink on non-editable elements
3212 caret
->SetIgnoreUserModify(true);
3213 // Tell the caret which selection to use
3214 caret
->SetSelection(domSelection
);
3216 // In content, we need to set the caret. The only special case is edit
3217 // fields, which have a different frame selection from the document.
3218 // They will take care of making the caret visible themselves.
3220 aPresShell
->SetCaretReadOnly(false);
3221 aPresShell
->SetCaretEnabled(aVisible
);
3228 void nsFocusManager::GetSelectionLocation(Document
* aDocument
,
3229 PresShell
* aPresShell
,
3230 nsIContent
** aStartContent
,
3231 nsIContent
** aEndContent
) {
3232 *aStartContent
= *aEndContent
= nullptr;
3234 nsPresContext
* presContext
= aPresShell
->GetPresContext();
3235 NS_ASSERTION(presContext
, "mPresContent is null!!");
3237 RefPtr
<Selection
> domSelection
=
3238 aPresShell
->ConstFrameSelection()->GetSelection(SelectionType::eNormal
);
3239 if (!domSelection
) {
3243 const nsRange
* domRange
= domSelection
->GetRangeAt(0);
3244 if (!domRange
|| !domRange
->IsPositioned()) {
3247 nsIContent
* start
= nsIContent::FromNode(domRange
->GetStartContainer());
3248 nsIContent
* end
= nsIContent::FromNode(domRange
->GetEndContainer());
3249 if (nsIContent
* child
= domRange
->StartRef().GetChildAtOffset()) {
3252 if (nsIContent
* child
= domRange
->EndRef().GetChildAtOffset()) {
3256 // Next check to see if our caret is at the very end of a text node. If so,
3257 // the caret is actually sitting in front of the next logical frame's primary
3258 // node - so for this case we need to change the content to that node.
3259 // Note that if the text does not have text frame, we do not need to retreive
3260 // caret frame. This could occur if text frame has only collapsisble white-
3261 // spaces and is around a block boundary or an ancestor of it is invisible.
3262 // XXX If there is a visible text sibling, should we return it in the former
3264 if (auto* text
= Text::FromNodeOrNull(start
);
3265 text
&& text
->GetPrimaryFrame() &&
3266 text
->TextDataLength() == domRange
->StartOffset() &&
3267 domSelection
->IsCollapsed()) {
3268 nsIFrame
* startFrame
= start
->GetPrimaryFrame();
3269 // Yes, indeed we were at the end of the last node
3271 domSelection
&& domSelection
->GetAncestorLimiter()
3272 ? domSelection
->GetAncestorLimiter()->GetPrimaryFrame()
3274 nsFrameIterator
frameIterator(presContext
, startFrame
,
3275 nsFrameIterator::Type::Leaf
,
3277 false, // aLockInScrollView
3278 true, // aFollowOOFs
3279 false, // aSkipPopupChecks
3282 nsIFrame
* newCaretFrame
= nullptr;
3283 nsIContent
* newCaretContent
= start
;
3284 const bool endOfSelectionInStartNode
= start
== end
;
3286 // Continue getting the next frame until the primary content for the
3287 // frame we are on changes - we don't want to be stuck in the same
3289 frameIterator
.Next();
3290 newCaretFrame
= frameIterator
.CurrentItem();
3291 if (!newCaretFrame
) {
3294 newCaretContent
= newCaretFrame
->GetContent();
3295 } while (!newCaretContent
|| newCaretContent
== start
);
3297 if (newCaretFrame
&& newCaretContent
) {
3298 // If the caret is exactly at the same position of the new frame,
3299 // then we can use the newCaretFrame and newCaretContent for our
3302 if (nsIFrame
* frame
= nsCaret::GetGeometry(domSelection
, &caretRect
)) {
3303 nsPoint caretWidgetOffset
;
3304 nsIWidget
* widget
= frame
->GetNearestWidget(caretWidgetOffset
);
3305 caretRect
.MoveBy(caretWidgetOffset
);
3306 nsPoint newCaretOffset
;
3307 nsIWidget
* newCaretWidget
=
3308 newCaretFrame
->GetNearestWidget(newCaretOffset
);
3309 if (widget
== newCaretWidget
&& caretRect
.TopLeft() == newCaretOffset
) {
3310 // The caret is at the start of the new element.
3311 startFrame
= newCaretFrame
;
3312 start
= newCaretContent
;
3313 if (endOfSelectionInStartNode
) {
3314 end
= newCaretContent
; // Ensure end of selection is
3322 NS_IF_ADDREF(*aStartContent
= start
);
3323 NS_IF_ADDREF(*aEndContent
= end
);
3326 nsresult
nsFocusManager::DetermineElementToMoveFocus(
3327 nsPIDOMWindowOuter
* aWindow
, nsIContent
* aStartContent
, int32_t aType
,
3328 bool aNoParentTraversal
, bool aNavigateByKey
, nsIContent
** aNextContent
) {
3329 *aNextContent
= nullptr;
3331 // This is used for document navigation only. It will be set to true if we
3332 // start navigating from a starting point. If this starting point is near the
3333 // end of the document (for example, an element on a statusbar), and there
3334 // are no child documents or panels before the end of the document, then we
3335 // will need to ensure that we don't consider the root chrome window when we
3336 // loop around and instead find the next child document/panel, as focus is
3337 // already in that window. This flag will be cleared once we navigate into
3338 // another document.
3339 bool mayFocusRoot
= (aStartContent
!= nullptr);
3341 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
3342 if (!startContent
&& aType
!= MOVEFOCUS_CARET
) {
3343 if (aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
) {
3344 // When moving between documents, make sure to get the right
3345 // starting content in a descendant.
3346 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
3347 startContent
= GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3348 getter_AddRefs(focusedWindow
));
3349 } else if (aType
!= MOVEFOCUS_LASTDOC
) {
3350 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
3351 // then we are document-navigating backwards from chrome to the content
3352 // process, and we don't want to use this so that we start from the end
3354 startContent
= aWindow
->GetFocusedElement();
3358 nsCOMPtr
<Document
> doc
;
3360 doc
= startContent
->GetComposedDoc();
3362 doc
= aWindow
->GetExtantDoc();
3363 if (!doc
) return NS_OK
;
3365 LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel
,
3366 &nsIContent::sTabFocusModel
);
3368 // True if we are navigating by document (F6/Shift+F6) or false if we are
3369 // navigating by element (Tab/Shift+Tab).
3370 const bool forDocumentNavigation
=
3371 aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
||
3372 aType
== MOVEFOCUS_FIRSTDOC
|| aType
== MOVEFOCUS_LASTDOC
;
3374 // If moving to the root or first document, find the root element and return.
3375 if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_FIRSTDOC
) {
3376 NS_IF_ADDREF(*aNextContent
= GetRootForFocus(aWindow
, doc
, false, false));
3377 if (!*aNextContent
&& aType
== MOVEFOCUS_FIRSTDOC
) {
3378 // When looking for the first document, if the root wasn't focusable,
3379 // find the next focusable document.
3380 aType
= MOVEFOCUS_FORWARDDOC
;
3386 // rootElement and presShell may be set to sub-document's ones so that they
3387 // cannot be `const`.
3388 RefPtr
<Element
> rootElement
= doc
->GetRootElement();
3389 NS_ENSURE_TRUE(rootElement
, NS_OK
);
3391 RefPtr
<PresShell
> presShell
= doc
->GetPresShell();
3392 NS_ENSURE_TRUE(presShell
, NS_OK
);
3394 if (aType
== MOVEFOCUS_FIRST
) {
3395 if (!aStartContent
) {
3396 startContent
= rootElement
;
3398 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3399 startContent
, true, 1, false, false,
3400 aNavigateByKey
, false, false, aNextContent
);
3402 if (aType
== MOVEFOCUS_LAST
) {
3403 if (!aStartContent
) {
3404 startContent
= rootElement
;
3406 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3407 startContent
, false, 0, false, false,
3408 aNavigateByKey
, false, false, aNextContent
);
3411 bool forward
= (aType
== MOVEFOCUS_FORWARD
|| aType
== MOVEFOCUS_FORWARDDOC
||
3412 aType
== MOVEFOCUS_CARET
);
3413 bool doNavigation
= true;
3414 bool ignoreTabIndex
= false;
3415 // when a popup is open, we want to ensure that tab navigation occurs only
3416 // within the most recently opened panel. If a popup is open, its frame will
3417 // be stored in popupFrame.
3418 nsIFrame
* popupFrame
= nullptr;
3420 int32_t tabIndex
= forward
? 1 : 0;
3422 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3423 tabIndex
= (frame
&& !startContent
->IsHTMLElement(nsGkAtoms::area
))
3424 ? frame
->IsFocusable().mTabIndex
3425 : startContent
->IsFocusableWithoutStyle().mTabIndex
;
3427 // if the current element isn't tabbable, ignore the tabindex and just
3428 // look for the next element. The root content won't have a tabindex
3429 // so just treat this as the beginning of the tab order.
3432 if (startContent
!= rootElement
) {
3433 ignoreTabIndex
= true;
3437 // check if the focus is currently inside a popup. Elements such as the
3438 // autocomplete widget use the noautofocus attribute to allow the focus to
3439 // remain outside the popup when it is opened.
3441 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3442 frame
, LayoutFrameType::MenuPopup
);
3445 if (popupFrame
&& !forDocumentNavigation
) {
3446 // Don't navigate outside of a popup, so pretend that the
3447 // root content is the popup itself
3448 rootElement
= popupFrame
->GetContent()->AsElement();
3449 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3450 } else if (!forward
) {
3451 // If focus moves backward and when current focused node is root
3452 // content or <body> element which is editable by contenteditable
3453 // attribute, focus should move to its parent document.
3454 if (startContent
== rootElement
) {
3455 doNavigation
= false;
3457 Document
* doc
= startContent
->GetComposedDoc();
3459 nsLayoutUtils::GetEditableRootContentByContentEditable(doc
)) {
3460 doNavigation
= false;
3465 if (aType
!= MOVEFOCUS_CARET
) {
3466 // if there is no focus, yet a panel is open, focus the first item in
3468 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
3470 popupFrame
= pm
->GetTopPopup(PopupType::Panel
);
3474 // When there is a popup open, and no starting content, start the search
3475 // at the topmost popup.
3476 startContent
= popupFrame
->GetContent();
3477 NS_ASSERTION(startContent
, "Popup frame doesn't have a content node");
3478 // Unless we are searching for documents, set the root content to the
3479 // popup as well, so that we don't tab-navigate outside the popup.
3480 // When navigating by documents, we start at the popup but can navigate
3481 // outside of it to look for other panels and documents.
3482 if (!forDocumentNavigation
) {
3483 rootElement
= startContent
->AsElement();
3486 doc
= startContent
? startContent
->GetComposedDoc() : nullptr;
3488 // Otherwise, for content shells, start from the location of the caret.
3489 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3490 if (docShell
&& docShell
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
3491 nsCOMPtr
<nsIContent
> endSelectionContent
;
3492 GetSelectionLocation(doc
, presShell
, getter_AddRefs(startContent
),
3493 getter_AddRefs(endSelectionContent
));
3494 // If the selection is on the rootElement, then there is no selection
3495 if (startContent
== rootElement
) {
3496 startContent
= nullptr;
3499 if (aType
== MOVEFOCUS_CARET
) {
3500 // GetFocusInSelection finds a focusable link near the caret.
3501 // If there is no start content though, don't do this to avoid
3502 // focusing something unexpected.
3504 GetFocusInSelection(aWindow
, startContent
, endSelectionContent
,
3511 // when starting from a selection, we always want to find the next or
3512 // previous element in the document. So the tabindex on elements
3513 // should be ignored.
3514 ignoreTabIndex
= true;
3518 if (!startContent
) {
3519 // otherwise, just use the root content as the starting point
3520 startContent
= rootElement
;
3521 NS_ENSURE_TRUE(startContent
, NS_OK
);
3526 // Check if the starting content is the same as the content assigned to the
3527 // retargetdocumentfocus attribute. Is so, we don't want to start searching
3528 // from there but instead from the beginning of the document. Otherwise, the
3529 // content that appears before the retargetdocumentfocus element will never
3530 // get checked as it will be skipped when the focus is retargetted to it.
3531 if (forDocumentNavigation
&& nsContentUtils::IsChromeDoc(doc
)) {
3532 nsAutoString retarget
;
3534 if (rootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
3535 nsIContent
* retargetElement
= doc
->GetElementById(retarget
);
3536 // The common case here is the urlbar where focus is on the anonymous
3537 // input inside the textbox, but the retargetdocumentfocus attribute
3538 // refers to the textbox. The Contains check will return false and the
3539 // IsInclusiveDescendantOf check will return true in this case.
3540 if (retargetElement
&&
3541 (retargetElement
== startContent
||
3542 (!retargetElement
->Contains(startContent
) &&
3543 startContent
->IsInclusiveDescendantOf(retargetElement
)))) {
3544 startContent
= rootElement
;
3549 NS_ASSERTION(startContent
, "starting content not set");
3551 // keep a reference to the starting content. If we find that again, it means
3552 // we've iterated around completely and we don't want to adjust the focus.
3553 // The skipOriginalContentCheck will be set to true only for the first time
3554 // GetNextTabbableContent is called. This ensures that we don't break out
3555 // when nothing is focused to start with. Specifically,
3556 // GetNextTabbableContent first checks the root content -- which happens to
3557 // be the same as the start content -- when nothing is focused and tabbing
3558 // forward. Without skipOriginalContentCheck set to true, we'd end up
3559 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
3560 // will never wrap around on its own, and can only return the original
3561 // content when it is called a second time or later.
3562 bool skipOriginalContentCheck
= true;
3563 const nsCOMPtr
<nsIContent
> originalStartContent
= startContent
;
3565 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent
.get());
3566 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
3567 forward
, tabIndex
, ignoreTabIndex
,
3568 forDocumentNavigation
));
3572 nsCOMPtr
<nsIContent
> nextFocus
;
3573 // TODO: MOZ_KnownLive is reruired due to bug 1770680
3574 nsresult rv
= GetNextTabbableContent(
3575 presShell
, rootElement
,
3576 MOZ_KnownLive(skipOriginalContentCheck
? nullptr
3577 : originalStartContent
.get()),
3578 startContent
, forward
, tabIndex
, ignoreTabIndex
,
3579 forDocumentNavigation
, aNavigateByKey
, false, false,
3580 getter_AddRefs(nextFocus
));
3581 NS_ENSURE_SUCCESS(rv
, rv
);
3582 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
3583 // Navigation was redirected to a child process, so just return.
3587 // found a content node to focus.
3589 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus
.get());
3591 // as long as the found node was not the same as the starting node,
3592 // set it as the return value. For document navigation, we can return
3593 // the same element in case there is only one content node that could
3594 // be returned, for example, in a child process document.
3595 if (nextFocus
!= originalStartContent
|| forDocumentNavigation
) {
3596 nextFocus
.forget(aNextContent
);
3601 if (popupFrame
&& !forDocumentNavigation
) {
3602 // in a popup, so start again from the beginning of the popup. However,
3603 // if we already started at the beginning, then there isn't anything to
3604 // focus, so just return
3605 if (startContent
!= rootElement
) {
3606 startContent
= rootElement
;
3607 tabIndex
= forward
? 1 : 0;
3614 doNavigation
= true;
3615 skipOriginalContentCheck
= forDocumentNavigation
;
3616 ignoreTabIndex
= false;
3618 if (aNoParentTraversal
) {
3619 if (startContent
== rootElement
) {
3623 startContent
= rootElement
;
3624 tabIndex
= forward
? 1 : 0;
3628 // Reached the beginning or end of the document. Next, navigate up to the
3629 // parent document and try again.
3630 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
3631 NS_ENSURE_TRUE(piWindow
, NS_ERROR_FAILURE
);
3633 nsCOMPtr
<nsIDocShell
> docShell
= piWindow
->GetDocShell();
3634 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
3636 // Get the frame element this window is inside and, from that, get the
3637 // parent document and presshell. If there is no enclosing frame element,
3638 // then this is a top-level, embedded or remote window.
3639 startContent
= piWindow
->GetFrameElementInternal();
3641 doc
= startContent
->GetComposedDoc();
3642 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
3644 rootElement
= doc
->GetRootElement();
3645 presShell
= doc
->GetPresShell();
3647 // We can focus the root element now that we have moved to another
3649 mayFocusRoot
= true;
3651 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3656 tabIndex
= frame
->IsFocusable().mTabIndex
;
3659 ignoreTabIndex
= true;
3662 // if the frame is inside a popup, make sure to scan only within the
3663 // popup. This handles the situation of tabbing amongst elements
3664 // inside an iframe which is itself inside a popup. Otherwise,
3665 // navigation would move outside the popup when tabbing outside the
3667 if (!forDocumentNavigation
) {
3668 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3669 frame
, LayoutFrameType::MenuPopup
);
3671 rootElement
= popupFrame
->GetContent()->AsElement();
3672 NS_ASSERTION(rootElement
, "Popup frame doesn't have a content node");
3676 if (aNavigateByKey
) {
3677 // There is no parent, so call the tree owner. This will tell the
3678 // embedder or parent process that it should take the focus.
3680 docShell
->TabToTreeOwner(forward
, forDocumentNavigation
, &tookFocus
);
3681 // If the tree owner took the focus, blur the current element.
3683 RefPtr
<BrowsingContext
> focusedBC
= GetFocusedBrowsingContext();
3684 if (focusedBC
&& focusedBC
->IsInProcess()) {
3685 Blur(focusedBC
, nullptr, true, true, false,
3686 GenerateFocusActionId());
3688 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docShell
->GetWindow();
3689 window
->SetFocusedElement(nullptr);
3695 // If we have reached the end of the top-level document, focus the
3696 // first element in the top-level document. This should always happen
3697 // when navigating by document forwards but when navigating backwards,
3698 // only do this if we started in another document or within a popup frame.
3699 // If the focus started in this window outside a popup however, we should
3700 // continue by looping around to the end again.
3701 if (forDocumentNavigation
&& (forward
|| mayFocusRoot
|| popupFrame
)) {
3702 // HTML content documents can have their root element focused by
3703 // pressing F6(a focus ring appears around the entire content area
3704 // frame). This root appears in the tab order before all of the elements
3705 // in the document. Chrome documents however cannot be focused directly,
3706 // so instead we focus the first focusable element within the window.
3707 // For example, the urlbar.
3708 RefPtr
<Element
> rootElementForFocus
=
3709 GetRootForFocus(piWindow
, doc
, true, true);
3710 return FocusFirst(rootElementForFocus
, aNextContent
,
3711 true /* aReachedToEndForDocumentNavigation */);
3714 // Once we have hit the top-level and have iterated to the end again, we
3715 // just want to break out next time we hit this spot to prevent infinite
3717 mayFocusRoot
= true;
3719 // reset the tab index and start again from the beginning or end
3720 startContent
= rootElement
;
3721 tabIndex
= forward
? 1 : 0;
3724 // wrapped all the way around and didn't find anything to move the focus
3725 // to, so just break out
3726 if (startContent
== originalStartContent
) {
3734 uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions
& aOptions
) {
3735 uint32_t flags
= FLAG_BYJS
;
3736 if (aOptions
.mPreventScroll
) {
3737 flags
|= FLAG_NOSCROLL
;
3739 if (aOptions
.mFocusVisible
.WasPassed()) {
3740 flags
|= aOptions
.mFocusVisible
.Value() ? FLAG_SHOWRING
: FLAG_NOSHOWRING
;
3742 if (UserActivation::IsHandlingKeyboardInput()) {
3743 flags
|= FLAG_BYKEY
;
3745 // TODO: We could do a similar thing if we're handling mouse input, but that
3746 // changes focusability of some elements so may be more risky.
3750 static bool IsHostOrSlot(const nsIContent
* aContent
) {
3751 return aContent
&& (aContent
->GetShadowRoot() ||
3752 aContent
->IsHTMLElement(nsGkAtoms::slot
));
3755 // Helper class to iterate contents in scope by traversing flattened tree
3757 class MOZ_STACK_CLASS ScopedContentTraversal
{
3759 ScopedContentTraversal(nsIContent
* aStartContent
, nsIContent
* aOwner
)
3760 : mCurrent(aStartContent
), mOwner(aOwner
) {
3761 MOZ_ASSERT(aStartContent
);
3767 void Reset() { SetCurrent(mOwner
); }
3769 nsIContent
* GetCurrent() const { return mCurrent
; }
3772 void SetCurrent(nsIContent
* aContent
) { mCurrent
= aContent
; }
3774 nsIContent
* mCurrent
;
3778 void ScopedContentTraversal::Next() {
3779 MOZ_ASSERT(mCurrent
);
3781 // Get mCurrent's first child if it's in the same scope.
3782 if (!IsHostOrSlot(mCurrent
) || mCurrent
== mOwner
) {
3783 StyleChildrenIterator
iter(mCurrent
);
3784 nsIContent
* child
= iter
.GetNextChild();
3791 // If mOwner has no children, END traversal
3792 if (mCurrent
== mOwner
) {
3793 SetCurrent(nullptr);
3797 nsIContent
* current
= mCurrent
;
3799 // Create parent's iterator and move to current
3800 nsIContent
* parent
= current
->GetFlattenedTreeParent();
3801 StyleChildrenIterator
parentIter(parent
);
3802 parentIter
.Seek(current
);
3804 // Get next sibling of current
3805 if (nsIContent
* next
= parentIter
.GetNextChild()) {
3810 // If no next sibling and parent is mOwner, END traversal
3811 if (parent
== mOwner
) {
3812 SetCurrent(nullptr);
3820 void ScopedContentTraversal::Prev() {
3821 MOZ_ASSERT(mCurrent
);
3825 if (mCurrent
== mOwner
) {
3826 // Get last child of mOwner
3827 StyleChildrenIterator
ownerIter(mOwner
, false /* aStartAtBeginning */);
3828 last
= ownerIter
.GetPreviousChild();
3832 // Create parent's iterator and move to mCurrent
3833 parent
= mCurrent
->GetFlattenedTreeParent();
3834 StyleChildrenIterator
parentIter(parent
);
3835 parentIter
.Seek(mCurrent
);
3837 // Get previous sibling
3838 last
= parentIter
.GetPreviousChild();
3843 if (IsHostOrSlot(parent
)) {
3844 // Skip contents in other scopes
3849 StyleChildrenIterator
iter(parent
, false /* aStartAtBeginning */);
3850 last
= iter
.GetPreviousChild();
3853 // If parent is mOwner and no previous sibling remains, END traversal
3854 SetCurrent(parent
== mOwner
? nullptr : parent
);
3857 static bool IsOpenPopoverWithInvoker(nsIContent
* aContent
) {
3858 if (auto* popover
= Element::FromNode(aContent
)) {
3859 return popover
&& popover
->IsPopoverOpen() &&
3860 popover
->GetPopoverData()->GetInvoker();
3865 static nsIContent
* InvokerForPopoverShowingState(nsIContent
* aContent
) {
3866 Element
* invoker
= Element::FromNode(aContent
);
3871 nsGenericHTMLElement
* popover
= invoker
->GetEffectivePopoverTargetElement();
3872 if (popover
&& popover
->IsPopoverOpen() &&
3873 popover
->GetPopoverData()->GetInvoker() == invoker
) {
3881 * Returns scope owner of aContent.
3882 * A scope owner is either a shadow host, or slot.
3884 static nsIContent
* FindScopeOwner(nsIContent
* aContent
) {
3885 nsIContent
* currentContent
= aContent
;
3886 while (currentContent
) {
3887 nsIContent
* parent
= currentContent
->GetFlattenedTreeParent();
3889 // Shadow host / Slot
3890 if (IsHostOrSlot(parent
)) {
3894 currentContent
= parent
;
3901 * Host and Slot elements need to be handled as if they had tabindex 0 even
3902 * when they don't have the attribute. This is a helper method to get the
3903 * right value for focus navigation. If aIsFocusable is passed, it is set to
3904 * true if the element itself is focusable.
3906 static int32_t HostOrSlotTabIndexValue(const nsIContent
* aContent
,
3907 bool* aIsFocusable
= nullptr) {
3908 MOZ_ASSERT(IsHostOrSlot(aContent
));
3911 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
3912 *aIsFocusable
= frame
&& frame
->IsFocusable().mTabIndex
>= 0;
3915 const nsAttrValue
* attrVal
=
3916 aContent
->AsElement()->GetParsedAttr(nsGkAtoms::tabindex
);
3921 if (attrVal
->Type() == nsAttrValue::eInteger
) {
3922 return attrVal
->GetIntegerValue();
3928 nsIContent
* nsFocusManager::GetNextTabbableContentInScope(
3929 nsIContent
* aOwner
, nsIContent
* aStartContent
,
3930 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t aCurrentTabIndex
,
3931 bool aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
3932 bool aSkipOwner
, bool aReachedToEndForDocumentNavigation
) {
3934 IsHostOrSlot(aOwner
) || IsOpenPopoverWithInvoker(aOwner
),
3935 "Scope owner should be host, slot or an open popover with invoker set.");
3937 // XXX: Why don't we ignore tabindex when the current tabindex < 0?
3938 MOZ_ASSERT_IF(aCurrentTabIndex
< 0, aIgnoreTabIndex
);
3940 if (!aSkipOwner
&& (aForward
&& aOwner
== aStartContent
)) {
3941 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3942 auto focusable
= frame
->IsFocusable();
3943 if (focusable
&& focusable
.mTabIndex
>= 0) {
3950 // Iterate contents in scope
3952 ScopedContentTraversal
contentTraversal(aStartContent
, aOwner
);
3953 nsCOMPtr
<nsIContent
> iterContent
;
3954 nsIContent
* firstNonChromeOnly
=
3955 aStartContent
->IsInNativeAnonymousSubtree()
3956 ? aStartContent
->FindFirstNonChromeOnlyAccessContent()
3959 // Iterate tab index to find corresponding contents in scope
3962 // Iterate remaining contents in scope to find next content to focus
3965 aForward
? contentTraversal
.Next() : contentTraversal
.Prev();
3966 iterContent
= contentTraversal
.GetCurrent();
3968 if (firstNonChromeOnly
&& firstNonChromeOnly
== iterContent
) {
3969 // We just broke out from the native anonymous content, so move
3970 // to the previous/next node of the native anonymous owner.
3972 contentTraversal
.Next();
3974 contentTraversal
.Prev();
3976 iterContent
= contentTraversal
.GetCurrent();
3983 int32_t tabIndex
= 0;
3984 if (iterContent
->IsInNativeAnonymousSubtree() &&
3985 iterContent
->GetPrimaryFrame()) {
3986 tabIndex
= iterContent
->GetPrimaryFrame()->IsFocusable().mTabIndex
;
3987 } else if (IsHostOrSlot(iterContent
)) {
3988 tabIndex
= HostOrSlotTabIndexValue(iterContent
);
3990 nsIFrame
* frame
= iterContent
->GetPrimaryFrame();
3994 tabIndex
= frame
->IsFocusable().mTabIndex
;
3996 if (tabIndex
< 0 || !(aIgnoreTabIndex
|| tabIndex
== aCurrentTabIndex
)) {
4000 if (!IsHostOrSlot(iterContent
)) {
4001 nsCOMPtr
<nsIContent
> elementInFrame
;
4002 bool checkSubDocument
= true;
4003 if (aForDocumentNavigation
&&
4004 TryDocumentNavigation(iterContent
, &checkSubDocument
,
4005 getter_AddRefs(elementInFrame
))) {
4006 return elementInFrame
;
4008 if (!checkSubDocument
) {
4012 if (TryToMoveFocusToSubDocument(iterContent
, aOriginalStartContent
,
4013 aForward
, aForDocumentNavigation
,
4015 aReachedToEndForDocumentNavigation
,
4016 getter_AddRefs(elementInFrame
))) {
4017 return elementInFrame
;
4020 // Found content to focus
4024 // Search in scope owned by iterContent
4025 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4026 iterContent
, iterContent
, aOriginalStartContent
, aForward
,
4027 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
4028 aNavigateByKey
, false /* aSkipOwner */,
4029 aReachedToEndForDocumentNavigation
);
4030 if (contentToFocus
) {
4031 return contentToFocus
;
4035 // If already at lowest priority tab (0), end search completely.
4036 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4037 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4041 // We've been just trying to find some focusable element, and haven't, so
4043 if (aIgnoreTabIndex
) {
4047 // Continue looking for next highest priority tabindex
4048 aCurrentTabIndex
= GetNextTabIndex(aOwner
, aCurrentTabIndex
, aForward
);
4049 contentTraversal
.Reset();
4052 // Return scope owner at last for backward navigation if its tabindex
4054 if (!aSkipOwner
&& !aForward
) {
4055 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
4056 auto focusable
= frame
->IsFocusable();
4057 if (focusable
&& focusable
.mTabIndex
>= 0) {
4066 nsIContent
* nsFocusManager::GetNextTabbableContentInAncestorScopes(
4067 nsIContent
* aStartOwner
, nsCOMPtr
<nsIContent
>& aStartContent
/* inout */,
4068 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t* aCurrentTabIndex
,
4069 bool* aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4070 bool aReachedToEndForDocumentNavigation
) {
4071 MOZ_ASSERT(aStartOwner
== FindScopeOwner(aStartContent
),
4072 "aStartOWner should be the scope owner of aStartContent");
4073 MOZ_ASSERT(IsHostOrSlot(aStartOwner
), "scope owner should be host or slot");
4075 nsCOMPtr
<nsIContent
> owner
= aStartOwner
;
4076 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4077 while (IsHostOrSlot(owner
)) {
4078 int32_t tabIndex
= 0;
4079 if (IsHostOrSlot(startContent
)) {
4080 tabIndex
= HostOrSlotTabIndexValue(startContent
);
4081 } else if (nsIFrame
* frame
= startContent
->GetPrimaryFrame()) {
4082 tabIndex
= frame
->IsFocusable().mTabIndex
;
4084 tabIndex
= startContent
->IsFocusableWithoutStyle().mTabIndex
;
4086 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4087 owner
, startContent
, aOriginalStartContent
, aForward
, tabIndex
,
4088 tabIndex
< 0, aForDocumentNavigation
, aNavigateByKey
,
4089 false /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4090 if (contentToFocus
) {
4091 return contentToFocus
;
4094 startContent
= owner
;
4095 owner
= FindScopeOwner(startContent
);
4098 // If not found in shadow DOM, search from the top level shadow host in light
4100 aStartContent
= startContent
;
4101 *aCurrentTabIndex
= HostOrSlotTabIndexValue(startContent
);
4103 if (*aCurrentTabIndex
< 0) {
4104 *aIgnoreTabIndex
= true;
4110 static nsIContent
* GetTopLevelScopeOwner(nsIContent
* aContent
) {
4111 nsIContent
* topLevelScopeOwner
= nullptr;
4113 if (HTMLSlotElement
* slot
= aContent
->GetAssignedSlot()) {
4115 topLevelScopeOwner
= aContent
;
4116 } else if (ShadowRoot
* shadowRoot
= aContent
->GetContainingShadow()) {
4117 aContent
= shadowRoot
->Host();
4118 topLevelScopeOwner
= aContent
;
4120 aContent
= aContent
->GetParent();
4121 if (aContent
&& (HTMLSlotElement::FromNode(aContent
) ||
4122 IsOpenPopoverWithInvoker(aContent
))) {
4123 topLevelScopeOwner
= aContent
;
4128 return topLevelScopeOwner
;
4131 nsresult
nsFocusManager::GetNextTabbableContent(
4132 PresShell
* aPresShell
, nsIContent
* aRootContent
,
4133 nsIContent
* aOriginalStartContent
, nsIContent
* aStartContent
, bool aForward
,
4134 int32_t aCurrentTabIndex
, bool aIgnoreTabIndex
, bool aForDocumentNavigation
,
4135 bool aNavigateByKey
, bool aSkipPopover
,
4136 bool aReachedToEndForDocumentNavigation
, nsIContent
** aResultContent
) {
4137 *aResultContent
= nullptr;
4139 if (!aStartContent
) {
4143 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
4144 nsCOMPtr
<nsIContent
> currentTopLevelScopeOwner
=
4145 GetTopLevelScopeOwner(startContent
);
4147 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent
);
4148 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex
));
4150 // If startContent is a shadow host or slot in forward navigation,
4151 // search in scope owned by startContent
4152 if (aForward
&& IsHostOrSlot(startContent
)) {
4153 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4154 startContent
, startContent
, aOriginalStartContent
, aForward
, 1,
4155 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4156 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4157 if (contentToFocus
) {
4158 NS_ADDREF(*aResultContent
= contentToFocus
);
4163 // If startContent is a popover invoker, search the popover scope.
4164 if (!aSkipPopover
) {
4165 if (InvokerForPopoverShowingState(startContent
)) {
4167 RefPtr
<nsIContent
> popover
=
4168 startContent
->GetEffectivePopoverTargetElement();
4169 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4170 popover
, popover
, aOriginalStartContent
, aForward
, 1,
4171 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4172 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4173 if (contentToFocus
) {
4174 NS_ADDREF(*aResultContent
= contentToFocus
);
4181 // If startContent is in a scope owned by Shadow DOM search from scope
4182 // including startContent
4183 if (nsCOMPtr
<nsIContent
> owner
= FindScopeOwner(startContent
)) {
4184 nsIContent
* contentToFocus
= GetNextTabbableContentInAncestorScopes(
4185 owner
, startContent
/* inout */, aOriginalStartContent
, aForward
,
4186 &aCurrentTabIndex
, &aIgnoreTabIndex
, aForDocumentNavigation
,
4187 aNavigateByKey
, aReachedToEndForDocumentNavigation
);
4188 if (contentToFocus
) {
4189 NS_ADDREF(*aResultContent
= contentToFocus
);
4194 // If we reach here, it means no next tabbable content in shadow DOM.
4195 // We need to continue searching in light DOM, starting at the top level
4196 // shadow host in light DOM (updated startContent) and its tabindex
4197 // (updated aCurrentTabIndex).
4198 MOZ_ASSERT(!FindScopeOwner(startContent
),
4199 "startContent should not be owned by Shadow DOM at this point");
4201 nsPresContext
* presContext
= aPresShell
->GetPresContext();
4203 bool getNextFrame
= true;
4204 nsCOMPtr
<nsIContent
> iterStartContent
= startContent
;
4205 nsIContent
* topLevelScopeStartContent
= startContent
;
4206 // Iterate tab index to find corresponding contents
4208 nsIFrame
* frame
= iterStartContent
->GetPrimaryFrame();
4209 // if there is no frame, look for another content node that has a frame
4211 // if the root content doesn't have a frame, just return
4212 if (iterStartContent
== aRootContent
) {
4216 // look for the next or previous content node in tree order
4217 iterStartContent
= aForward
? iterStartContent
->GetNextNode()
4218 : iterStartContent
->GetPrevNode();
4219 if (!iterStartContent
) {
4223 frame
= iterStartContent
->GetPrimaryFrame();
4224 // Host without frame, enter its scope.
4225 if (!frame
&& iterStartContent
->GetShadowRoot()) {
4226 int32_t tabIndex
= HostOrSlotTabIndexValue(iterStartContent
);
4227 if (tabIndex
>= 0 &&
4228 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4229 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4230 iterStartContent
, iterStartContent
, aOriginalStartContent
,
4231 aForward
, aForward
? 1 : 0, aIgnoreTabIndex
,
4232 aForDocumentNavigation
, aNavigateByKey
, true /* aSkipOwner */,
4233 aReachedToEndForDocumentNavigation
);
4234 if (contentToFocus
) {
4235 NS_ADDREF(*aResultContent
= contentToFocus
);
4240 // we've already skipped over the initial focused content, so we
4241 // don't want to traverse frames.
4242 getNextFrame
= false;
4245 Maybe
<nsFrameIterator
> frameIterator
;
4247 // For tab navigation, pass false for aSkipPopupChecks so that we don't
4248 // iterate into or out of a popup. For document naviation pass true to
4249 // ignore these boundaries.
4250 frameIterator
.emplace(presContext
, frame
, nsFrameIterator::Type::PreOrder
,
4252 false, // aLockInScrollView
4253 true, // aFollowOOFs
4254 aForDocumentNavigation
// aSkipPopupChecks
4256 MOZ_ASSERT(frameIterator
);
4258 if (iterStartContent
== aRootContent
) {
4260 frameIterator
->Last();
4261 } else if (aRootContent
->IsFocusableWithoutStyle()) {
4262 frameIterator
->Next();
4264 frame
= frameIterator
->CurrentItem();
4265 } else if (getNextFrame
&&
4266 (!iterStartContent
||
4267 !iterStartContent
->IsHTMLElement(nsGkAtoms::area
))) {
4268 // Need to do special check in case we're in an imagemap which has
4269 // multiple content nodes per frame, so don't skip over the starting
4271 frame
= frameIterator
->Traverse(aForward
);
4275 nsIContent
* oldTopLevelScopeOwner
= nullptr;
4276 // Walk frames to find something tabbable matching aCurrentTabIndex
4278 // Try to find the topmost scope owner, since we want to skip the node
4279 // that is not owned by document in frame traversal.
4280 const nsCOMPtr
<nsIContent
> currentContent
= frame
->GetContent();
4281 if (currentTopLevelScopeOwner
) {
4282 oldTopLevelScopeOwner
= currentTopLevelScopeOwner
;
4284 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(currentContent
);
4286 // We handle popover case separately.
4287 if (currentTopLevelScopeOwner
&&
4288 currentTopLevelScopeOwner
== oldTopLevelScopeOwner
&&
4289 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner
)) {
4290 // We're within non-document scope, continue.
4293 frameIterator
->Next();
4295 frameIterator
->Prev();
4297 frame
= frameIterator
->CurrentItem();
4298 // For the usage of GetPrevContinuation, see the comment
4299 // at the end of while (frame) loop.
4300 } while (frame
&& frame
->GetPrevContinuation());
4304 // Stepping out popover scope.
4305 // For forward, search for the next tabbable content after invoker.
4306 // For backward, we should get back to the invoker.
4307 if (oldTopLevelScopeOwner
&&
4308 IsOpenPopoverWithInvoker(oldTopLevelScopeOwner
) &&
4309 currentTopLevelScopeOwner
!= oldTopLevelScopeOwner
) {
4310 if (auto* popover
= Element::FromNode(oldTopLevelScopeOwner
)) {
4311 RefPtr
<nsIContent
> invokerContent
=
4312 popover
->GetPopoverData()->GetInvoker()->AsContent();
4314 nsIFrame
* frame
= invokerContent
->GetPrimaryFrame();
4315 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4316 RefPtr
<nsIContent
> rootElement
= invokerContent
;
4317 if (auto* doc
= invokerContent
->GetComposedDoc()) {
4318 rootElement
= doc
->GetRootElement();
4320 if (tabIndex
>= 0 &&
4321 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4322 nsresult rv
= GetNextTabbableContent(
4323 aPresShell
, rootElement
, nullptr, invokerContent
, true,
4324 tabIndex
, false, false, aNavigateByKey
, true,
4325 aReachedToEndForDocumentNavigation
, aResultContent
);
4326 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4330 } else if (invokerContent
&&
4331 invokerContent
->IsFocusableWithoutStyle()) {
4332 // FIXME(emilio): The check above should probably use
4333 // nsIFrame::IsFocusable, not IsFocusableWithoutStyle.
4334 invokerContent
.forget(aResultContent
);
4341 if (InvokerForPopoverShowingState(currentContent
)) {
4342 RefPtr
<nsIContent
> popover
=
4343 currentContent
->GetEffectivePopoverTargetElement();
4344 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4345 popover
, popover
, aOriginalStartContent
, aForward
, 0,
4346 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4347 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4349 if (contentToFocus
) {
4350 NS_ADDREF(*aResultContent
= contentToFocus
);
4355 // For document navigation, check if this element is an open panel. Since
4356 // panels aren't focusable (tabIndex would be -1), we'll just assume that
4357 // for document navigation, the tabIndex is 0.
4358 if (aForDocumentNavigation
&& currentContent
&& (aCurrentTabIndex
== 0) &&
4359 currentContent
->IsXULElement(nsGkAtoms::panel
)) {
4360 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
4361 // Check if the panel is open. Closed panels are ignored since you can't
4362 // focus anything in them.
4363 if (popupFrame
&& popupFrame
->IsOpen()) {
4364 // When moving backward, skip the popup we started in otherwise it
4365 // will be selected again.
4366 bool validPopup
= true;
4368 nsIContent
* content
= topLevelScopeStartContent
;
4370 if (content
== currentContent
) {
4375 content
= content
->GetParent();
4380 // Since a panel isn't focusable itself, find the first focusable
4381 // content within the popup. If there isn't any focusable content
4382 // in the popup, skip this popup and continue iterating through the
4383 // frames. We pass the panel itself (currentContent) as the starting
4384 // and root content, so that we only find content within the panel.
4385 // Note also that we pass false for aForDocumentNavigation since we
4386 // want to locate the first content, not the first document.
4387 nsresult rv
= GetNextTabbableContent(
4388 aPresShell
, currentContent
, nullptr, currentContent
, true, 1,
4389 false, false, aNavigateByKey
, false,
4390 aReachedToEndForDocumentNavigation
, aResultContent
);
4391 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4398 // As of now, 2018/04/12, sequential focus navigation is still
4399 // in the obsolete Shadow DOM specification.
4400 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
4401 // "if ELEMENT is focusable, a shadow host, or a slot element,
4402 // append ELEMENT to NAVIGATION-ORDER."
4403 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
4404 // hosts and slots are handled before other elements.
4405 if (currentTopLevelScopeOwner
&&
4406 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner
)) {
4407 bool focusableHostSlot
;
4408 int32_t tabIndex
= HostOrSlotTabIndexValue(currentTopLevelScopeOwner
,
4409 &focusableHostSlot
);
4410 // Host or slot itself isn't focusable or going backwards, enter its
4412 if ((!aForward
|| !focusableHostSlot
) && tabIndex
>= 0 &&
4413 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4414 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4415 currentTopLevelScopeOwner
, currentTopLevelScopeOwner
,
4416 aOriginalStartContent
, aForward
, aForward
? 1 : 0,
4417 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4418 true /* aSkipOwner */, aReachedToEndForDocumentNavigation
);
4419 if (contentToFocus
) {
4420 NS_ADDREF(*aResultContent
= contentToFocus
);
4423 // If we've wrapped around already, then carry on.
4424 if (aOriginalStartContent
&&
4425 currentTopLevelScopeOwner
==
4426 GetTopLevelScopeOwner(aOriginalStartContent
)) {
4427 // FIXME: Shouldn't this return null instead? aOriginalStartContent
4428 // isn't focusable after all.
4429 NS_ADDREF(*aResultContent
= aOriginalStartContent
);
4433 // There is no next tabbable content in currentTopLevelScopeOwner's
4434 // scope. We should continue the loop in order to skip all contents that
4435 // is in currentTopLevelScopeOwner's scope.
4440 !GetTopLevelScopeOwner(currentContent
) ||
4441 IsOpenPopoverWithInvoker(GetTopLevelScopeOwner(currentContent
)),
4442 "currentContent should be in top-level-scope at this point unless "
4443 "for popover case");
4445 // TabIndex not set defaults to 0 for form elements, anchors and other
4446 // elements that are normally focusable. Tabindex defaults to -1
4447 // for elements that are not normally focusable.
4448 // The returned computed tabindex from IsFocusable() is as follows:
4450 // < 0 not tabbable at all
4451 // == 0 in normal tab order (last after positive tabindexed items)
4452 // > 0 can be tabbed to in the order specified by this value
4454 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4456 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame
->GetContent());
4458 (" with tabindex: %d expected: %d", tabIndex
, aCurrentTabIndex
));
4460 if (tabIndex
>= 0) {
4461 NS_ASSERTION(currentContent
,
4462 "IsFocusable set a tabindex for a frame with no content");
4463 if (!aForDocumentNavigation
&&
4464 currentContent
->IsHTMLElement(nsGkAtoms::img
) &&
4465 currentContent
->AsElement()->HasAttr(nsGkAtoms::usemap
)) {
4466 // This is an image with a map. Image map areas are not traversed by
4467 // nsFrameIterator so look for the next or previous area element.
4468 nsIContent
* areaContent
= GetNextTabbableMapArea(
4469 aForward
, aCurrentTabIndex
, currentContent
->AsElement(),
4472 NS_ADDREF(*aResultContent
= areaContent
);
4475 } else if (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
) {
4476 // break out if we've wrapped around to the start again.
4477 if (aOriginalStartContent
&&
4478 currentContent
== aOriginalStartContent
) {
4479 NS_ADDREF(*aResultContent
= currentContent
);
4483 // If this is a remote child browser, call NavigateDocument to have
4484 // the child process continue the navigation. Return a special error
4485 // code to have the caller return early. If the child ends up not
4486 // being focusable in some way, the child process will call back
4487 // into document navigation again by calling MoveFocus.
4488 if (BrowserParent
* remote
= BrowserParent::GetFrom(currentContent
)) {
4489 if (aNavigateByKey
) {
4490 remote
->NavigateByKey(aForward
, aForDocumentNavigation
);
4491 return NS_SUCCESS_DOM_NO_OPERATION
;
4496 // Same as above but for out-of-process iframes
4497 if (auto* bbc
= BrowserBridgeChild::GetFrom(currentContent
)) {
4498 if (aNavigateByKey
) {
4499 bbc
->NavigateByKey(aForward
, aForDocumentNavigation
);
4500 return NS_SUCCESS_DOM_NO_OPERATION
;
4505 // Next, for document navigation, check if this a non-remote child
4507 bool checkSubDocument
= true;
4508 if (aForDocumentNavigation
&&
4509 TryDocumentNavigation(currentContent
, &checkSubDocument
,
4514 if (checkSubDocument
) {
4515 // found a node with a matching tab index. Check if it is a child
4516 // frame. If so, navigate into the child frame instead.
4517 if (TryToMoveFocusToSubDocument(
4518 currentContent
, aOriginalStartContent
, aForward
,
4519 aForDocumentNavigation
, aNavigateByKey
,
4520 aReachedToEndForDocumentNavigation
, aResultContent
)) {
4521 MOZ_ASSERT(*aResultContent
);
4524 // otherwise, use this as the next content node to tab to, unless
4525 // this was the element we started on. This would happen for
4526 // instance on an element with child frames, where frame navigation
4527 // could return the original element again. In that case, just skip
4528 // it. Also, if the next content node is the root content, then
4529 // return it. This latter case would happen only if someone made a
4531 else if (currentContent
== aRootContent
||
4532 currentContent
!= startContent
) {
4533 NS_ADDREF(*aResultContent
= currentContent
);
4536 } else if (currentContent
&& aReachedToEndForDocumentNavigation
&&
4537 StaticPrefs::dom_disable_tab_focus_to_root_element() &&
4538 nsContentUtils::IsChromeDoc(
4539 currentContent
->GetComposedDoc())) {
4540 // aReachedToEndForDocumentNavigation is true means
4541 // 1. This is a document navigation (VK_F6)
4542 // 2. This is the top-level document (Note that we may start from
4544 // 3. We've searched through the this top-level document already
4545 if (!GetRootForChildDocument(currentContent
)) {
4546 // We'd like to focus the first focusable element of this
4547 // top-level chrome document.
4548 if (currentContent
== aRootContent
||
4549 currentContent
!= startContent
) {
4550 NS_ADDREF(*aResultContent
= currentContent
);
4556 } else if (aOriginalStartContent
&&
4557 currentContent
== aOriginalStartContent
) {
4558 // not focusable, so return if we have wrapped around to the original
4559 // content. This is necessary in case the original starting content was
4562 // FIXME: Shouldn't this return null instead? currentContent isn't
4563 // focusable after all.
4564 NS_ADDREF(*aResultContent
= currentContent
);
4568 // Move to the next or previous frame, but ignore continuation frames
4569 // since only the first frame should be involved in focusability.
4570 // Otherwise, a loop will occur in the following example:
4571 // <span tabindex="1">...<a/><a/>...</span>
4572 // where the text wraps onto multiple lines. Tabbing from the second
4573 // link can find one of the span's continuation frames between the link
4574 // and the end of the span, and the span would end up getting focused
4578 frameIterator
->Next();
4580 frameIterator
->Prev();
4582 frame
= frameIterator
->CurrentItem();
4583 } while (frame
&& frame
->GetPrevContinuation());
4586 // If already at lowest priority tab (0), end search completely.
4587 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4588 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4589 // if going backwards, the canvas should be focused once the beginning
4590 // has been reached, so get the root element.
4591 if (!aForward
&& !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4592 nsCOMPtr
<nsPIDOMWindowOuter
> window
= GetCurrentWindow(aRootContent
);
4593 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
4595 RefPtr
<Element
> docRoot
= GetRootForFocus(
4596 window
, aRootContent
->GetComposedDoc(), false, true);
4597 FocusFirst(docRoot
, aResultContent
,
4598 false /* aReachedToEndForDocumentNavigation */);
4603 // continue looking for next highest priority tabindex
4605 GetNextTabIndex(aRootContent
, aCurrentTabIndex
, aForward
);
4606 startContent
= iterStartContent
= aRootContent
;
4607 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(startContent
);
4613 bool nsFocusManager::TryDocumentNavigation(nsIContent
* aCurrentContent
,
4614 bool* aCheckSubDocument
,
4615 nsIContent
** aResultContent
) {
4616 *aCheckSubDocument
= true;
4617 if (RefPtr
<Element
> rootElementForChildDocument
=
4618 GetRootForChildDocument(aCurrentContent
)) {
4619 // If GetRootForChildDocument returned something then call
4620 // FocusFirst to find the root or first element to focus within
4621 // the child document. If this is a frameset though, skip this and
4622 // fall through to normal tab navigation to iterate into
4623 // the frameset's frames and locate the first focusable frame.
4624 if (!rootElementForChildDocument
->IsHTMLElement(nsGkAtoms::frameset
)) {
4625 *aCheckSubDocument
= false;
4626 Unused
<< FocusFirst(rootElementForChildDocument
, aResultContent
,
4627 false /* aReachedToEndForDocumentNavigation */);
4628 return *aResultContent
!= nullptr;
4631 // Set aCheckSubDocument to false, as this was neither a frame
4632 // type element or a child document that was focusable.
4633 *aCheckSubDocument
= false;
4639 bool nsFocusManager::TryToMoveFocusToSubDocument(
4640 nsIContent
* aCurrentContent
, nsIContent
* aOriginalStartContent
,
4641 bool aForward
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4642 bool aReachedToEndForDocumentNavigation
, nsIContent
** aResultContent
) {
4643 Document
* doc
= aCurrentContent
->GetComposedDoc();
4644 NS_ASSERTION(doc
, "content not in document");
4645 Document
* subdoc
= doc
->GetSubDocumentFor(aCurrentContent
);
4646 if (subdoc
&& !subdoc
->EventHandlingSuppressed()) {
4647 if (aForward
&& !StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4648 // When tabbing forward into a frame, return the root
4649 // frame so that the canvas becomes focused.
4650 if (nsCOMPtr
<nsPIDOMWindowOuter
> subframe
= subdoc
->GetWindow()) {
4651 *aResultContent
= GetRootForFocus(subframe
, subdoc
, false, true);
4652 if (*aResultContent
) {
4653 NS_ADDREF(*aResultContent
);
4658 if (RefPtr
<Element
> rootElement
= subdoc
->GetRootElement()) {
4659 if (RefPtr
<PresShell
> subPresShell
= subdoc
->GetPresShell()) {
4660 nsresult rv
= GetNextTabbableContent(
4661 subPresShell
, rootElement
, aOriginalStartContent
, rootElement
,
4662 aForward
, (aForward
? 1 : 0), false, aForDocumentNavigation
,
4663 aNavigateByKey
, false, aReachedToEndForDocumentNavigation
,
4665 NS_ENSURE_SUCCESS(rv
, false);
4666 if (*aResultContent
) {
4669 if (rootElement
->IsEditable() &&
4670 StaticPrefs::dom_disable_tab_focus_to_root_element()) {
4671 // Only move to the root element with a valid reason
4672 *aResultContent
= rootElement
;
4673 NS_ADDREF(*aResultContent
);
4682 nsIContent
* nsFocusManager::GetNextTabbableMapArea(bool aForward
,
4683 int32_t aCurrentTabIndex
,
4684 Element
* aImageContent
,
4685 nsIContent
* aStartContent
) {
4686 if (aImageContent
->IsInComposedDoc()) {
4687 HTMLImageElement
* imgElement
= HTMLImageElement::FromNode(aImageContent
);
4688 // The caller should check the element type, so we can assert here.
4689 MOZ_ASSERT(imgElement
);
4691 nsCOMPtr
<nsIContent
> mapContent
= imgElement
->FindImageMap();
4695 // First see if the the start content is in this map
4696 Maybe
<uint32_t> indexOfStartContent
=
4697 mapContent
->ComputeIndexOf(aStartContent
);
4698 nsIContent
* scanStartContent
;
4699 Focusable focusable
;
4700 if (indexOfStartContent
.isNothing() ||
4701 ((focusable
= aStartContent
->IsFocusableWithoutStyle()) &&
4702 focusable
.mTabIndex
!= aCurrentTabIndex
)) {
4703 // If aStartContent is in this map we must start iterating past it.
4704 // We skip the case where aStartContent has tabindex == aStartContent
4705 // since the next tab ordered element might be before it
4706 // (or after for backwards) in the child list.
4708 aForward
? mapContent
->GetFirstChild() : mapContent
->GetLastChild();
4710 scanStartContent
= aForward
? aStartContent
->GetNextSibling()
4711 : aStartContent
->GetPreviousSibling();
4714 for (nsCOMPtr
<nsIContent
> areaContent
= scanStartContent
; areaContent
;
4715 areaContent
= aForward
? areaContent
->GetNextSibling()
4716 : areaContent
->GetPreviousSibling()) {
4717 focusable
= areaContent
->IsFocusableWithoutStyle();
4718 if (focusable
&& focusable
.mTabIndex
== aCurrentTabIndex
) {
4727 int32_t nsFocusManager::GetNextTabIndex(nsIContent
* aParent
,
4728 int32_t aCurrentTabIndex
,
4730 int32_t tabIndex
, childTabIndex
;
4731 StyleChildrenIterator
iter(aParent
);
4735 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4736 child
= iter
.GetNextChild()) {
4737 // Skip child's descendants if child is a shadow host or slot, as they are
4738 // in the focus navigation scope owned by child's shadow root
4739 if (!IsHostOrSlot(child
)) {
4740 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4741 if (childTabIndex
> aCurrentTabIndex
&& childTabIndex
!= tabIndex
) {
4742 tabIndex
= (tabIndex
== 0 || childTabIndex
< tabIndex
) ? childTabIndex
4747 nsAutoString tabIndexStr
;
4748 if (child
->IsElement()) {
4749 child
->AsElement()->GetAttr(nsGkAtoms::tabindex
, tabIndexStr
);
4752 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4753 if (NS_SUCCEEDED(ec
) && val
> aCurrentTabIndex
&& val
!= tabIndex
) {
4754 tabIndex
= (tabIndex
== 0 || val
< tabIndex
) ? val
: tabIndex
;
4757 } else { /* !aForward */
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 ((aCurrentTabIndex
== 0 && childTabIndex
> tabIndex
) ||
4766 (childTabIndex
< aCurrentTabIndex
&& childTabIndex
> tabIndex
)) {
4767 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
)) {
4778 if ((aCurrentTabIndex
== 0 && val
> tabIndex
) ||
4779 (val
< aCurrentTabIndex
&& val
> tabIndex
)) {
4789 nsresult
nsFocusManager::FocusFirst(Element
* aRootElement
,
4790 nsIContent
** aNextContent
,
4791 bool aReachedToEndForDocumentNavigation
) {
4792 if (!aRootElement
) {
4796 Document
* doc
= aRootElement
->GetComposedDoc();
4798 if (nsContentUtils::IsChromeDoc(doc
)) {
4799 // If the redirectdocumentfocus attribute is set, redirect the focus to a
4800 // specific element. This is primarily used to retarget the focus to the
4801 // urlbar during document navigation.
4802 nsAutoString retarget
;
4804 if (aRootElement
->GetAttr(nsGkAtoms::retargetdocumentfocus
, retarget
)) {
4805 RefPtr
<Element
> element
= doc
->GetElementById(retarget
);
4806 nsCOMPtr
<nsIContent
> retargetElement
=
4807 FlushAndCheckIfFocusable(element
, 0);
4808 if (retargetElement
) {
4809 retargetElement
.forget(aNextContent
);
4815 nsCOMPtr
<nsIDocShell
> docShell
= doc
->GetDocShell();
4816 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4817 // If the found content is in a chrome shell, navigate forward one
4818 // tabbable item so that the first item is focused. Note that we
4819 // always go forward and not back here.
4820 if (RefPtr
<PresShell
> presShell
= doc
->GetPresShell()) {
4821 return GetNextTabbableContent(
4822 presShell
, aRootElement
, nullptr, aRootElement
, true, 1, false,
4823 StaticPrefs::dom_disable_tab_focus_to_root_element()
4824 ? aReachedToEndForDocumentNavigation
4826 true, false, aReachedToEndForDocumentNavigation
, aNextContent
);
4831 NS_ADDREF(*aNextContent
= aRootElement
);
4835 Element
* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter
* aWindow
,
4836 Document
* aDocument
,
4837 bool aForDocumentNavigation
,
4838 bool aCheckVisibility
) {
4839 if (!aForDocumentNavigation
) {
4840 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
4841 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4846 if (aCheckVisibility
&& !IsWindowVisible(aWindow
)) return nullptr;
4848 // If the body is contenteditable, use the editor's root element rather than
4849 // the actual root element.
4850 RefPtr
<Element
> rootElement
=
4851 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument
);
4852 if (!rootElement
|| !rootElement
->GetPrimaryFrame()) {
4853 rootElement
= aDocument
->GetRootElement();
4859 if (aCheckVisibility
&& !rootElement
->GetPrimaryFrame()) {
4863 // Finally, check if this is a frameset
4864 if (aDocument
&& aDocument
->IsHTMLOrXHTML()) {
4865 Element
* htmlChild
= aDocument
->GetHtmlChildElement(nsGkAtoms::frameset
);
4867 // In document navigation mode, return the frameset so that navigation
4868 // descends into the child frames.
4869 return aForDocumentNavigation
? htmlChild
: nullptr;
4876 Element
* nsFocusManager::GetRootForChildDocument(nsIContent
* aContent
) {
4877 // Check for elements that represent child documents, that is, browsers,
4878 // editors or frames from a frameset. We don't include iframes since we
4879 // consider them to be an integral part of the same window or page.
4880 if (!aContent
|| !(aContent
->IsXULElement(nsGkAtoms::browser
) ||
4881 aContent
->IsXULElement(nsGkAtoms::editor
) ||
4882 aContent
->IsHTMLElement(nsGkAtoms::frame
))) {
4886 Document
* doc
= aContent
->GetComposedDoc();
4891 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
4892 if (!subdoc
|| subdoc
->EventHandlingSuppressed()) {
4896 nsCOMPtr
<nsPIDOMWindowOuter
> window
= subdoc
->GetWindow();
4897 return GetRootForFocus(window
, subdoc
, true, true);
4900 static bool IsLink(nsIContent
* aContent
) {
4901 return aContent
->IsElement() && aContent
->AsElement()->IsLink();
4904 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter
* aWindow
,
4905 nsIContent
* aStartSelection
,
4906 nsIContent
* aEndSelection
,
4907 nsIContent
** aFocusedContent
) {
4908 *aFocusedContent
= nullptr;
4910 nsCOMPtr
<nsIContent
> testContent
= aStartSelection
;
4911 nsCOMPtr
<nsIContent
> nextTestContent
= aEndSelection
;
4913 nsCOMPtr
<nsIContent
> currentFocus
= aWindow
->GetFocusedElement();
4915 // We now have the correct start node in selectionContent!
4916 // Search for focusable elements, starting with selectionContent
4918 // Method #1: Keep going up while we look - an ancestor might be focusable
4919 // We could end the loop earlier, such as when we're no longer
4920 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4921 // with a variable holding the starting selectionContent
4922 while (testContent
) {
4923 // Keep testing while selectionContent is equal to something,
4924 // eventually we'll run out of ancestors
4926 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4927 testContent
.forget(aFocusedContent
);
4932 testContent
= testContent
->GetParent();
4935 // We run this loop again, checking the ancestor chain of the selection's
4937 testContent
= nextTestContent
;
4938 nextTestContent
= nullptr;
4942 // We couldn't find an anchor that was an ancestor of the selection start
4943 // Method #2: look for anchor in selection's primary range (depth first
4946 nsCOMPtr
<nsIContent
> selectionNode
= aStartSelection
;
4947 nsCOMPtr
<nsIContent
> endSelectionNode
= aEndSelection
;
4948 nsCOMPtr
<nsIContent
> testNode
;
4951 testContent
= selectionNode
;
4953 // We're looking for any focusable link that could be part of the
4954 // main document's selection.
4955 if (testContent
== currentFocus
|| IsLink(testContent
)) {
4956 testContent
.forget(aFocusedContent
);
4960 nsIContent
* testNode
= selectionNode
->GetFirstChild();
4962 selectionNode
= testNode
;
4966 if (selectionNode
== endSelectionNode
) {
4969 testNode
= selectionNode
->GetNextSibling();
4971 selectionNode
= testNode
;
4976 // GetParent is OK here, instead of GetParentNode, because the only case
4977 // where the latter returns something different from the former is when
4978 // GetParentNode is the document. But in that case we would simply get
4979 // null for selectionNode when setting it to testNode->GetNextSibling()
4980 // (because a document has no next sibling). And then the next iteration
4981 // of this loop would get null for GetParentNode anyway, and break out of
4983 testNode
= selectionNode
->GetParent();
4984 if (!testNode
|| testNode
== endSelectionNode
) {
4985 selectionNode
= nullptr;
4988 selectionNode
= testNode
->GetNextSibling();
4989 if (selectionNode
) {
4992 selectionNode
= testNode
;
4994 } while (selectionNode
&& selectionNode
!= endSelectionNode
);
4997 static void MaybeUnlockPointer(BrowsingContext
* aCurrentFocusedContext
) {
4998 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext
)) {
4999 PointerLockManager::Unlock();
5003 class PointerUnlocker
: public Runnable
{
5005 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
5006 MOZ_ASSERT(XRE_IsParentProcess());
5007 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker
);
5008 PointerUnlocker::sActiveUnlocker
= this;
5011 ~PointerUnlocker() {
5012 if (PointerUnlocker::sActiveUnlocker
== this) {
5013 PointerUnlocker::sActiveUnlocker
= nullptr;
5017 NS_IMETHOD
Run() override
{
5018 if (PointerUnlocker::sActiveUnlocker
== this) {
5019 PointerUnlocker::sActiveUnlocker
= nullptr;
5021 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
5022 nsPIDOMWindowOuter
* focused
=
5023 nsFocusManager::GetFocusManager()->GetFocusedWindow();
5024 MaybeUnlockPointer(focused
? focused
->GetBrowsingContext() : nullptr);
5028 static PointerUnlocker
* sActiveUnlocker
;
5031 PointerUnlocker
* PointerUnlocker::sActiveUnlocker
= nullptr;
5033 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext
* aContext
,
5034 uint64_t aActionId
) {
5035 if (XRE_IsParentProcess()) {
5038 MOZ_ASSERT(!ActionIdComparableAndLower(
5039 aActionId
, mActionIdForFocusedBrowsingContextInContent
));
5040 mFocusedBrowsingContextInContent
= aContext
;
5041 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
5043 // We don't send the unset but instead expect the set from
5044 // elsewhere to take care of it. XXX Is that bad?
5045 MOZ_ASSERT(aContext
->IsInProcess());
5046 mozilla::dom::ContentChild
* contentChild
=
5047 mozilla::dom::ContentChild::GetSingleton();
5048 MOZ_ASSERT(contentChild
);
5049 contentChild
->SendSetFocusedBrowsingContext(aContext
, aActionId
);
5053 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
5054 BrowsingContext
* aContext
, uint64_t aActionId
) {
5055 MOZ_ASSERT(!XRE_IsParentProcess());
5056 MOZ_ASSERT(aContext
);
5057 if (ActionIdComparableAndLower(aActionId
,
5058 mActionIdForFocusedBrowsingContextInContent
)) {
5059 // Unclear if this ever happens.
5061 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5062 "focused from another process due to stale action id %" PRIu64
".",
5063 aContext
, aActionId
));
5066 if (aContext
->IsInProcess()) {
5067 // This message has been in transit for long enough that
5068 // the process association of aContext has changed since
5069 // the other content process sent the message, because
5070 // an iframe in that process became an out-of-process
5071 // iframe while the IPC broadcast that we're receiving
5072 // was in-flight. Let's just ignore this.
5074 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5075 "focused from another process, actionid: %" PRIu64
".",
5076 aContext
, aActionId
));
5079 mFocusedBrowsingContextInContent
= aContext
;
5080 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
5081 mFocusedElement
= nullptr;
5082 mFocusedWindow
= nullptr;
5085 bool nsFocusManager::SetFocusedBrowsingContextInChrome(
5086 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5087 MOZ_ASSERT(aActionId
);
5088 if (ProcessPendingFocusedBrowsingContextActionId(aActionId
)) {
5089 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5090 aActionId
, mActionIdForFocusedBrowsingContextInChrome
));
5091 mFocusedBrowsingContextInChrome
= aContext
;
5092 mActionIdForFocusedBrowsingContextInChrome
= aActionId
;
5098 BrowsingContext
* nsFocusManager::GetFocusedBrowsingContextInChrome() {
5099 return mFocusedBrowsingContextInChrome
;
5102 void nsFocusManager::BrowsingContextDetached(BrowsingContext
* aContext
) {
5103 if (mFocusedBrowsingContextInChrome
== aContext
) {
5104 mFocusedBrowsingContextInChrome
= nullptr;
5105 // Deliberately not adjusting the corresponding action id, because
5106 // we don't want changes from the past to take effect.
5108 if (mActiveBrowsingContextInChrome
== aContext
) {
5109 mActiveBrowsingContextInChrome
= nullptr;
5110 // Deliberately not adjusting the corresponding action id, because
5111 // we don't want changes from the past to take effect.
5115 void nsFocusManager::SetActiveBrowsingContextInContent(
5116 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5117 MOZ_ASSERT(!XRE_IsParentProcess());
5118 MOZ_ASSERT(!aContext
|| aContext
->IsInProcess());
5119 mozilla::dom::ContentChild
* contentChild
=
5120 mozilla::dom::ContentChild::GetSingleton();
5121 MOZ_ASSERT(contentChild
);
5123 if (ActionIdComparableAndLower(aActionId
,
5124 mActionIdForActiveBrowsingContextInContent
)) {
5126 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5127 "the active browsing context due to a stale action id %" PRIu64
".",
5128 aContext
, aActionId
));
5132 if (aContext
!= mActiveBrowsingContextInContent
) {
5134 contentChild
->SendSetActiveBrowsingContext(aContext
, aActionId
);
5135 } else if (mActiveBrowsingContextInContent
) {
5136 // We want to sync this over only if this isn't happening
5137 // due to the active BrowsingContext switching processes,
5138 // in which case the BrowserChild has already marked itself
5140 nsPIDOMWindowOuter
* outer
=
5141 mActiveBrowsingContextInContent
->GetDOMWindow();
5143 nsPIDOMWindowInner
* inner
= outer
->GetCurrentInnerWindow();
5145 WindowGlobalChild
* globalChild
= inner
->GetWindowGlobalChild();
5147 RefPtr
<BrowserChild
> browserChild
= globalChild
->GetBrowserChild();
5148 if (browserChild
&& !browserChild
->IsDestroyed()) {
5149 contentChild
->SendUnsetActiveBrowsingContext(
5150 mActiveBrowsingContextInContent
, aActionId
);
5157 mActiveBrowsingContextInContentSetFromOtherProcess
= false;
5158 mActiveBrowsingContextInContent
= aContext
;
5159 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5160 MaybeUnlockPointer(aContext
);
5163 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
5164 BrowsingContext
* aContext
, uint64_t aActionId
) {
5165 MOZ_ASSERT(!XRE_IsParentProcess());
5166 MOZ_ASSERT(aContext
);
5167 if (ActionIdComparableAndLower(aActionId
,
5168 mActionIdForActiveBrowsingContextInContent
)) {
5170 ("Ignored an attempt to set active BrowsingContext [%p] from "
5171 "another process due to a stale action id %" PRIu64
".",
5172 aContext
, aActionId
));
5175 if (aContext
->IsInProcess()) {
5176 // This message has been in transit for long enough that
5177 // the process association of aContext has changed since
5178 // the other content process sent the message, because
5179 // an iframe in that process became an out-of-process
5180 // iframe while the IPC broadcast that we're receiving
5181 // was in-flight. Let's just ignore this.
5183 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5184 "active from another process. actionid: %" PRIu64
,
5185 aContext
, aActionId
));
5188 mActiveBrowsingContextInContentSetFromOtherProcess
= true;
5189 mActiveBrowsingContextInContent
= aContext
;
5190 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5191 MaybeUnlockPointer(aContext
);
5194 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
5195 BrowsingContext
* aContext
, uint64_t aActionId
) {
5196 MOZ_ASSERT(!XRE_IsParentProcess());
5197 MOZ_ASSERT(aContext
);
5198 if (ActionIdComparableAndLower(aActionId
,
5199 mActionIdForActiveBrowsingContextInContent
)) {
5201 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5202 "another process due to stale action id: %" PRIu64
".",
5203 aContext
, aActionId
));
5206 if (mActiveBrowsingContextInContent
== aContext
) {
5207 mActiveBrowsingContextInContent
= nullptr;
5208 mActionIdForActiveBrowsingContextInContent
= aActionId
;
5209 MaybeUnlockPointer(nullptr);
5212 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5213 "another process. actionid: %" PRIu64
,
5214 aContext
, aActionId
));
5218 void nsFocusManager::ReviseActiveBrowsingContext(
5219 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5220 uint64_t aNewActionId
) {
5221 MOZ_ASSERT(XRE_IsContentProcess());
5222 if (mActionIdForActiveBrowsingContextInContent
== aOldActionId
) {
5223 LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64
5225 "actionid: %" PRIu64
,
5226 aContext
, aOldActionId
, aNewActionId
));
5227 mActiveBrowsingContextInContent
= aContext
;
5228 mActionIdForActiveBrowsingContextInContent
= aNewActionId
;
5231 ("Ignored a stale attempt to revise the active BrowsingContext [%p]. "
5232 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5233 aContext
, aOldActionId
, aNewActionId
));
5237 void nsFocusManager::ReviseFocusedBrowsingContext(
5238 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
5239 uint64_t aNewActionId
) {
5240 MOZ_ASSERT(XRE_IsContentProcess());
5241 if (mActionIdForFocusedBrowsingContextInContent
== aOldActionId
) {
5243 ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64
5245 "actionid: %" PRIu64
,
5246 aContext
, aOldActionId
, aNewActionId
));
5247 mFocusedBrowsingContextInContent
= aContext
;
5248 mActionIdForFocusedBrowsingContextInContent
= aNewActionId
;
5249 mFocusedElement
= nullptr;
5252 ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. "
5253 "old actionid: %" PRIu64
", new actionid: %" PRIu64
,
5254 aContext
, aOldActionId
, aNewActionId
));
5258 bool nsFocusManager::SetActiveBrowsingContextInChrome(
5259 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
5260 MOZ_ASSERT(aActionId
);
5261 if (ProcessPendingActiveBrowsingContextActionId(aActionId
, aContext
)) {
5262 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5263 aActionId
, mActionIdForActiveBrowsingContextInChrome
));
5264 mActiveBrowsingContextInChrome
= aContext
;
5265 mActionIdForActiveBrowsingContextInChrome
= aActionId
;
5271 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
5272 return mActionIdForActiveBrowsingContextInChrome
;
5275 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
5276 return mActionIdForFocusedBrowsingContextInChrome
;
5279 BrowsingContext
* nsFocusManager::GetActiveBrowsingContextInChrome() {
5280 return mActiveBrowsingContextInChrome
;
5283 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId
) {
5284 LOGFOCUS(("InsertNewFocusActionId %" PRIu64
, aActionId
));
5285 MOZ_ASSERT(XRE_IsParentProcess());
5286 MOZ_ASSERT(!mPendingActiveBrowsingContextActions
.Contains(aActionId
));
5287 mPendingActiveBrowsingContextActions
.AppendElement(aActionId
);
5288 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions
.Contains(aActionId
));
5289 mPendingFocusedBrowsingContextActions
.AppendElement(aActionId
);
5292 static void RemoveContentInitiatedActionsUntil(
5293 nsTArray
<uint64_t>& aPendingActions
,
5294 nsTArray
<uint64_t>::index_type aUntil
) {
5295 nsTArray
<uint64_t>::index_type i
= 0;
5296 while (i
< aUntil
) {
5297 auto [actionProc
, actionId
] =
5298 nsContentUtils::SplitProcessSpecificId(aPendingActions
[i
]);
5301 aPendingActions
.RemoveElementAt(i
);
5309 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
5310 uint64_t aActionId
, bool aSettingToNonNull
) {
5311 MOZ_ASSERT(XRE_IsParentProcess());
5312 auto index
= mPendingActiveBrowsingContextActions
.IndexOf(aActionId
);
5313 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5316 // When aSettingToNonNull is true, we need to remove one more
5317 // element to remove the action id itself in addition to
5318 // removing the older ones.
5319 if (aSettingToNonNull
) {
5322 auto [actionProc
, actionId
] =
5323 nsContentUtils::SplitProcessSpecificId(aActionId
);
5326 // Action from content: We allow parent-initiated actions
5327 // to take precedence over content-initiated ones, so we
5328 // remove only prior content-initiated actions.
5329 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions
,
5332 // Action from chrome
5333 mPendingActiveBrowsingContextActions
.RemoveElementsAt(0, index
);
5338 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
5339 uint64_t aActionId
) {
5340 MOZ_ASSERT(XRE_IsParentProcess());
5341 auto index
= mPendingFocusedBrowsingContextActions
.IndexOf(aActionId
);
5342 if (index
== nsTArray
<uint64_t>::NoIndex
) {
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(mPendingFocusedBrowsingContextActions
,
5356 // Action from chrome
5357 mPendingFocusedBrowsingContextActions
.RemoveElementsAt(0, index
);
5363 uint64_t nsFocusManager::GenerateFocusActionId() {
5365 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter
);
5366 if (XRE_IsParentProcess()) {
5367 nsFocusManager
* fm
= GetFocusManager();
5369 fm
->InsertNewFocusActionId(id
);
5372 mozilla::dom::ContentChild
* contentChild
=
5373 mozilla::dom::ContentChild::GetSingleton();
5374 MOZ_ASSERT(contentChild
);
5375 contentChild
->SendInsertNewFocusActionId(id
);
5377 LOGFOCUS(("GenerateFocusActionId %" PRIu64
, id
));
5381 static bool IsInPointerLockContext(nsPIDOMWindowOuter
* aWin
) {
5382 return PointerLockManager::IsInLockContext(aWin
? aWin
->GetBrowsingContext()
5386 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter
* aWindow
,
5388 bool aSyncBrowsingContext
) {
5389 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker
&&
5390 IsInPointerLockContext(mFocusedWindow
) &&
5391 !IsInPointerLockContext(aWindow
)) {
5392 nsCOMPtr
<nsIRunnable
> runnable
= new PointerUnlocker();
5393 NS_DispatchToCurrentThread(runnable
);
5396 // Update the last focus time on any affected documents
5397 if (aWindow
&& aWindow
!= mFocusedWindow
) {
5398 const TimeStamp
now(TimeStamp::Now());
5399 for (Document
* doc
= aWindow
->GetExtantDoc(); doc
;
5400 doc
= doc
->GetInProcessParentDocument()) {
5401 doc
->SetLastFocusTime(now
);
5405 // This function may be called with zero action id to indicate that the
5406 // action id should be ignored.
5407 if (XRE_IsContentProcess() && aActionId
&&
5408 ActionIdComparableAndLower(aActionId
,
5409 mActionIdForFocusedBrowsingContextInContent
)) {
5410 // Unclear if this ever happens.
5412 ("Ignored an attempt to set an in-process BrowsingContext as "
5413 "focused due to stale action id %" PRIu64
".",
5418 mFocusedWindow
= aWindow
;
5419 BrowsingContext
* bc
= aWindow
? aWindow
->GetBrowsingContext() : nullptr;
5420 if (aSyncBrowsingContext
) {
5421 MOZ_ASSERT(aActionId
,
5422 "aActionId must not be zero if aSyncBrowsingContext is true");
5423 SetFocusedBrowsingContext(bc
, aActionId
);
5424 } else if (XRE_IsContentProcess()) {
5425 MOZ_ASSERT(mFocusedBrowsingContextInContent
== bc
,
5426 "Not syncing BrowsingContext even when different.");
5430 void nsFocusManager::NotifyOfReFocus(Element
& aElement
) {
5431 nsPIDOMWindowOuter
* window
= GetCurrentWindow(&aElement
);
5432 if (!window
|| window
!= mFocusedWindow
) {
5435 if (!aElement
.IsInComposedDoc() || IsNonFocusableRoot(&aElement
)) {
5438 nsIDocShell
* docShell
= window
->GetDocShell();
5442 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
5446 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
5450 IMEStateManager::OnReFocus(*presContext
, aElement
);
5453 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration
) {
5458 if (sInstance
->mActiveWindow
) {
5459 sInstance
->mActiveWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5461 if (sInstance
->mFocusedWindow
) {
5462 sInstance
->mFocusedWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5464 if (sInstance
->mWindowBeingLowered
) {
5465 sInstance
->mWindowBeingLowered
->MarkUncollectableForCCGeneration(
5468 if (sInstance
->mFocusedElement
) {
5469 sInstance
->mFocusedElement
->OwnerDoc()->MarkUncollectableForCCGeneration(
5472 if (sInstance
->mFirstBlurEvent
) {
5473 sInstance
->mFirstBlurEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5476 if (sInstance
->mFirstFocusEvent
) {
5477 sInstance
->mFirstFocusEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5482 bool nsFocusManager::CanSkipFocus(nsIContent
* aContent
) {
5487 if (mFocusedElement
== aContent
) {
5491 nsIDocShell
* ds
= aContent
->OwnerDoc()->GetDocShell();
5496 if (XRE_IsParentProcess()) {
5497 nsCOMPtr
<nsIDocShellTreeItem
> root
;
5498 ds
->GetInProcessRootTreeItem(getter_AddRefs(root
));
5499 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
=
5500 root
? root
->GetWindow() : nullptr;
5501 if (mActiveWindow
!= newRootWindow
) {
5502 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5503 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5508 BrowsingContext
* bc
= aContent
->OwnerDoc()->GetBrowsingContext();
5509 BrowsingContext
* top
= bc
? bc
->Top() : nullptr;
5510 if (GetActiveBrowsingContext() != top
) {
5511 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5512 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5522 Element
* nsFocusManager::GetTheFocusableArea(Element
* aTarget
,
5524 MOZ_ASSERT(aTarget
);
5525 nsIFrame
* frame
= aTarget
->GetPrimaryFrame();
5530 // If focus target is the document element of its Document.
5531 if (aTarget
== aTarget
->OwnerDoc()->GetRootElement()) {
5532 // the root content can always be focused,
5533 // except in userfocusignored context.
5537 // If focus target is an area element with one or more shapes that are
5539 if (auto* area
= HTMLAreaElement::FromNode(aTarget
)) {
5540 return IsAreaElementFocusable(*area
) ? area
: nullptr;
5543 // For these 3 steps mentioned in the spec
5544 // 1. If focus target is an element with one or more scrollable regions that
5545 // are focusable areas
5546 // 2. If focus target is a navigable
5547 // 3. If focus target is a navigable container with a non-null content
5549 // nsIFrame::IsFocusable will effectively perform the checks for them.
5550 if (frame
->IsFocusable(aFlags
& FLAG_BYMOUSE
)) {
5554 // If focus target is a shadow host whose shadow root's delegates focus is
5556 if (ShadowRoot
* root
= aTarget
->GetShadowRoot()) {
5557 if (root
->DelegatesFocus()) {
5558 // If focus target is a shadow-including inclusive ancestor of the
5559 // currently focused area of a top-level browsing context's DOM anchor,
5560 // then return the already-focused element.
5561 if (nsPIDOMWindowInner
* innerWindow
=
5562 aTarget
->OwnerDoc()->GetInnerWindow()) {
5563 if (Element
* focusedElement
= innerWindow
->GetFocusedElement()) {
5564 if (focusedElement
->IsShadowIncludingInclusiveDescendantOf(aTarget
)) {
5565 return focusedElement
;
5570 if (Element
* firstFocusable
=
5571 root
->GetFocusDelegate(aFlags
& FLAG_BYMOUSE
)) {
5572 return firstFocusable
;
5580 bool nsFocusManager::IsAreaElementFocusable(HTMLAreaElement
& aArea
) {
5581 nsIFrame
* frame
= aArea
.GetPrimaryFrame();
5585 // HTML areas do not have their own frame, and the img frame we get from
5586 // GetPrimaryFrame() is not relevant as to whether it is focusable or
5587 // not, so we have to do all the relevant checks manually for them.
5588 return frame
->IsVisibleConsideringAncestors() &&
5589 aArea
.IsFocusableWithoutStyle(false /* aWithMouse */);
5592 nsresult
NS_NewFocusManager(nsIFocusManager
** aResult
) {
5593 NS_IF_ADDREF(*aResult
= nsFocusManager::GetFocusManager());